@ayepi/log 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/dist/index.js ADDED
@@ -0,0 +1,218 @@
1
+ import { S as settleDeep, _ as partialMask, a as LOG_MAYBE, b as runWith, c as containsThenable, d as formatJson, f as formatText, g as merge, h as logMaybe, i as LOG_CONTEXT, l as createSanitizer, m as isLazy, o as UNRESOLVED, p as getContext, r as LEVELS, s as buildRecord, t as CONSOLE_LEVEL_MAP, u as deepEqual, v as resolveLog, x as serializeError, y as resolveLogValue } from "./internal.js";
2
+ //#region src/index.ts
3
+ /**
4
+ * # @ayepi/log
5
+ *
6
+ * Structured logging with **AsyncLocalStorage trace context**. Stack context with
7
+ * {@link logWith} and it flows through the whole async call tree (and onto thrown
8
+ * errors); {@link log} builds a record from mixed primitive/object/Error args;
9
+ * output goes to pluggable {@link Transport}s (console here, file via
10
+ * `@ayepi/log/file`); console.* can be intercepted (opt-in).
11
+ *
12
+ * ```ts
13
+ * import { createLogger } from '@ayepi/log'
14
+ * const log = createLogger({ level: 'debug' })
15
+ *
16
+ * log.logWith({ reqId: 'abc' }, async () => {
17
+ * log.info('handling', { userId: 'u1' }) // → reqId + userId on the record
18
+ * await work() // a rejection here is tagged with { reqId, userId }
19
+ * })
20
+ * ```
21
+ *
22
+ * Bare `import` has **no side effects** — console interception is opt-in.
23
+ *
24
+ * @module
25
+ */
26
+ const noopConsole = {
27
+ log() {},
28
+ info() {},
29
+ debug() {},
30
+ warn() {},
31
+ error() {}
32
+ };
33
+ const globalConsole = () => globalThis.console ?? noopConsole;
34
+ const defaultMethod = (level) => level === "error" ? "error" : level === "warn" ? "warn" : level === "debug" ? "debug" : "log";
35
+ /** A {@link Transport} that writes the formatted line to a console. */
36
+ function consoleTransport(opts = {}) {
37
+ const target = opts.console ?? globalConsole();
38
+ const pick = opts.method ?? defaultMethod;
39
+ return {
40
+ name: "console",
41
+ write(record, text) {
42
+ target[pick(record.level)](text);
43
+ }
44
+ };
45
+ }
46
+ /** Capture the current console methods (bound) so the transport + restore use the originals. */
47
+ function captureConsole(c) {
48
+ return {
49
+ log: c.log.bind(c),
50
+ info: c.info.bind(c),
51
+ debug: c.debug.bind(c),
52
+ warn: c.warn.bind(c),
53
+ error: c.error.bind(c)
54
+ };
55
+ }
56
+ /**
57
+ * Create a {@link Logger}.
58
+ */
59
+ function createLogger(config = {}) {
60
+ let level = config.level ?? "info";
61
+ const structured = config.structured ?? false;
62
+ const timestamp = config.timestamp ?? "iso";
63
+ const now = config.now ?? (() => Date.now());
64
+ const errorCfg = config.error ?? {};
65
+ const consoleMap = config.consoleMap ?? CONSOLE_LEVEL_MAP;
66
+ const targetConsole = config.console ?? globalConsole();
67
+ const originals = captureConsole(targetConsole);
68
+ let transports = config.transports ?? [consoleTransport({ console: originals })];
69
+ let intercepting = false;
70
+ let writing = false;
71
+ const report = (err) => {
72
+ try {
73
+ config.onError?.(err);
74
+ } catch {}
75
+ };
76
+ const sanitize = config.sanitize ? createSanitizer(config.sanitize) : void 0;
77
+ const resolveOpts = {
78
+ onError: report,
79
+ serializers: config.serializers
80
+ };
81
+ /** Build → filter → sanitize → format → write a record from already-resolved args. */
82
+ const deliver = (lvl, args) => {
83
+ let record;
84
+ let text;
85
+ try {
86
+ record = buildRecord(lvl, args, {
87
+ now,
88
+ timestamp,
89
+ error: errorCfg
90
+ });
91
+ if (config.filter) {
92
+ const filtered = config.filter(record);
93
+ if (!filtered) return;
94
+ record = filtered;
95
+ }
96
+ if (sanitize) {
97
+ const cleaned = sanitize(record);
98
+ if (!cleaned) return;
99
+ record = cleaned;
100
+ }
101
+ text = structured ? formatJson(record) : formatText(record);
102
+ } catch (err) {
103
+ report(err);
104
+ return;
105
+ }
106
+ writing = true;
107
+ try {
108
+ for (const t of transports) try {
109
+ t.write(record, text);
110
+ } catch (err) {
111
+ report(err);
112
+ }
113
+ } finally {
114
+ writing = false;
115
+ }
116
+ };
117
+ /** Produce a {@link logMaybe}'s value for this level (only now that we know we'll log), or pass through. */
118
+ const seedArg = (raw, lvl) => {
119
+ if (!isLazy(raw)) return raw;
120
+ try {
121
+ return raw[LOG_MAYBE](lvl);
122
+ } catch (err) {
123
+ report(err);
124
+ return UNRESOLVED;
125
+ }
126
+ };
127
+ const emit = (lvl, args) => {
128
+ if (LEVELS[lvl] < LEVELS[level] || writing) return;
129
+ let resolved;
130
+ try {
131
+ resolved = args.map((raw) => resolveLog(seedArg(raw, lvl), "", /* @__PURE__ */ new WeakSet(), resolveOpts));
132
+ } catch (err) {
133
+ report(err);
134
+ return;
135
+ }
136
+ if (resolved.some((v) => containsThenable(v))) settleDeep(resolved).then((settled) => deliver(lvl, settled)).catch(report);
137
+ else deliver(lvl, resolved);
138
+ };
139
+ let installed = [];
140
+ const restore = () => {
141
+ if (!intercepting) return;
142
+ intercepting = false;
143
+ const c = targetConsole;
144
+ for (const { method, original } of installed) c[method] = original;
145
+ installed = [];
146
+ };
147
+ const install = () => {
148
+ if (intercepting) return restore;
149
+ intercepting = true;
150
+ const c = targetConsole;
151
+ installed = [];
152
+ for (const method of Object.keys(consoleMap)) {
153
+ const lvl = consoleMap[method];
154
+ installed.push({
155
+ method,
156
+ original: c[method]
157
+ });
158
+ c[method] = (...args) => emit(lvl, args);
159
+ }
160
+ return restore;
161
+ };
162
+ /** Run `op` over every transport in parallel, awaiting all and reporting (never throwing) failures. */
163
+ const drain = (op) => Promise.all(transports.map(async (t) => {
164
+ try {
165
+ await op(t);
166
+ } catch (err) {
167
+ report(err);
168
+ }
169
+ })).then(() => void 0);
170
+ const logger = {
171
+ log: (lvl, ...args) => emit(lvl, args),
172
+ debug: (...args) => emit("debug", args),
173
+ info: (...args) => emit("info", args),
174
+ warn: (...args) => emit("warn", args),
175
+ error: (...args) => emit("error", args),
176
+ logWith: (add, inner) => runWith(add, inner),
177
+ context: () => Object.freeze({ ...getContext() }),
178
+ setTransports: (t) => {
179
+ transports = t;
180
+ },
181
+ setLevel: (l) => {
182
+ level = l;
183
+ },
184
+ isLevelEnabled: (l) => LEVELS[l] >= LEVELS[level],
185
+ flush: () => drain((t) => t.flush?.()),
186
+ close: () => drain((t) => t.close?.()),
187
+ config: {
188
+ get level() {
189
+ return level;
190
+ },
191
+ structured,
192
+ timestamp
193
+ },
194
+ interceptConsole: install,
195
+ restoreConsole: restore
196
+ };
197
+ if (config.interceptConsole ?? false) install();
198
+ return logger;
199
+ }
200
+ const instance = createLogger();
201
+ /** Emit a record at `level` on the default logger. */
202
+ const log = (level, ...args) => instance.log(level, ...args);
203
+ const debug = (...args) => instance.debug(...args);
204
+ const info = (...args) => instance.info(...args);
205
+ const warn = (...args) => instance.warn(...args);
206
+ const error = (...args) => instance.error(...args);
207
+ /** Stack trace context on the default logger (and tag promise rejections). */
208
+ const logWith = (add, inner) => instance.logWith(add, inner);
209
+ /** The default logger's current trace context. */
210
+ const context = () => instance.context();
211
+ /** Intercept `console.*` through the default logger; returns a restore function. */
212
+ const interceptConsole = () => instance.interceptConsole();
213
+ /** Restore console interception installed by the default logger. */
214
+ const restoreConsole = () => instance.restoreConsole();
215
+ /** The default logger instance. */
216
+ const logger = instance;
217
+ //#endregion
218
+ export { LOG_CONTEXT, consoleTransport, context, createLogger, createSanitizer, debug, deepEqual, error, getContext, info, interceptConsole, log, logMaybe, logWith, logger, merge, partialMask, resolveLogValue, restoreConsole, serializeError, warn };