()
| 138 | * @returns true if PostHog error tracking was enabled, false if POSTHOG_API_KEY is not set |
| 139 | */ |
| 140 | export function initPostHogErrorTracking(): boolean { |
| 141 | if (!POSTHOG_API_KEY) { |
| 142 | return false; |
| 143 | } |
| 144 | |
| 145 | // Ensure client is initialized |
| 146 | getPostHog(); |
| 147 | |
| 148 | // Set up the error hook in the logger |
| 149 | setErrorHook((message, error, context, level) => { |
| 150 | const client = getPostHog(); |
| 151 | if (!client) return; |
| 152 | |
| 153 | const now = Date.now(); |
| 154 | |
| 155 | // Rate limit: skip if too soon after last error |
| 156 | if (now - lastErrorTime < ERROR_RATE_LIMIT_MS) { |
| 157 | return; |
| 158 | } |
| 159 | |
| 160 | // Dedupe: skip if same error message within window |
| 161 | const errorKey = `${message}:${error?.name || 'Error'}`; |
| 162 | const lastSeen = recentErrors.get(errorKey); |
| 163 | if (lastSeen && now - lastSeen < ERROR_DEDUP_WINDOW_MS) { |
| 164 | return; |
| 165 | } |
| 166 | |
| 167 | lastErrorTime = now; |
| 168 | recentErrors.set(errorKey, now); |
| 169 | |
| 170 | // Clean up old entries periodically |
| 171 | if (recentErrors.size > 100) { |
| 172 | const cutoff = now - ERROR_DEDUP_WINDOW_MS; |
| 173 | for (const [key, time] of recentErrors) { |
| 174 | if (time < cutoff) recentErrors.delete(key); |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | // Extract module from context if available (set by createLogger) |
| 179 | const module = (context?.module as string) || 'unknown'; |
| 180 | |
| 181 | client.capture({ |
| 182 | distinctId: 'server-logs', |
| 183 | event: '$exception', |
| 184 | properties: { |
| 185 | $exception_message: message, |
| 186 | $exception_type: error?.name || 'Error', |
| 187 | $exception_stack_trace_raw: error?.stack, |
| 188 | // Include context for debugging |
| 189 | module, |
| 190 | ...context, |
| 191 | $lib: 'posthog-node', |
| 192 | source: 'server-logger', |
| 193 | }, |
| 194 | }); |
| 195 | |
| 196 | // Fatal-level errors get an immediate Slack notification to ops channel. |
| 197 | if (level && level >= 60) { |
no test coverage detected