unnbound-logger-sdk 3.1.0-beta.1 → 3.1.1
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 +45 -33
- package/dist/axios.d.ts +4 -2
- package/dist/axios.js +8 -2
- package/dist/logger.js +0 -2
- package/package.json +13 -12
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ logger.trace('Trace information');
|
|
|
22
22
|
|
|
23
23
|
// Log with object messages (merged into top level)
|
|
24
24
|
logger.info({ event: 'user_login', userId: '123' }, 'Event received.');
|
|
25
|
-
// Results in: { "event": "user_login", "userId": "123", "message": "
|
|
25
|
+
// Results in: { "event": "user_login", "userId": "123", "message": "Event received.", ... }
|
|
26
26
|
|
|
27
27
|
// Log with both string message and metadata (metadata merged into top level)
|
|
28
28
|
logger.info({ userId: '123' }, 'User logged in');
|
|
@@ -44,7 +44,7 @@ interface Log<T extends LogType = 'general'> {
|
|
|
44
44
|
serviceId?: string; // From UNNBOUND_SERVICE_ID environment variable
|
|
45
45
|
deploymentId?: string; // From UNNBOUND_DEPLOYMENT_ID environment variable
|
|
46
46
|
workflowId?: string; // From UNNBOUND_WORKFLOW_ID environment variable
|
|
47
|
-
environment?: string; // From ENVIRONMENT
|
|
47
|
+
environment?: string; // From WORKFLOW_ENV, UNNBOUND_ENVIRONMENT, or ENVIRONMENT, whichever is set first
|
|
48
48
|
err?: unknown; // Only present for Error objects
|
|
49
49
|
duration?: number; // Duration in milliseconds for span operations
|
|
50
50
|
http?: T extends 'http' ? HttpPayload : never;
|
|
@@ -87,7 +87,7 @@ export UNNBOUND_DEPLOYMENT_ID="v1.2.3-prod-20231201"
|
|
|
87
87
|
UNNBOUND_DEPLOYMENT_ID=v1.2.3-prod-20231201
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
If the environment variables are not set, the fields
|
|
90
|
+
If the environment variables are not set, the fields are omitted from the log entry. These fields help with:
|
|
91
91
|
|
|
92
92
|
- **Workflow ID**: Unique identifier for the workflow (logged in each entry)
|
|
93
93
|
- **Workflow URL**: Used internally for URL construction in webhook endpoints (not logged as a field)
|
|
@@ -98,36 +98,55 @@ If the environment variables are not set, the fields will be empty strings. Thes
|
|
|
98
98
|
|
|
99
99
|
## Object Logging Behavior
|
|
100
100
|
|
|
101
|
-
The logger
|
|
101
|
+
The logger follows Pino's argument order. **The context object comes first and the message comes second.**
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
The results below show only the fields each call contributes. The leading `...` stands for the
|
|
104
|
+
fields the logger adds itself: `level`, `logId`, and `source` on every entry, plus `workflowId`,
|
|
105
|
+
`serviceId`, `deploymentId`, and `environment` when their environment variables are set, and
|
|
106
|
+
`traceId` and `spanId` when the call runs inside a trace or span. See [Log Format](#log-format).
|
|
104
107
|
|
|
105
|
-
|
|
108
|
+
### Context Object with a Message
|
|
106
109
|
|
|
107
110
|
```typescript
|
|
108
|
-
logger.info(
|
|
109
|
-
// Result: { "
|
|
111
|
+
logger.info({ userId: '123', action: 'login' }, 'User action completed');
|
|
112
|
+
// Result: { ..., "userId": "123", "action": "login", "message": "User action completed" }
|
|
110
113
|
```
|
|
111
114
|
|
|
112
|
-
|
|
115
|
+
Every property of the object is merged into the top level of the log entry.
|
|
116
|
+
|
|
117
|
+
A context object always needs a message alongside it. `ILogger` declares each level as
|
|
118
|
+
`(object, message)` or `(message)` and nothing else, so `logger.info({ userId: '123' })` does not
|
|
119
|
+
compile.
|
|
120
|
+
|
|
121
|
+
### Reversing the Order Drops Your Context
|
|
113
122
|
|
|
114
|
-
|
|
123
|
+
Passing the message first discards the object:
|
|
115
124
|
|
|
116
125
|
```typescript
|
|
117
|
-
logger.info({ userId: '123', action: 'login' });
|
|
118
|
-
// Result: { "
|
|
126
|
+
logger.info('User action completed', { userId: '123', action: 'login' });
|
|
127
|
+
// Result: { ..., "message": "User action completed" }
|
|
128
|
+
// userId and action are gone. No error, no warning.
|
|
119
129
|
```
|
|
120
130
|
|
|
131
|
+
TypeScript rejects the reversed order, because `info` is declared only as `(object, message)` or
|
|
132
|
+
`(message)`. Running `tsc` reports `Argument of type 'string' is not assignable to parameter of
|
|
133
|
+
type 'object'`, so a build catches the mistake. Transpile-only runners such as `tsx` do not
|
|
134
|
+
type-check, so under those the fields disappear at runtime with no error. Always put the object
|
|
135
|
+
first.
|
|
136
|
+
|
|
121
137
|
### Error Objects
|
|
122
138
|
|
|
123
|
-
|
|
139
|
+
`error` takes an object containing `err` plus a message:
|
|
124
140
|
|
|
125
141
|
```typescript
|
|
126
|
-
logger.error({ err: new Error('
|
|
127
|
-
// Result: {
|
|
142
|
+
logger.error({ err: new Error('Connection refused') }, 'Failed to sync order.');
|
|
143
|
+
// Result: { ...,
|
|
144
|
+
// "err": { "type": "Error", "message": "Connection refused", "stack": "..." },
|
|
145
|
+
// "message": "Failed to sync order." }
|
|
128
146
|
```
|
|
129
147
|
|
|
130
|
-
|
|
148
|
+
Errors are serialized by Pino's `stdSerializers.err`, which emits `type`, `message`, and `stack`.
|
|
149
|
+
Unlike the other levels, `error` has no single-argument overload, so the message is required.
|
|
131
150
|
|
|
132
151
|
## HTTP Request/Response Logging
|
|
133
152
|
|
|
@@ -206,11 +225,10 @@ const uploadFile = async (filePath: string, content: string) => {
|
|
|
206
225
|
return await startSpan(
|
|
207
226
|
'SFTP upload operation',
|
|
208
227
|
async () => {
|
|
209
|
-
|
|
210
|
-
logger.info('Uploading file', { filePath, contentLength: content.length });
|
|
228
|
+
logger.info({ filePath, contentLength: content.length }, 'Uploading file');
|
|
211
229
|
return { success: true, filePath };
|
|
212
230
|
},
|
|
213
|
-
(
|
|
231
|
+
() => ({
|
|
214
232
|
type: 'sftp',
|
|
215
233
|
sftp: {
|
|
216
234
|
host: 'sftp.example.com',
|
|
@@ -291,8 +309,7 @@ In case the function doesn't run inside an HTTP handler (for example a cron job)
|
|
|
291
309
|
import { withTrace } from 'unnbound-logger-sdk';
|
|
292
310
|
|
|
293
311
|
const operation = async (value: number) => {
|
|
294
|
-
|
|
295
|
-
logger.info('Processing value', { value });
|
|
312
|
+
logger.info({ value }, 'Processing value');
|
|
296
313
|
return value * 2;
|
|
297
314
|
};
|
|
298
315
|
|
|
@@ -306,19 +323,14 @@ The `startSpan` function allows you to wrap any async operation with automatic s
|
|
|
306
323
|
```typescript
|
|
307
324
|
import { logger, startSpan } from 'unnbound-logger-sdk';
|
|
308
325
|
|
|
309
|
-
// Example: Wrapping a function with span tracking
|
|
310
326
|
const operation = async (value: number) => {
|
|
311
|
-
logger.info(
|
|
327
|
+
logger.info({ value }, 'Processing value');
|
|
312
328
|
return value * 2;
|
|
313
329
|
};
|
|
314
330
|
|
|
315
|
-
|
|
316
|
-
const result = await startSpan('Processing operation', operation, () => ({
|
|
331
|
+
const result = await startSpan('Processing operation', () => operation(21), () => ({
|
|
317
332
|
operationType: 'calculation',
|
|
318
333
|
}));
|
|
319
|
-
|
|
320
|
-
// Execute the function
|
|
321
|
-
const result = await startSpan('Processing operation', () => operation(21)); // Returns 42
|
|
322
334
|
```
|
|
323
335
|
|
|
324
336
|
_Note: In case the `traceId` is missing from the context, `startSpan` will generate one. It is recommended to use a single `traceId` across your handler though, so always consider using `traceMiddleware` and `withTrace` to inject the `traceId` instead of relying on `startSpan` to create one._
|
|
@@ -329,15 +341,15 @@ The span context is maintained across async operations:
|
|
|
329
341
|
|
|
330
342
|
```typescript
|
|
331
343
|
const asyncOperation = async (value: number) => {
|
|
332
|
-
logger.info('First step'
|
|
344
|
+
logger.info({ value }, 'First step');
|
|
333
345
|
|
|
334
346
|
await someAsyncWork();
|
|
335
347
|
|
|
336
|
-
logger.info('Second step'
|
|
348
|
+
logger.info({ value }, 'Second step');
|
|
337
349
|
return value * 2;
|
|
338
350
|
};
|
|
339
351
|
|
|
340
|
-
const result = await startSpan('Async operation', asyncOperation, () => ({
|
|
352
|
+
const result = await startSpan('Async operation', () => asyncOperation(21), () => ({
|
|
341
353
|
operationType: 'async_calculation',
|
|
342
354
|
}));
|
|
343
355
|
```
|
|
@@ -452,5 +464,5 @@ console.log(traceId); // "550e8400-e29b-41d4-a716-446655440000"
|
|
|
452
464
|
- `UNNBOUND_SERVICE_ID` - Service identifier (included in all logs)
|
|
453
465
|
- `UNNBOUND_DEPLOYMENT_ID` - Deployment identifier (included in all logs)
|
|
454
466
|
- `UNNBOUND_WORKFLOW_URL` - Base URL for webhook endpoint logging
|
|
455
|
-
- `ENVIRONMENT` - Environment name (included in all logs)
|
|
456
|
-
- `LOG_LEVEL` - Log level (default: '
|
|
467
|
+
- `WORKFLOW_ENV`, `UNNBOUND_ENVIRONMENT`, `ENVIRONMENT` - Environment name, read in that order (included in all logs)
|
|
468
|
+
- `LOG_LEVEL` - Log level (default: 'debug')
|
package/dist/axios.d.ts
CHANGED
|
@@ -17,8 +17,10 @@ interface HttpClientOptions extends HttpOptions<GetPayload> {
|
|
|
17
17
|
silent?: boolean;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* Wraps an axios instance to add tracing and span tracking
|
|
21
|
-
*
|
|
20
|
+
* Wraps an axios instance to add tracing and span tracking.
|
|
21
|
+
*
|
|
22
|
+
* Repeated calls for the same instance are no-ops; the first call's options remain active.
|
|
23
|
+
* @param client - The axios instance to wrap
|
|
22
24
|
* @param options - Configuration options for HTTP tracing
|
|
23
25
|
* @returns The wrapped axios instance with span tracking
|
|
24
26
|
*/
|
package/dist/axios.js
CHANGED
|
@@ -37,9 +37,12 @@ const buildOutgoingHttpPayload = (config, res, redact) => ({
|
|
|
37
37
|
});
|
|
38
38
|
const getNoopPayload = () => ({});
|
|
39
39
|
const onNoopRequest = (config, callback) => callback(config);
|
|
40
|
+
const tracedMarker = Symbol.for('unnbound-logger-sdk.traceAxios.traced');
|
|
40
41
|
/**
|
|
41
|
-
* Wraps an axios instance to add tracing and span tracking
|
|
42
|
-
*
|
|
42
|
+
* Wraps an axios instance to add tracing and span tracking.
|
|
43
|
+
*
|
|
44
|
+
* Repeated calls for the same instance are no-ops; the first call's options remain active.
|
|
45
|
+
* @param client - The axios instance to wrap
|
|
43
46
|
* @param options - Configuration options for HTTP tracing
|
|
44
47
|
* @returns The wrapped axios instance with span tracking
|
|
45
48
|
*/
|
|
@@ -49,6 +52,8 @@ const traceAxios = (client, { ignoreTraceRoutes = types_1.defaultIgnoreTraceRout
|
|
|
49
52
|
getPayload: getNoopPayload,
|
|
50
53
|
onRequest: onNoopRequest,
|
|
51
54
|
}) => {
|
|
55
|
+
if (tracedMarker in client)
|
|
56
|
+
return client;
|
|
52
57
|
const redact = typeof redactOptions === 'boolean' ? { response: redactOptions } : redactOptions;
|
|
53
58
|
const createSpanWrappedRequest = (originalMethod, method) => {
|
|
54
59
|
const { headers: defaultHeaders, ...partialDefaultConfig } = client.defaults;
|
|
@@ -93,6 +98,7 @@ const traceAxios = (client, { ignoreTraceRoutes = types_1.defaultIgnoreTraceRout
|
|
|
93
98
|
const func = createSpanWrappedRequest(request, method);
|
|
94
99
|
client[method] = (url, config) => func({ ...config, method, url });
|
|
95
100
|
});
|
|
101
|
+
Object.defineProperty(client, tracedMarker, { value: true });
|
|
96
102
|
return client;
|
|
97
103
|
};
|
|
98
104
|
exports.traceAxios = traceAxios;
|
package/dist/logger.js
CHANGED
|
@@ -11,7 +11,6 @@ const internal_1 = require("./internal");
|
|
|
11
11
|
const storage_1 = require("./storage");
|
|
12
12
|
const levels = new Set(['debug', 'info', 'warn', 'error']);
|
|
13
13
|
const workflowEnvironment = process.env.WORKFLOW_ENV ?? process.env.UNNBOUND_ENVIRONMENT ?? process.env.ENVIRONMENT;
|
|
14
|
-
const stackEnvironment = process.env.ENVIRONMENT ?? process.env.APP_ENV;
|
|
15
14
|
const formatLevel = (level) => ({ level });
|
|
16
15
|
const formatLog = (log, visited = new WeakSet()) => {
|
|
17
16
|
// Prevent infinite loops from circular references
|
|
@@ -56,7 +55,6 @@ const loggerOptions = {
|
|
|
56
55
|
level: process.env.LOG_LEVEL ?? 'debug',
|
|
57
56
|
base: {
|
|
58
57
|
environment: workflowEnvironment,
|
|
59
|
-
stack: stackEnvironment,
|
|
60
58
|
workflowId: process.env.UNNBOUND_WORKFLOW_ID,
|
|
61
59
|
serviceId: process.env.UNNBOUND_SERVICE_ID,
|
|
62
60
|
deploymentId: process.env.UNNBOUND_DEPLOYMENT_ID,
|
package/package.json
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unnbound-logger-sdk",
|
|
3
3
|
"description": "A structured logging library with TypeScript support using Pino. Provides consistent, well-typed logging with automatic logId, workflowId, traceId, and deploymentId tracking across operational contexts.",
|
|
4
|
-
"version": "3.1.
|
|
4
|
+
"version": "3.1.1",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "vitest run src",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"format": "biome format --write .",
|
|
12
|
+
"format:check": "biome format .",
|
|
13
|
+
"prepublishOnly": "npm run build",
|
|
14
|
+
"start:example": "tsx watch examples/node-express.ts",
|
|
15
|
+
"version:bump": "npm version patch"
|
|
16
|
+
},
|
|
7
17
|
"keywords": [
|
|
8
18
|
"logging",
|
|
9
19
|
"structured-logging",
|
|
@@ -56,14 +66,5 @@
|
|
|
56
66
|
"engines": {
|
|
57
67
|
"node": ">=22"
|
|
58
68
|
},
|
|
59
|
-
"sideEffects": false
|
|
60
|
-
|
|
61
|
-
"build": "tsc",
|
|
62
|
-
"test": "vitest run src",
|
|
63
|
-
"typecheck": "tsc --noEmit",
|
|
64
|
-
"format": "biome format --write .",
|
|
65
|
-
"format:check": "biome format .",
|
|
66
|
-
"start:example": "tsx watch examples/node-express.ts",
|
|
67
|
-
"version:bump": "npm version patch"
|
|
68
|
-
}
|
|
69
|
-
}
|
|
69
|
+
"sideEffects": false
|
|
70
|
+
}
|