tpaga-logger 0.0.7 → 0.0.8

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
@@ -39,50 +39,166 @@ export const handler = withLogger(logger, 'createCharge', async (event, log) =>
39
39
 
40
40
  ### Express
41
41
 
42
- Use `withTpagaExpressLogger` as a global middleware and `getLog` to access the log context inside route handlers.
42
+ Register two middlewares from the library one before routes, one after. Both are zero-config.
43
43
 
44
44
  ```typescript
45
45
  import express from 'express';
46
- import { createLogger, withTpagaExpressLogger, getLog } from 'tpaga-logger';
46
+ import { createLogger, withTpagaExpressLogger, tpagaExpressErrorLogger } from 'tpaga-logger';
47
+ import { errorHandler } from './middleware/errorHandler';
47
48
 
48
- const logger = createLogger({
49
- service: 'url-signer-service',
50
- environment: process.env.NODE_ENV ?? 'dev',
51
- });
49
+ const logger = createLogger({ service: 'url-signer-service', environment: process.env.NODE_ENV ?? 'dev' });
52
50
 
53
51
  const app = express();
54
52
  app.use(express.json());
55
- app.use(withTpagaExpressLogger(logger)); // register once, covers all routes
53
+ app.use(withTpagaExpressLogger(logger)); // before routes: timing, correlationId, req.log
56
54
 
57
- app.post('/api/v1/sign', (req, res) => {
58
- req.log.with({ userId: req.body.userId, resource: req.body.resource });
55
+ app.use('/api/v1/sign', signRouter);
59
56
 
60
- const url = signUrl(req.body);
61
- res.json({ url });
62
- // terminal log is emitted automatically on res.finish
63
- });
57
+ app.use(tpagaExpressErrorLogger); // after routes: serialize + log any error
58
+ app.use(errorHandler); // after routes: map error → HTTP response (your code)
64
59
  ```
65
60
 
61
+ > Express error middleware must be registered **after** routes — this is an Express constraint.
62
+
66
63
  **What `withTpagaExpressLogger` does automatically:**
67
- - Extracts `correlationId` from headers (generates a UUID if absent)
68
- - Attaches a typed `WideEventBuilder` to `req.log` (available in all route handlers)
64
+ - Extracts `correlationId` from `x-correlation-id` / `x-request-id` headers (generates UUID if absent)
65
+ - Attaches a typed `WideEventBuilder` to `req.log` and `res.log`
66
+ - Emits `outcome: "success"` for `statusCode < 400`, `outcome: "error"` otherwise
67
+ - Calculates `durationMs` — always matches the actual response time
68
+
69
+ **What `tpagaExpressErrorLogger` does automatically:**
70
+ - Serializes the error via `serializeError` — no stack, structured `errorDetails` for Zod-like errors
71
+ - Adds `error` to the log context via `req.log.with({ error })`
72
+ - Calls `next(err)` to pass the error to your `errorHandler`
73
+
74
+ **`req.log` / `res.log` in controllers:**
75
+
76
+ `req.log` is available anywhere in the request lifecycle — no import needed:
77
+
78
+ ```typescript
79
+ export const signUrl = async (req: Request, res: Response, next: NextFunction) => {
80
+ try {
81
+ const input = signUrlSchema.parse(req.body);
82
+ req.log.with({ url: input.url, ttlSeconds: input.ttlSeconds });
83
+ res.status(200).json(generateSignedUrl(input));
84
+ } catch (err) {
85
+ req.log.with({ attemptedUrl: req.body?.url, stage: 'signing' }); // extra context on error
86
+ next(err);
87
+ }
88
+ };
89
+ ```
90
+
91
+ `res.log` works the same — useful in error handler middleware where `req` params are prefixed with `_`.
92
+
93
+ **`statusCode` in the log always matches the HTTP response** — it's read from `res.statusCode` after the response is sent.
94
+
95
+ **`serializeError` output:**
96
+
97
+ | Error type | `type` | `message` | `errorDetails` |
98
+ |-----------|--------|-----------|----------------|
99
+ | ZodError | `ZodError` | `Validation failed` | array of Zod issues |
100
+ | AppError / Error | `Error` | original message | — |
101
+ | Unknown | `UnknownError` | `String(err)` | — |
102
+
103
+ ---
104
+
105
+ ### NestJS + Fastify <sup>v0.0.8+</sup>
106
+
107
+ Import from `tpaga-logger/fastify`. Register the plugin and the global interceptor — both are zero-config.
108
+
109
+ ```typescript
110
+ // app.module.ts
111
+ import { Module } from '@nestjs/common';
112
+ import { TpagaLoggerModule } from 'tpaga-logger/fastify';
113
+
114
+ @Module({
115
+ imports: [
116
+ TpagaLoggerModule.forRoot({
117
+ service: process.env.SERVICE_NAME ?? 'my-service',
118
+ environment: process.env.NODE_ENV ?? 'dev',
119
+ }),
120
+ ],
121
+ })
122
+ export class AppModule {}
123
+ ```
124
+
125
+ ```typescript
126
+ // main.ts
127
+ import { NestFactory } from '@nestjs/core';
128
+ import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
129
+ import { withTpagaFastifyLogger, TpagaLoggerInterceptor, TPAGA_LOGGER } from 'tpaga-logger/fastify';
130
+ import type { Logger } from 'tpaga-logger';
131
+ import { AppModule } from './app.module';
132
+
133
+ async function bootstrap() {
134
+ const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter());
135
+
136
+ const logger = app.get<Logger>(TPAGA_LOGGER);
137
+ await app.register(withTpagaFastifyLogger(logger)); // before routes: timing, correlationId, request.tpagaLog
138
+ app.useGlobalInterceptors(new TpagaLoggerInterceptor()); // catch errors before exception filters
139
+
140
+ await app.listen(3000, '0.0.0.0');
141
+ }
142
+ bootstrap();
143
+ ```
144
+
145
+ **What `withTpagaFastifyLogger` does automatically:**
146
+ - Extracts `correlationId` from `x-correlation-id` / `x-request-id` headers (generates UUID if absent)
147
+ - Attaches a typed `WideEventBuilder` to `request.tpagaLog`
69
148
  - Emits `outcome: "success"` for `statusCode < 400`, `outcome: "error"` otherwise
70
- - Calculates `durationMs` from when the request entered the middleware
149
+ - Calculates `durationMs` always matches the actual response time
150
+
151
+ **What `TpagaLoggerInterceptor` does automatically:**
152
+ - Wraps every route handler and catches thrown exceptions before NestJS exception filters run
153
+ - Serializes the error via `serializeError` and adds it to the log context via `request.tpagaLog.with({ error })`
154
+ - Re-throws so NestJS exception handling continues normally
155
+
156
+ > Use `request.tpagaLog` (not `request.log`) — Fastify already attaches its own pino logger to `request.log`.
157
+
158
+ **`request.tpagaLog` in controllers:**
159
+
160
+ ```typescript
161
+ import { Controller, Get, Param, Req } from '@nestjs/common';
162
+ import type { FastifyRequest } from 'fastify';
163
+
164
+ @Controller('charges')
165
+ export class ChargesController {
166
+ @Get(':id')
167
+ async getCharge(@Req() req: FastifyRequest, @Param('id') id: string) {
168
+ req.tpagaLog.with({ chargeId: id });
169
+ const charge = await this.chargesService.findById(id);
170
+ req.tpagaLog.with({ merchantId: charge.merchantId });
171
+ return charge;
172
+ }
173
+ }
174
+ ```
71
175
 
72
176
  ---
73
177
 
74
178
  ## API
75
179
 
180
+ **`tpaga-logger`** (Express + Lambda)
181
+
76
182
  | Export | Description |
77
183
  |--------|-------------|
78
184
  | `createLogger(config)` | Creates a logger instance for a service |
79
185
  | `withLogger(logger, name, handler)` | Lambda handler wrapper |
80
186
  | `withTpagaExpressLogger(logger)` | Express middleware (register with `app.use`) |
187
+ | `tpagaExpressErrorLogger` | Express error middleware (register after routes) |
81
188
  | `getLog(res)` | Gets the `WideEventBuilder` from an Express response |
82
189
  | `serializeError(err)` | Serializes an unknown error to a structured object |
83
190
  | `LOG_LEVELS` | `['info', 'warn', 'error', 'debug']` |
84
191
  | `OUTCOMES` | `['success', 'error', 'validation_failed', ...]` |
85
192
 
193
+ **`tpaga-logger/fastify`** (NestJS + Fastify)
194
+
195
+ | Export | Description |
196
+ |--------|-------------|
197
+ | `withTpagaFastifyLogger(logger)` | Fastify plugin — register with `app.register()` |
198
+ | `TpagaLoggerInterceptor` | NestJS interceptor — register with `app.useGlobalInterceptors()` |
199
+ | `TpagaLoggerModule` | NestJS dynamic module — import in `AppModule` |
200
+ | `TPAGA_LOGGER` | Injection token to retrieve the `Logger` instance via DI |
201
+
86
202
  ### `LoggerConfig`
87
203
 
88
204
  ```typescript
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
9
+ var __typeError = (msg) => {
10
+ throw TypeError(msg);
11
+ };
12
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
27
+ // If the importer is in node compatibility mode or this is not an ESM
28
+ // file that has been converted to a CommonJS file using a Babel-
29
+ // compatible transform (i.e. "__esModule" has not been set), then set
30
+ // "default" to the CommonJS "module.exports" for node compatibility.
31
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
32
+ mod
33
+ ));
34
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
35
+ var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
36
+ var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
37
+ var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
38
+ var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
39
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
40
+ var __runInitializers = (array, flags, self, value) => {
41
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
42
+ return value;
43
+ };
44
+ var __decorateElement = (array, flags, name, decorators, target, extra) => {
45
+ var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
46
+ var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
47
+ var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
48
+ var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
49
+ return __privateGet(this, extra);
50
+ }, set [name](x) {
51
+ return __privateSet(this, extra, x);
52
+ } }, name));
53
+ k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
54
+ for (var i = decorators.length - 1; i >= 0; i--) {
55
+ ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
56
+ if (k) {
57
+ ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
58
+ if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
59
+ if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
60
+ }
61
+ it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
62
+ if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
63
+ else if (typeof it !== "object" || it === null) __typeError("Object expected");
64
+ else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
65
+ }
66
+ return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
67
+ };
68
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
69
+ var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
70
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
71
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
72
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
73
+
74
+ // src/fastify.ts
75
+ var fastify_exports = {};
76
+ __export(fastify_exports, {
77
+ TPAGA_LOGGER: () => TPAGA_LOGGER,
78
+ TpagaLoggerInterceptor: () => TpagaLoggerInterceptor,
79
+ TpagaLoggerModule: () => TpagaLoggerModule,
80
+ withTpagaFastifyLogger: () => withTpagaFastifyLogger
81
+ });
82
+ module.exports = __toCommonJS(fastify_exports);
83
+ var import_rxjs = require("rxjs");
84
+ var import_common = require("@nestjs/common");
85
+
86
+ // src/logger.ts
87
+ var import_pino = __toESM(require("pino"), 1);
88
+
89
+ // src/utils.ts
90
+ var import_crypto = require("crypto");
91
+ var resolveCorrelationId = (headers = {}) => {
92
+ const h = Object.fromEntries(
93
+ Object.entries(headers).map(([k, v]) => [k.toLowerCase(), Array.isArray(v) ? v[0] : v])
94
+ );
95
+ return h["x-correlation-id"] ?? h["x-request-id"] ?? (0, import_crypto.randomUUID)();
96
+ };
97
+
98
+ // src/logger.ts
99
+ var serializeError = (err) => {
100
+ if (!(err instanceof Error)) return { type: "UnknownError", message: String(err) };
101
+ const errorDetails = err.issues;
102
+ return {
103
+ type: err.name,
104
+ message: errorDetails != null ? "Validation failed" : err.message,
105
+ code: err.code,
106
+ errorDetails
107
+ };
108
+ };
109
+ var createWideEventBuilder = (base, log) => {
110
+ const ctx = { ...base };
111
+ return {
112
+ with: (fields) => Object.assign(ctx, fields),
113
+ emit: (level, message, terminal) => log[level]({ ...ctx, ...terminal }, message)
114
+ };
115
+ };
116
+ var createLogger = (config) => {
117
+ const pretty = process.env.LOG_PRETTY === "true";
118
+ const log = (0, import_pino.default)({
119
+ level: process.env.LOG_LEVEL ?? "info",
120
+ base: { service: config.service, environment: config.environment },
121
+ formatters: {
122
+ level: (label) => ({ level: label })
123
+ },
124
+ timestamp: import_pino.default.stdTimeFunctions.isoTime,
125
+ redact: {
126
+ paths: config.redactKeys ?? [],
127
+ censor: "[REDACTED]"
128
+ },
129
+ ...pretty ? { transport: { target: "pino-pretty", options: { colorize: true } } } : {}
130
+ });
131
+ return {
132
+ startOperation: (functionName, base = {}) => createWideEventBuilder({ function: functionName, ...base }, log)
133
+ };
134
+ };
135
+
136
+ // src/fastify.ts
137
+ var START_KEY = /* @__PURE__ */ Symbol("tpagaStart");
138
+ var withTpagaFastifyLogger = (logger) => async (fastify) => {
139
+ fastify.addHook("onRequest", (request, _reply, done) => {
140
+ const correlationId = resolveCorrelationId(request.headers);
141
+ request.tpagaLog = logger.startOperation(request.url, {
142
+ correlationId,
143
+ method: request.method
144
+ });
145
+ request[START_KEY] = Date.now();
146
+ done();
147
+ });
148
+ fastify.addHook("onResponse", (request, reply, done) => {
149
+ const start = request[START_KEY];
150
+ request.tpagaLog.emit("info", "request completed", {
151
+ outcome: reply.statusCode < 400 ? "success" : "error",
152
+ durationMs: Date.now() - start,
153
+ statusCode: reply.statusCode
154
+ });
155
+ done();
156
+ });
157
+ fastify.addHook("onError", (request, _reply, error, done) => {
158
+ request.tpagaLog.with({ error: serializeError(error) });
159
+ done();
160
+ });
161
+ };
162
+ var _TpagaLoggerInterceptor_decorators, _init;
163
+ _TpagaLoggerInterceptor_decorators = [(0, import_common.Injectable)()];
164
+ var TpagaLoggerInterceptor = class {
165
+ intercept(context, next) {
166
+ return next.handle().pipe(
167
+ (0, import_rxjs.catchError)((error) => {
168
+ const request = context.switchToHttp().getRequest();
169
+ request.tpagaLog?.with({ error: serializeError(error) });
170
+ return (0, import_rxjs.throwError)(() => error);
171
+ })
172
+ );
173
+ }
174
+ };
175
+ _init = __decoratorStart(null);
176
+ TpagaLoggerInterceptor = __decorateElement(_init, 0, "TpagaLoggerInterceptor", _TpagaLoggerInterceptor_decorators, TpagaLoggerInterceptor);
177
+ __runInitializers(_init, 1, TpagaLoggerInterceptor);
178
+ var TPAGA_LOGGER = "TPAGA_LOGGER";
179
+ var TpagaLoggerModule = class _TpagaLoggerModule {
180
+ static forRoot(config) {
181
+ const logger = createLogger(config);
182
+ return {
183
+ module: _TpagaLoggerModule,
184
+ global: true,
185
+ providers: [{ provide: TPAGA_LOGGER, useValue: logger }],
186
+ exports: [TPAGA_LOGGER]
187
+ };
188
+ }
189
+ };
190
+ // Annotate the CommonJS export names for ESM import in node:
191
+ 0 && (module.exports = {
192
+ TPAGA_LOGGER,
193
+ TpagaLoggerInterceptor,
194
+ TpagaLoggerModule,
195
+ withTpagaFastifyLogger
196
+ });
197
+ //# sourceMappingURL=fastify.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/fastify.ts","../src/logger.ts","../src/utils.ts"],"sourcesContent":["import { catchError, throwError } from 'rxjs';\nimport type { Observable } from 'rxjs';\nimport type { FastifyInstance, FastifyRequest } from 'fastify';\nimport type { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';\nimport { Injectable } from '@nestjs/common';\nimport { createLogger, serializeError } from './logger.js';\nimport { resolveCorrelationId } from './utils.js';\nimport type { Logger, LoggerConfig, WideEventBuilder } from './types.js';\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n tpagaLog: WideEventBuilder;\n }\n}\n\nconst START_KEY = Symbol('tpagaStart');\n\nexport const withTpagaFastifyLogger = (logger: Logger) =>\n async (fastify: FastifyInstance): Promise<void> => {\n fastify.addHook('onRequest', (request, _reply, done) => {\n const correlationId = resolveCorrelationId(request.headers);\n request.tpagaLog = logger.startOperation(request.url, {\n correlationId,\n method: request.method,\n });\n (request as unknown as Record<symbol, number>)[START_KEY] = Date.now();\n done();\n });\n\n fastify.addHook('onResponse', (request, reply, done) => {\n const start = (request as unknown as Record<symbol, number>)[START_KEY];\n request.tpagaLog.emit('info', 'request completed', {\n outcome: reply.statusCode < 400 ? 'success' : 'error',\n durationMs: Date.now() - start,\n statusCode: reply.statusCode,\n });\n done();\n });\n\n fastify.addHook('onError', (request, _reply, error, done) => {\n request.tpagaLog.with({ error: serializeError(error) });\n done();\n });\n };\n\n@Injectable()\nexport class TpagaLoggerInterceptor implements NestInterceptor {\n intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {\n return next.handle().pipe(\n catchError((error: unknown) => {\n const request = context.switchToHttp().getRequest<FastifyRequest>();\n request.tpagaLog?.with({ error: serializeError(error) });\n return throwError(() => error);\n }),\n );\n }\n}\n\nexport const TPAGA_LOGGER = 'TPAGA_LOGGER' as const;\n\nexport class TpagaLoggerModule {\n static forRoot(config: LoggerConfig) {\n const logger = createLogger(config);\n return {\n module: TpagaLoggerModule,\n global: true,\n providers: [{ provide: TPAGA_LOGGER, useValue: logger }],\n exports: [TPAGA_LOGGER],\n };\n }\n}\n","import pino from 'pino';\nimport type { NextFunction, Request, Response } from 'express';\nimport type { Logger, LoggerConfig, SerializedError, TerminalFields, WideEventBuilder } from './types.js';\nimport { resolveCorrelationId } from './utils.js';\n\ndeclare global {\n namespace Express {\n interface Request {\n log: WideEventBuilder;\n }\n interface Response {\n log: WideEventBuilder;\n }\n }\n}\n\nexport const serializeError = (err: unknown): SerializedError => {\n if (!(err instanceof Error)) return { type: 'UnknownError', message: String(err) };\n const errorDetails = (err as { issues?: unknown }).issues;\n return {\n type: err.name,\n message: errorDetails != null ? 'Validation failed' : err.message,\n code: (err as { code?: string }).code,\n errorDetails,\n };\n};\n\nconst createWideEventBuilder = (\n base: Record<string, unknown>,\n log: pino.Logger,\n): WideEventBuilder => {\n const ctx = { ...base };\n return {\n with: (fields) => Object.assign(ctx, fields),\n emit: (level, message, terminal: TerminalFields) => log[level]({ ...ctx, ...terminal }, message),\n };\n};\n\nexport const createLogger = (config: LoggerConfig): Logger => {\n const pretty = process.env.LOG_PRETTY === 'true';\n const log = pino({\n level: process.env.LOG_LEVEL ?? 'info',\n base: { service: config.service, environment: config.environment },\n formatters: {\n level: (label) => ({ level: label }),\n },\n timestamp: pino.stdTimeFunctions.isoTime,\n redact: {\n paths: (config.redactKeys as string[]) ?? [],\n censor: '[REDACTED]',\n },\n ...(pretty ? { transport: { target: 'pino-pretty', options: { colorize: true } } } : {}),\n });\n\n return {\n startOperation: (functionName, base = {}) =>\n createWideEventBuilder({ function: functionName, ...base }, log),\n };\n};\n\ntype EventWithHeaders = { headers?: Record<string, string | undefined> };\ntype HandlerWithBuilder<TEvent> = (event: TEvent, builder: WideEventBuilder) => Promise<unknown>;\n\nconst EXPRESS_LOG_KEY = 'tpagaLog';\n\nexport const withTpagaExpressLogger = (logger: Logger) =>\n (req: Request, res: Response, next: NextFunction): void => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(req.headers as Record<string, string | undefined>);\n const log = logger.startOperation(req.path, { correlationId, method: req.method });\n\n req.log = log;\n res.log = log;\n res.locals[EXPRESS_LOG_KEY] = log;\n\n res.on('finish', () => {\n log.emit('info', 'request completed', {\n outcome: res.statusCode < 400 ? 'success' : 'error',\n durationMs: Date.now() - start,\n statusCode: res.statusCode,\n });\n });\n\n next();\n };\n\nexport const tpagaExpressErrorLogger = (err: unknown, req: Request, _res: Response, next: NextFunction): void => {\n req.log.with({ error: serializeError(err) });\n next(err);\n};\n\nexport const getLog = (res: Response): WideEventBuilder =>\n res.locals[EXPRESS_LOG_KEY] as WideEventBuilder;\n\nexport const withLogger = <TEvent extends EventWithHeaders>(\n logger: Logger,\n operationName: string,\n handler: HandlerWithBuilder<TEvent>,\n) =>\n async (event: TEvent, ..._rest: unknown[]): Promise<unknown> => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(event.headers);\n const builder = logger.startOperation(operationName, { correlationId });\n\n try {\n const result = await handler(event, builder);\n const statusCode =\n result != null &&\n typeof result === 'object' &&\n 'statusCode' in result &&\n typeof (result as { statusCode: unknown }).statusCode === 'number'\n ? (result as { statusCode: number }).statusCode\n : undefined;\n builder.emit('info', `${operationName} completed`, {\n outcome: 'success',\n durationMs: Date.now() - start,\n ...(statusCode !== undefined ? { statusCode } : {}),\n });\n return result;\n } catch (err) {\n builder.emit('error', `${operationName} failed`, {\n outcome: 'error',\n durationMs: Date.now() - start,\n error: serializeError(err),\n });\n throw err;\n }\n };\n","import { randomUUID } from 'crypto';\n\nexport const resolveCorrelationId = (\n headers: Record<string, string | string[] | undefined> = {},\n): string => {\n const h = Object.fromEntries(\n Object.entries(headers).map(([k, v]) => [k.toLowerCase(), Array.isArray(v) ? v[0] : v]),\n );\n return h['x-correlation-id'] ?? h['x-request-id'] ?? randomUUID();\n};\n"],"mappings":"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","names":["pino"]}
@@ -0,0 +1,28 @@
1
+ import { Observable } from 'rxjs';
2
+ import { FastifyInstance } from 'fastify';
3
+ import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
4
+ import { W as WideEventBuilder, c as LoggerConfig, b as Logger } from './types-BtTFSWeZ.cjs';
5
+
6
+ declare module 'fastify' {
7
+ interface FastifyRequest {
8
+ tpagaLog: WideEventBuilder;
9
+ }
10
+ }
11
+ declare const withTpagaFastifyLogger: (logger: Logger) => (fastify: FastifyInstance) => Promise<void>;
12
+ declare class TpagaLoggerInterceptor implements NestInterceptor {
13
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
14
+ }
15
+ declare const TPAGA_LOGGER: "TPAGA_LOGGER";
16
+ declare class TpagaLoggerModule {
17
+ static forRoot(config: LoggerConfig): {
18
+ module: typeof TpagaLoggerModule;
19
+ global: boolean;
20
+ providers: {
21
+ provide: "TPAGA_LOGGER";
22
+ useValue: Logger;
23
+ }[];
24
+ exports: "TPAGA_LOGGER"[];
25
+ };
26
+ }
27
+
28
+ export { TPAGA_LOGGER, TpagaLoggerInterceptor, TpagaLoggerModule, withTpagaFastifyLogger };
@@ -0,0 +1,28 @@
1
+ import { Observable } from 'rxjs';
2
+ import { FastifyInstance } from 'fastify';
3
+ import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
4
+ import { W as WideEventBuilder, c as LoggerConfig, b as Logger } from './types-BtTFSWeZ.js';
5
+
6
+ declare module 'fastify' {
7
+ interface FastifyRequest {
8
+ tpagaLog: WideEventBuilder;
9
+ }
10
+ }
11
+ declare const withTpagaFastifyLogger: (logger: Logger) => (fastify: FastifyInstance) => Promise<void>;
12
+ declare class TpagaLoggerInterceptor implements NestInterceptor {
13
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
14
+ }
15
+ declare const TPAGA_LOGGER: "TPAGA_LOGGER";
16
+ declare class TpagaLoggerModule {
17
+ static forRoot(config: LoggerConfig): {
18
+ module: typeof TpagaLoggerModule;
19
+ global: boolean;
20
+ providers: {
21
+ provide: "TPAGA_LOGGER";
22
+ useValue: Logger;
23
+ }[];
24
+ exports: "TPAGA_LOGGER"[];
25
+ };
26
+ }
27
+
28
+ export { TPAGA_LOGGER, TpagaLoggerInterceptor, TpagaLoggerModule, withTpagaFastifyLogger };
@@ -0,0 +1,163 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
5
+ var __typeError = (msg) => {
6
+ throw TypeError(msg);
7
+ };
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
10
+ var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
11
+ var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
12
+ var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
13
+ var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
14
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
15
+ var __runInitializers = (array, flags, self, value) => {
16
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
17
+ return value;
18
+ };
19
+ var __decorateElement = (array, flags, name, decorators, target, extra) => {
20
+ var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
21
+ var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
22
+ var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
23
+ var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
24
+ return __privateGet(this, extra);
25
+ }, set [name](x) {
26
+ return __privateSet(this, extra, x);
27
+ } }, name));
28
+ k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
29
+ for (var i = decorators.length - 1; i >= 0; i--) {
30
+ ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
31
+ if (k) {
32
+ ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
33
+ if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
34
+ if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
35
+ }
36
+ it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
37
+ if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
38
+ else if (typeof it !== "object" || it === null) __typeError("Object expected");
39
+ else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
40
+ }
41
+ return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
42
+ };
43
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
44
+ var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
45
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
46
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
47
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
48
+
49
+ // src/fastify.ts
50
+ import { catchError, throwError } from "rxjs";
51
+ import { Injectable } from "@nestjs/common";
52
+
53
+ // src/logger.ts
54
+ import pino from "pino";
55
+
56
+ // src/utils.ts
57
+ import { randomUUID } from "crypto";
58
+ var resolveCorrelationId = (headers = {}) => {
59
+ const h = Object.fromEntries(
60
+ Object.entries(headers).map(([k, v]) => [k.toLowerCase(), Array.isArray(v) ? v[0] : v])
61
+ );
62
+ return h["x-correlation-id"] ?? h["x-request-id"] ?? randomUUID();
63
+ };
64
+
65
+ // src/logger.ts
66
+ var serializeError = (err) => {
67
+ if (!(err instanceof Error)) return { type: "UnknownError", message: String(err) };
68
+ const errorDetails = err.issues;
69
+ return {
70
+ type: err.name,
71
+ message: errorDetails != null ? "Validation failed" : err.message,
72
+ code: err.code,
73
+ errorDetails
74
+ };
75
+ };
76
+ var createWideEventBuilder = (base, log) => {
77
+ const ctx = { ...base };
78
+ return {
79
+ with: (fields) => Object.assign(ctx, fields),
80
+ emit: (level, message, terminal) => log[level]({ ...ctx, ...terminal }, message)
81
+ };
82
+ };
83
+ var createLogger = (config) => {
84
+ const pretty = process.env.LOG_PRETTY === "true";
85
+ const log = pino({
86
+ level: process.env.LOG_LEVEL ?? "info",
87
+ base: { service: config.service, environment: config.environment },
88
+ formatters: {
89
+ level: (label) => ({ level: label })
90
+ },
91
+ timestamp: pino.stdTimeFunctions.isoTime,
92
+ redact: {
93
+ paths: config.redactKeys ?? [],
94
+ censor: "[REDACTED]"
95
+ },
96
+ ...pretty ? { transport: { target: "pino-pretty", options: { colorize: true } } } : {}
97
+ });
98
+ return {
99
+ startOperation: (functionName, base = {}) => createWideEventBuilder({ function: functionName, ...base }, log)
100
+ };
101
+ };
102
+
103
+ // src/fastify.ts
104
+ var START_KEY = /* @__PURE__ */ Symbol("tpagaStart");
105
+ var withTpagaFastifyLogger = (logger) => async (fastify) => {
106
+ fastify.addHook("onRequest", (request, _reply, done) => {
107
+ const correlationId = resolveCorrelationId(request.headers);
108
+ request.tpagaLog = logger.startOperation(request.url, {
109
+ correlationId,
110
+ method: request.method
111
+ });
112
+ request[START_KEY] = Date.now();
113
+ done();
114
+ });
115
+ fastify.addHook("onResponse", (request, reply, done) => {
116
+ const start = request[START_KEY];
117
+ request.tpagaLog.emit("info", "request completed", {
118
+ outcome: reply.statusCode < 400 ? "success" : "error",
119
+ durationMs: Date.now() - start,
120
+ statusCode: reply.statusCode
121
+ });
122
+ done();
123
+ });
124
+ fastify.addHook("onError", (request, _reply, error, done) => {
125
+ request.tpagaLog.with({ error: serializeError(error) });
126
+ done();
127
+ });
128
+ };
129
+ var _TpagaLoggerInterceptor_decorators, _init;
130
+ _TpagaLoggerInterceptor_decorators = [Injectable()];
131
+ var TpagaLoggerInterceptor = class {
132
+ intercept(context, next) {
133
+ return next.handle().pipe(
134
+ catchError((error) => {
135
+ const request = context.switchToHttp().getRequest();
136
+ request.tpagaLog?.with({ error: serializeError(error) });
137
+ return throwError(() => error);
138
+ })
139
+ );
140
+ }
141
+ };
142
+ _init = __decoratorStart(null);
143
+ TpagaLoggerInterceptor = __decorateElement(_init, 0, "TpagaLoggerInterceptor", _TpagaLoggerInterceptor_decorators, TpagaLoggerInterceptor);
144
+ __runInitializers(_init, 1, TpagaLoggerInterceptor);
145
+ var TPAGA_LOGGER = "TPAGA_LOGGER";
146
+ var TpagaLoggerModule = class _TpagaLoggerModule {
147
+ static forRoot(config) {
148
+ const logger = createLogger(config);
149
+ return {
150
+ module: _TpagaLoggerModule,
151
+ global: true,
152
+ providers: [{ provide: TPAGA_LOGGER, useValue: logger }],
153
+ exports: [TPAGA_LOGGER]
154
+ };
155
+ }
156
+ };
157
+ export {
158
+ TPAGA_LOGGER,
159
+ TpagaLoggerInterceptor,
160
+ TpagaLoggerModule,
161
+ withTpagaFastifyLogger
162
+ };
163
+ //# sourceMappingURL=fastify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/fastify.ts","../src/logger.ts","../src/utils.ts"],"sourcesContent":["import { catchError, throwError } from 'rxjs';\nimport type { Observable } from 'rxjs';\nimport type { FastifyInstance, FastifyRequest } from 'fastify';\nimport type { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';\nimport { Injectable } from '@nestjs/common';\nimport { createLogger, serializeError } from './logger.js';\nimport { resolveCorrelationId } from './utils.js';\nimport type { Logger, LoggerConfig, WideEventBuilder } from './types.js';\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n tpagaLog: WideEventBuilder;\n }\n}\n\nconst START_KEY = Symbol('tpagaStart');\n\nexport const withTpagaFastifyLogger = (logger: Logger) =>\n async (fastify: FastifyInstance): Promise<void> => {\n fastify.addHook('onRequest', (request, _reply, done) => {\n const correlationId = resolveCorrelationId(request.headers);\n request.tpagaLog = logger.startOperation(request.url, {\n correlationId,\n method: request.method,\n });\n (request as unknown as Record<symbol, number>)[START_KEY] = Date.now();\n done();\n });\n\n fastify.addHook('onResponse', (request, reply, done) => {\n const start = (request as unknown as Record<symbol, number>)[START_KEY];\n request.tpagaLog.emit('info', 'request completed', {\n outcome: reply.statusCode < 400 ? 'success' : 'error',\n durationMs: Date.now() - start,\n statusCode: reply.statusCode,\n });\n done();\n });\n\n fastify.addHook('onError', (request, _reply, error, done) => {\n request.tpagaLog.with({ error: serializeError(error) });\n done();\n });\n };\n\n@Injectable()\nexport class TpagaLoggerInterceptor implements NestInterceptor {\n intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {\n return next.handle().pipe(\n catchError((error: unknown) => {\n const request = context.switchToHttp().getRequest<FastifyRequest>();\n request.tpagaLog?.with({ error: serializeError(error) });\n return throwError(() => error);\n }),\n );\n }\n}\n\nexport const TPAGA_LOGGER = 'TPAGA_LOGGER' as const;\n\nexport class TpagaLoggerModule {\n static forRoot(config: LoggerConfig) {\n const logger = createLogger(config);\n return {\n module: TpagaLoggerModule,\n global: true,\n providers: [{ provide: TPAGA_LOGGER, useValue: logger }],\n exports: [TPAGA_LOGGER],\n };\n }\n}\n","import pino from 'pino';\nimport type { NextFunction, Request, Response } from 'express';\nimport type { Logger, LoggerConfig, SerializedError, TerminalFields, WideEventBuilder } from './types.js';\nimport { resolveCorrelationId } from './utils.js';\n\ndeclare global {\n namespace Express {\n interface Request {\n log: WideEventBuilder;\n }\n interface Response {\n log: WideEventBuilder;\n }\n }\n}\n\nexport const serializeError = (err: unknown): SerializedError => {\n if (!(err instanceof Error)) return { type: 'UnknownError', message: String(err) };\n const errorDetails = (err as { issues?: unknown }).issues;\n return {\n type: err.name,\n message: errorDetails != null ? 'Validation failed' : err.message,\n code: (err as { code?: string }).code,\n errorDetails,\n };\n};\n\nconst createWideEventBuilder = (\n base: Record<string, unknown>,\n log: pino.Logger,\n): WideEventBuilder => {\n const ctx = { ...base };\n return {\n with: (fields) => Object.assign(ctx, fields),\n emit: (level, message, terminal: TerminalFields) => log[level]({ ...ctx, ...terminal }, message),\n };\n};\n\nexport const createLogger = (config: LoggerConfig): Logger => {\n const pretty = process.env.LOG_PRETTY === 'true';\n const log = pino({\n level: process.env.LOG_LEVEL ?? 'info',\n base: { service: config.service, environment: config.environment },\n formatters: {\n level: (label) => ({ level: label }),\n },\n timestamp: pino.stdTimeFunctions.isoTime,\n redact: {\n paths: (config.redactKeys as string[]) ?? [],\n censor: '[REDACTED]',\n },\n ...(pretty ? { transport: { target: 'pino-pretty', options: { colorize: true } } } : {}),\n });\n\n return {\n startOperation: (functionName, base = {}) =>\n createWideEventBuilder({ function: functionName, ...base }, log),\n };\n};\n\ntype EventWithHeaders = { headers?: Record<string, string | undefined> };\ntype HandlerWithBuilder<TEvent> = (event: TEvent, builder: WideEventBuilder) => Promise<unknown>;\n\nconst EXPRESS_LOG_KEY = 'tpagaLog';\n\nexport const withTpagaExpressLogger = (logger: Logger) =>\n (req: Request, res: Response, next: NextFunction): void => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(req.headers as Record<string, string | undefined>);\n const log = logger.startOperation(req.path, { correlationId, method: req.method });\n\n req.log = log;\n res.log = log;\n res.locals[EXPRESS_LOG_KEY] = log;\n\n res.on('finish', () => {\n log.emit('info', 'request completed', {\n outcome: res.statusCode < 400 ? 'success' : 'error',\n durationMs: Date.now() - start,\n statusCode: res.statusCode,\n });\n });\n\n next();\n };\n\nexport const tpagaExpressErrorLogger = (err: unknown, req: Request, _res: Response, next: NextFunction): void => {\n req.log.with({ error: serializeError(err) });\n next(err);\n};\n\nexport const getLog = (res: Response): WideEventBuilder =>\n res.locals[EXPRESS_LOG_KEY] as WideEventBuilder;\n\nexport const withLogger = <TEvent extends EventWithHeaders>(\n logger: Logger,\n operationName: string,\n handler: HandlerWithBuilder<TEvent>,\n) =>\n async (event: TEvent, ..._rest: unknown[]): Promise<unknown> => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(event.headers);\n const builder = logger.startOperation(operationName, { correlationId });\n\n try {\n const result = await handler(event, builder);\n const statusCode =\n result != null &&\n typeof result === 'object' &&\n 'statusCode' in result &&\n typeof (result as { statusCode: unknown }).statusCode === 'number'\n ? (result as { statusCode: number }).statusCode\n : undefined;\n builder.emit('info', `${operationName} completed`, {\n outcome: 'success',\n durationMs: Date.now() - start,\n ...(statusCode !== undefined ? { statusCode } : {}),\n });\n return result;\n } catch (err) {\n builder.emit('error', `${operationName} failed`, {\n outcome: 'error',\n durationMs: Date.now() - start,\n error: serializeError(err),\n });\n throw err;\n }\n };\n","import { randomUUID } from 'crypto';\n\nexport const resolveCorrelationId = (\n headers: Record<string, string | string[] | undefined> = {},\n): string => {\n const h = Object.fromEntries(\n Object.entries(headers).map(([k, v]) => [k.toLowerCase(), Array.isArray(v) ? v[0] : v]),\n );\n return h['x-correlation-id'] ?? h['x-request-id'] ?? randomUUID();\n};\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -42,8 +42,18 @@ __export(index_exports, {
42
42
  module.exports = __toCommonJS(index_exports);
43
43
 
44
44
  // src/logger.ts
45
- var import_crypto = require("crypto");
46
45
  var import_pino = __toESM(require("pino"), 1);
46
+
47
+ // src/utils.ts
48
+ var import_crypto = require("crypto");
49
+ var resolveCorrelationId = (headers = {}) => {
50
+ const h = Object.fromEntries(
51
+ Object.entries(headers).map(([k, v]) => [k.toLowerCase(), Array.isArray(v) ? v[0] : v])
52
+ );
53
+ return h["x-correlation-id"] ?? h["x-request-id"] ?? (0, import_crypto.randomUUID)();
54
+ };
55
+
56
+ // src/logger.ts
47
57
  var serializeError = (err) => {
48
58
  if (!(err instanceof Error)) return { type: "UnknownError", message: String(err) };
49
59
  const errorDetails = err.issues;
@@ -80,10 +90,6 @@ var createLogger = (config) => {
80
90
  startOperation: (functionName, base = {}) => createWideEventBuilder({ function: functionName, ...base }, log)
81
91
  };
82
92
  };
83
- var resolveCorrelationId = (headers = {}) => {
84
- const h = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]));
85
- return h["x-correlation-id"] ?? h["x-request-id"] ?? (0, import_crypto.randomUUID)();
86
- };
87
93
  var EXPRESS_LOG_KEY = "tpagaLog";
88
94
  var withTpagaExpressLogger = (logger) => (req, res, next) => {
89
95
  const start = Date.now();
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/logger.ts","../src/constants.ts"],"sourcesContent":["export { createLogger, serializeError, withLogger, withTpagaExpressLogger, tpagaExpressErrorLogger, getLog } from './logger.js';\nexport { LOG_LEVELS, OUTCOMES } from './constants.js';\nexport type {\n Logger,\n LoggerConfig,\n LogLevel,\n Outcome,\n SerializedError,\n TerminalFields,\n WideEventBuilder,\n} from './types.js';\n","import { randomUUID } from 'crypto';\nimport pino from 'pino';\nimport type { NextFunction, Request, Response } from 'express';\nimport type { Logger, LoggerConfig, SerializedError, TerminalFields, WideEventBuilder } from './types.js';\n\ndeclare global {\n namespace Express {\n interface Request {\n log: WideEventBuilder;\n }\n interface Response {\n log: WideEventBuilder;\n }\n }\n}\n\nexport const serializeError = (err: unknown): SerializedError => {\n if (!(err instanceof Error)) return { type: 'UnknownError', message: String(err) };\n const errorDetails = (err as { issues?: unknown }).issues;\n return {\n type: err.name,\n message: errorDetails != null ? 'Validation failed' : err.message,\n code: (err as { code?: string }).code,\n errorDetails,\n };\n};\n\nconst createWideEventBuilder = (\n base: Record<string, unknown>,\n log: pino.Logger,\n): WideEventBuilder => {\n const ctx = { ...base };\n return {\n with: (fields) => Object.assign(ctx, fields),\n emit: (level, message, terminal: TerminalFields) => log[level]({ ...ctx, ...terminal }, message),\n };\n};\n\nexport const createLogger = (config: LoggerConfig): Logger => {\n const pretty = process.env.LOG_PRETTY === 'true';\n const log = pino({\n level: process.env.LOG_LEVEL ?? 'info',\n base: { service: config.service, environment: config.environment },\n formatters: {\n level: (label) => ({ level: label }),\n },\n timestamp: pino.stdTimeFunctions.isoTime,\n redact: {\n paths: (config.redactKeys as string[]) ?? [],\n censor: '[REDACTED]',\n },\n ...(pretty ? { transport: { target: 'pino-pretty', options: { colorize: true } } } : {}),\n });\n\n return {\n startOperation: (functionName, base = {}) =>\n createWideEventBuilder({ function: functionName, ...base }, log),\n };\n};\n\ntype EventWithHeaders = { headers?: Record<string, string | undefined> };\ntype HandlerWithBuilder<TEvent> = (event: TEvent, builder: WideEventBuilder) => Promise<unknown>;\n\nconst resolveCorrelationId = (headers: Record<string, string | undefined> = {}): string => {\n const h = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]));\n return h['x-correlation-id'] ?? h['x-request-id'] ?? randomUUID();\n};\n\nconst EXPRESS_LOG_KEY = 'tpagaLog';\n\nexport const withTpagaExpressLogger = (logger: Logger) =>\n (req: Request, res: Response, next: NextFunction): void => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(req.headers as Record<string, string | undefined>);\n const log = logger.startOperation(req.path, { correlationId, method: req.method });\n\n req.log = log;\n res.log = log;\n res.locals[EXPRESS_LOG_KEY] = log;\n\n res.on('finish', () => {\n log.emit('info', 'request completed', {\n outcome: res.statusCode < 400 ? 'success' : 'error',\n durationMs: Date.now() - start,\n statusCode: res.statusCode,\n });\n });\n\n next();\n };\n\nexport const tpagaExpressErrorLogger = (err: unknown, req: Request, _res: Response, next: NextFunction): void => {\n req.log.with({ error: serializeError(err) });\n next(err);\n};\n\nexport const getLog = (res: Response): WideEventBuilder =>\n res.locals[EXPRESS_LOG_KEY] as WideEventBuilder;\n\nexport const withLogger = <TEvent extends EventWithHeaders>(\n logger: Logger,\n operationName: string,\n handler: HandlerWithBuilder<TEvent>,\n) =>\n async (event: TEvent, ..._rest: unknown[]): Promise<unknown> => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(event.headers);\n const builder = logger.startOperation(operationName, { correlationId });\n\n try {\n const result = await handler(event, builder);\n const statusCode =\n result != null &&\n typeof result === 'object' &&\n 'statusCode' in result &&\n typeof (result as { statusCode: unknown }).statusCode === 'number'\n ? (result as { statusCode: number }).statusCode\n : undefined;\n builder.emit('info', `${operationName} completed`, {\n outcome: 'success',\n durationMs: Date.now() - start,\n ...(statusCode !== undefined ? { statusCode } : {}),\n });\n return result;\n } catch (err) {\n builder.emit('error', `${operationName} failed`, {\n outcome: 'error',\n durationMs: Date.now() - start,\n error: serializeError(err),\n });\n throw err;\n }\n };\n","export const LOG_LEVELS = ['info', 'warn', 'error', 'debug'] as const;\n\nexport const OUTCOMES = [\n 'success',\n 'error',\n 'validation_failed',\n 'not_found',\n 'conflict',\n 'timeout',\n 'skipped',\n] as const;\n"],"mappings":"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","names":["pino"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/logger.ts","../src/utils.ts","../src/constants.ts"],"sourcesContent":["export { createLogger, serializeError, withLogger, withTpagaExpressLogger, tpagaExpressErrorLogger, getLog } from './logger.js';\nexport { LOG_LEVELS, OUTCOMES } from './constants.js';\nexport type {\n Logger,\n LoggerConfig,\n LogLevel,\n Outcome,\n SerializedError,\n TerminalFields,\n WideEventBuilder,\n} from './types.js';\n","import pino from 'pino';\nimport type { NextFunction, Request, Response } from 'express';\nimport type { Logger, LoggerConfig, SerializedError, TerminalFields, WideEventBuilder } from './types.js';\nimport { resolveCorrelationId } from './utils.js';\n\ndeclare global {\n namespace Express {\n interface Request {\n log: WideEventBuilder;\n }\n interface Response {\n log: WideEventBuilder;\n }\n }\n}\n\nexport const serializeError = (err: unknown): SerializedError => {\n if (!(err instanceof Error)) return { type: 'UnknownError', message: String(err) };\n const errorDetails = (err as { issues?: unknown }).issues;\n return {\n type: err.name,\n message: errorDetails != null ? 'Validation failed' : err.message,\n code: (err as { code?: string }).code,\n errorDetails,\n };\n};\n\nconst createWideEventBuilder = (\n base: Record<string, unknown>,\n log: pino.Logger,\n): WideEventBuilder => {\n const ctx = { ...base };\n return {\n with: (fields) => Object.assign(ctx, fields),\n emit: (level, message, terminal: TerminalFields) => log[level]({ ...ctx, ...terminal }, message),\n };\n};\n\nexport const createLogger = (config: LoggerConfig): Logger => {\n const pretty = process.env.LOG_PRETTY === 'true';\n const log = pino({\n level: process.env.LOG_LEVEL ?? 'info',\n base: { service: config.service, environment: config.environment },\n formatters: {\n level: (label) => ({ level: label }),\n },\n timestamp: pino.stdTimeFunctions.isoTime,\n redact: {\n paths: (config.redactKeys as string[]) ?? [],\n censor: '[REDACTED]',\n },\n ...(pretty ? { transport: { target: 'pino-pretty', options: { colorize: true } } } : {}),\n });\n\n return {\n startOperation: (functionName, base = {}) =>\n createWideEventBuilder({ function: functionName, ...base }, log),\n };\n};\n\ntype EventWithHeaders = { headers?: Record<string, string | undefined> };\ntype HandlerWithBuilder<TEvent> = (event: TEvent, builder: WideEventBuilder) => Promise<unknown>;\n\nconst EXPRESS_LOG_KEY = 'tpagaLog';\n\nexport const withTpagaExpressLogger = (logger: Logger) =>\n (req: Request, res: Response, next: NextFunction): void => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(req.headers as Record<string, string | undefined>);\n const log = logger.startOperation(req.path, { correlationId, method: req.method });\n\n req.log = log;\n res.log = log;\n res.locals[EXPRESS_LOG_KEY] = log;\n\n res.on('finish', () => {\n log.emit('info', 'request completed', {\n outcome: res.statusCode < 400 ? 'success' : 'error',\n durationMs: Date.now() - start,\n statusCode: res.statusCode,\n });\n });\n\n next();\n };\n\nexport const tpagaExpressErrorLogger = (err: unknown, req: Request, _res: Response, next: NextFunction): void => {\n req.log.with({ error: serializeError(err) });\n next(err);\n};\n\nexport const getLog = (res: Response): WideEventBuilder =>\n res.locals[EXPRESS_LOG_KEY] as WideEventBuilder;\n\nexport const withLogger = <TEvent extends EventWithHeaders>(\n logger: Logger,\n operationName: string,\n handler: HandlerWithBuilder<TEvent>,\n) =>\n async (event: TEvent, ..._rest: unknown[]): Promise<unknown> => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(event.headers);\n const builder = logger.startOperation(operationName, { correlationId });\n\n try {\n const result = await handler(event, builder);\n const statusCode =\n result != null &&\n typeof result === 'object' &&\n 'statusCode' in result &&\n typeof (result as { statusCode: unknown }).statusCode === 'number'\n ? (result as { statusCode: number }).statusCode\n : undefined;\n builder.emit('info', `${operationName} completed`, {\n outcome: 'success',\n durationMs: Date.now() - start,\n ...(statusCode !== undefined ? { statusCode } : {}),\n });\n return result;\n } catch (err) {\n builder.emit('error', `${operationName} failed`, {\n outcome: 'error',\n durationMs: Date.now() - start,\n error: serializeError(err),\n });\n throw err;\n }\n };\n","import { randomUUID } from 'crypto';\n\nexport const resolveCorrelationId = (\n headers: Record<string, string | string[] | undefined> = {},\n): string => {\n const h = Object.fromEntries(\n Object.entries(headers).map(([k, v]) => [k.toLowerCase(), Array.isArray(v) ? v[0] : v]),\n );\n return h['x-correlation-id'] ?? h['x-request-id'] ?? randomUUID();\n};\n","export const LOG_LEVELS = ['info', 'warn', 'error', 'debug'] as const;\n\nexport const OUTCOMES = [\n 'success',\n 'error',\n 'validation_failed',\n 'not_found',\n 'conflict',\n 'timeout',\n 'skipped',\n] as const;\n"],"mappings":"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","names":["pino"]}
package/dist/index.d.cts CHANGED
@@ -1,34 +1,6 @@
1
1
  import { Response, Request, NextFunction } from 'express';
2
-
3
- declare const LOG_LEVELS: readonly ["info", "warn", "error", "debug"];
4
- declare const OUTCOMES: readonly ["success", "error", "validation_failed", "not_found", "conflict", "timeout", "skipped"];
5
-
6
- type LogLevel = (typeof LOG_LEVELS)[number];
7
- type Outcome = (typeof OUTCOMES)[number];
8
- type LoggerConfig = {
9
- service: string;
10
- environment?: string;
11
- redactKeys?: readonly string[];
12
- };
13
- type SerializedError = {
14
- type: string;
15
- message: string;
16
- code?: string;
17
- errorDetails?: unknown;
18
- };
19
- type TerminalFields = {
20
- outcome: Outcome | string;
21
- durationMs: number;
22
- statusCode?: number;
23
- error?: SerializedError;
24
- };
25
- type WideEventBuilder = {
26
- with: (fields: Record<string, unknown>) => void;
27
- emit: (level: LogLevel, message: string, terminal: TerminalFields) => void;
28
- };
29
- type Logger = {
30
- startOperation: (functionName: string, base?: Record<string, unknown>) => WideEventBuilder;
31
- };
2
+ import { W as WideEventBuilder, c as LoggerConfig, b as Logger, S as SerializedError } from './types-BtTFSWeZ.cjs';
3
+ export { L as LOG_LEVELS, a as LogLevel, O as OUTCOMES, d as Outcome, T as TerminalFields } from './types-BtTFSWeZ.cjs';
32
4
 
33
5
  declare global {
34
6
  namespace Express {
@@ -51,4 +23,4 @@ declare const tpagaExpressErrorLogger: (err: unknown, req: Request, _res: Respon
51
23
  declare const getLog: (res: Response) => WideEventBuilder;
52
24
  declare const withLogger: <TEvent extends EventWithHeaders>(logger: Logger, operationName: string, handler: HandlerWithBuilder<TEvent>) => (event: TEvent, ..._rest: unknown[]) => Promise<unknown>;
53
25
 
54
- export { LOG_LEVELS, type LogLevel, type Logger, type LoggerConfig, OUTCOMES, type Outcome, type SerializedError, type TerminalFields, type WideEventBuilder, createLogger, getLog, serializeError, tpagaExpressErrorLogger, withLogger, withTpagaExpressLogger };
26
+ export { Logger, LoggerConfig, SerializedError, WideEventBuilder, createLogger, getLog, serializeError, tpagaExpressErrorLogger, withLogger, withTpagaExpressLogger };
package/dist/index.d.ts CHANGED
@@ -1,34 +1,6 @@
1
1
  import { Response, Request, NextFunction } from 'express';
2
-
3
- declare const LOG_LEVELS: readonly ["info", "warn", "error", "debug"];
4
- declare const OUTCOMES: readonly ["success", "error", "validation_failed", "not_found", "conflict", "timeout", "skipped"];
5
-
6
- type LogLevel = (typeof LOG_LEVELS)[number];
7
- type Outcome = (typeof OUTCOMES)[number];
8
- type LoggerConfig = {
9
- service: string;
10
- environment?: string;
11
- redactKeys?: readonly string[];
12
- };
13
- type SerializedError = {
14
- type: string;
15
- message: string;
16
- code?: string;
17
- errorDetails?: unknown;
18
- };
19
- type TerminalFields = {
20
- outcome: Outcome | string;
21
- durationMs: number;
22
- statusCode?: number;
23
- error?: SerializedError;
24
- };
25
- type WideEventBuilder = {
26
- with: (fields: Record<string, unknown>) => void;
27
- emit: (level: LogLevel, message: string, terminal: TerminalFields) => void;
28
- };
29
- type Logger = {
30
- startOperation: (functionName: string, base?: Record<string, unknown>) => WideEventBuilder;
31
- };
2
+ import { W as WideEventBuilder, c as LoggerConfig, b as Logger, S as SerializedError } from './types-BtTFSWeZ.js';
3
+ export { L as LOG_LEVELS, a as LogLevel, O as OUTCOMES, d as Outcome, T as TerminalFields } from './types-BtTFSWeZ.js';
32
4
 
33
5
  declare global {
34
6
  namespace Express {
@@ -51,4 +23,4 @@ declare const tpagaExpressErrorLogger: (err: unknown, req: Request, _res: Respon
51
23
  declare const getLog: (res: Response) => WideEventBuilder;
52
24
  declare const withLogger: <TEvent extends EventWithHeaders>(logger: Logger, operationName: string, handler: HandlerWithBuilder<TEvent>) => (event: TEvent, ..._rest: unknown[]) => Promise<unknown>;
53
25
 
54
- export { LOG_LEVELS, type LogLevel, type Logger, type LoggerConfig, OUTCOMES, type Outcome, type SerializedError, type TerminalFields, type WideEventBuilder, createLogger, getLog, serializeError, tpagaExpressErrorLogger, withLogger, withTpagaExpressLogger };
26
+ export { Logger, LoggerConfig, SerializedError, WideEventBuilder, createLogger, getLog, serializeError, tpagaExpressErrorLogger, withLogger, withTpagaExpressLogger };
package/dist/index.js CHANGED
@@ -1,6 +1,16 @@
1
1
  // src/logger.ts
2
- import { randomUUID } from "crypto";
3
2
  import pino from "pino";
3
+
4
+ // src/utils.ts
5
+ import { randomUUID } from "crypto";
6
+ var resolveCorrelationId = (headers = {}) => {
7
+ const h = Object.fromEntries(
8
+ Object.entries(headers).map(([k, v]) => [k.toLowerCase(), Array.isArray(v) ? v[0] : v])
9
+ );
10
+ return h["x-correlation-id"] ?? h["x-request-id"] ?? randomUUID();
11
+ };
12
+
13
+ // src/logger.ts
4
14
  var serializeError = (err) => {
5
15
  if (!(err instanceof Error)) return { type: "UnknownError", message: String(err) };
6
16
  const errorDetails = err.issues;
@@ -37,10 +47,6 @@ var createLogger = (config) => {
37
47
  startOperation: (functionName, base = {}) => createWideEventBuilder({ function: functionName, ...base }, log)
38
48
  };
39
49
  };
40
- var resolveCorrelationId = (headers = {}) => {
41
- const h = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]));
42
- return h["x-correlation-id"] ?? h["x-request-id"] ?? randomUUID();
43
- };
44
50
  var EXPRESS_LOG_KEY = "tpagaLog";
45
51
  var withTpagaExpressLogger = (logger) => (req, res, next) => {
46
52
  const start = Date.now();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/logger.ts","../src/constants.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport pino from 'pino';\nimport type { NextFunction, Request, Response } from 'express';\nimport type { Logger, LoggerConfig, SerializedError, TerminalFields, WideEventBuilder } from './types.js';\n\ndeclare global {\n namespace Express {\n interface Request {\n log: WideEventBuilder;\n }\n interface Response {\n log: WideEventBuilder;\n }\n }\n}\n\nexport const serializeError = (err: unknown): SerializedError => {\n if (!(err instanceof Error)) return { type: 'UnknownError', message: String(err) };\n const errorDetails = (err as { issues?: unknown }).issues;\n return {\n type: err.name,\n message: errorDetails != null ? 'Validation failed' : err.message,\n code: (err as { code?: string }).code,\n errorDetails,\n };\n};\n\nconst createWideEventBuilder = (\n base: Record<string, unknown>,\n log: pino.Logger,\n): WideEventBuilder => {\n const ctx = { ...base };\n return {\n with: (fields) => Object.assign(ctx, fields),\n emit: (level, message, terminal: TerminalFields) => log[level]({ ...ctx, ...terminal }, message),\n };\n};\n\nexport const createLogger = (config: LoggerConfig): Logger => {\n const pretty = process.env.LOG_PRETTY === 'true';\n const log = pino({\n level: process.env.LOG_LEVEL ?? 'info',\n base: { service: config.service, environment: config.environment },\n formatters: {\n level: (label) => ({ level: label }),\n },\n timestamp: pino.stdTimeFunctions.isoTime,\n redact: {\n paths: (config.redactKeys as string[]) ?? [],\n censor: '[REDACTED]',\n },\n ...(pretty ? { transport: { target: 'pino-pretty', options: { colorize: true } } } : {}),\n });\n\n return {\n startOperation: (functionName, base = {}) =>\n createWideEventBuilder({ function: functionName, ...base }, log),\n };\n};\n\ntype EventWithHeaders = { headers?: Record<string, string | undefined> };\ntype HandlerWithBuilder<TEvent> = (event: TEvent, builder: WideEventBuilder) => Promise<unknown>;\n\nconst resolveCorrelationId = (headers: Record<string, string | undefined> = {}): string => {\n const h = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]));\n return h['x-correlation-id'] ?? h['x-request-id'] ?? randomUUID();\n};\n\nconst EXPRESS_LOG_KEY = 'tpagaLog';\n\nexport const withTpagaExpressLogger = (logger: Logger) =>\n (req: Request, res: Response, next: NextFunction): void => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(req.headers as Record<string, string | undefined>);\n const log = logger.startOperation(req.path, { correlationId, method: req.method });\n\n req.log = log;\n res.log = log;\n res.locals[EXPRESS_LOG_KEY] = log;\n\n res.on('finish', () => {\n log.emit('info', 'request completed', {\n outcome: res.statusCode < 400 ? 'success' : 'error',\n durationMs: Date.now() - start,\n statusCode: res.statusCode,\n });\n });\n\n next();\n };\n\nexport const tpagaExpressErrorLogger = (err: unknown, req: Request, _res: Response, next: NextFunction): void => {\n req.log.with({ error: serializeError(err) });\n next(err);\n};\n\nexport const getLog = (res: Response): WideEventBuilder =>\n res.locals[EXPRESS_LOG_KEY] as WideEventBuilder;\n\nexport const withLogger = <TEvent extends EventWithHeaders>(\n logger: Logger,\n operationName: string,\n handler: HandlerWithBuilder<TEvent>,\n) =>\n async (event: TEvent, ..._rest: unknown[]): Promise<unknown> => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(event.headers);\n const builder = logger.startOperation(operationName, { correlationId });\n\n try {\n const result = await handler(event, builder);\n const statusCode =\n result != null &&\n typeof result === 'object' &&\n 'statusCode' in result &&\n typeof (result as { statusCode: unknown }).statusCode === 'number'\n ? (result as { statusCode: number }).statusCode\n : undefined;\n builder.emit('info', `${operationName} completed`, {\n outcome: 'success',\n durationMs: Date.now() - start,\n ...(statusCode !== undefined ? { statusCode } : {}),\n });\n return result;\n } catch (err) {\n builder.emit('error', `${operationName} failed`, {\n outcome: 'error',\n durationMs: Date.now() - start,\n error: serializeError(err),\n });\n throw err;\n }\n };\n","export const LOG_LEVELS = ['info', 'warn', 'error', 'debug'] as const;\n\nexport const OUTCOMES = [\n 'success',\n 'error',\n 'validation_failed',\n 'not_found',\n 'conflict',\n 'timeout',\n 'skipped',\n] as const;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/logger.ts","../src/utils.ts","../src/constants.ts"],"sourcesContent":["import pino from 'pino';\nimport type { NextFunction, Request, Response } from 'express';\nimport type { Logger, LoggerConfig, SerializedError, TerminalFields, WideEventBuilder } from './types.js';\nimport { resolveCorrelationId } from './utils.js';\n\ndeclare global {\n namespace Express {\n interface Request {\n log: WideEventBuilder;\n }\n interface Response {\n log: WideEventBuilder;\n }\n }\n}\n\nexport const serializeError = (err: unknown): SerializedError => {\n if (!(err instanceof Error)) return { type: 'UnknownError', message: String(err) };\n const errorDetails = (err as { issues?: unknown }).issues;\n return {\n type: err.name,\n message: errorDetails != null ? 'Validation failed' : err.message,\n code: (err as { code?: string }).code,\n errorDetails,\n };\n};\n\nconst createWideEventBuilder = (\n base: Record<string, unknown>,\n log: pino.Logger,\n): WideEventBuilder => {\n const ctx = { ...base };\n return {\n with: (fields) => Object.assign(ctx, fields),\n emit: (level, message, terminal: TerminalFields) => log[level]({ ...ctx, ...terminal }, message),\n };\n};\n\nexport const createLogger = (config: LoggerConfig): Logger => {\n const pretty = process.env.LOG_PRETTY === 'true';\n const log = pino({\n level: process.env.LOG_LEVEL ?? 'info',\n base: { service: config.service, environment: config.environment },\n formatters: {\n level: (label) => ({ level: label }),\n },\n timestamp: pino.stdTimeFunctions.isoTime,\n redact: {\n paths: (config.redactKeys as string[]) ?? [],\n censor: '[REDACTED]',\n },\n ...(pretty ? { transport: { target: 'pino-pretty', options: { colorize: true } } } : {}),\n });\n\n return {\n startOperation: (functionName, base = {}) =>\n createWideEventBuilder({ function: functionName, ...base }, log),\n };\n};\n\ntype EventWithHeaders = { headers?: Record<string, string | undefined> };\ntype HandlerWithBuilder<TEvent> = (event: TEvent, builder: WideEventBuilder) => Promise<unknown>;\n\nconst EXPRESS_LOG_KEY = 'tpagaLog';\n\nexport const withTpagaExpressLogger = (logger: Logger) =>\n (req: Request, res: Response, next: NextFunction): void => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(req.headers as Record<string, string | undefined>);\n const log = logger.startOperation(req.path, { correlationId, method: req.method });\n\n req.log = log;\n res.log = log;\n res.locals[EXPRESS_LOG_KEY] = log;\n\n res.on('finish', () => {\n log.emit('info', 'request completed', {\n outcome: res.statusCode < 400 ? 'success' : 'error',\n durationMs: Date.now() - start,\n statusCode: res.statusCode,\n });\n });\n\n next();\n };\n\nexport const tpagaExpressErrorLogger = (err: unknown, req: Request, _res: Response, next: NextFunction): void => {\n req.log.with({ error: serializeError(err) });\n next(err);\n};\n\nexport const getLog = (res: Response): WideEventBuilder =>\n res.locals[EXPRESS_LOG_KEY] as WideEventBuilder;\n\nexport const withLogger = <TEvent extends EventWithHeaders>(\n logger: Logger,\n operationName: string,\n handler: HandlerWithBuilder<TEvent>,\n) =>\n async (event: TEvent, ..._rest: unknown[]): Promise<unknown> => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(event.headers);\n const builder = logger.startOperation(operationName, { correlationId });\n\n try {\n const result = await handler(event, builder);\n const statusCode =\n result != null &&\n typeof result === 'object' &&\n 'statusCode' in result &&\n typeof (result as { statusCode: unknown }).statusCode === 'number'\n ? (result as { statusCode: number }).statusCode\n : undefined;\n builder.emit('info', `${operationName} completed`, {\n outcome: 'success',\n durationMs: Date.now() - start,\n ...(statusCode !== undefined ? { statusCode } : {}),\n });\n return result;\n } catch (err) {\n builder.emit('error', `${operationName} failed`, {\n outcome: 'error',\n durationMs: Date.now() - start,\n error: serializeError(err),\n });\n throw err;\n }\n };\n","import { randomUUID } from 'crypto';\n\nexport const resolveCorrelationId = (\n headers: Record<string, string | string[] | undefined> = {},\n): string => {\n const h = Object.fromEntries(\n Object.entries(headers).map(([k, v]) => [k.toLowerCase(), Array.isArray(v) ? v[0] : v]),\n );\n return h['x-correlation-id'] ?? h['x-request-id'] ?? randomUUID();\n};\n","export const LOG_LEVELS = ['info', 'warn', 'error', 'debug'] as const;\n\nexport const OUTCOMES = [\n 'success',\n 'error',\n 'validation_failed',\n 'not_found',\n 'conflict',\n 'timeout',\n 'skipped',\n] as const;\n"],"mappings":"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","names":[]}
@@ -0,0 +1,31 @@
1
+ declare const LOG_LEVELS: readonly ["info", "warn", "error", "debug"];
2
+ declare const OUTCOMES: readonly ["success", "error", "validation_failed", "not_found", "conflict", "timeout", "skipped"];
3
+
4
+ type LogLevel = (typeof LOG_LEVELS)[number];
5
+ type Outcome = (typeof OUTCOMES)[number];
6
+ type LoggerConfig = {
7
+ service: string;
8
+ environment?: string;
9
+ redactKeys?: readonly string[];
10
+ };
11
+ type SerializedError = {
12
+ type: string;
13
+ message: string;
14
+ code?: string;
15
+ errorDetails?: unknown;
16
+ };
17
+ type TerminalFields = {
18
+ outcome: Outcome | string;
19
+ durationMs: number;
20
+ statusCode?: number;
21
+ error?: SerializedError;
22
+ };
23
+ type WideEventBuilder = {
24
+ with: (fields: Record<string, unknown>) => void;
25
+ emit: (level: LogLevel, message: string, terminal: TerminalFields) => void;
26
+ };
27
+ type Logger = {
28
+ startOperation: (functionName: string, base?: Record<string, unknown>) => WideEventBuilder;
29
+ };
30
+
31
+ export { LOG_LEVELS as L, OUTCOMES as O, type SerializedError as S, type TerminalFields as T, type WideEventBuilder as W, type LogLevel as a, type Logger as b, type LoggerConfig as c, type Outcome as d };
@@ -0,0 +1,31 @@
1
+ declare const LOG_LEVELS: readonly ["info", "warn", "error", "debug"];
2
+ declare const OUTCOMES: readonly ["success", "error", "validation_failed", "not_found", "conflict", "timeout", "skipped"];
3
+
4
+ type LogLevel = (typeof LOG_LEVELS)[number];
5
+ type Outcome = (typeof OUTCOMES)[number];
6
+ type LoggerConfig = {
7
+ service: string;
8
+ environment?: string;
9
+ redactKeys?: readonly string[];
10
+ };
11
+ type SerializedError = {
12
+ type: string;
13
+ message: string;
14
+ code?: string;
15
+ errorDetails?: unknown;
16
+ };
17
+ type TerminalFields = {
18
+ outcome: Outcome | string;
19
+ durationMs: number;
20
+ statusCode?: number;
21
+ error?: SerializedError;
22
+ };
23
+ type WideEventBuilder = {
24
+ with: (fields: Record<string, unknown>) => void;
25
+ emit: (level: LogLevel, message: string, terminal: TerminalFields) => void;
26
+ };
27
+ type Logger = {
28
+ startOperation: (functionName: string, base?: Record<string, unknown>) => WideEventBuilder;
29
+ };
30
+
31
+ export { LOG_LEVELS as L, OUTCOMES as O, type SerializedError as S, type TerminalFields as T, type WideEventBuilder as W, type LogLevel as a, type Logger as b, type LoggerConfig as c, type Outcome as d };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tpaga-logger",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Structured logging SDK for Tpaga microservices (wide events → CloudWatch)",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -14,6 +14,11 @@
14
14
  "types": "./dist/index.d.ts",
15
15
  "import": "./dist/index.js",
16
16
  "require": "./dist/index.cjs"
17
+ },
18
+ "./fastify": {
19
+ "types": "./dist/fastify.d.ts",
20
+ "import": "./dist/fastify.js",
21
+ "require": "./dist/fastify.cjs"
17
22
  }
18
23
  },
19
24
  "files": [
@@ -22,33 +27,48 @@
22
27
  "engmake sines": {
23
28
  "node": ">=20"
24
29
  },
30
+ "scripts": {
31
+ "build": "tsup",
32
+ "test": "vitest run",
33
+ "test:watch": "vitest",
34
+ "lint": "eslint src tests",
35
+ "typecheck": "tsc --noEmit"
36
+ },
25
37
  "dependencies": {
26
38
  "pino": "^10.3.1",
27
39
  "pino-pretty": "^13.1.3"
28
40
  },
29
41
  "peerDependencies": {
30
- "express": ">=4"
42
+ "@nestjs/common": ">=10",
43
+ "express": ">=4",
44
+ "fastify": ">=4",
45
+ "rxjs": ">=7"
31
46
  },
32
47
  "peerDependenciesMeta": {
33
48
  "express": {
34
49
  "optional": true
50
+ },
51
+ "fastify": {
52
+ "optional": true
53
+ },
54
+ "@nestjs/common": {
55
+ "optional": true
56
+ },
57
+ "rxjs": {
58
+ "optional": true
35
59
  }
36
60
  },
37
61
  "devDependencies": {
38
62
  "@eslint/js": "^9.28.0",
63
+ "@nestjs/common": "^11.1.27",
39
64
  "@types/express": "^5.0.6",
40
65
  "@types/node": "^22.15.0",
41
66
  "eslint": "^9.28.0",
67
+ "fastify": "^5.9.0",
68
+ "rxjs": "^7.8.2",
42
69
  "tsup": "^8.5.0",
43
70
  "typescript": "^5.8.3",
44
71
  "typescript-eslint": "^8.33.0",
45
72
  "vitest": "^3.2.0"
46
- },
47
- "scripts": {
48
- "build": "tsup",
49
- "test": "vitest run",
50
- "test:watch": "vitest",
51
- "lint": "eslint src tests",
52
- "typecheck": "tsc --noEmit"
53
73
  }
54
- }
74
+ }