@outfitter/logging 0.1.0-rc.3 → 0.2.0

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/README.md CHANGED
@@ -184,6 +184,7 @@ Routes logs to stdout/stderr based on level:
184
184
 
185
185
  - `trace`, `debug`, `info` -> stdout
186
186
  - `warn`, `error`, `fatal` -> stderr
187
+ - Falls back to `console.*` when process streams are unavailable (edge/serverless)
187
188
 
188
189
  ```typescript
189
190
  import { createConsoleSink } from "@outfitter/logging";
package/dist/index.d.ts CHANGED
@@ -1,4 +1,12 @@
1
1
  /**
2
+ * @outfitter/logging
3
+ *
4
+ * Structured logging via logtape with automatic sensitive data redaction.
5
+ * Provides consistent log formatting across CLI, MCP, and server contexts.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ /**
2
10
  * Log levels supported by the logger, ordered from lowest to highest severity.
3
11
  *
4
12
  * Level priority (lowest to highest): trace (0) < debug (1) < info (2) < warn (3) < error (4) < fatal (5)
@@ -208,36 +216,42 @@ interface LoggerInstance {
208
216
  * @param metadata - Optional structured metadata
209
217
  */
210
218
  trace(message: string, metadata?: Record<string, unknown>): void;
219
+ trace(metadata: Record<string, unknown>, message: string): never;
211
220
  /**
212
221
  * Log at debug level (development debugging).
213
222
  * @param message - Human-readable log message
214
223
  * @param metadata - Optional structured metadata
215
224
  */
216
225
  debug(message: string, metadata?: Record<string, unknown>): void;
226
+ debug(metadata: Record<string, unknown>, message: string): never;
217
227
  /**
218
228
  * Log at info level (normal operations).
219
229
  * @param message - Human-readable log message
220
230
  * @param metadata - Optional structured metadata
221
231
  */
222
232
  info(message: string, metadata?: Record<string, unknown>): void;
233
+ info(metadata: Record<string, unknown>, message: string): never;
223
234
  /**
224
235
  * Log at warn level (unexpected but handled situations).
225
236
  * @param message - Human-readable log message
226
237
  * @param metadata - Optional structured metadata
227
238
  */
228
239
  warn(message: string, metadata?: Record<string, unknown>): void;
240
+ warn(metadata: Record<string, unknown>, message: string): never;
229
241
  /**
230
242
  * Log at error level (failures requiring attention).
231
243
  * @param message - Human-readable log message
232
244
  * @param metadata - Optional structured metadata
233
245
  */
234
246
  error(message: string, metadata?: Record<string, unknown>): void;
247
+ error(metadata: Record<string, unknown>, message: string): never;
235
248
  /**
236
249
  * Log at fatal level (unrecoverable failures).
237
250
  * @param message - Human-readable log message
238
251
  * @param metadata - Optional structured metadata
239
252
  */
240
253
  fatal(message: string, metadata?: Record<string, unknown>): void;
254
+ fatal(metadata: Record<string, unknown>, message: string): never;
241
255
  /**
242
256
  * Get the current context metadata attached to this logger.
243
257
  * @returns Copy of the logger's context object
@@ -302,6 +316,33 @@ interface PrettyFormatterOptions {
302
316
  timestamp?: boolean;
303
317
  }
304
318
  /**
319
+ * Options for the console sink.
320
+ *
321
+ * Controls ANSI color output for terminal display. By default, colors are
322
+ * auto-detected based on whether stdout is a TTY.
323
+ *
324
+ * @example
325
+ * ```typescript
326
+ * // Auto-detect TTY (default)
327
+ * const sink = createConsoleSink();
328
+ *
329
+ * // Force colors off (for piped output, CI)
330
+ * const plainSink = createConsoleSink({ colors: false });
331
+ *
332
+ * // Force colors on (even in non-TTY)
333
+ * const colorSink = createConsoleSink({ colors: true });
334
+ * ```
335
+ */
336
+ interface ConsoleSinkOptions {
337
+ /**
338
+ * Enable ANSI colors in output.
339
+ * - `undefined` (default): Auto-detect based on stdout TTY status
340
+ * - `true`: Always use colors
341
+ * - `false`: Never use colors
342
+ */
343
+ colors?: boolean;
344
+ }
345
+ /**
305
346
  * Options for the file sink.
306
347
  *
307
348
  * Configures the file path and append behavior for file-based logging.
@@ -326,7 +367,7 @@ interface FileSinkOptions {
326
367
  /** Absolute path to the log file */
327
368
  path: string;
328
369
  /**
329
- * Append to existing file or truncate on start.
370
+ * Append to existing file or truncate before the first write.
330
371
  * @defaultValue true
331
372
  */
332
373
  append?: boolean;
@@ -444,9 +485,10 @@ declare function createJsonFormatter(): Formatter;
444
485
  */
445
486
  declare function createPrettyFormatter(options?: PrettyFormatterOptions): Formatter;
446
487
  /**
447
- * Create a console sink that writes to stdout/stderr.
448
- * Info and below go to stdout, warn and above go to stderr.
488
+ * Create a console sink that writes via console methods.
489
+ * Info and below go to console.info/debug, warn and above go to console.warn/error.
449
490
  *
491
+ * @param options - Console sink options
450
492
  * @returns Sink configured for console output
451
493
  *
452
494
  * @example
@@ -455,9 +497,15 @@ declare function createPrettyFormatter(options?: PrettyFormatterOptions): Format
455
497
  * name: "app",
456
498
  * sinks: [createConsoleSink()],
457
499
  * });
500
+ *
501
+ * // Disable colors for CI/piped output
502
+ * const plainLogger = createLogger({
503
+ * name: "app",
504
+ * sinks: [createConsoleSink({ colors: false })],
505
+ * });
458
506
  * ```
459
507
  */
460
- declare function createConsoleSink(): Sink;
508
+ declare function createConsoleSink(options?: ConsoleSinkOptions): Sink;
461
509
  /**
462
510
  * Create a file sink that writes to a specified file path.
463
511
  *
@@ -501,4 +549,4 @@ declare function configureRedaction(config: GlobalRedactionConfig): void;
501
549
  * ```
502
550
  */
503
551
  declare function flush(): Promise<void>;
504
- export { flush, createPrettyFormatter, createLogger, createJsonFormatter, createFileSink, createConsoleSink, createChildLogger, configureRedaction, Sink, RedactionConfig, PrettyFormatterOptions, LoggerInstance, LoggerConfig, LogRecord, LogLevel, GlobalRedactionConfig, Formatter, FileSinkOptions, DEFAULT_PATTERNS };
552
+ export { flush, createPrettyFormatter, createLogger, createJsonFormatter, createFileSink, createConsoleSink, createChildLogger, configureRedaction, Sink, RedactionConfig, PrettyFormatterOptions, LoggerInstance, LoggerConfig, LogRecord, LogLevel, GlobalRedactionConfig, Formatter, FileSinkOptions, DEFAULT_PATTERNS, ConsoleSinkOptions };
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  // src/index.ts
2
- import { writeFileSync } from "node:fs";
3
2
  var LEVEL_PRIORITY = {
4
3
  trace: 0,
5
4
  debug: 1,
@@ -215,12 +214,30 @@ function createChildLogger(parent, context) {
215
214
  const parentContext = parent.getContext();
216
215
  const mergedContext = { ...parentContext, ...context };
217
216
  const childLogger = {
218
- trace: (message, metadata) => parent.trace(message, { ...context, ...metadata }),
219
- debug: (message, metadata) => parent.debug(message, { ...context, ...metadata }),
220
- info: (message, metadata) => parent.info(message, { ...context, ...metadata }),
221
- warn: (message, metadata) => parent.warn(message, { ...context, ...metadata }),
222
- error: (message, metadata) => parent.error(message, { ...context, ...metadata }),
223
- fatal: (message, metadata) => parent.fatal(message, { ...context, ...metadata }),
217
+ trace: (message, metadata) => parent.trace(message, {
218
+ ...context,
219
+ ...metadata
220
+ }),
221
+ debug: (message, metadata) => parent.debug(message, {
222
+ ...context,
223
+ ...metadata
224
+ }),
225
+ info: (message, metadata) => parent.info(message, {
226
+ ...context,
227
+ ...metadata
228
+ }),
229
+ warn: (message, metadata) => parent.warn(message, {
230
+ ...context,
231
+ ...metadata
232
+ }),
233
+ error: (message, metadata) => parent.error(message, {
234
+ ...context,
235
+ ...metadata
236
+ }),
237
+ fatal: (message, metadata) => parent.fatal(message, {
238
+ ...context,
239
+ ...metadata
240
+ }),
224
241
  getContext: () => mergedContext,
225
242
  setLevel: (level) => parent.setLevel(level),
226
243
  addSink: (sink) => parent.addSink(sink),
@@ -286,20 +303,29 @@ function createPrettyFormatter(options) {
286
303
  }
287
304
  };
288
305
  }
289
- function createConsoleSink() {
290
- const formatter = createPrettyFormatter({ colors: true });
306
+ function createConsoleSink(options) {
307
+ const useColors = options?.colors ?? (typeof process !== "undefined" ? Boolean(process.stdout?.isTTY) : false);
308
+ const formatter = createPrettyFormatter({ colors: useColors });
291
309
  const sink = {
292
310
  formatter,
293
311
  write(record, formatted) {
294
312
  const output = formatted ?? formatter.format(record);
295
- const outputWithNewline = output.endsWith(`
296
- `) ? output : `${output}
297
- `;
298
- if (LEVEL_PRIORITY[record.level] >= LEVEL_PRIORITY.warn) {
299
- process.stderr.write(outputWithNewline);
300
- } else {
301
- process.stdout.write(outputWithNewline);
313
+ const outputLine = output.endsWith(`
314
+ `) ? output.slice(0, -1) : output;
315
+ const runtimeConsole = globalThis["console"];
316
+ if (record.level === "fatal" || record.level === "error") {
317
+ runtimeConsole.error(outputLine);
318
+ return;
302
319
  }
320
+ if (record.level === "warn") {
321
+ runtimeConsole.warn(outputLine);
322
+ return;
323
+ }
324
+ if (record.level === "debug" || record.level === "trace") {
325
+ runtimeConsole.debug(outputLine);
326
+ return;
327
+ }
328
+ runtimeConsole.info(outputLine);
303
329
  }
304
330
  };
305
331
  registeredSinks.add(sink);
@@ -310,9 +336,7 @@ function createFileSink(options) {
310
336
  const buffer = [];
311
337
  const { path } = options;
312
338
  const append = options.append ?? true;
313
- if (!append) {
314
- writeFileSync(path, "");
315
- }
339
+ let cachedContent = append ? null : "";
316
340
  const sink = {
317
341
  formatter,
318
342
  write(record, formatted) {
@@ -326,9 +350,14 @@ function createFileSink(options) {
326
350
  if (buffer.length > 0) {
327
351
  const content = buffer.join("");
328
352
  buffer.length = 0;
329
- const file = Bun.file(path);
330
- const existing = await file.exists() ? await file.text() : "";
331
- await Bun.write(path, existing + content);
353
+ if (append) {
354
+ const file = Bun.file(path);
355
+ const existing = await file.exists() ? await file.text() : "";
356
+ await Bun.write(path, existing + content);
357
+ return;
358
+ }
359
+ cachedContent = (cachedContent ?? "") + content;
360
+ await Bun.write(path, cachedContent);
332
361
  }
333
362
  }
334
363
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@outfitter/logging",
3
3
  "description": "Structured logging via logtape with redaction support for Outfitter",
4
- "version": "0.1.0-rc.3",
4
+ "version": "0.2.0",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist"
@@ -27,7 +27,7 @@
27
27
  "clean": "rm -rf dist"
28
28
  },
29
29
  "dependencies": {
30
- "@outfitter/contracts": "0.1.0-rc.2",
30
+ "@outfitter/contracts": "0.2.0",
31
31
  "@logtape/logtape": "^2.0.0"
32
32
  },
33
33
  "devDependencies": {