@amkisko/json-logging 0.1.0 → 0.1.1

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
@@ -14,7 +14,7 @@ Zero runtime dependencies. Ambient `withContext` / block `tagged` use AsyncLocal
14
14
  | Cloudflare Workers | Compatible | Full ALS with `nodejs_compat`. Without it: import and basic logging still work; `withContext` does not retain fields. |
15
15
  | Browsers | Compatible | Basic logging and sinks work. Ambient context requires a host that provides ALS (unusual in browsers). |
16
16
 
17
- On the JSR package settings page, mark Node.js, Bun, Deno, Cloudflare Workers, and Browsers as compatible. Set the package description to the same summary as `package.json`.
17
+ On the JSR package settings page, mark Node.js, Bun, Deno, Cloudflare Workers, and Browsers as compatible. Keep the package description aligned with `package.json` (`JSR_TOKEN=... ./usr/bin/sync-jsr-settings`, or set it in the Settings tab).
18
18
 
19
19
  ## Install
20
20
 
@@ -1,14 +1,19 @@
1
1
  import { type ConsoleMethodName, type ConsoleMethods } from "./console-state.js";
2
2
  import type { Logger } from "./logger.js";
3
3
  import type { LoggerOptions, LogMessage } from "./types.js";
4
+ /** Options for {@link installConsoleBridge}. */
4
5
  export type ConsoleBridgeOptions = {
6
+ /** Which console methods to patch. Default: log, info, warn, error, debug. */
5
7
  methods?: readonly ConsoleMethodName[];
6
8
  };
9
+ /** Result of {@link installStructuredLogging}: logger plus uninstall handle. */
7
10
  export type StructuredLoggingInstall = {
8
11
  logger: Logger;
9
12
  uninstall: () => void;
10
13
  };
14
+ /** True when console methods are currently routed through a logger. */
11
15
  export declare function isConsoleBridgeInstalled(): boolean;
16
+ /** Restore original console methods if a bridge was installed. */
12
17
  export declare function uninstallConsoleBridge(): void;
13
18
  /**
14
19
  * Routes console.log/info/warn/error/debug through `logger` as structured JSON.
@@ -21,6 +26,7 @@ export declare function installConsoleBridge(logger: Logger, options?: ConsoleBr
21
26
  * Prefer this when the goal is "app + console.* become structured JSON lines".
22
27
  */
23
28
  export declare function installStructuredLogging(options?: LoggerOptions & ConsoleBridgeOptions): StructuredLoggingInstall;
29
+ /** Map `console.*` arguments into a {@link LogMessage} for structured emit. */
24
30
  export declare function messageFromConsoleArgs(args: unknown[]): LogMessage;
25
31
  export type { ConsoleMethods, ConsoleMethodName };
26
32
  //# sourceMappingURL=console-bridge.d.ts.map
@@ -11,9 +11,11 @@ const LEVEL_BY_METHOD = {
11
11
  debug: "debug",
12
12
  };
13
13
  let installedMethods = null;
14
+ /** True when console methods are currently routed through a logger. */
14
15
  export function isConsoleBridgeInstalled() {
15
16
  return getBridgeOriginals() !== null;
16
17
  }
18
+ /** Restore original console methods if a bridge was installed. */
17
19
  export function uninstallConsoleBridge() {
18
20
  const originals = getBridgeOriginals();
19
21
  if (!originals || !installedMethods) {
@@ -73,6 +75,7 @@ export function installStructuredLogging(options = {}) {
73
75
  const uninstall = installConsoleBridge(logger, methods ? { methods } : {});
74
76
  return { logger, uninstall };
75
77
  }
78
+ /** Map `console.*` arguments into a {@link LogMessage} for structured emit. */
76
79
  export function messageFromConsoleArgs(args) {
77
80
  if (args.length === 0) {
78
81
  return "";
@@ -1,11 +1,19 @@
1
+ /** Console method names the bridge may patch. */
1
2
  export type ConsoleMethodName = "log" | "info" | "warn" | "error" | "debug";
3
+ /** Bound console method bag used by sinks and the bridge. */
2
4
  export type ConsoleMethods = {
3
5
  [K in ConsoleMethodName]: (...args: unknown[]) => void;
4
6
  };
7
+ /** Store or clear the pre-patch console methods while the bridge is installed. */
5
8
  export declare function setBridgeOriginals(methods: ConsoleMethods | null): void;
9
+ /** Pre-patch console methods, or null when the bridge is not installed. */
6
10
  export declare function getBridgeOriginals(): ConsoleMethods | null;
11
+ /** Enter a re-entrancy guard so bridge writes use original console methods. */
7
12
  export declare function enterBridge(): void;
13
+ /** Leave the re-entrancy guard opened by {@link enterBridge}. */
8
14
  export declare function leaveBridge(): void;
15
+ /** True while a bridged console write is in progress. */
9
16
  export declare function isInBridge(): boolean;
17
+ /** Snapshot console methods from `target` (defaults to global `console`). */
10
18
  export declare function captureConsoleMethods(target?: Partial<ConsoleMethods> & object): ConsoleMethods;
11
19
  //# sourceMappingURL=console-state.d.ts.map
@@ -1,22 +1,28 @@
1
1
  let bridgeOriginals = null;
2
2
  let bridgeDepth = 0;
3
+ /** Store or clear the pre-patch console methods while the bridge is installed. */
3
4
  export function setBridgeOriginals(methods) {
4
5
  bridgeOriginals = methods;
5
6
  }
7
+ /** Pre-patch console methods, or null when the bridge is not installed. */
6
8
  export function getBridgeOriginals() {
7
9
  return bridgeOriginals;
8
10
  }
11
+ /** Enter a re-entrancy guard so bridge writes use original console methods. */
9
12
  export function enterBridge() {
10
13
  bridgeDepth += 1;
11
14
  }
15
+ /** Leave the re-entrancy guard opened by {@link enterBridge}. */
12
16
  export function leaveBridge() {
13
17
  if (bridgeDepth > 0) {
14
18
  bridgeDepth -= 1;
15
19
  }
16
20
  }
21
+ /** True while a bridged console write is in progress. */
17
22
  export function isInBridge() {
18
23
  return bridgeDepth > 0;
19
24
  }
25
+ /** Snapshot console methods from `target` (defaults to global `console`). */
20
26
  export function captureConsoleMethods(target = console) {
21
27
  return {
22
28
  log: bindMethod(target, "log"),
@@ -1,5 +1,7 @@
1
1
  import type { Logger } from "./logger.js";
2
+ /** Set the process-wide ambient logger used by HTTP adapters. */
2
3
  export declare function bindHttpLogger(logger: Logger): void;
4
+ /** Current ambient HTTP logger, if {@link bindHttpLogger} was called. */
3
5
  export declare function getAmbientHttpLogger(): Logger | undefined;
4
6
  /** Bind options.logger once when building an adapter factory. */
5
7
  export declare function bindLoggerFromOptions(options?: {
@@ -1,7 +1,9 @@
1
1
  let ambientLogger;
2
+ /** Set the process-wide ambient logger used by HTTP adapters. */
2
3
  export function bindHttpLogger(logger) {
3
4
  ambientLogger = logger;
4
5
  }
6
+ /** Current ambient HTTP logger, if {@link bindHttpLogger} was called. */
5
7
  export function getAmbientHttpLogger() {
6
8
  return ambientLogger;
7
9
  }
@@ -1,8 +1,10 @@
1
1
  import type { Logger } from "./logger.js";
2
2
  import type { LoggerOptions } from "./types.js";
3
+ /** Headers-like object with a `get` method (Fetch `Headers`, etc.). */
3
4
  export type HeaderBag = {
4
5
  get(name: string): string | null;
5
6
  };
7
+ /** Request fields used to build ambient HTTP context. */
6
8
  export type HttpRequestInput = {
7
9
  method?: string;
8
10
  url?: string;
@@ -10,9 +12,13 @@ export type HttpRequestInput = {
10
12
  headers?: HeaderBag | Record<string, string | string[] | undefined>;
11
13
  requestId?: string;
12
14
  };
15
+ /** Options for {@link withHttpContext} and framework adapters. */
13
16
  export type WithHttpContextOptions = {
17
+ /** Header name for inbound request id. Default `x-request-id`. */
14
18
  headerName?: string;
19
+ /** Explicit logger; otherwise ambient or a new logger from loggerOptions. */
15
20
  logger?: Logger;
21
+ /** Used when creating a logger if none is bound. */
16
22
  loggerOptions?: LoggerOptions;
17
23
  /** Optional allowlisted fields merged under http fields (http fields win). */
18
24
  extra?: Record<string, unknown>;
@@ -24,6 +30,7 @@ export type WithHttpContextOptions = {
24
30
  /** When true, adapters emit one completion line after the request. */
25
31
  logCompletion?: boolean;
26
32
  };
33
+ /** Fields for {@link logRequestCompletion}. */
27
34
  export type RequestCompletionFields = {
28
35
  method?: string;
29
36
  path?: string;
@@ -33,17 +40,26 @@ export type RequestCompletionFields = {
33
40
  };
34
41
  /** Accept inbound request ids that are short and correlation-safe. */
35
42
  export declare const REQUEST_ID_PATTERN: RegExp;
43
+ /** Monotonic fallback request id when crypto UUID is unavailable. */
36
44
  export declare function fallbackRequestId(now?: () => number): string;
45
+ /** Prefer `crypto.randomUUID`, else {@link fallbackRequestId}. */
37
46
  export declare function defaultRandomId(randomUUID?: (() => string) | null): string;
47
+ /** True when `value` matches {@link REQUEST_ID_PATTERN}. */
38
48
  export declare function isValidRequestId(value: string): boolean;
49
+ /** Keep a valid inbound id, otherwise allocate a new one. */
39
50
  export declare function allocateRequestId(existing: string | null | undefined, randomId?: () => string): string;
51
+ /** Pathname from an absolute or relative URL string. */
40
52
  export declare function requestPathFromUrl(url: string): string | undefined;
53
+ /** Pick named keys from a bag; missing keys are omitted. */
41
54
  export declare function pickContextKeys(bag: Record<string, unknown> | undefined, keys: readonly string[] | undefined): Record<string, unknown>;
55
+ /** Build requestId / method / path fields from {@link HttpRequestInput}. */
42
56
  export declare function httpFieldsFrom(input: HttpRequestInput, headerName?: string): Record<string, unknown>;
57
+ /** Copy allowlisted header names into a sanitized string map. */
43
58
  export declare function pickAllowlistedHeaders(headers: HeaderBag | Record<string, string | string[] | undefined> | undefined, names: readonly string[] | undefined): Record<string, unknown>;
44
59
  /**
45
60
  * One structured access line. Level is error when status >= 500 or error is set.
46
61
  */
47
62
  export declare function logRequestCompletion(logger: Logger, fields: RequestCompletionFields): void;
63
+ /** Run `fn` under ALS context built from the HTTP request input. */
48
64
  export declare function withHttpContext<T>(input: HttpRequestInput, fn: () => T | Promise<T>, options?: WithHttpContextOptions): T | Promise<T>;
49
65
  //# sourceMappingURL=http-context.d.ts.map
@@ -3,10 +3,12 @@ import { sanitizeError, sanitizeString } from "./sanitize.js";
3
3
  /** Accept inbound request ids that are short and correlation-safe. */
4
4
  export const REQUEST_ID_PATTERN = /^[\w.\-:]{1,128}$/;
5
5
  let fallbackCounter = 0;
6
+ /** Monotonic fallback request id when crypto UUID is unavailable. */
6
7
  export function fallbackRequestId(now = Date.now) {
7
8
  fallbackCounter += 1;
8
9
  return `req_${now().toString(36)}_${fallbackCounter.toString(36)}`;
9
10
  }
11
+ /** Prefer `crypto.randomUUID`, else {@link fallbackRequestId}. */
10
12
  export function defaultRandomId(randomUUID) {
11
13
  const resolved = randomUUID === undefined ? globalThis.crypto?.randomUUID?.bind(globalThis.crypto) : randomUUID;
12
14
  if (typeof resolved === "function") {
@@ -14,9 +16,11 @@ export function defaultRandomId(randomUUID) {
14
16
  }
15
17
  return fallbackRequestId();
16
18
  }
19
+ /** True when `value` matches {@link REQUEST_ID_PATTERN}. */
17
20
  export function isValidRequestId(value) {
18
21
  return REQUEST_ID_PATTERN.test(value);
19
22
  }
23
+ /** Keep a valid inbound id, otherwise allocate a new one. */
20
24
  export function allocateRequestId(existing, randomId = defaultRandomId) {
21
25
  if (existing && isValidRequestId(existing)) {
22
26
  return existing;
@@ -28,6 +32,7 @@ export function allocateRequestId(existing, randomId = defaultRandomId) {
28
32
  return fallbackRequestId();
29
33
  }
30
34
  }
35
+ /** Pathname from an absolute or relative URL string. */
31
36
  export function requestPathFromUrl(url) {
32
37
  if (url.length === 0) {
33
38
  return undefined;
@@ -66,6 +71,7 @@ function headerGet(headers, name) {
66
71
  }
67
72
  return typeof direct === "string" ? direct : null;
68
73
  }
74
+ /** Pick named keys from a bag; missing keys are omitted. */
69
75
  export function pickContextKeys(bag, keys) {
70
76
  if (!bag || !keys || keys.length === 0) {
71
77
  return {};
@@ -78,6 +84,7 @@ export function pickContextKeys(bag, keys) {
78
84
  }
79
85
  return out;
80
86
  }
87
+ /** Build requestId / method / path fields from {@link HttpRequestInput}. */
81
88
  export function httpFieldsFrom(input, headerName = "x-request-id") {
82
89
  const fromHeader = headerGet(input.headers, headerName);
83
90
  const requestId = allocateRequestId(input.requestId ?? fromHeader);
@@ -91,6 +98,7 @@ export function httpFieldsFrom(input, headerName = "x-request-id") {
91
98
  }
92
99
  return fields;
93
100
  }
101
+ /** Copy allowlisted header names into a sanitized string map. */
94
102
  export function pickAllowlistedHeaders(headers, names) {
95
103
  if (!headers || !names || names.length === 0) {
96
104
  return {};
@@ -135,6 +143,7 @@ export function logRequestCompletion(logger, fields) {
135
143
  logger.info(payload);
136
144
  }
137
145
  }
146
+ /** Run `fn` under ALS context built from the HTTP request input. */
138
147
  export function withHttpContext(input, fn, options = {}) {
139
148
  const headerName = options.headerName ?? "x-request-id";
140
149
  const fields = {
package/dist/next.d.ts CHANGED
@@ -31,6 +31,7 @@ export type WithRequestLoggingOptions = {
31
31
  logger?: Logger;
32
32
  loggerOptions?: LoggerOptions;
33
33
  };
34
+ /** Re-export request-id helpers for Next entrypoint consumers. */
34
35
  export { allocateRequestId, defaultRandomId, fallbackRequestId };
35
36
  /**
36
37
  * Wrap a Next.js middleware or route handler with request-scoped context.
package/dist/next.js CHANGED
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import { bindLoggerFromOptions } from "./http-ambient.js";
17
17
  import { allocateRequestId, defaultRandomId, fallbackRequestId, withHttpContext, } from "./http-context.js";
18
+ /** Re-export request-id helpers for Next entrypoint consumers. */
18
19
  export { allocateRequestId, defaultRandomId, fallbackRequestId };
19
20
  /**
20
21
  * Wrap a Next.js middleware or route handler with request-scoped context.
@@ -9,12 +9,17 @@ export declare const MAX_DEPTH = 10;
9
9
  export declare const MAX_BACKTRACE_LINES = 20;
10
10
  /** Case-insensitive sensitive key names (password, secret, token, authorization, api_key, ...). */
11
11
  export declare const SENSITIVE_KEY_PATTERN: RegExp;
12
+ /** True when a key name matches {@link SENSITIVE_KEY_PATTERN}. */
12
13
  export declare function isSensitiveKey(key: string): boolean;
14
+ /** Strip control characters and truncate to {@link MAX_STRING_LENGTH}. */
13
15
  export declare function sanitizeString(value: string): string;
16
+ /** Coerce tags to non-empty sanitized strings, dropping nullish entries. */
14
17
  export declare function prepareTags(tags: readonly unknown[]): string[];
18
+ /** Shape an Error under an `error` key for log payloads (name, message, stack, cause). */
15
19
  export declare function sanitizeError(error: Error): {
16
20
  [key: string]: JsonValue;
17
21
  };
18
22
  export declare function sanitizeValue(value: unknown, depth: number, seen: WeakSet<object>): JsonValue;
23
+ /** Sanitize a plain object: filter sensitive keys, bound depth/size, mark cycles. */
19
24
  export declare function sanitizeHash(hash: Record<string, unknown>, depth?: number): Record<string, unknown>;
20
25
  //# sourceMappingURL=sanitize.d.ts.map
package/dist/sanitize.js CHANGED
@@ -12,9 +12,11 @@ const CONTROL_CHARS = /[\x00-\x1F\x7F]/g;
12
12
  export const SENSITIVE_KEY_PATTERN = /(passw|pwd|secret|token|authorization|auth|api[_-]?key|_key$|crypt|salt|certificate|otp|ssn|cvv|cvc|credential)/i;
13
13
  const FILTERED = "[FILTERED]";
14
14
  const CIRCULAR = "[Circular]";
15
+ /** True when a key name matches {@link SENSITIVE_KEY_PATTERN}. */
15
16
  export function isSensitiveKey(key) {
16
17
  return SENSITIVE_KEY_PATTERN.test(key);
17
18
  }
19
+ /** Strip control characters and truncate to {@link MAX_STRING_LENGTH}. */
18
20
  export function sanitizeString(value) {
19
21
  try {
20
22
  let result = value.replace(CONTROL_CHARS, "");
@@ -27,6 +29,7 @@ export function sanitizeString(value) {
27
29
  return "<sanitization_error>";
28
30
  }
29
31
  }
32
+ /** Coerce tags to non-empty sanitized strings, dropping nullish entries. */
30
33
  export function prepareTags(tags) {
31
34
  const prepared = [];
32
35
  for (const tag of tags) {
@@ -40,6 +43,7 @@ export function prepareTags(tags) {
40
43
  }
41
44
  return prepared;
42
45
  }
46
+ /** Shape an Error under an `error` key for log payloads (name, message, stack, cause). */
43
47
  export function sanitizeError(error) {
44
48
  try {
45
49
  const body = {
@@ -134,6 +138,7 @@ function sanitizeObject(value, depth, seen) {
134
138
  }
135
139
  return result;
136
140
  }
141
+ /** Sanitize a plain object: filter sensitive keys, bound depth/size, mark cycles. */
137
142
  export function sanitizeHash(hash, depth = 0) {
138
143
  try {
139
144
  if (depth > MAX_DEPTH) {
@@ -6,6 +6,7 @@ export { currentTimestamp, defaultPerformance, formatTimestamp, } from "./timest
6
6
  export declare const SYSTEM_KEYS: ReadonlySet<string>;
7
7
  /** Normalize a log message into root fields (string, object, or Error). */
8
8
  export declare function parseMessage(message: LogMessage): Record<string, unknown>;
9
+ /** Inputs for {@link buildRecord} on the log hot path. */
9
10
  export type BuildLineInput = {
10
11
  message: LogMessage;
11
12
  level: Level;
@@ -21,7 +22,10 @@ export type BuildLineInput = {
21
22
  serializers?: Readonly<Record<string, FieldSerializer>>;
22
23
  contextProvider?: ContextProvider;
23
24
  };
25
+ /** Assemble one log record: message, severity, tags, context, serializers, redact. */
24
26
  export declare function buildRecord(input: BuildLineInput): LogRecord;
27
+ /** Serialize a record to one JSON line with a trailing newline. */
25
28
  export declare function toJsonLine(record: LogRecord): string;
29
+ /** Never-raise fallback record when the primary emit path throws. */
26
30
  export declare function buildFallbackRecord(level: Level, message: unknown, error: unknown, clock?: () => Date): LogRecord;
27
31
  //# sourceMappingURL=serialize.d.ts.map
package/dist/serialize.js CHANGED
@@ -82,6 +82,7 @@ function mergeProviderContext(payload, provider) {
82
82
  ...readObjectField(payload, "context"),
83
83
  };
84
84
  }
85
+ /** Assemble one log record: message, severity, tags, context, serializers, redact. */
85
86
  export function buildRecord(input) {
86
87
  const payload = {};
87
88
  if (input.base && Object.keys(input.base).length > 0) {
@@ -176,11 +177,13 @@ function uniqueTags(tags) {
176
177
  }
177
178
  return result;
178
179
  }
180
+ /** Serialize a record to one JSON line with a trailing newline. */
179
181
  export function toJsonLine(record) {
180
182
  const seen = new WeakSet();
181
183
  const safe = sanitizeValue(record, 0, seen);
182
184
  return `${JSON.stringify(safe)}\n`;
183
185
  }
186
+ /** Never-raise fallback record when the primary emit path throws. */
184
187
  export function buildFallbackRecord(level, message, error, clock) {
185
188
  const err = error instanceof Error ? error : new Error(typeof error === "string" ? error : "unknown");
186
189
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amkisko/json-logging",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Structured JSON logging with ActiveSupport-style tagged and withContext helpers",
5
5
  "type": "module",
6
6
  "license": "MIT",