( level: LogLevel, data: LogData, msg?: string, ...args: LogArgs )
| 113 | // In dev mode, use appendFileSync for real-time file logging (Bun has issues with pino sync) |
| 114 | // Also output to console so logs remain visible in the terminal |
| 115 | function logWithSync( |
| 116 | level: LogLevel, |
| 117 | data: LogData, |
| 118 | msg?: string, |
| 119 | ...args: LogArgs |
| 120 | ): void { |
| 121 | const formattedMsg = format(msg ?? '', ...args) |
| 122 | if (IS_DEV) { |
| 123 | // Write to file for real-time logging |
| 124 | if (debugDir) { |
| 125 | const logEntry = JSON.stringify({ |
| 126 | level: level.toUpperCase(), |
| 127 | timestamp: new Date().toISOString(), |
| 128 | ...(data && typeof data === 'object' ? data : { data }), |
| 129 | msg: formattedMsg, |
| 130 | }) |
| 131 | try { |
| 132 | appendFileSync(path.join(debugDir, 'web.jsonl'), logEntry + '\n') |
| 133 | } catch { |
| 134 | // Ignore write errors |
| 135 | } |
| 136 | } |
| 137 | // Also output to console for interactive debugging (don't use pinoLogger here |
| 138 | // as it's configured to write to the same file, which would cause double logging) |
| 139 | console[level === 'fatal' ? 'error' : level](formattedMsg, data) |
| 140 | } else { |
| 141 | const analyticsPayloads = analyticsDispatcher.process({ |
| 142 | data, |
| 143 | level, |
| 144 | msg: formattedMsg, |
| 145 | }) |
| 146 | |
| 147 | analyticsPayloads.forEach((payload) => { |
| 148 | trackEvent({ |
| 149 | event: payload.event, |
| 150 | userId: payload.userId, |
| 151 | properties: payload.properties, |
| 152 | logger: logger as unknown as typeof logger, |
| 153 | }) |
| 154 | }) |
| 155 | |
| 156 | // In prod, use pino with splitAndLog for large payloads |
| 157 | splitAndLog(level, data, msg, ...args) |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | export const logger: Record<LogLevel, pino.LogFn> = Object.fromEntries( |
| 162 | loggingLevels.map((level) => { |
no test coverage detected