tpaga-logger 0.0.2 → 0.0.3
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 +131 -20
- package/dist/index.cjs +27 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +24 -3
- package/dist/index.js.map +1 -1
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -1,22 +1,138 @@
|
|
|
1
|
-
#
|
|
1
|
+
# tpaga-logger
|
|
2
2
|
|
|
3
3
|
Structured logging SDK for Tpaga Node.js microservices. Emits **wide-event JSON** (canonical log line) to stdout for **AWS CloudWatch Logs Insights**, following [loggingsucks.com](https://loggingsucks.com/).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add tpaga-logger
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### AWS Lambda
|
|
14
|
+
|
|
15
|
+
Use `withLogger` to wrap a Lambda handler. It automatically handles timing, `correlationId` extraction from headers, and emits the terminal log event on success or error.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { createLogger, withLogger } from 'tpaga-logger';
|
|
19
|
+
|
|
20
|
+
const logger = createLogger({
|
|
21
|
+
service: 'my-service',
|
|
22
|
+
environment: process.env.STAGE ?? 'dev',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const handler = withLogger(logger, 'createCharge', async (event, log) => {
|
|
26
|
+
log.with({ orderId: event.body.orderId, amount: event.body.amount });
|
|
27
|
+
const result = await chargeService.create(event.body);
|
|
28
|
+
return { statusCode: 200, body: JSON.stringify(result) };
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**What `withLogger` does automatically:**
|
|
33
|
+
- Extracts `correlationId` from `x-correlation-id` or `x-request-id` headers (generates a UUID if absent)
|
|
34
|
+
- Starts a timer and calculates `durationMs`
|
|
35
|
+
- Emits `outcome: "success"` with `statusCode` on completion
|
|
36
|
+
- Emits `outcome: "error"` with serialized error on failure, then re-throws
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### Express
|
|
41
|
+
|
|
42
|
+
Use `withTpagaExpressLogger` as a global middleware and `getLog` to access the log context inside route handlers.
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import express from 'express';
|
|
46
|
+
import { createLogger, withTpagaExpressLogger, getLog } from 'tpaga-logger';
|
|
47
|
+
|
|
48
|
+
const logger = createLogger({
|
|
49
|
+
service: 'url-signer-service',
|
|
50
|
+
environment: process.env.NODE_ENV ?? 'dev',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const app = express();
|
|
54
|
+
app.use(express.json());
|
|
55
|
+
app.use(withTpagaExpressLogger(logger)); // register once, covers all routes
|
|
56
|
+
|
|
57
|
+
app.post('/api/v1/sign', (req, res) => {
|
|
58
|
+
req.log.with({ userId: req.body.userId, resource: req.body.resource });
|
|
59
|
+
|
|
60
|
+
const url = signUrl(req.body);
|
|
61
|
+
res.json({ url });
|
|
62
|
+
// terminal log is emitted automatically on res.finish
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**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)
|
|
69
|
+
- Emits `outcome: "success"` for `statusCode < 400`, `outcome: "error"` otherwise
|
|
70
|
+
- Calculates `durationMs` from when the request entered the middleware
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## API
|
|
13
75
|
|
|
14
|
-
|
|
76
|
+
| Export | Description |
|
|
77
|
+
|--------|-------------|
|
|
78
|
+
| `createLogger(config)` | Creates a logger instance for a service |
|
|
79
|
+
| `withLogger(logger, name, handler)` | Lambda handler wrapper |
|
|
80
|
+
| `withTpagaExpressLogger(logger)` | Express middleware (register with `app.use`) |
|
|
81
|
+
| `getLog(res)` | Gets the `WideEventBuilder` from an Express response |
|
|
82
|
+
| `serializeError(err)` | Serializes an unknown error to a structured object |
|
|
83
|
+
| `LOG_LEVELS` | `['info', 'warn', 'error', 'debug']` |
|
|
84
|
+
| `OUTCOMES` | `['success', 'error', 'validation_failed', ...]` |
|
|
15
85
|
|
|
16
|
-
|
|
17
|
-
- Plan: [`.agents/docs/SERF-5796_TPAGA_LOGGER_IMPLEMENTATION_PLAN.md`](.agents/docs/SERF-5796_TPAGA_LOGGER_IMPLEMENTATION_PLAN.md)
|
|
86
|
+
### `LoggerConfig`
|
|
18
87
|
|
|
19
|
-
|
|
88
|
+
```typescript
|
|
89
|
+
type LoggerConfig = {
|
|
90
|
+
service: string;
|
|
91
|
+
environment?: string;
|
|
92
|
+
redactKeys?: readonly string[]; // paths redacted with '[REDACTED]'
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### `WideEventBuilder` (`log`)
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
type WideEventBuilder = {
|
|
100
|
+
with: (fields: Record<string, unknown>) => void; // add context fields
|
|
101
|
+
emit: (level: LogLevel, message: string, terminal: TerminalFields) => void; // send the log line
|
|
102
|
+
};
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
> `emit` is called automatically by `withLogger` and `withTpagaExpressLogger`. Call it manually only when using `startOperation` directly.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Local development (pretty logs)
|
|
110
|
+
|
|
111
|
+
`pino-pretty` is bundled in the library — no extra install needed in your service. Just start with `LOG_PRETTY=true`:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
LOG_PRETTY=true node dist/index.js
|
|
115
|
+
# or for Lambda local testing:
|
|
116
|
+
LOG_PRETTY=true npx ts-node src/handler.ts
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Output in the console:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
[09:11:00.142] INFO: createCharge completed
|
|
123
|
+
service: "cash-in-manager"
|
|
124
|
+
correlationId: "abc-123"
|
|
125
|
+
orderId: "ORD-456"
|
|
126
|
+
amount: 50000
|
|
127
|
+
outcome: "success"
|
|
128
|
+
durationMs: 142
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
In production, leave `LOG_PRETTY` unset — raw JSON goes to stdout and CloudWatch Logs Insights queries it natively.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Development
|
|
20
136
|
|
|
21
137
|
```bash
|
|
22
138
|
pnpm install
|
|
@@ -26,15 +142,10 @@ pnpm test
|
|
|
26
142
|
pnpm build
|
|
27
143
|
```
|
|
28
144
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
## Instalación (cuando se publique)
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
pnpm add @tpaga/logger
|
|
35
|
-
```
|
|
145
|
+
## Docs
|
|
36
146
|
|
|
37
|
-
|
|
147
|
+
- Log contract: [`.agents/docs/SERF-5797_LOG_STRUCTURE_CONTRACT.md`](.agents/docs/SERF-5797_LOG_STRUCTURE_CONTRACT.md)
|
|
148
|
+
- Implementation plan: [`.agents/docs/SERF-5796_TPAGA_LOGGER_IMPLEMENTATION_PLAN.md`](.agents/docs/SERF-5796_TPAGA_LOGGER_IMPLEMENTATION_PLAN.md)
|
|
38
149
|
|
|
39
150
|
## Repo
|
|
40
151
|
|
package/dist/index.cjs
CHANGED
|
@@ -33,8 +33,10 @@ __export(index_exports, {
|
|
|
33
33
|
LOG_LEVELS: () => LOG_LEVELS,
|
|
34
34
|
OUTCOMES: () => OUTCOMES,
|
|
35
35
|
createLogger: () => createLogger,
|
|
36
|
+
getLog: () => getLog,
|
|
36
37
|
serializeError: () => serializeError,
|
|
37
|
-
withLogger: () => withLogger
|
|
38
|
+
withLogger: () => withLogger,
|
|
39
|
+
withTpagaExpressLogger: () => withTpagaExpressLogger
|
|
38
40
|
});
|
|
39
41
|
module.exports = __toCommonJS(index_exports);
|
|
40
42
|
|
|
@@ -50,11 +52,12 @@ var serializeError = (err) => err instanceof Error ? {
|
|
|
50
52
|
var createWideEventBuilder = (base, log) => {
|
|
51
53
|
const ctx = { ...base };
|
|
52
54
|
return {
|
|
53
|
-
|
|
55
|
+
with: (fields) => Object.assign(ctx, fields),
|
|
54
56
|
emit: (level, message, terminal) => log[level]({ ...ctx, ...terminal }, message)
|
|
55
57
|
};
|
|
56
58
|
};
|
|
57
59
|
var createLogger = (config) => {
|
|
60
|
+
const pretty = process.env.LOG_PRETTY === "true";
|
|
58
61
|
const log = (0, import_pino.default)({
|
|
59
62
|
level: process.env.LOG_LEVEL ?? "info",
|
|
60
63
|
base: { service: config.service, environment: config.environment },
|
|
@@ -65,7 +68,8 @@ var createLogger = (config) => {
|
|
|
65
68
|
redact: {
|
|
66
69
|
paths: config.redactKeys ?? [],
|
|
67
70
|
censor: "[REDACTED]"
|
|
68
|
-
}
|
|
71
|
+
},
|
|
72
|
+
...pretty ? { transport: { target: "pino-pretty", options: { colorize: true } } } : {}
|
|
69
73
|
});
|
|
70
74
|
return {
|
|
71
75
|
startOperation: (functionName, base = {}) => createWideEventBuilder({ function: functionName, ...base }, log)
|
|
@@ -75,6 +79,23 @@ var resolveCorrelationId = (headers = {}) => {
|
|
|
75
79
|
const h = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]));
|
|
76
80
|
return h["x-correlation-id"] ?? h["x-request-id"] ?? (0, import_crypto.randomUUID)();
|
|
77
81
|
};
|
|
82
|
+
var EXPRESS_LOG_KEY = "tpagaLog";
|
|
83
|
+
var withTpagaExpressLogger = (logger) => (req, res, next) => {
|
|
84
|
+
const start = Date.now();
|
|
85
|
+
const correlationId = resolveCorrelationId(req.headers);
|
|
86
|
+
const log = logger.startOperation(req.path, { correlationId, method: req.method });
|
|
87
|
+
req.log = log;
|
|
88
|
+
res.locals[EXPRESS_LOG_KEY] = log;
|
|
89
|
+
res.on("finish", () => {
|
|
90
|
+
log.emit("info", "request completed", {
|
|
91
|
+
outcome: res.statusCode < 400 ? "success" : "error",
|
|
92
|
+
durationMs: Date.now() - start,
|
|
93
|
+
statusCode: res.statusCode
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
next();
|
|
97
|
+
};
|
|
98
|
+
var getLog = (res) => res.locals[EXPRESS_LOG_KEY];
|
|
78
99
|
var withLogger = (logger, operationName, handler) => async (event, ..._rest) => {
|
|
79
100
|
const start = Date.now();
|
|
80
101
|
const correlationId = resolveCorrelationId(event.headers);
|
|
@@ -114,7 +135,9 @@ var OUTCOMES = [
|
|
|
114
135
|
LOG_LEVELS,
|
|
115
136
|
OUTCOMES,
|
|
116
137
|
createLogger,
|
|
138
|
+
getLog,
|
|
117
139
|
serializeError,
|
|
118
|
-
withLogger
|
|
140
|
+
withLogger,
|
|
141
|
+
withTpagaExpressLogger
|
|
119
142
|
});
|
|
120
143
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/logger.ts","../src/constants.ts"],"sourcesContent":["export { createLogger, serializeError, withLogger } 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 { Logger, LoggerConfig, SerializedError, TerminalFields, WideEventBuilder } from './types.js';\n\nexport const serializeError = (err: unknown): SerializedError =>\n err instanceof Error\n ? {\n type: err.name,\n message: err.message,\n code: (err as { code?: string }).code,\n stack: err.stack,\n }\n : { type: 'UnknownError', message: String(err) };\n\nconst createWideEventBuilder = (\n base: Record<string, unknown>,\n log: pino.Logger,\n): WideEventBuilder => {\n const ctx = { ...base };\n return {\n
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/logger.ts","../src/constants.ts"],"sourcesContent":["export { createLogger, serializeError, withLogger, withTpagaExpressLogger, 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 }\n}\n\nexport const serializeError = (err: unknown): SerializedError =>\n err instanceof Error\n ? {\n type: err.name,\n message: err.message,\n code: (err as { code?: string }).code,\n stack: err.stack,\n }\n : { type: 'UnknownError', message: String(err) };\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.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 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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Response, Request, NextFunction } from 'express';
|
|
2
|
+
|
|
1
3
|
declare const LOG_LEVELS: readonly ["info", "warn", "error", "debug"];
|
|
2
4
|
declare const OUTCOMES: readonly ["success", "error", "validation_failed", "not_found", "conflict", "timeout", "skipped"];
|
|
3
5
|
|
|
@@ -21,19 +23,28 @@ type TerminalFields = {
|
|
|
21
23
|
error?: SerializedError;
|
|
22
24
|
};
|
|
23
25
|
type WideEventBuilder = {
|
|
24
|
-
|
|
26
|
+
with: (fields: Record<string, unknown>) => void;
|
|
25
27
|
emit: (level: LogLevel, message: string, terminal: TerminalFields) => void;
|
|
26
28
|
};
|
|
27
29
|
type Logger = {
|
|
28
30
|
startOperation: (functionName: string, base?: Record<string, unknown>) => WideEventBuilder;
|
|
29
31
|
};
|
|
30
32
|
|
|
33
|
+
declare global {
|
|
34
|
+
namespace Express {
|
|
35
|
+
interface Request {
|
|
36
|
+
log: WideEventBuilder;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
31
40
|
declare const serializeError: (err: unknown) => SerializedError;
|
|
32
41
|
declare const createLogger: (config: LoggerConfig) => Logger;
|
|
33
42
|
type EventWithHeaders = {
|
|
34
43
|
headers?: Record<string, string | undefined>;
|
|
35
44
|
};
|
|
36
45
|
type HandlerWithBuilder<TEvent> = (event: TEvent, builder: WideEventBuilder) => Promise<unknown>;
|
|
46
|
+
declare const withTpagaExpressLogger: (logger: Logger) => (req: Request, res: Response, next: NextFunction) => void;
|
|
47
|
+
declare const getLog: (res: Response) => WideEventBuilder;
|
|
37
48
|
declare const withLogger: <TEvent extends EventWithHeaders>(logger: Logger, operationName: string, handler: HandlerWithBuilder<TEvent>) => (event: TEvent, ..._rest: unknown[]) => Promise<unknown>;
|
|
38
49
|
|
|
39
|
-
export { LOG_LEVELS, type LogLevel, type Logger, type LoggerConfig, OUTCOMES, type Outcome, type SerializedError, type TerminalFields, type WideEventBuilder, createLogger, serializeError, withLogger };
|
|
50
|
+
export { LOG_LEVELS, type LogLevel, type Logger, type LoggerConfig, OUTCOMES, type Outcome, type SerializedError, type TerminalFields, type WideEventBuilder, createLogger, getLog, serializeError, withLogger, withTpagaExpressLogger };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Response, Request, NextFunction } from 'express';
|
|
2
|
+
|
|
1
3
|
declare const LOG_LEVELS: readonly ["info", "warn", "error", "debug"];
|
|
2
4
|
declare const OUTCOMES: readonly ["success", "error", "validation_failed", "not_found", "conflict", "timeout", "skipped"];
|
|
3
5
|
|
|
@@ -21,19 +23,28 @@ type TerminalFields = {
|
|
|
21
23
|
error?: SerializedError;
|
|
22
24
|
};
|
|
23
25
|
type WideEventBuilder = {
|
|
24
|
-
|
|
26
|
+
with: (fields: Record<string, unknown>) => void;
|
|
25
27
|
emit: (level: LogLevel, message: string, terminal: TerminalFields) => void;
|
|
26
28
|
};
|
|
27
29
|
type Logger = {
|
|
28
30
|
startOperation: (functionName: string, base?: Record<string, unknown>) => WideEventBuilder;
|
|
29
31
|
};
|
|
30
32
|
|
|
33
|
+
declare global {
|
|
34
|
+
namespace Express {
|
|
35
|
+
interface Request {
|
|
36
|
+
log: WideEventBuilder;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
31
40
|
declare const serializeError: (err: unknown) => SerializedError;
|
|
32
41
|
declare const createLogger: (config: LoggerConfig) => Logger;
|
|
33
42
|
type EventWithHeaders = {
|
|
34
43
|
headers?: Record<string, string | undefined>;
|
|
35
44
|
};
|
|
36
45
|
type HandlerWithBuilder<TEvent> = (event: TEvent, builder: WideEventBuilder) => Promise<unknown>;
|
|
46
|
+
declare const withTpagaExpressLogger: (logger: Logger) => (req: Request, res: Response, next: NextFunction) => void;
|
|
47
|
+
declare const getLog: (res: Response) => WideEventBuilder;
|
|
37
48
|
declare const withLogger: <TEvent extends EventWithHeaders>(logger: Logger, operationName: string, handler: HandlerWithBuilder<TEvent>) => (event: TEvent, ..._rest: unknown[]) => Promise<unknown>;
|
|
38
49
|
|
|
39
|
-
export { LOG_LEVELS, type LogLevel, type Logger, type LoggerConfig, OUTCOMES, type Outcome, type SerializedError, type TerminalFields, type WideEventBuilder, createLogger, serializeError, withLogger };
|
|
50
|
+
export { LOG_LEVELS, type LogLevel, type Logger, type LoggerConfig, OUTCOMES, type Outcome, type SerializedError, type TerminalFields, type WideEventBuilder, createLogger, getLog, serializeError, withLogger, withTpagaExpressLogger };
|
package/dist/index.js
CHANGED
|
@@ -10,11 +10,12 @@ var serializeError = (err) => err instanceof Error ? {
|
|
|
10
10
|
var createWideEventBuilder = (base, log) => {
|
|
11
11
|
const ctx = { ...base };
|
|
12
12
|
return {
|
|
13
|
-
|
|
13
|
+
with: (fields) => Object.assign(ctx, fields),
|
|
14
14
|
emit: (level, message, terminal) => log[level]({ ...ctx, ...terminal }, message)
|
|
15
15
|
};
|
|
16
16
|
};
|
|
17
17
|
var createLogger = (config) => {
|
|
18
|
+
const pretty = process.env.LOG_PRETTY === "true";
|
|
18
19
|
const log = pino({
|
|
19
20
|
level: process.env.LOG_LEVEL ?? "info",
|
|
20
21
|
base: { service: config.service, environment: config.environment },
|
|
@@ -25,7 +26,8 @@ var createLogger = (config) => {
|
|
|
25
26
|
redact: {
|
|
26
27
|
paths: config.redactKeys ?? [],
|
|
27
28
|
censor: "[REDACTED]"
|
|
28
|
-
}
|
|
29
|
+
},
|
|
30
|
+
...pretty ? { transport: { target: "pino-pretty", options: { colorize: true } } } : {}
|
|
29
31
|
});
|
|
30
32
|
return {
|
|
31
33
|
startOperation: (functionName, base = {}) => createWideEventBuilder({ function: functionName, ...base }, log)
|
|
@@ -35,6 +37,23 @@ var resolveCorrelationId = (headers = {}) => {
|
|
|
35
37
|
const h = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]));
|
|
36
38
|
return h["x-correlation-id"] ?? h["x-request-id"] ?? randomUUID();
|
|
37
39
|
};
|
|
40
|
+
var EXPRESS_LOG_KEY = "tpagaLog";
|
|
41
|
+
var withTpagaExpressLogger = (logger) => (req, res, next) => {
|
|
42
|
+
const start = Date.now();
|
|
43
|
+
const correlationId = resolveCorrelationId(req.headers);
|
|
44
|
+
const log = logger.startOperation(req.path, { correlationId, method: req.method });
|
|
45
|
+
req.log = log;
|
|
46
|
+
res.locals[EXPRESS_LOG_KEY] = log;
|
|
47
|
+
res.on("finish", () => {
|
|
48
|
+
log.emit("info", "request completed", {
|
|
49
|
+
outcome: res.statusCode < 400 ? "success" : "error",
|
|
50
|
+
durationMs: Date.now() - start,
|
|
51
|
+
statusCode: res.statusCode
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
next();
|
|
55
|
+
};
|
|
56
|
+
var getLog = (res) => res.locals[EXPRESS_LOG_KEY];
|
|
38
57
|
var withLogger = (logger, operationName, handler) => async (event, ..._rest) => {
|
|
39
58
|
const start = Date.now();
|
|
40
59
|
const correlationId = resolveCorrelationId(event.headers);
|
|
@@ -73,7 +92,9 @@ export {
|
|
|
73
92
|
LOG_LEVELS,
|
|
74
93
|
OUTCOMES,
|
|
75
94
|
createLogger,
|
|
95
|
+
getLog,
|
|
76
96
|
serializeError,
|
|
77
|
-
withLogger
|
|
97
|
+
withLogger,
|
|
98
|
+
withTpagaExpressLogger
|
|
78
99
|
};
|
|
79
100
|
//# sourceMappingURL=index.js.map
|
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 { Logger, LoggerConfig, SerializedError, TerminalFields, WideEventBuilder } from './types.js';\n\nexport const serializeError = (err: unknown): SerializedError =>\n err instanceof Error\n ? {\n type: err.name,\n message: err.message,\n code: (err as { code?: string }).code,\n stack: err.stack,\n }\n : { type: 'UnknownError', message: String(err) };\n\nconst createWideEventBuilder = (\n base: Record<string, unknown>,\n log: pino.Logger,\n): WideEventBuilder => {\n const ctx = { ...base };\n return {\n
|
|
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 }\n}\n\nexport const serializeError = (err: unknown): SerializedError =>\n err instanceof Error\n ? {\n type: err.name,\n message: err.message,\n code: (err as { code?: string }).code,\n stack: err.stack,\n }\n : { type: 'UnknownError', message: String(err) };\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.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 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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tpaga-logger",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Structured logging SDK for Tpaga microservices (wide events → CloudWatch)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -19,14 +19,24 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"dist"
|
|
21
21
|
],
|
|
22
|
-
"
|
|
22
|
+
"engmake sines": {
|
|
23
23
|
"node": ">=20"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"pino": "^10.3.1"
|
|
26
|
+
"pino": "^10.3.1",
|
|
27
|
+
"pino-pretty": "^13.1.3"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"express": ">=4"
|
|
31
|
+
},
|
|
32
|
+
"peerDependenciesMeta": {
|
|
33
|
+
"express": {
|
|
34
|
+
"optional": true
|
|
35
|
+
}
|
|
27
36
|
},
|
|
28
37
|
"devDependencies": {
|
|
29
38
|
"@eslint/js": "^9.28.0",
|
|
39
|
+
"@types/express": "^5.0.6",
|
|
30
40
|
"@types/node": "^22.15.0",
|
|
31
41
|
"eslint": "^9.28.0",
|
|
32
42
|
"tsup": "^8.5.0",
|