@goodfoot/claude-code-hooks 1.0.10 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +599 -604
- package/dist/constants.js +13 -12
- package/dist/env.js +36 -38
- package/dist/hooks.js +62 -22
- package/dist/index.js +37 -67
- package/dist/logger.js +371 -362
- package/dist/outputs.js +51 -34
- package/dist/runtime.js +99 -96
- package/dist/scaffold.js +180 -183
- package/dist/tool-helpers.js +255 -103
- package/dist/types.js +36 -0
- package/package.json +8 -11
- package/types/cli.d.ts +67 -98
- package/types/constants.d.ts +1 -1
- package/types/env.d.ts +16 -16
- package/types/hooks.d.ts +203 -254
- package/types/index.d.ts +15 -130
- package/types/logger.d.ts +285 -285
- package/types/outputs.d.ts +193 -229
- package/types/runtime.d.ts +4 -6
- package/types/scaffold.d.ts +6 -6
- package/types/tool-helpers.d.ts +206 -77
- package/types/{inputs.d.ts → types.d.ts} +186 -252
- package/dist/inputs.js +0 -35
- package/dist/tool-inputs.js +0 -21
- package/types/tool-inputs.d.ts +0 -228
package/dist/logger.js
CHANGED
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
* ```
|
|
20
20
|
* @see https://code.claude.com/docs/en/hooks
|
|
21
21
|
*/
|
|
22
|
-
import { closeSync, existsSync, mkdirSync, openSync, writeSync } from
|
|
23
|
-
import { dirname } from
|
|
22
|
+
import { closeSync, existsSync, mkdirSync, openSync, writeSync } from "node:fs";
|
|
23
|
+
import { dirname } from "node:path";
|
|
24
24
|
/**
|
|
25
25
|
* All log levels in order of severity (lowest to highest).
|
|
26
26
|
*/
|
|
27
|
-
export const LOG_LEVELS = [
|
|
27
|
+
export const LOG_LEVELS = ["debug", "info", "warn", "error"];
|
|
28
28
|
// ============================================================================
|
|
29
29
|
// Logger Class
|
|
30
30
|
// ============================================================================
|
|
@@ -63,376 +63,385 @@ export const LOG_LEVELS = ['debug', 'info', 'warn', 'error'];
|
|
|
63
63
|
* ```
|
|
64
64
|
*/
|
|
65
65
|
export class Logger {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Registered event handlers by log level.
|
|
68
|
+
*/
|
|
69
|
+
handlers = new Map();
|
|
70
|
+
/**
|
|
71
|
+
* File descriptor for log file output.
|
|
72
|
+
* Lazily initialized on first write.
|
|
73
|
+
*/
|
|
74
|
+
logFileFd = null;
|
|
75
|
+
/**
|
|
76
|
+
* Path to the log file, if configured.
|
|
77
|
+
*/
|
|
78
|
+
logFilePath = null;
|
|
79
|
+
/**
|
|
80
|
+
* Whether file initialization has been attempted.
|
|
81
|
+
*/
|
|
82
|
+
fileInitialized = false;
|
|
83
|
+
/**
|
|
84
|
+
* Current hook context for enriching log events.
|
|
85
|
+
*/
|
|
86
|
+
currentHookType;
|
|
87
|
+
/**
|
|
88
|
+
* Current hook input for enriching log events.
|
|
89
|
+
*/
|
|
90
|
+
currentInput;
|
|
91
|
+
/**
|
|
92
|
+
* Creates a new Logger instance.
|
|
93
|
+
*
|
|
94
|
+
* Typically you should use the exported `logger` singleton rather than
|
|
95
|
+
* creating new instances.
|
|
96
|
+
* @param config - Optional configuration
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* // Use singleton (recommended)
|
|
100
|
+
* import { logger } from '@goodfoot/claude-code-hooks';
|
|
101
|
+
*
|
|
102
|
+
* // Or create custom instance
|
|
103
|
+
* const customLogger = new Logger({ logFilePath: '/var/log/hooks.log' });
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
constructor(config = {}) {
|
|
107
|
+
// Initialize handlers map for each level
|
|
108
|
+
for (const level of LOG_LEVELS) {
|
|
109
|
+
this.handlers.set(level, new Set());
|
|
110
|
+
}
|
|
111
|
+
// Set log file path from config or environment
|
|
112
|
+
this.logFilePath = config.logFilePath ?? process.env.CLAUDE_CODE_HOOKS_LOG_FILE ?? null;
|
|
110
113
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
*/
|
|
126
|
-
debug(message, context) {
|
|
127
|
-
this.emit('debug', message, context);
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Logs an info message.
|
|
131
|
-
*
|
|
132
|
-
* Use for general operational events like hook invocations, successful
|
|
133
|
-
* completions, or state changes.
|
|
134
|
-
* @param message - The info message
|
|
135
|
-
* @param context - Optional additional context
|
|
136
|
-
* @example
|
|
137
|
-
* ```typescript
|
|
138
|
-
* logger.info('Session started', { source: 'startup', sessionId: 'abc123' });
|
|
139
|
-
* ```
|
|
140
|
-
*/
|
|
141
|
-
info(message, context) {
|
|
142
|
-
this.emit('info', message, context);
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Logs a warning message.
|
|
146
|
-
*
|
|
147
|
-
* Use for conditions that may indicate issues but don't prevent
|
|
148
|
-
* operation, such as deprecated patterns or performance concerns.
|
|
149
|
-
* @param message - The warning message
|
|
150
|
-
* @param context - Optional additional context
|
|
151
|
-
* @example
|
|
152
|
-
* ```typescript
|
|
153
|
-
* logger.warn('Deprecated hook pattern detected', { pattern: 'legacyMatcher' });
|
|
154
|
-
* ```
|
|
155
|
-
*/
|
|
156
|
-
warn(message, context) {
|
|
157
|
-
this.emit('warn', message, context);
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Logs an error message.
|
|
161
|
-
*
|
|
162
|
-
* Use for error conditions that require attention but were handled
|
|
163
|
-
* gracefully. For exceptions, prefer {@link logError}.
|
|
164
|
-
* @param message - The error message
|
|
165
|
-
* @param context - Optional additional context
|
|
166
|
-
* @example
|
|
167
|
-
* ```typescript
|
|
168
|
-
* logger.error('Failed to validate tool input', { toolName: 'Bash', reason: 'empty command' });
|
|
169
|
-
* ```
|
|
170
|
-
*/
|
|
171
|
-
error(message, context) {
|
|
172
|
-
this.emit('error', message, context);
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Logs a structured error with full error details.
|
|
176
|
-
*
|
|
177
|
-
* Use this method when logging caught exceptions to capture the full
|
|
178
|
-
* error context including name, message, stack trace, and cause chain.
|
|
179
|
-
* @param error - The error to log
|
|
180
|
-
* @param message - Human-readable description of what failed
|
|
181
|
-
* @param context - Optional additional context
|
|
182
|
-
* @example
|
|
183
|
-
* ```typescript
|
|
184
|
-
* try {
|
|
185
|
-
* await dangerousOperation();
|
|
186
|
-
* } catch (err) {
|
|
187
|
-
* logger.logError(err, 'Failed to execute dangerous operation', {
|
|
188
|
-
* operation: 'delete',
|
|
189
|
-
* target: '/important/file.txt'
|
|
190
|
-
* });
|
|
191
|
-
* }
|
|
192
|
-
* ```
|
|
193
|
-
*/
|
|
194
|
-
logError(error, message, context) {
|
|
195
|
-
const errorInfo = this.extractErrorInfo(error);
|
|
196
|
-
const event = {
|
|
197
|
-
timestamp: new Date().toISOString(),
|
|
198
|
-
level: 'error',
|
|
199
|
-
hookType: this.currentHookType,
|
|
200
|
-
message,
|
|
201
|
-
input: this.currentInput,
|
|
202
|
-
error: errorInfo,
|
|
203
|
-
context
|
|
204
|
-
};
|
|
205
|
-
this.deliverEvent(event);
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Subscribes a handler to log events at the specified level.
|
|
209
|
-
*
|
|
210
|
-
* The handler will be called for every log event at the specified level.
|
|
211
|
-
* Returns an unsubscribe function that should be called when the handler
|
|
212
|
-
* is no longer needed.
|
|
213
|
-
* @param level - The log level to subscribe to
|
|
214
|
-
* @param handler - The handler function to call for each event
|
|
215
|
-
* @returns A function to unsubscribe the handler
|
|
216
|
-
* @example
|
|
217
|
-
* ```typescript
|
|
218
|
-
* // Subscribe to error events
|
|
219
|
-
* const unsubscribe = logger.on('error', (event) => {
|
|
220
|
-
* console.error(`[${event.hookType}] ${event.message}`);
|
|
221
|
-
* if (event.error) {
|
|
222
|
-
* console.error(event.error.stack);
|
|
223
|
-
* }
|
|
224
|
-
* });
|
|
225
|
-
*
|
|
226
|
-
* // Later, clean up
|
|
227
|
-
* unsubscribe();
|
|
228
|
-
* ```
|
|
229
|
-
* @example
|
|
230
|
-
* ```typescript
|
|
231
|
-
* // Forward to external logging library
|
|
232
|
-
* import pino from 'pino';
|
|
233
|
-
* const pinoLogger = pino();
|
|
234
|
-
*
|
|
235
|
-
* logger.on('info', (event) => pinoLogger.info(event, event.message));
|
|
236
|
-
* logger.on('warn', (event) => pinoLogger.warn(event, event.message));
|
|
237
|
-
* logger.on('error', (event) => pinoLogger.error(event, event.message));
|
|
238
|
-
* ```
|
|
239
|
-
*/
|
|
240
|
-
on(level, handler) {
|
|
241
|
-
const levelHandlers = this.handlers.get(level);
|
|
242
|
-
if (levelHandlers) {
|
|
243
|
-
levelHandlers.add(handler);
|
|
114
|
+
/**
|
|
115
|
+
* Logs a debug message.
|
|
116
|
+
*
|
|
117
|
+
* Use for detailed debugging information that is typically only useful
|
|
118
|
+
* during development or troubleshooting.
|
|
119
|
+
* @param message - The debug message
|
|
120
|
+
* @param context - Optional additional context
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* logger.debug('Processing tool input', { toolName: 'Bash', inputSize: 256 });
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
debug(message, context) {
|
|
127
|
+
this.emit("debug", message, context);
|
|
244
128
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
this.currentHookType = hookType;
|
|
260
|
-
this.currentInput = input;
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Clears the current hook context.
|
|
264
|
-
*
|
|
265
|
-
* Called internally by the runtime after hook execution completes.
|
|
266
|
-
* @internal
|
|
267
|
-
*/
|
|
268
|
-
clearContext() {
|
|
269
|
-
this.currentHookType = undefined;
|
|
270
|
-
this.currentInput = undefined;
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Configures the log file path at runtime.
|
|
274
|
-
*
|
|
275
|
-
* Call this to enable or change file logging. Setting to `null` disables
|
|
276
|
-
* file logging (but doesn't close existing file handle immediately).
|
|
277
|
-
* @param filePath - Path to the log file, or null to disable
|
|
278
|
-
* @example
|
|
279
|
-
* ```typescript
|
|
280
|
-
* // Enable file logging at runtime
|
|
281
|
-
* logger.setLogFile('/var/log/claude-hooks.log');
|
|
282
|
-
*
|
|
283
|
-
* // Disable file logging
|
|
284
|
-
* logger.setLogFile(null);
|
|
285
|
-
* ```
|
|
286
|
-
*/
|
|
287
|
-
setLogFile(filePath) {
|
|
288
|
-
// Close existing file if open
|
|
289
|
-
if (this.logFileFd !== null) {
|
|
290
|
-
try {
|
|
291
|
-
closeSync(this.logFileFd);
|
|
292
|
-
} catch {
|
|
293
|
-
// Ignore errors on close
|
|
294
|
-
}
|
|
295
|
-
this.logFileFd = null;
|
|
129
|
+
/**
|
|
130
|
+
* Logs an info message.
|
|
131
|
+
*
|
|
132
|
+
* Use for general operational events like hook invocations, successful
|
|
133
|
+
* completions, or state changes.
|
|
134
|
+
* @param message - The info message
|
|
135
|
+
* @param context - Optional additional context
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* logger.info('Session started', { source: 'startup', sessionId: 'abc123' });
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
info(message, context) {
|
|
142
|
+
this.emit("info", message, context);
|
|
296
143
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
close() {
|
|
312
|
-
if (this.logFileFd !== null) {
|
|
313
|
-
try {
|
|
314
|
-
closeSync(this.logFileFd);
|
|
315
|
-
} catch {
|
|
316
|
-
// Ignore errors on close
|
|
317
|
-
}
|
|
318
|
-
this.logFileFd = null;
|
|
144
|
+
/**
|
|
145
|
+
* Logs a warning message.
|
|
146
|
+
*
|
|
147
|
+
* Use for conditions that may indicate issues but don't prevent
|
|
148
|
+
* operation, such as deprecated patterns or performance concerns.
|
|
149
|
+
* @param message - The warning message
|
|
150
|
+
* @param context - Optional additional context
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* logger.warn('Deprecated hook pattern detected', { pattern: 'legacyMatcher' });
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
warn(message, context) {
|
|
157
|
+
this.emit("warn", message, context);
|
|
319
158
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Logs an error message.
|
|
161
|
+
*
|
|
162
|
+
* Use for error conditions that require attention but were handled
|
|
163
|
+
* gracefully. For exceptions, prefer {@link logError}.
|
|
164
|
+
* @param message - The error message
|
|
165
|
+
* @param context - Optional additional context
|
|
166
|
+
* @example
|
|
167
|
+
* ```typescript
|
|
168
|
+
* logger.error('Failed to validate tool input', { toolName: 'Bash', reason: 'empty command' });
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
error(message, context) {
|
|
172
|
+
this.emit("error", message, context);
|
|
331
173
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Logs a structured error with full error details.
|
|
176
|
+
*
|
|
177
|
+
* Use this method when logging caught exceptions to capture the full
|
|
178
|
+
* error context including name, message, stack trace, and cause chain.
|
|
179
|
+
* @param error - The error to log
|
|
180
|
+
* @param message - Human-readable description of what failed
|
|
181
|
+
* @param context - Optional additional context
|
|
182
|
+
* @example
|
|
183
|
+
* ```typescript
|
|
184
|
+
* try {
|
|
185
|
+
* await dangerousOperation();
|
|
186
|
+
* } catch (err) {
|
|
187
|
+
* logger.logError(err, 'Failed to execute dangerous operation', {
|
|
188
|
+
* operation: 'delete',
|
|
189
|
+
* target: '/important/file.txt'
|
|
190
|
+
* });
|
|
191
|
+
* }
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
logError(error, message, context) {
|
|
195
|
+
const errorInfo = this.extractErrorInfo(error);
|
|
196
|
+
const event = {
|
|
197
|
+
timestamp: new Date().toISOString(),
|
|
198
|
+
level: "error",
|
|
199
|
+
hookType: this.currentHookType,
|
|
200
|
+
message,
|
|
201
|
+
input: this.currentInput,
|
|
202
|
+
error: errorInfo,
|
|
203
|
+
context,
|
|
204
|
+
};
|
|
205
|
+
this.deliverEvent(event);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Subscribes a handler to log events at the specified level.
|
|
209
|
+
*
|
|
210
|
+
* The handler will be called for every log event at the specified level.
|
|
211
|
+
* Returns an unsubscribe function that should be called when the handler
|
|
212
|
+
* is no longer needed.
|
|
213
|
+
* @param level - The log level to subscribe to
|
|
214
|
+
* @param handler - The handler function to call for each event
|
|
215
|
+
* @returns A function to unsubscribe the handler
|
|
216
|
+
* @example
|
|
217
|
+
* ```typescript
|
|
218
|
+
* // Subscribe to error events
|
|
219
|
+
* const unsubscribe = logger.on('error', (event) => {
|
|
220
|
+
* console.error(`[${event.hookType}] ${event.message}`);
|
|
221
|
+
* if (event.error) {
|
|
222
|
+
* console.error(event.error.stack);
|
|
223
|
+
* }
|
|
224
|
+
* });
|
|
225
|
+
*
|
|
226
|
+
* // Later, clean up
|
|
227
|
+
* unsubscribe();
|
|
228
|
+
* ```
|
|
229
|
+
* @example
|
|
230
|
+
* ```typescript
|
|
231
|
+
* // Forward to external logging library
|
|
232
|
+
* import pino from 'pino';
|
|
233
|
+
* const pinoLogger = pino();
|
|
234
|
+
*
|
|
235
|
+
* logger.on('info', (event) => pinoLogger.info(event, event.message));
|
|
236
|
+
* logger.on('warn', (event) => pinoLogger.warn(event, event.message));
|
|
237
|
+
* logger.on('error', (event) => pinoLogger.error(event, event.message));
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
on(level, handler) {
|
|
241
|
+
const levelHandlers = this.handlers.get(level);
|
|
242
|
+
if (levelHandlers) {
|
|
243
|
+
levelHandlers.add(handler);
|
|
367
244
|
}
|
|
368
|
-
|
|
245
|
+
return () => {
|
|
246
|
+
levelHandlers?.delete(handler);
|
|
247
|
+
};
|
|
369
248
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
249
|
+
/**
|
|
250
|
+
* Sets the current hook context for enriching log events.
|
|
251
|
+
*
|
|
252
|
+
* This is called internally by the runtime before invoking hook handlers.
|
|
253
|
+
* You typically don't need to call this directly.
|
|
254
|
+
* @param hookType - The type of hook being executed
|
|
255
|
+
* @param input - The hook input data
|
|
256
|
+
* @internal
|
|
257
|
+
*/
|
|
258
|
+
setContext(hookType, input) {
|
|
259
|
+
this.currentHookType = hookType;
|
|
260
|
+
this.currentInput = input;
|
|
382
261
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
262
|
+
/**
|
|
263
|
+
* Clears the current hook context.
|
|
264
|
+
*
|
|
265
|
+
* Called internally by the runtime after hook execution completes.
|
|
266
|
+
* @internal
|
|
267
|
+
*/
|
|
268
|
+
clearContext() {
|
|
269
|
+
this.currentHookType = undefined;
|
|
270
|
+
this.currentInput = undefined;
|
|
391
271
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Configures the log file path at runtime.
|
|
274
|
+
*
|
|
275
|
+
* Call this to enable or change file logging. Setting to `null` disables
|
|
276
|
+
* file logging (but doesn't close existing file handle immediately).
|
|
277
|
+
* @param filePath - Path to the log file, or null to disable
|
|
278
|
+
* @example
|
|
279
|
+
* ```typescript
|
|
280
|
+
* // Enable file logging at runtime
|
|
281
|
+
* logger.setLogFile('/var/log/claude-hooks.log');
|
|
282
|
+
*
|
|
283
|
+
* // Disable file logging
|
|
284
|
+
* logger.setLogFile(null);
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
setLogFile(filePath) {
|
|
288
|
+
// Close existing file if open
|
|
289
|
+
if (this.logFileFd !== null) {
|
|
290
|
+
try {
|
|
291
|
+
closeSync(this.logFileFd);
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
// Ignore errors on close
|
|
295
|
+
}
|
|
296
|
+
this.logFileFd = null;
|
|
297
|
+
}
|
|
298
|
+
this.logFilePath = filePath;
|
|
299
|
+
this.fileInitialized = false;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Closes all resources held by the logger.
|
|
303
|
+
*
|
|
304
|
+
* Call this during graceful shutdown to ensure all log data is flushed.
|
|
305
|
+
* @example
|
|
306
|
+
* ```typescript
|
|
307
|
+
* process.on('exit', () => {
|
|
308
|
+
* logger.close();
|
|
309
|
+
* });
|
|
310
|
+
* ```
|
|
311
|
+
*/
|
|
312
|
+
close() {
|
|
313
|
+
if (this.logFileFd !== null) {
|
|
314
|
+
try {
|
|
315
|
+
closeSync(this.logFileFd);
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
// Ignore errors on close
|
|
319
|
+
}
|
|
320
|
+
this.logFileFd = null;
|
|
321
|
+
}
|
|
322
|
+
this.fileInitialized = false;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Checks if there are any active handlers or destinations.
|
|
326
|
+
*
|
|
327
|
+
* Returns true if any handlers are registered or file logging is enabled.
|
|
328
|
+
* @returns Whether the logger has any active output destinations
|
|
329
|
+
*/
|
|
330
|
+
hasDestinations() {
|
|
331
|
+
for (const handlers of this.handlers.values()) {
|
|
332
|
+
if (handlers.size > 0)
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
return this.logFilePath !== null;
|
|
336
|
+
}
|
|
337
|
+
// ============================================================================
|
|
338
|
+
// Private Methods
|
|
339
|
+
// ============================================================================
|
|
340
|
+
/**
|
|
341
|
+
* Emits a log event.
|
|
342
|
+
* @param level - The severity level of the event
|
|
343
|
+
* @param message - The log message
|
|
344
|
+
* @param context - Optional additional context data
|
|
345
|
+
*/
|
|
346
|
+
emit(level, message, context) {
|
|
347
|
+
const event = {
|
|
348
|
+
timestamp: new Date().toISOString(),
|
|
349
|
+
level,
|
|
350
|
+
hookType: this.currentHookType,
|
|
351
|
+
message,
|
|
352
|
+
input: this.currentInput,
|
|
353
|
+
context,
|
|
354
|
+
};
|
|
355
|
+
this.deliverEvent(event);
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Delivers an event to all registered destinations.
|
|
359
|
+
* @param event - The log event to deliver
|
|
360
|
+
*/
|
|
361
|
+
deliverEvent(event) {
|
|
362
|
+
// Deliver to event handlers
|
|
363
|
+
const levelHandlers = this.handlers.get(event.level);
|
|
364
|
+
if (levelHandlers) {
|
|
365
|
+
for (const handler of levelHandlers) {
|
|
366
|
+
try {
|
|
367
|
+
handler(event);
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
// Silently ignore handler errors to not disrupt hook execution
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// Write to file if configured
|
|
375
|
+
this.writeToFile(event);
|
|
410
376
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
377
|
+
/**
|
|
378
|
+
* Writes an event to the log file.
|
|
379
|
+
* @param event - The log event to write
|
|
380
|
+
*/
|
|
381
|
+
writeToFile(event) {
|
|
382
|
+
if (!this.logFilePath)
|
|
383
|
+
return;
|
|
384
|
+
// Lazy initialization of file handle
|
|
385
|
+
if (!this.fileInitialized) {
|
|
386
|
+
this.initializeFile();
|
|
387
|
+
}
|
|
388
|
+
if (this.logFileFd === null)
|
|
389
|
+
return;
|
|
390
|
+
try {
|
|
391
|
+
const line = `${JSON.stringify(event)}\n`;
|
|
392
|
+
writeSync(this.logFileFd, line);
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
// Silently ignore file write errors to not disrupt hook execution
|
|
396
|
+
// This follows the risk mitigation: "Graceful degradation - log write
|
|
397
|
+
// failures are silently ignored to not disrupt hook execution"
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Initializes the log file for writing.
|
|
402
|
+
*/
|
|
403
|
+
initializeFile() {
|
|
404
|
+
this.fileInitialized = true;
|
|
405
|
+
if (!this.logFilePath)
|
|
406
|
+
return;
|
|
407
|
+
try {
|
|
408
|
+
// Ensure directory exists
|
|
409
|
+
const dir = dirname(this.logFilePath);
|
|
410
|
+
if (!existsSync(dir)) {
|
|
411
|
+
mkdirSync(dir, { recursive: true });
|
|
412
|
+
}
|
|
413
|
+
// Open file for appending
|
|
414
|
+
this.logFileFd = openSync(this.logFilePath, "a");
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
// Silently ignore file initialization errors
|
|
418
|
+
this.logFileFd = null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Extracts structured error information from an unknown error.
|
|
423
|
+
* @param error - The error to extract information from
|
|
424
|
+
* @returns Structured error information
|
|
425
|
+
*/
|
|
426
|
+
extractErrorInfo(error) {
|
|
427
|
+
if (error instanceof Error) {
|
|
428
|
+
const info = {
|
|
429
|
+
name: error.name,
|
|
430
|
+
message: error.message,
|
|
431
|
+
stack: error.stack,
|
|
432
|
+
};
|
|
433
|
+
// Extract cause chain if present
|
|
434
|
+
if (error.cause !== undefined) {
|
|
435
|
+
info.cause = this.extractErrorInfo(error.cause);
|
|
436
|
+
}
|
|
437
|
+
return info;
|
|
438
|
+
}
|
|
439
|
+
// Handle non-Error values
|
|
440
|
+
return {
|
|
441
|
+
name: "UnknownError",
|
|
442
|
+
message: String(error),
|
|
443
|
+
};
|
|
429
444
|
}
|
|
430
|
-
// Handle non-Error values
|
|
431
|
-
return {
|
|
432
|
-
name: 'UnknownError',
|
|
433
|
-
message: String(error)
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
445
|
}
|
|
437
446
|
// ============================================================================
|
|
438
447
|
// Singleton Export
|