@eventuras/logger 0.7.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 ADDED
@@ -0,0 +1,309 @@
1
+ # @eventuras/logger
2
+
3
+ A structured logging library with pluggable transports and optional OpenTelemetry integration. Works in Node.js and browser environments.
4
+
5
+ ## Features
6
+
7
+ - 🎯 **Scoped loggers** — Create instances with persistent context and namespace
8
+ - 🔌 **Pluggable transports** — Use Pino (default), console, or bring your own
9
+ - 🔒 **Auto-redaction** — Protect sensitive fields in log output
10
+ - 📊 **Log levels** — Control verbosity per logger or globally
11
+ - 🔍 **Correlation IDs** — Track requests across services
12
+ - 🌐 **OpenTelemetry** — Optional integration for any OTel-compatible backend
13
+ - 🌍 **Cross-runtime** — Works in Node.js, browsers, and edge runtimes
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm add @eventuras/logger
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### Scoped Logger (recommended)
24
+
25
+ ```typescript
26
+ import { Logger } from "@eventuras/logger";
27
+
28
+ const logger = Logger.create({
29
+ namespace: "web:admin:events",
30
+ context: { module: "EventEditor" },
31
+ });
32
+
33
+ logger.info({ eventId: 42 }, "Event updated");
34
+ logger.error({ error }, "Failed to save event");
35
+ ```
36
+
37
+ ### Static Methods (one-off logs)
38
+
39
+ ```typescript
40
+ import { Logger } from "@eventuras/logger";
41
+
42
+ Logger.info("Server started");
43
+ Logger.warn({ memoryUsage: "85%" }, "Memory usage high");
44
+ Logger.error({ error }, "Unhandled exception");
45
+ ```
46
+
47
+ ## Log Levels
48
+
49
+ From most to least verbose:
50
+
51
+ | Level | Value | Use case |
52
+ | ------- | ----- | ---------------------------- |
53
+ | `trace` | 10 | Fine-grained debugging |
54
+ | `debug` | 20 | Development debugging |
55
+ | `info` | 30 | General events **(default)** |
56
+ | `warn` | 40 | Warnings |
57
+ | `error` | 50 | Errors |
58
+ | `fatal` | 60 | Critical/shutdown errors |
59
+
60
+ ## Configuration
61
+
62
+ Configure the logger once at application startup:
63
+
64
+ ```typescript
65
+ import { Logger } from "@eventuras/logger";
66
+
67
+ Logger.configure({
68
+ level: "debug",
69
+ prettyPrint: process.env.NODE_ENV === "development",
70
+ redact: ["password", "token", "apiKey", "authorization", "secret"],
71
+ destination: "/var/log/app.log", // Optional file output
72
+ });
73
+ ```
74
+
75
+ ### Environment Variables
76
+
77
+ ```bash
78
+ LOG_LEVEL=debug # Set global log level
79
+ NODE_ENV=development # Enables pretty printing
80
+ ```
81
+
82
+ ## Transports
83
+
84
+ The library uses a pluggable transport system. A transport implements the `LogTransport` interface:
85
+
86
+ ```typescript
87
+ interface LogTransport {
88
+ log(level: LogLevel, data: Record<string, unknown>, msg?: string): void;
89
+ child(bindings: Record<string, unknown>): LogTransport;
90
+ flush?(): Promise<void>;
91
+ shutdown?(): Promise<void>;
92
+ }
93
+ ```
94
+
95
+ ### PinoTransport (default in Node.js)
96
+
97
+ [Pino](https://getpino.io) is included as a dependency and used automatically in Node.js environments. In browser/edge runtimes, `ConsoleTransport` is used as the default instead.
98
+
99
+ ```typescript
100
+ import { Logger, PinoTransport } from "@eventuras/logger";
101
+
102
+ // Explicit Pino configuration
103
+ Logger.configure({
104
+ transport: new PinoTransport({
105
+ level: "debug",
106
+ prettyPrint: true,
107
+ redact: ["password", "secret"],
108
+ }),
109
+ });
110
+ ```
111
+
112
+ ### ConsoleTransport
113
+
114
+ A lightweight transport using native `console` methods. Automatically selected as the default in browser and edge runtimes. Also useful for testing:
115
+
116
+ ```typescript
117
+ import { Logger, ConsoleTransport } from "@eventuras/logger";
118
+
119
+ Logger.configure({
120
+ transport: new ConsoleTransport(),
121
+ });
122
+ ```
123
+
124
+ ### Custom Transport
125
+
126
+ Implement the `LogTransport` interface for any logging backend:
127
+
128
+ ```typescript
129
+ import type { LogTransport, LogLevel } from "@eventuras/logger";
130
+
131
+ class DatadogTransport implements LogTransport {
132
+ log(level: LogLevel, data: Record<string, unknown>, msg?: string) {
133
+ // Send to Datadog, Winston, Bunyan, or any backend
134
+ }
135
+
136
+ child(bindings: Record<string, unknown>): LogTransport {
137
+ // Return a new transport with merged bindings
138
+ return new DatadogTransport({ ...this.config, bindings });
139
+ }
140
+ }
141
+
142
+ Logger.configure({ transport: new DatadogTransport() });
143
+ ```
144
+
145
+ ## Auto-Redaction
146
+
147
+ Sensitive fields are automatically redacted:
148
+
149
+ ```typescript
150
+ logger.info(
151
+ {
152
+ username: "john",
153
+ password: "secret123", // → '[REDACTED]'
154
+ apiKey: "key_123", // → '[REDACTED]'
155
+ },
156
+ "User login",
157
+ );
158
+ ```
159
+
160
+ Default redacted paths: `password`, `token`, `apiKey`, `authorization`, `secret`.
161
+
162
+ Configure additional paths via `Logger.configure({ redact: [...] })`.
163
+
164
+ ## HTTP Header Redaction
165
+
166
+ Utility for redacting sensitive HTTP headers:
167
+
168
+ ```typescript
169
+ import { redactHeaders } from "@eventuras/logger";
170
+
171
+ const safe = redactHeaders(request.headers);
172
+ logger.info({ headers: safe }, "Incoming request");
173
+ ```
174
+
175
+ Redacts `authorization`, `cookie`, `set-cookie`, `x-api-key`, `x-auth-token`, and `proxy-authorization`.
176
+
177
+ ## OpenTelemetry Integration
178
+
179
+ Send logs to any OTel-compatible backend (Sentry, Grafana, Jaeger, etc.) without vendor lock-in.
180
+
181
+ ### Install OTel Packages
182
+
183
+ ```bash
184
+ pnpm add @opentelemetry/api @opentelemetry/api-logs @opentelemetry/sdk-logs \
185
+ @opentelemetry/instrumentation-pino @opentelemetry/exporter-logs-otlp-http
186
+ ```
187
+
188
+ These are optional peer dependencies — the library works fine without them.
189
+
190
+ ### Setup
191
+
192
+ ```typescript
193
+ import { setupOpenTelemetryLogger } from "@eventuras/logger/opentelemetry";
194
+ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
195
+ import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
196
+
197
+ setupOpenTelemetryLogger({
198
+ serviceName: "my-app",
199
+ logRecordProcessor: new BatchLogRecordProcessor(
200
+ new OTLPLogExporter({
201
+ url: process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
202
+ }),
203
+ ),
204
+ });
205
+ ```
206
+
207
+ ### Shutdown
208
+
209
+ ```typescript
210
+ import { shutdownOpenTelemetryLogger } from "@eventuras/logger/opentelemetry";
211
+
212
+ process.on("SIGTERM", async () => {
213
+ await shutdownOpenTelemetryLogger();
214
+ process.exit(0);
215
+ });
216
+ ```
217
+
218
+ ### Environment Variables
219
+
220
+ ```bash
221
+ OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://...
222
+ OTEL_EXPORTER_OTLP_LOGS_HEADERS=x-api-key=YOUR_KEY
223
+ OTEL_SERVICE_NAME=my-app
224
+ ```
225
+
226
+ ## Examples
227
+
228
+ ### API Route Handler
229
+
230
+ ```typescript
231
+ import { Logger } from "@eventuras/logger";
232
+
233
+ export async function POST(req: Request) {
234
+ const logger = Logger.create({
235
+ namespace: "events-api",
236
+ correlationId: req.headers.get("x-correlation-id") || crypto.randomUUID(),
237
+ });
238
+
239
+ try {
240
+ const data = await req.json();
241
+ logger.info("Creating event");
242
+
243
+ const result = await createEvent(data);
244
+ logger.info({ eventId: result.id }, "Event created");
245
+
246
+ return Response.json(result);
247
+ } catch (error) {
248
+ logger.error({ error }, "Failed to create event");
249
+ return Response.json({ error: "Internal server error" }, { status: 500 });
250
+ }
251
+ }
252
+ ```
253
+
254
+ ### Server Action
255
+
256
+ ```typescript
257
+ "use server";
258
+
259
+ import { Logger } from "@eventuras/logger";
260
+
261
+ const logger = Logger.create({ namespace: "web:admin:collections" });
262
+
263
+ export async function updateCollection(data: CollectionDto) {
264
+ logger.info({ collectionId: data.id }, "Updating collection");
265
+
266
+ try {
267
+ const result = await api.put(data);
268
+ logger.info({ collectionId: data.id }, "Collection updated");
269
+ return { success: true, data: result };
270
+ } catch (error) {
271
+ logger.error({ error, collectionId: data.id }, "Failed to update");
272
+ return { success: false, error: "Update failed" };
273
+ }
274
+ }
275
+ ```
276
+
277
+ ## TypeScript
278
+
279
+ All types are exported:
280
+
281
+ ```typescript
282
+ import type {
283
+ LogTransport,
284
+ LogLevel,
285
+ LoggerOptions,
286
+ LoggerConfig,
287
+ ErrorLoggerOptions,
288
+ } from "@eventuras/logger";
289
+ ```
290
+
291
+ ## Subpath Exports
292
+
293
+ | Import path | Contents | Environment |
294
+ | --------------------------------- | --------------------------------------------------------------- | ----------- |
295
+ | `@eventuras/logger` | Logger, types, PinoTransport, ConsoleTransport, httpLogger | Universal |
296
+ | `@eventuras/logger/node` | `formatLogLine`, `createPrettyStream` (depends on `node:stream`) | Node.js |
297
+ | `@eventuras/logger/opentelemetry` | `setupOpenTelemetryLogger`, `shutdownOpenTelemetryLogger` | Node.js |
298
+
299
+ ### Node-only Pretty-print Utilities
300
+
301
+ The pretty-print formatter and stream are available via a separate entry point to avoid pulling `node:stream` into browser bundles:
302
+
303
+ ```typescript
304
+ import { createPrettyStream, formatLogLine } from "@eventuras/logger/node";
305
+ ```
306
+
307
+ ## License
308
+
309
+ Apache-2.0
@@ -0,0 +1,105 @@
1
+ import { ErrorLoggerOptions, LoggerConfig, LoggerOptions, LogTransport } from './types';
2
+ export declare class Logger {
3
+ private static transport;
4
+ private static config;
5
+ private readonly options;
6
+ private readonly childTransport?;
7
+ private constructor();
8
+ /**
9
+ * Configure global logger settings. Call once at application startup.
10
+ *
11
+ * Supply a custom `transport` to replace the default Pino backend,
12
+ * or omit it to keep PinoTransport with the provided options.
13
+ *
14
+ * @example
15
+ * Logger.configure({ level: 'debug', redact: ['password', 'apiKey'] });
16
+ *
17
+ * @example
18
+ * import { ConsoleTransport } from '@eventuras/logger';
19
+ * Logger.configure({ transport: new ConsoleTransport() });
20
+ */
21
+ static configure(config: Partial<LoggerConfig>): void;
22
+ /**
23
+ * Get the active transport for advanced integrations.
24
+ *
25
+ * If you need access to the underlying Pino instance (e.g. for OTel
26
+ * instrumentation), check `transport instanceof PinoTransport` and
27
+ * access `.pino` on it.
28
+ */
29
+ static getTransport(): LogTransport;
30
+ /**
31
+ * @deprecated Use `Logger.getTransport()` instead. If you need the raw
32
+ * Pino instance, cast the transport: `(Logger.getTransport() as PinoTransport).pino`
33
+ */
34
+ static getPinoInstance(): import('pino').Logger;
35
+ /**
36
+ * Normalize arguments for static log methods.
37
+ * Supports both `Logger.info('msg')` and `Logger.info({ namespace: 'x' }, 'msg')`.
38
+ */
39
+ private static normalizeArgs;
40
+ private static isDevelopment;
41
+ private static formatError;
42
+ private static buildLogData;
43
+ private static staticLog;
44
+ private static staticErrorLog;
45
+ /** Log at info level with options and message(s). */
46
+ static info(options: LoggerOptions, ...msg: unknown[]): void;
47
+ /** Log at info level with just a message string. */
48
+ static info(msg: string, ...args: unknown[]): void;
49
+ /** Log at debug level with options and message(s). */
50
+ static debug(options: LoggerOptions, ...msg: unknown[]): void;
51
+ /** Log at debug level with just a message string. */
52
+ static debug(msg: string, ...args: unknown[]): void;
53
+ /** Log at trace level with options and message(s). */
54
+ static trace(options: LoggerOptions, ...msg: unknown[]): void;
55
+ /** Log at trace level with just a message string. */
56
+ static trace(msg: string, ...args: unknown[]): void;
57
+ /** Log at warn level with options and message(s). */
58
+ static warn(options: LoggerOptions, ...msg: unknown[]): void;
59
+ /** Log at warn level with just a message string. */
60
+ static warn(msg: string, ...args: unknown[]): void;
61
+ /** Log at error level with options and message(s). */
62
+ static error(options: ErrorLoggerOptions, ...msg: unknown[]): void;
63
+ /** Log at error level with just a message string. */
64
+ static error(msg: string, ...args: unknown[]): void;
65
+ /** Log at fatal level with options and message(s). */
66
+ static fatal(options: ErrorLoggerOptions, ...msg: unknown[]): void;
67
+ /** Log at fatal level with just a message string. */
68
+ static fatal(msg: string, ...args: unknown[]): void;
69
+ private static rebindStaticMethods;
70
+ /**
71
+ * Create a scoped logger instance with predefined options.
72
+ *
73
+ * @example
74
+ * const logger = Logger.create({ namespace: 'CollectionEditor' });
75
+ * logger.info('Something happened');
76
+ *
77
+ * @example
78
+ * const logger = Logger.create({
79
+ * namespace: 'API',
80
+ * context: { userId: 123 },
81
+ * correlationId: req.headers['x-correlation-id'],
82
+ * });
83
+ * logger.info({ eventId: 789 }, 'Event added');
84
+ */
85
+ static create(options?: LoggerOptions): Logger;
86
+ private logInstance;
87
+ /** Log at `trace` level. Pass a string or `{ data }` with an optional message. */
88
+ trace(data?: Record<string, unknown> | string, msg?: string): void;
89
+ /** Log at `debug` level. */
90
+ debug(data?: Record<string, unknown> | string, msg?: string): void;
91
+ /** Log at `info` level. */
92
+ info(data?: Record<string, unknown> | string, msg?: string): void;
93
+ /** Log at `warn` level. */
94
+ warn(data?: Record<string, unknown> | string, msg?: string): void;
95
+ /**
96
+ * Log at `error` level. Accepts an `Error` instance, a data object, or a plain string.
97
+ * Error instances are serialized automatically.
98
+ */
99
+ error(errorOrData?: unknown, msg?: string): void;
100
+ /**
101
+ * Log at `fatal` level. Same signature as `error()` but signals a critical/shutdown failure.
102
+ */
103
+ fatal(errorOrData?: unknown, msg?: string): void;
104
+ }
105
+ //# sourceMappingURL=Logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Logger.d.ts","sourceRoot":"","sources":["../src/Logger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { t as e } from "./chunk-NnHqS4_Y.js";
2
+ //#region __vite-browser-external
3
+ var t = /* @__PURE__ */ e(((e, t) => {
4
+ t.exports = {};
5
+ }));
6
+ //#endregion
7
+ export { t };
@@ -0,0 +1,20 @@
1
+ //#region \0rolldown/runtime.js
2
+ var e = Object.create, t = Object.defineProperty, n = Object.getOwnPropertyDescriptor, r = Object.getOwnPropertyNames, i = Object.getPrototypeOf, a = Object.prototype.hasOwnProperty, o = (e, t) => () => (e && (t = e(e = 0)), t), s = (e, t) => () => (t || e((t = { exports: {} }).exports, t), t.exports), c = (e, n) => {
3
+ let r = {};
4
+ for (var i in e) t(r, i, {
5
+ get: e[i],
6
+ enumerable: !0
7
+ });
8
+ return n || t(r, Symbol.toStringTag, { value: "Module" }), r;
9
+ }, l = (e, i, o, s) => {
10
+ if (i && typeof i == "object" || typeof i == "function") for (var c = r(i), l = 0, u = c.length, d; l < u; l++) d = c[l], !a.call(e, d) && d !== o && t(e, d, {
11
+ get: ((e) => i[e]).bind(null, d),
12
+ enumerable: !(s = n(i, d)) || s.enumerable
13
+ });
14
+ return e;
15
+ }, u = (n, r, a) => (a = n == null ? {} : e(i(n)), l(r || !n || !n.__esModule ? t(a, "default", {
16
+ value: n,
17
+ enumerable: !0
18
+ }) : a, n)), d = (e) => a.call(e, "module.exports") ? e["module.exports"] : l(t({}, "__esModule", { value: !0 }), e);
19
+ //#endregion
20
+ export { u as a, d as i, o as n, c as r, s as t };