@outfitter/logging 0.1.0 → 0.3.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";
@@ -303,6 +304,34 @@ await flush();
303
304
  process.exit(0);
304
305
  ```
305
306
 
307
+ ## Environment-Aware Log Level
308
+
309
+ ### `resolveLogLevel(level?)`
310
+
311
+ Resolve the log level from environment configuration. Use this instead of hardcoding levels so your app responds to `OUTFITTER_ENV` and `OUTFITTER_LOG_LEVEL` automatically.
312
+
313
+ **Precedence** (highest wins):
314
+ 1. `OUTFITTER_LOG_LEVEL` environment variable
315
+ 2. Explicit `level` parameter
316
+ 3. `OUTFITTER_ENV` profile defaults (`"debug"` in development)
317
+ 4. `"info"` (default)
318
+
319
+ ```typescript
320
+ import { createLogger, resolveLogLevel } from "@outfitter/logging";
321
+
322
+ const logger = createLogger({
323
+ name: "my-app",
324
+ level: resolveLogLevel(),
325
+ sinks: [createConsoleSink()],
326
+ });
327
+
328
+ // With OUTFITTER_ENV=development → "debug"
329
+ // With OUTFITTER_LOG_LEVEL=error → "error" (overrides everything)
330
+ // With nothing set → "info"
331
+ ```
332
+
333
+ MCP-style level names are mapped automatically: `warning` to `warn`, `emergency`/`critical`/`alert` to `fatal`, `notice` to `info`.
334
+
306
335
  ## API Reference
307
336
 
308
337
  ### Functions
@@ -311,6 +340,7 @@ process.exit(0);
311
340
  | ----------------------- | --------------------------------------------------- |
312
341
  | `createLogger` | Create a configured logger instance |
313
342
  | `createChildLogger` | Create a child logger with merged context |
343
+ | `resolveLogLevel` | Resolve log level from env vars and profile |
314
344
  | `configureRedaction` | Configure global redaction patterns and keys |
315
345
  | `flush` | Flush all pending log writes across all sinks |
316
346
  | `createJsonFormatter` | Create a JSON formatter for structured output |
package/dist/index.d.ts CHANGED
@@ -208,36 +208,42 @@ interface LoggerInstance {
208
208
  * @param metadata - Optional structured metadata
209
209
  */
210
210
  trace(message: string, metadata?: Record<string, unknown>): void;
211
+ trace(metadata: Record<string, unknown>, message: string): never;
211
212
  /**
212
213
  * Log at debug level (development debugging).
213
214
  * @param message - Human-readable log message
214
215
  * @param metadata - Optional structured metadata
215
216
  */
216
217
  debug(message: string, metadata?: Record<string, unknown>): void;
218
+ debug(metadata: Record<string, unknown>, message: string): never;
217
219
  /**
218
220
  * Log at info level (normal operations).
219
221
  * @param message - Human-readable log message
220
222
  * @param metadata - Optional structured metadata
221
223
  */
222
224
  info(message: string, metadata?: Record<string, unknown>): void;
225
+ info(metadata: Record<string, unknown>, message: string): never;
223
226
  /**
224
227
  * Log at warn level (unexpected but handled situations).
225
228
  * @param message - Human-readable log message
226
229
  * @param metadata - Optional structured metadata
227
230
  */
228
231
  warn(message: string, metadata?: Record<string, unknown>): void;
232
+ warn(metadata: Record<string, unknown>, message: string): never;
229
233
  /**
230
234
  * Log at error level (failures requiring attention).
231
235
  * @param message - Human-readable log message
232
236
  * @param metadata - Optional structured metadata
233
237
  */
234
238
  error(message: string, metadata?: Record<string, unknown>): void;
239
+ error(metadata: Record<string, unknown>, message: string): never;
235
240
  /**
236
241
  * Log at fatal level (unrecoverable failures).
237
242
  * @param message - Human-readable log message
238
243
  * @param metadata - Optional structured metadata
239
244
  */
240
245
  fatal(message: string, metadata?: Record<string, unknown>): void;
246
+ fatal(metadata: Record<string, unknown>, message: string): never;
241
247
  /**
242
248
  * Get the current context metadata attached to this logger.
243
249
  * @returns Copy of the logger's context object
@@ -353,7 +359,7 @@ interface FileSinkOptions {
353
359
  /** Absolute path to the log file */
354
360
  path: string;
355
361
  /**
356
- * Append to existing file or truncate on start.
362
+ * Append to existing file or truncate before the first write.
357
363
  * @defaultValue true
358
364
  */
359
365
  append?: boolean;
@@ -471,8 +477,8 @@ declare function createJsonFormatter(): Formatter;
471
477
  */
472
478
  declare function createPrettyFormatter(options?: PrettyFormatterOptions): Formatter;
473
479
  /**
474
- * Create a console sink that writes to stdout/stderr.
475
- * Info and below go to stdout, warn and above go to stderr.
480
+ * Create a console sink that writes via console methods.
481
+ * Info and below go to console.info/debug, warn and above go to console.warn/error.
476
482
  *
477
483
  * @param options - Console sink options
478
484
  * @returns Sink configured for console output
@@ -535,4 +541,32 @@ declare function configureRedaction(config: GlobalRedactionConfig): void;
535
541
  * ```
536
542
  */
537
543
  declare function flush(): Promise<void>;
538
- export { flush, createPrettyFormatter, createLogger, createJsonFormatter, createFileSink, createConsoleSink, createChildLogger, configureRedaction, Sink, RedactionConfig, PrettyFormatterOptions, LoggerInstance, LoggerConfig, LogRecord, LogLevel, GlobalRedactionConfig, Formatter, FileSinkOptions, DEFAULT_PATTERNS, ConsoleSinkOptions };
544
+ /**
545
+ * Resolve the log level from environment configuration.
546
+ *
547
+ * Precedence (highest wins):
548
+ * 1. `OUTFITTER_LOG_LEVEL` environment variable
549
+ * 2. Explicit `level` parameter
550
+ * 3. `OUTFITTER_ENV` environment profile defaults
551
+ * 4. `"info"` (default)
552
+ *
553
+ * @param level - Optional explicit log level (overridden by env var)
554
+ * @returns Resolved LogLevel
555
+ *
556
+ * @example
557
+ * ```typescript
558
+ * import { createLogger, resolveLogLevel } from "@outfitter/logging";
559
+ *
560
+ * // Auto-resolve from environment
561
+ * const logger = createLogger({
562
+ * name: "my-app",
563
+ * level: resolveLogLevel(),
564
+ * });
565
+ *
566
+ * // With OUTFITTER_ENV=development → "debug"
567
+ * // With OUTFITTER_LOG_LEVEL=error → "error" (overrides everything)
568
+ * // With nothing set → "info"
569
+ * ```
570
+ */
571
+ declare function resolveLogLevel(level?: LogLevel): LogLevel;
572
+ export { resolveLogLevel, 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,8 @@
1
1
  // src/index.ts
2
- import { writeFileSync } from "node:fs";
2
+ import {
3
+ getEnvironment as _getEnvironment,
4
+ getEnvironmentDefaults as _getEnvironmentDefaults
5
+ } from "@outfitter/config";
3
6
  var LEVEL_PRIORITY = {
4
7
  trace: 0,
5
8
  debug: 1,
@@ -215,12 +218,30 @@ function createChildLogger(parent, context) {
215
218
  const parentContext = parent.getContext();
216
219
  const mergedContext = { ...parentContext, ...context };
217
220
  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 }),
221
+ trace: (message, metadata) => parent.trace(message, {
222
+ ...context,
223
+ ...metadata
224
+ }),
225
+ debug: (message, metadata) => parent.debug(message, {
226
+ ...context,
227
+ ...metadata
228
+ }),
229
+ info: (message, metadata) => parent.info(message, {
230
+ ...context,
231
+ ...metadata
232
+ }),
233
+ warn: (message, metadata) => parent.warn(message, {
234
+ ...context,
235
+ ...metadata
236
+ }),
237
+ error: (message, metadata) => parent.error(message, {
238
+ ...context,
239
+ ...metadata
240
+ }),
241
+ fatal: (message, metadata) => parent.fatal(message, {
242
+ ...context,
243
+ ...metadata
244
+ }),
224
245
  getContext: () => mergedContext,
225
246
  setLevel: (level) => parent.setLevel(level),
226
247
  addSink: (sink) => parent.addSink(sink),
@@ -287,20 +308,28 @@ function createPrettyFormatter(options) {
287
308
  };
288
309
  }
289
310
  function createConsoleSink(options) {
290
- const useColors = options?.colors ?? process.stdout.isTTY ?? false;
311
+ const useColors = options?.colors ?? (typeof process !== "undefined" ? Boolean(process.stdout?.isTTY) : false);
291
312
  const formatter = createPrettyFormatter({ colors: useColors });
292
313
  const sink = {
293
314
  formatter,
294
315
  write(record, formatted) {
295
316
  const output = formatted ?? formatter.format(record);
296
- const outputWithNewline = output.endsWith(`
297
- `) ? output : `${output}
298
- `;
299
- if (LEVEL_PRIORITY[record.level] >= LEVEL_PRIORITY.warn) {
300
- process.stderr.write(outputWithNewline);
301
- } else {
302
- process.stdout.write(outputWithNewline);
317
+ const outputLine = output.endsWith(`
318
+ `) ? output.slice(0, -1) : output;
319
+ const runtimeConsole = globalThis["console"];
320
+ if (record.level === "fatal" || record.level === "error") {
321
+ runtimeConsole.error(outputLine);
322
+ return;
303
323
  }
324
+ if (record.level === "warn") {
325
+ runtimeConsole.warn(outputLine);
326
+ return;
327
+ }
328
+ if (record.level === "debug" || record.level === "trace") {
329
+ runtimeConsole.debug(outputLine);
330
+ return;
331
+ }
332
+ runtimeConsole.info(outputLine);
304
333
  }
305
334
  };
306
335
  registeredSinks.add(sink);
@@ -311,9 +340,7 @@ function createFileSink(options) {
311
340
  const buffer = [];
312
341
  const { path } = options;
313
342
  const append = options.append ?? true;
314
- if (!append) {
315
- writeFileSync(path, "");
316
- }
343
+ let cachedContent = append ? null : "";
317
344
  const sink = {
318
345
  formatter,
319
346
  write(record, formatted) {
@@ -327,9 +354,14 @@ function createFileSink(options) {
327
354
  if (buffer.length > 0) {
328
355
  const content = buffer.join("");
329
356
  buffer.length = 0;
330
- const file = Bun.file(path);
331
- const existing = await file.exists() ? await file.text() : "";
332
- await Bun.write(path, existing + content);
357
+ if (append) {
358
+ const file = Bun.file(path);
359
+ const existing = await file.exists() ? await file.text() : "";
360
+ await Bun.write(path, existing + content);
361
+ return;
362
+ }
363
+ cachedContent = (cachedContent ?? "") + content;
364
+ await Bun.write(path, cachedContent);
333
365
  }
334
366
  }
335
367
  };
@@ -359,7 +391,43 @@ async function flush() {
359
391
  }
360
392
  await Promise.all(flushPromises);
361
393
  }
394
+ var ENV_LEVEL_MAP = {
395
+ trace: "trace",
396
+ debug: "debug",
397
+ info: "info",
398
+ notice: "info",
399
+ warn: "warn",
400
+ warning: "warn",
401
+ error: "error",
402
+ critical: "fatal",
403
+ alert: "fatal",
404
+ emergency: "fatal",
405
+ fatal: "fatal",
406
+ silent: "silent"
407
+ };
408
+ function resolveLogLevel(level) {
409
+ const envLogLevel = process.env["OUTFITTER_LOG_LEVEL"];
410
+ if (envLogLevel !== undefined) {
411
+ const mapped = ENV_LEVEL_MAP[envLogLevel];
412
+ if (mapped !== undefined) {
413
+ return mapped;
414
+ }
415
+ }
416
+ if (level !== undefined) {
417
+ return level;
418
+ }
419
+ const env = _getEnvironment();
420
+ const defaults = _getEnvironmentDefaults(env);
421
+ if (defaults.logLevel !== null) {
422
+ const mapped = ENV_LEVEL_MAP[defaults.logLevel];
423
+ if (mapped !== undefined) {
424
+ return mapped;
425
+ }
426
+ }
427
+ return "info";
428
+ }
362
429
  export {
430
+ resolveLogLevel,
363
431
  flush,
364
432
  createPrettyFormatter,
365
433
  createLogger,
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",
4
+ "version": "0.3.0",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist"
@@ -27,7 +27,8 @@
27
27
  "clean": "rm -rf dist"
28
28
  },
29
29
  "dependencies": {
30
- "@outfitter/contracts": "0.1.0",
30
+ "@outfitter/config": "0.3.0",
31
+ "@outfitter/contracts": "0.2.0",
31
32
  "@logtape/logtape": "^2.0.0"
32
33
  },
33
34
  "devDependencies": {