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 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": "Application started", ... }
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 environment variable
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 will be empty strings. These fields help with:
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 uses Pino's standard behavior for handling different message types:
101
+ The logger follows Pino's argument order. **The context object comes first and the message comes second.**
102
102
 
103
- ### String Messages with Metadata
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
- When you pass a string message with additional metadata, the metadata is merged into the top level:
108
+ ### Context Object with a Message
106
109
 
107
110
  ```typescript
108
- logger.info('User action completed', { userId: '123', action: 'login' });
109
- // Result: { "message": "User action completed", "userId": "123", "action": "login", ... }
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
- ### Object Messages
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
- When you pass an object as the message, it's merged into the top level:
123
+ Passing the message first discards the object:
115
124
 
116
125
  ```typescript
117
- logger.info({ userId: '123', action: 'login' });
118
- // Result: { "userId": "123", "action": "login", "message": "Application started", ... }
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
- Error objects are handled specially and include serialized error information:
139
+ `error` takes an object containing `err` plus a message:
124
140
 
125
141
  ```typescript
126
- logger.error({ err: new Error('Something went wrong') });
127
- // Result: { "message": "Error", "err": { "name": "Error", "message": "Something went wrong", "stack": "..." }, ... }
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
- This follows Pino's standard behavior where all object properties are merged into the top level of the log entry.
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
- // Your SFTP upload logic here
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
- (result) => ({
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
- // This will log a { traceId, value }
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('Processing value', { value });
327
+ logger.info({ value }, 'Processing value');
312
328
  return value * 2;
313
329
  };
314
330
 
315
- // Wrap the function with span tracking
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', { value });
344
+ logger.info({ value }, 'First step');
333
345
 
334
346
  await someAsyncWork();
335
347
 
336
- logger.info('Second step', { value });
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: 'info')
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
- * @param axios - The axios instance to wrap
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
- * @param axios - The axios instance to wrap
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.0-beta.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
- "scripts": {
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
+ }