@amkisko/json-logging 0.0.0-stage → 0.1.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/LICENSE.md +21 -0
- package/README.md +231 -2
- package/dist/bun-serve.d.ts +30 -0
- package/dist/bun-serve.js +60 -0
- package/dist/console-bridge.d.ts +26 -0
- package/dist/console-bridge.js +155 -0
- package/dist/console-state.d.ts +11 -0
- package/dist/console-state.js +40 -0
- package/dist/console.d.ts +18 -0
- package/dist/console.js +17 -0
- package/dist/context.d.ts +40 -0
- package/dist/context.js +162 -0
- package/dist/express.d.ts +41 -0
- package/dist/express.js +48 -0
- package/dist/fastify.d.ts +37 -0
- package/dist/fastify.js +53 -0
- package/dist/hono.d.ts +37 -0
- package/dist/hono.js +50 -0
- package/dist/http-ambient.d.ts +10 -0
- package/dist/http-ambient.js +18 -0
- package/dist/http-context.d.ts +49 -0
- package/dist/http-context.js +147 -0
- package/dist/http.d.ts +24 -0
- package/dist/http.js +34 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +26 -0
- package/dist/json-logging.d.ts +9 -0
- package/dist/json-logging.js +12 -0
- package/dist/koa.d.ts +34 -0
- package/dist/koa.js +54 -0
- package/dist/logger.d.ts +46 -0
- package/dist/logger.js +130 -0
- package/dist/next.d.ts +40 -0
- package/dist/next.js +34 -0
- package/dist/objects.d.ts +12 -0
- package/dist/objects.js +18 -0
- package/dist/redact.d.ts +9 -0
- package/dist/redact.js +77 -0
- package/dist/remix.d.ts +43 -0
- package/dist/remix.js +38 -0
- package/dist/sanitize.d.ts +20 -0
- package/dist/sanitize.js +148 -0
- package/dist/serialize.d.ts +27 -0
- package/dist/serialize.js +200 -0
- package/dist/severity.d.ts +10 -0
- package/dist/severity.js +25 -0
- package/dist/sinks.d.ts +14 -0
- package/dist/sinks.js +66 -0
- package/dist/timestamp.d.ts +15 -0
- package/dist/timestamp.js +47 -0
- package/dist/types.d.ts +42 -0
- package/dist/types.js +2 -0
- package/package.json +98 -4
package/dist/http.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP helpers: ambient logger binding, request context, and completion lines.
|
|
3
|
+
* Framework adapters live on separate entrypoints (`./hono`, `./express`, ...).
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { bindHttpLogger, httpLogger, withHttpContext } from "@amkisko/json-logging/http";
|
|
8
|
+
*
|
|
9
|
+
* bindHttpLogger(httpLogger({ loggerOptions: { base: { service: "api" } } }));
|
|
10
|
+
* await withHttpContext({ method: "GET", path: "/" }, () => {
|
|
11
|
+
* httpLogger().info("inside request");
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import { bindHttpLogger, getAmbientHttpLogger } from "./http-ambient.js";
|
|
18
|
+
import { createLogger } from "./json-logging.js";
|
|
19
|
+
export { bindHttpLogger, bindLoggerFromOptions, resetHttpLoggerForTests, } from "./http-ambient.js";
|
|
20
|
+
export { allocateRequestId, defaultRandomId, fallbackRequestId, httpFieldsFrom, isValidRequestId, logRequestCompletion, pickAllowlistedHeaders, pickContextKeys, REQUEST_ID_PATTERN, requestPathFromUrl, withHttpContext, } from "./http-context.js";
|
|
21
|
+
/** Resolve the ambient HTTP logger, or create and bind one from options. */
|
|
22
|
+
export function httpLogger(options) {
|
|
23
|
+
if (options?.logger) {
|
|
24
|
+
return options.logger;
|
|
25
|
+
}
|
|
26
|
+
const ambient = getAmbientHttpLogger();
|
|
27
|
+
if (ambient) {
|
|
28
|
+
return ambient;
|
|
29
|
+
}
|
|
30
|
+
const created = createLogger(options?.loggerOptions);
|
|
31
|
+
bindHttpLogger(created);
|
|
32
|
+
return created;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=http.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured JSON logging with ActiveSupport-style `tagged` and `withContext`.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { createLogger, withContext } from "@amkisko/json-logging";
|
|
7
|
+
*
|
|
8
|
+
* const logger = createLogger({ base: { service: "api" } });
|
|
9
|
+
* await withContext({ requestId: "abc" }, async () => {
|
|
10
|
+
* logger.info("handled request");
|
|
11
|
+
* });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
export type { ConsoleMethodName, ConsoleMethods } from "./console-state.js";
|
|
17
|
+
export { captureConsoleMethods, getBridgeOriginals, isInBridge, } from "./console-state.js";
|
|
18
|
+
export type { AlsConstructor, AlsInstance, ContextFields } from "./context.js";
|
|
19
|
+
export { getBlockTags, getContext, isAsyncLocalStorageAvailable, peekLogStore, withContext, } from "./context.js";
|
|
20
|
+
export { createLogger, JsonLogging } from "./json-logging.js";
|
|
21
|
+
export { Logger } from "./logger.js";
|
|
22
|
+
export type { CompiledRedactPath } from "./redact.js";
|
|
23
|
+
export { applyCompiledRedactPaths, applyRedactPaths, compileRedactPaths, } from "./redact.js";
|
|
24
|
+
export { isSensitiveKey, MAX_CONTEXT_SIZE, MAX_DEPTH, MAX_STRING_LENGTH, prepareTags, sanitizeError, sanitizeHash, sanitizeString, } from "./sanitize.js";
|
|
25
|
+
export { buildFallbackRecord, buildRecord, currentTimestamp, formatTimestamp, parseMessage, SYSTEM_KEYS, toJsonLine, } from "./serialize.js";
|
|
26
|
+
export { isEnabled, LEVEL_ORDER, SEVERITY_NAMES, severityName } from "./severity.js";
|
|
27
|
+
export { consoleSink, consoleSinkFrom, streamSink } from "./sinks.js";
|
|
28
|
+
export type { PerformanceLike } from "./timestamp.js";
|
|
29
|
+
export { defaultPerformance } from "./timestamp.js";
|
|
30
|
+
export type { ContextProvider, FieldSerializer, JsonPrimitive, JsonValue, Level, LoggerOptions, LogMessage, LogRecord, SeverityName, Sink, } from "./types.js";
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured JSON logging with ActiveSupport-style `tagged` and `withContext`.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { createLogger, withContext } from "@amkisko/json-logging";
|
|
7
|
+
*
|
|
8
|
+
* const logger = createLogger({ base: { service: "api" } });
|
|
9
|
+
* await withContext({ requestId: "abc" }, async () => {
|
|
10
|
+
* logger.info("handled request");
|
|
11
|
+
* });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
export { captureConsoleMethods, getBridgeOriginals, isInBridge, } from "./console-state.js";
|
|
17
|
+
export { getBlockTags, getContext, isAsyncLocalStorageAvailable, peekLogStore, withContext, } from "./context.js";
|
|
18
|
+
export { createLogger, JsonLogging } from "./json-logging.js";
|
|
19
|
+
export { Logger } from "./logger.js";
|
|
20
|
+
export { applyCompiledRedactPaths, applyRedactPaths, compileRedactPaths, } from "./redact.js";
|
|
21
|
+
export { isSensitiveKey, MAX_CONTEXT_SIZE, MAX_DEPTH, MAX_STRING_LENGTH, prepareTags, sanitizeError, sanitizeHash, sanitizeString, } from "./sanitize.js";
|
|
22
|
+
export { buildFallbackRecord, buildRecord, currentTimestamp, formatTimestamp, parseMessage, SYSTEM_KEYS, toJsonLine, } from "./serialize.js";
|
|
23
|
+
export { isEnabled, LEVEL_ORDER, SEVERITY_NAMES, severityName } from "./severity.js";
|
|
24
|
+
export { consoleSink, consoleSinkFrom, streamSink } from "./sinks.js";
|
|
25
|
+
export { defaultPerformance } from "./timestamp.js";
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Logger } from "./logger.js";
|
|
2
|
+
import type { LoggerOptions } from "./types.js";
|
|
3
|
+
/** Create a {@link Logger} with optional level, sink, base fields, redact, and serializers. */
|
|
4
|
+
export declare function createLogger(options?: LoggerOptions): Logger;
|
|
5
|
+
/** ActiveSupport-style factory: `JsonLogging.new(options)` mirrors {@link createLogger}. */
|
|
6
|
+
export declare const JsonLogging: {
|
|
7
|
+
new: (options?: LoggerOptions) => Logger;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=json-logging.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Logger } from "./logger.js";
|
|
2
|
+
/** Create a {@link Logger} with optional level, sink, base fields, redact, and serializers. */
|
|
3
|
+
export function createLogger(options = {}) {
|
|
4
|
+
return new Logger(options);
|
|
5
|
+
}
|
|
6
|
+
/** ActiveSupport-style factory: `JsonLogging.new(options)` mirrors {@link createLogger}. */
|
|
7
|
+
export const JsonLogging = {
|
|
8
|
+
new(options = {}) {
|
|
9
|
+
return createLogger(options);
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=json-logging.js.map
|
package/dist/koa.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Koa middleware that attaches request context (and optional completion lines).
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { withKoaLogging } from "@amkisko/json-logging/koa";
|
|
7
|
+
* app.use(withKoaLogging({ logCompletion: true }));
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import type { Logger } from "./logger.js";
|
|
13
|
+
import type { LoggerOptions } from "./types.js";
|
|
14
|
+
/** Minimal Koa context shape (no Koa runtime dependency). */
|
|
15
|
+
export type KoaContextLike = {
|
|
16
|
+
method: string;
|
|
17
|
+
path?: string;
|
|
18
|
+
url?: string;
|
|
19
|
+
status?: number;
|
|
20
|
+
get: (name: string) => string;
|
|
21
|
+
};
|
|
22
|
+
/** Options for {@link withKoaLogging}. */
|
|
23
|
+
export type WithKoaLoggingOptions = {
|
|
24
|
+
headerName?: string;
|
|
25
|
+
logger?: Logger;
|
|
26
|
+
loggerOptions?: LoggerOptions;
|
|
27
|
+
headersAllowlist?: readonly string[];
|
|
28
|
+
logCompletion?: boolean;
|
|
29
|
+
};
|
|
30
|
+
/** Koa-style middleware function. */
|
|
31
|
+
export type KoaMiddleware = (ctx: KoaContextLike, next: () => Promise<void>) => Promise<void>;
|
|
32
|
+
/** Build Koa middleware: `(ctx, next) => ...`. Bind loggers via `./http`. */
|
|
33
|
+
export declare function withKoaLogging(options?: WithKoaLoggingOptions): KoaMiddleware;
|
|
34
|
+
//# sourceMappingURL=koa.d.ts.map
|
package/dist/koa.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Koa middleware that attaches request context (and optional completion lines).
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { withKoaLogging } from "@amkisko/json-logging/koa";
|
|
7
|
+
* app.use(withKoaLogging({ logCompletion: true }));
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import { bindLoggerFromOptions, getAmbientHttpLogger } from "./http-ambient.js";
|
|
13
|
+
import { httpFieldsFrom, logRequestCompletion, withHttpContext, } from "./http-context.js";
|
|
14
|
+
/** Build Koa middleware: `(ctx, next) => ...`. Bind loggers via `./http`. */
|
|
15
|
+
export function withKoaLogging(options = {}) {
|
|
16
|
+
bindLoggerFromOptions(options);
|
|
17
|
+
const headerName = options.headerName ?? "x-request-id";
|
|
18
|
+
return async (ctx, next) => {
|
|
19
|
+
const existing = ctx.get(headerName);
|
|
20
|
+
const input = {
|
|
21
|
+
method: ctx.method,
|
|
22
|
+
path: ctx.path,
|
|
23
|
+
url: ctx.url,
|
|
24
|
+
requestId: existing.length > 0 ? existing : undefined,
|
|
25
|
+
headers: {
|
|
26
|
+
get: (name) => {
|
|
27
|
+
const value = ctx.get(name);
|
|
28
|
+
return value.length > 0 ? value : null;
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
const started = Date.now();
|
|
33
|
+
const fields = httpFieldsFrom(input, headerName);
|
|
34
|
+
await withHttpContext(input, async () => {
|
|
35
|
+
try {
|
|
36
|
+
await next();
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
if (options.logCompletion) {
|
|
40
|
+
const logger = options.logger ?? getAmbientHttpLogger();
|
|
41
|
+
if (logger) {
|
|
42
|
+
logRequestCompletion(logger, {
|
|
43
|
+
method: typeof fields.method === "string" ? fields.method : undefined,
|
|
44
|
+
path: typeof fields.path === "string" ? fields.path : undefined,
|
|
45
|
+
status: typeof ctx.status === "number" ? ctx.status : 0,
|
|
46
|
+
elapsedMs: Date.now() - started,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}, options);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=koa.js.map
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ContextProvider, FieldSerializer, Level, LoggerOptions, LogMessage, Sink } from "./types.js";
|
|
2
|
+
/** Structured logger: severity methods, sticky tags, child loggers, never-raise emit path. */
|
|
3
|
+
export declare class Logger {
|
|
4
|
+
/** Minimum level that will be written. */
|
|
5
|
+
readonly level: Level;
|
|
6
|
+
/** Destination for JSON lines. */
|
|
7
|
+
readonly sink: Sink;
|
|
8
|
+
/** Sanitized root fields merged into every line. */
|
|
9
|
+
readonly base: Record<string, unknown>;
|
|
10
|
+
/** Optional clock override (tests). */
|
|
11
|
+
readonly clock?: () => Date;
|
|
12
|
+
/** Sticky tags from construction or {@link Logger.tagged}. */
|
|
13
|
+
readonly tags: readonly string[];
|
|
14
|
+
/** Configured redact paths (dot notation). */
|
|
15
|
+
readonly redact: readonly string[];
|
|
16
|
+
/** Root-field serializers. */
|
|
17
|
+
readonly serializers: Readonly<Record<string, FieldSerializer>>;
|
|
18
|
+
/** Optional ambient context provider. */
|
|
19
|
+
readonly contextProvider?: ContextProvider;
|
|
20
|
+
private readonly compiledRedact;
|
|
21
|
+
constructor(options?: LoggerOptions);
|
|
22
|
+
/** True when a message at `level` would be emitted. */
|
|
23
|
+
isLevelEnabled(level: Level): boolean;
|
|
24
|
+
/** Emit a debug line when enabled. */
|
|
25
|
+
debug(message: LogMessage): void;
|
|
26
|
+
/** Emit an info line when enabled. */
|
|
27
|
+
info(message: LogMessage): void;
|
|
28
|
+
/** Emit a warn line when enabled. */
|
|
29
|
+
warn(message: LogMessage): void;
|
|
30
|
+
/** Emit an error line when enabled. */
|
|
31
|
+
error(message: LogMessage): void;
|
|
32
|
+
/** Emit a fatal line when enabled. */
|
|
33
|
+
fatal(message: LogMessage): void;
|
|
34
|
+
/** Child logger with extra root bindings merged into `base`. */
|
|
35
|
+
child(bindings: Record<string, unknown>): Logger;
|
|
36
|
+
/**
|
|
37
|
+
* Block form runs `fn` with nested tags; sticky form returns a child logger.
|
|
38
|
+
* Pass tags then a callback, or only tags for a sticky child.
|
|
39
|
+
*/
|
|
40
|
+
tagged(callback: () => unknown): unknown;
|
|
41
|
+
tagged(tag: string | number | boolean, callback: () => unknown): unknown;
|
|
42
|
+
tagged(tag: string | number | boolean, tag2: string | number | boolean, callback: () => unknown): unknown;
|
|
43
|
+
tagged(...tags: (string | number | boolean | null | undefined)[]): Logger;
|
|
44
|
+
private log;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=logger.d.ts.map
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { withBlockTags } from "./context.js";
|
|
2
|
+
import { compileRedactPaths } from "./redact.js";
|
|
3
|
+
import { prepareTags, sanitizeHash } from "./sanitize.js";
|
|
4
|
+
import { buildFallbackRecord, buildRecord, toJsonLine } from "./serialize.js";
|
|
5
|
+
import { isEnabled } from "./severity.js";
|
|
6
|
+
import { consoleSink } from "./sinks.js";
|
|
7
|
+
function isCallback(value) {
|
|
8
|
+
return typeof value === "function";
|
|
9
|
+
}
|
|
10
|
+
/** Structured logger: severity methods, sticky tags, child loggers, never-raise emit path. */
|
|
11
|
+
export class Logger {
|
|
12
|
+
/** Minimum level that will be written. */
|
|
13
|
+
level;
|
|
14
|
+
/** Destination for JSON lines. */
|
|
15
|
+
sink;
|
|
16
|
+
/** Sanitized root fields merged into every line. */
|
|
17
|
+
base;
|
|
18
|
+
/** Optional clock override (tests). */
|
|
19
|
+
clock;
|
|
20
|
+
/** Sticky tags from construction or {@link Logger.tagged}. */
|
|
21
|
+
tags;
|
|
22
|
+
/** Configured redact paths (dot notation). */
|
|
23
|
+
redact;
|
|
24
|
+
/** Root-field serializers. */
|
|
25
|
+
serializers;
|
|
26
|
+
/** Optional ambient context provider. */
|
|
27
|
+
contextProvider;
|
|
28
|
+
compiledRedact;
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
this.level = options.level ?? "info";
|
|
31
|
+
this.sink = options.sink ?? consoleSink();
|
|
32
|
+
this.base = options.base ? sanitizeHash(options.base) : {};
|
|
33
|
+
this.clock = options.clock;
|
|
34
|
+
this.tags = options.tags ? prepareTags(options.tags) : [];
|
|
35
|
+
this.redact = options.redact ? [...options.redact] : [];
|
|
36
|
+
this.serializers = options.serializers ? { ...options.serializers } : {};
|
|
37
|
+
this.contextProvider = options.contextProvider;
|
|
38
|
+
this.compiledRedact = compileRedactPaths(this.redact);
|
|
39
|
+
}
|
|
40
|
+
/** True when a message at `level` would be emitted. */
|
|
41
|
+
isLevelEnabled(level) {
|
|
42
|
+
return isEnabled(this.level, level);
|
|
43
|
+
}
|
|
44
|
+
/** Emit a debug line when enabled. */
|
|
45
|
+
debug(message) {
|
|
46
|
+
this.log("debug", message);
|
|
47
|
+
}
|
|
48
|
+
/** Emit an info line when enabled. */
|
|
49
|
+
info(message) {
|
|
50
|
+
this.log("info", message);
|
|
51
|
+
}
|
|
52
|
+
/** Emit a warn line when enabled. */
|
|
53
|
+
warn(message) {
|
|
54
|
+
this.log("warn", message);
|
|
55
|
+
}
|
|
56
|
+
/** Emit an error line when enabled. */
|
|
57
|
+
error(message) {
|
|
58
|
+
this.log("error", message);
|
|
59
|
+
}
|
|
60
|
+
/** Emit a fatal line when enabled. */
|
|
61
|
+
fatal(message) {
|
|
62
|
+
this.log("fatal", message);
|
|
63
|
+
}
|
|
64
|
+
/** Child logger with extra root bindings merged into `base`. */
|
|
65
|
+
child(bindings) {
|
|
66
|
+
return new Logger({
|
|
67
|
+
level: this.level,
|
|
68
|
+
sink: this.sink,
|
|
69
|
+
clock: this.clock,
|
|
70
|
+
tags: this.tags,
|
|
71
|
+
redact: this.redact,
|
|
72
|
+
serializers: this.serializers,
|
|
73
|
+
contextProvider: this.contextProvider,
|
|
74
|
+
base: { ...this.base, ...sanitizeHash(bindings) },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
tagged(...args) {
|
|
78
|
+
if (args.length === 0) {
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
const last = args[args.length - 1];
|
|
82
|
+
if (isCallback(last)) {
|
|
83
|
+
const tags = prepareTags(args.slice(0, -1));
|
|
84
|
+
return withBlockTags(tags, last);
|
|
85
|
+
}
|
|
86
|
+
const tags = prepareTags(args);
|
|
87
|
+
return new Logger({
|
|
88
|
+
level: this.level,
|
|
89
|
+
sink: this.sink,
|
|
90
|
+
clock: this.clock,
|
|
91
|
+
base: this.base,
|
|
92
|
+
redact: this.redact,
|
|
93
|
+
serializers: this.serializers,
|
|
94
|
+
contextProvider: this.contextProvider,
|
|
95
|
+
tags: [...this.tags, ...tags],
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
log(level, message) {
|
|
99
|
+
if (!this.isLevelEnabled(level)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const record = buildRecord({
|
|
104
|
+
message,
|
|
105
|
+
level,
|
|
106
|
+
stickyTags: this.tags,
|
|
107
|
+
base: this.base,
|
|
108
|
+
preparedBase: true,
|
|
109
|
+
preparedTags: true,
|
|
110
|
+
clock: this.clock,
|
|
111
|
+
compiledRedact: this.compiledRedact,
|
|
112
|
+
serializers: this.serializers,
|
|
113
|
+
contextProvider: this.contextProvider,
|
|
114
|
+
});
|
|
115
|
+
const line = toJsonLine(record);
|
|
116
|
+
void this.sink.write(line, record);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
try {
|
|
120
|
+
const fallback = buildFallbackRecord(level, message, error, this.clock);
|
|
121
|
+
const line = toJsonLine(fallback);
|
|
122
|
+
void this.sink.write(line, fallback);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Last resort: never throw from the log path.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=logger.js.map
|
package/dist/next.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js helpers: wrap middleware or route handlers with request-scoped log context.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { withRequestLogging } from "@amkisko/json-logging/next";
|
|
7
|
+
* import { httpLogger } from "@amkisko/json-logging/http";
|
|
8
|
+
*
|
|
9
|
+
* export default withRequestLogging(async (req) => {
|
|
10
|
+
* httpLogger().info({ path: req.nextUrl?.pathname });
|
|
11
|
+
* });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import { allocateRequestId, defaultRandomId, fallbackRequestId } from "./http-context.js";
|
|
17
|
+
import type { Logger } from "./logger.js";
|
|
18
|
+
import type { LoggerOptions } from "./types.js";
|
|
19
|
+
/** Minimal Next request shape used by the wrapper (no Next runtime dependency). */
|
|
20
|
+
export type RequestLike = {
|
|
21
|
+
headers: Headers;
|
|
22
|
+
method?: string;
|
|
23
|
+
url?: string;
|
|
24
|
+
nextUrl?: {
|
|
25
|
+
pathname?: string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/** Options for {@link withRequestLogging}. */
|
|
29
|
+
export type WithRequestLoggingOptions = {
|
|
30
|
+
headerName?: string;
|
|
31
|
+
logger?: Logger;
|
|
32
|
+
loggerOptions?: LoggerOptions;
|
|
33
|
+
};
|
|
34
|
+
export { allocateRequestId, defaultRandomId, fallbackRequestId };
|
|
35
|
+
/**
|
|
36
|
+
* Wrap a Next.js middleware or route handler with request-scoped context.
|
|
37
|
+
* Bind loggers via `@amkisko/json-logging/http` (`bindHttpLogger` / `httpLogger`).
|
|
38
|
+
*/
|
|
39
|
+
export declare function withRequestLogging<TArgs extends unknown[], TResult>(handler: (request: RequestLike, ...rest: TArgs) => TResult | Promise<TResult>, options?: WithRequestLoggingOptions): (request: RequestLike, ...rest: TArgs) => Promise<TResult>;
|
|
40
|
+
//# sourceMappingURL=next.d.ts.map
|
package/dist/next.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js helpers: wrap middleware or route handlers with request-scoped log context.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { withRequestLogging } from "@amkisko/json-logging/next";
|
|
7
|
+
* import { httpLogger } from "@amkisko/json-logging/http";
|
|
8
|
+
*
|
|
9
|
+
* export default withRequestLogging(async (req) => {
|
|
10
|
+
* httpLogger().info({ path: req.nextUrl?.pathname });
|
|
11
|
+
* });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import { bindLoggerFromOptions } from "./http-ambient.js";
|
|
17
|
+
import { allocateRequestId, defaultRandomId, fallbackRequestId, withHttpContext, } from "./http-context.js";
|
|
18
|
+
export { allocateRequestId, defaultRandomId, fallbackRequestId };
|
|
19
|
+
/**
|
|
20
|
+
* Wrap a Next.js middleware or route handler with request-scoped context.
|
|
21
|
+
* Bind loggers via `@amkisko/json-logging/http` (`bindHttpLogger` / `httpLogger`).
|
|
22
|
+
*/
|
|
23
|
+
export function withRequestLogging(handler, options = {}) {
|
|
24
|
+
bindLoggerFromOptions(options);
|
|
25
|
+
return async (request, ...rest) => {
|
|
26
|
+
return (await withHttpContext({
|
|
27
|
+
headers: request.headers,
|
|
28
|
+
method: request.method,
|
|
29
|
+
url: request.url,
|
|
30
|
+
path: request.nextUrl?.pathname,
|
|
31
|
+
}, () => handler(request, ...rest), options));
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=next.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function isPlainObject(value: unknown): value is {
|
|
2
|
+
[key: string]: unknown;
|
|
3
|
+
};
|
|
4
|
+
export declare function plainObjectFrom(value: object): {
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
};
|
|
7
|
+
export declare function readObjectField(record: {
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}, key: string): {
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=objects.d.ts.map
|
package/dist/objects.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function isPlainObject(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
export function plainObjectFrom(value) {
|
|
5
|
+
const result = {};
|
|
6
|
+
for (const key of Object.keys(value)) {
|
|
7
|
+
result[key] = Reflect.get(value, key);
|
|
8
|
+
}
|
|
9
|
+
return result;
|
|
10
|
+
}
|
|
11
|
+
export function readObjectField(record, key) {
|
|
12
|
+
const value = record[key];
|
|
13
|
+
if (isPlainObject(value)) {
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=objects.js.map
|
package/dist/redact.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Dot-path segments after {@link compileRedactPaths} (config-time). */
|
|
2
|
+
export type CompiledRedactPath = readonly string[];
|
|
3
|
+
/** Compile redact paths once at logger construction. Config-time only. */
|
|
4
|
+
export declare function compileRedactPaths(paths: readonly string[]): CompiledRedactPath[];
|
|
5
|
+
/** Apply precompiled redact paths to a value tree. */
|
|
6
|
+
export declare function applyCompiledRedactPaths(value: unknown, paths: readonly CompiledRedactPath[]): unknown;
|
|
7
|
+
/** Apply string paths (compiles each call). Prefer compileRedactPaths on hot paths. */
|
|
8
|
+
export declare function applyRedactPaths(value: unknown, paths: readonly string[]): unknown;
|
|
9
|
+
//# sourceMappingURL=redact.d.ts.map
|
package/dist/redact.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { plainObjectFrom } from "./objects.js";
|
|
2
|
+
const FILTERED = "[FILTERED]";
|
|
3
|
+
function splitPath(path) {
|
|
4
|
+
if (path.length === 0) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
const parts = path.split(".");
|
|
8
|
+
if (parts.some((part) => part.length === 0)) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
return parts;
|
|
12
|
+
}
|
|
13
|
+
/** Compile redact paths once at logger construction. Config-time only. */
|
|
14
|
+
export function compileRedactPaths(paths) {
|
|
15
|
+
const compiled = [];
|
|
16
|
+
for (const path of paths) {
|
|
17
|
+
const parts = splitPath(path);
|
|
18
|
+
if (parts) {
|
|
19
|
+
compiled.push(parts);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return compiled;
|
|
23
|
+
}
|
|
24
|
+
function redactArray(value, parts, index, key) {
|
|
25
|
+
if (key === "*") {
|
|
26
|
+
return value.map((item) => redactAt(item, parts, index + 1));
|
|
27
|
+
}
|
|
28
|
+
const idx = Number(key);
|
|
29
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= value.length) {
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
const copy = value.slice();
|
|
33
|
+
copy[idx] = redactAt(copy[idx], parts, index + 1);
|
|
34
|
+
return copy;
|
|
35
|
+
}
|
|
36
|
+
function redactObject(value, parts, index, key) {
|
|
37
|
+
if (key === "*") {
|
|
38
|
+
const out = {};
|
|
39
|
+
for (const childKey of Object.keys(value)) {
|
|
40
|
+
out[childKey] = redactAt(value[childKey], parts, index + 1);
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
if (!(key in value)) {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
...value,
|
|
49
|
+
[key]: redactAt(value[key], parts, index + 1),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function redactAt(value, parts, index) {
|
|
53
|
+
if (index >= parts.length) {
|
|
54
|
+
return FILTERED;
|
|
55
|
+
}
|
|
56
|
+
if (typeof value !== "object" || value === null) {
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
const key = String(parts[index]);
|
|
60
|
+
if (Array.isArray(value)) {
|
|
61
|
+
return redactArray(value, parts, index, key);
|
|
62
|
+
}
|
|
63
|
+
return redactObject(plainObjectFrom(value), parts, index, key);
|
|
64
|
+
}
|
|
65
|
+
/** Apply precompiled redact paths to a value tree. */
|
|
66
|
+
export function applyCompiledRedactPaths(value, paths) {
|
|
67
|
+
let current = value;
|
|
68
|
+
for (const parts of paths) {
|
|
69
|
+
current = redactAt(current, parts, 0);
|
|
70
|
+
}
|
|
71
|
+
return current;
|
|
72
|
+
}
|
|
73
|
+
/** Apply string paths (compiles each call). Prefer compileRedactPaths on hot paths. */
|
|
74
|
+
export function applyRedactPaths(value, paths) {
|
|
75
|
+
return applyCompiledRedactPaths(value, compileRedactPaths(paths));
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=redact.js.map
|
package/dist/remix.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remix helpers: run handleRequest / loader bodies with request-scoped log context.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { withRemixContext } from "@amkisko/json-logging/remix";
|
|
7
|
+
* import { httpLogger } from "@amkisko/json-logging/http";
|
|
8
|
+
*
|
|
9
|
+
* return withRemixContext({ request, context }, () => {
|
|
10
|
+
* httpLogger().info("loading");
|
|
11
|
+
* return handle(request);
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import type { Logger } from "./logger.js";
|
|
18
|
+
import type { LoggerOptions } from "./types.js";
|
|
19
|
+
/** Minimal Remix request shape (no Remix runtime dependency). */
|
|
20
|
+
export type RemixRequestLike = {
|
|
21
|
+
method?: string;
|
|
22
|
+
url?: string;
|
|
23
|
+
headers?: Headers;
|
|
24
|
+
};
|
|
25
|
+
/** Load / app context bag passed into Remix. */
|
|
26
|
+
export type RemixContextBag = Record<string, unknown>;
|
|
27
|
+
/** Options for {@link withRemixContext}. */
|
|
28
|
+
export type WithRemixContextOptions = {
|
|
29
|
+
logger?: Logger;
|
|
30
|
+
loggerOptions?: LoggerOptions;
|
|
31
|
+
requestIdHeader?: string;
|
|
32
|
+
/** Allowlisted keys copied from load context into log context. Default: none. */
|
|
33
|
+
contextKeys?: readonly string[];
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Run a Remix handleRequest / middleware / loader body with ALS context.
|
|
37
|
+
* Bind loggers via `@amkisko/json-logging/http` (`bindHttpLogger` / `httpLogger`).
|
|
38
|
+
*/
|
|
39
|
+
export declare function withRemixContext<T>(input: {
|
|
40
|
+
request?: RemixRequestLike;
|
|
41
|
+
context?: RemixContextBag;
|
|
42
|
+
}, fn: () => T | Promise<T>, options?: WithRemixContextOptions): T | Promise<T>;
|
|
43
|
+
//# sourceMappingURL=remix.d.ts.map
|
package/dist/remix.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remix helpers: run handleRequest / loader bodies with request-scoped log context.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { withRemixContext } from "@amkisko/json-logging/remix";
|
|
7
|
+
* import { httpLogger } from "@amkisko/json-logging/http";
|
|
8
|
+
*
|
|
9
|
+
* return withRemixContext({ request, context }, () => {
|
|
10
|
+
* httpLogger().info("loading");
|
|
11
|
+
* return handle(request);
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import { bindLoggerFromOptions } from "./http-ambient.js";
|
|
18
|
+
import { pickContextKeys, withHttpContext } from "./http-context.js";
|
|
19
|
+
/**
|
|
20
|
+
* Run a Remix handleRequest / middleware / loader body with ALS context.
|
|
21
|
+
* Bind loggers via `@amkisko/json-logging/http` (`bindHttpLogger` / `httpLogger`).
|
|
22
|
+
*/
|
|
23
|
+
export function withRemixContext(input, fn, options = {}) {
|
|
24
|
+
bindLoggerFromOptions(options);
|
|
25
|
+
const requestId = input.context?.requestId != null ? String(input.context.requestId) : undefined;
|
|
26
|
+
return withHttpContext({
|
|
27
|
+
headers: input.request?.headers,
|
|
28
|
+
method: input.request?.method,
|
|
29
|
+
url: input.request?.url,
|
|
30
|
+
requestId,
|
|
31
|
+
}, fn, {
|
|
32
|
+
headerName: options.requestIdHeader,
|
|
33
|
+
logger: options.logger,
|
|
34
|
+
loggerOptions: options.loggerOptions,
|
|
35
|
+
extra: pickContextKeys(input.context, options.contextKeys),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=remix.js.map
|