unnbound-logger-sdk 3.1.1 → 3.1.2

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
@@ -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 WORKFLOW_ENV, UNNBOUND_ENVIRONMENT, or ENVIRONMENT, whichever is set first
47
+ environment?: string; // From TEMPER_ENVIRONMENT, 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;
@@ -464,5 +464,5 @@ console.log(traceId); // "550e8400-e29b-41d4-a716-446655440000"
464
464
  - `UNNBOUND_SERVICE_ID` - Service identifier (included in all logs)
465
465
  - `UNNBOUND_DEPLOYMENT_ID` - Deployment identifier (included in all logs)
466
466
  - `UNNBOUND_WORKFLOW_URL` - Base URL for webhook endpoint logging
467
- - `WORKFLOW_ENV`, `UNNBOUND_ENVIRONMENT`, `ENVIRONMENT` - Environment name, read in that order (included in all logs)
467
+ - `TEMPER_ENVIRONMENT`, `WORKFLOW_ENV`, `UNNBOUND_ENVIRONMENT`, `ENVIRONMENT` - Environment name, read in that order (included in all logs). Temper injects `TEMPER_ENVIRONMENT`; the other names are legacy fallbacks.
468
468
  - `LOG_LEVEL` - Log level (default: 'debug')
package/dist/axios.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from 'axios';
2
+ import { startSpan } from './span';
2
3
  import { type HttpOptions } from './types';
3
4
  type GetPayload = (config: AxiosRequestConfig, res?: AxiosResponse) => object;
4
5
  type RedactOptions = {
@@ -15,6 +16,7 @@ interface HttpClientOptions extends HttpOptions<GetPayload> {
15
16
  * marker, suppression does not depend on runtime env vars.
16
17
  */
17
18
  silent?: boolean;
19
+ startSpan?: typeof startSpan;
18
20
  }
19
21
  /**
20
22
  * Wraps an axios instance to add tracing and span tracking.
@@ -24,5 +26,5 @@ interface HttpClientOptions extends HttpOptions<GetPayload> {
24
26
  * @param options - Configuration options for HTTP tracing
25
27
  * @returns The wrapped axios instance with span tracking
26
28
  */
27
- export declare const traceAxios: (client: AxiosInstance, { ignoreTraceRoutes, traceHeaderKey, getPayload, onRequest, redact: redactOptions, silent, }?: HttpClientOptions) => AxiosInstance;
29
+ export declare const traceAxios: (client: AxiosInstance, { ignoreTraceRoutes, traceHeaderKey, getPayload, onRequest, redact: redactOptions, silent, startSpan: startSpanImpl, }?: HttpClientOptions) => AxiosInstance;
28
30
  export {};
package/dist/axios.js CHANGED
@@ -46,7 +46,7 @@ const tracedMarker = Symbol.for('unnbound-logger-sdk.traceAxios.traced');
46
46
  * @param options - Configuration options for HTTP tracing
47
47
  * @returns The wrapped axios instance with span tracking
48
48
  */
49
- const traceAxios = (client, { ignoreTraceRoutes = types_1.defaultIgnoreTraceRoutes, traceHeaderKey = types_1.defaultTraceHeaderKey, getPayload = getNoopPayload, onRequest = onNoopRequest, redact: redactOptions, silent = false, } = {
49
+ const traceAxios = (client, { ignoreTraceRoutes = types_1.defaultIgnoreTraceRoutes, traceHeaderKey = types_1.defaultTraceHeaderKey, getPayload = getNoopPayload, onRequest = onNoopRequest, redact: redactOptions, silent = false, startSpan: startSpanImpl = span_1.startSpan, } = {
50
50
  ignoreTraceRoutes: types_1.defaultIgnoreTraceRoutes,
51
51
  traceHeaderKey: types_1.defaultTraceHeaderKey,
52
52
  getPayload: getNoopPayload,
@@ -54,7 +54,7 @@ const traceAxios = (client, { ignoreTraceRoutes = types_1.defaultIgnoreTraceRout
54
54
  }) => {
55
55
  if (tracedMarker in client)
56
56
  return client;
57
- const redact = typeof redactOptions === 'boolean' ? { response: redactOptions } : redactOptions;
57
+ const redact = redactOptions === true || redactOptions === false ? { response: redactOptions } : redactOptions;
58
58
  const createSpanWrappedRequest = (originalMethod, method) => {
59
59
  const { headers: defaultHeaders, ...partialDefaultConfig } = client.defaults;
60
60
  const headers = { ...defaultHeaders.common, ...(method && defaultHeaders[method]) };
@@ -67,11 +67,14 @@ const traceAxios = (client, { ignoreTraceRoutes = types_1.defaultIgnoreTraceRout
67
67
  return originalMethod(config);
68
68
  const traceId = storage_1.storage.getStore()?.traceId;
69
69
  config = { ...config, headers: { ...config.headers, [traceHeaderKey]: traceId } };
70
- if (silent)
70
+ if (silent) {
71
+ // SAFETY: onRequest returns the originalMethod promise, typed as Axios R
71
72
  return onRequest(config, (config) => originalMethod(config));
73
+ }
74
+ // SAFETY: startSpan resolves to the originalMethod promise, typed as Axios R
72
75
  return onRequest(config, (config) => {
73
76
  // Execute the request within a span
74
- return (0, span_1.startSpan)('Outgoing HTTP request', () => originalMethod(config), (options) => {
77
+ return startSpanImpl('Outgoing HTTP request', () => originalMethod(config), (options) => {
75
78
  const response = options
76
79
  ? options.error
77
80
  ? (0, axios_1.isAxiosError)(options.error)
package/dist/emf.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Bindings } from 'pino';
1
2
  /**
2
3
  * CloudWatch Embedded Metric Format (EMF) support.
3
4
  *
@@ -18,7 +19,7 @@ export interface EmitMetricOptions {
18
19
  /** Dimension names to use for grouping (keys from dimensions object) */
19
20
  dimensionKeys?: string[];
20
21
  /** Additional properties to include in the log */
21
- properties?: Record<string, unknown>;
22
+ properties?: Bindings;
22
23
  }
23
24
  /**
24
25
  * Emit a metric via CloudWatch EMF format.
package/dist/index.d.ts CHANGED
@@ -1,9 +1,3 @@
1
- /**
2
- * unnbound-logger
3
- *
4
- * A structured logging library built on Pino with TypeScript support.
5
- * Provides consistent, well-typed logging across different operational contexts.
6
- */
7
1
  export { type OnRequest, traceAxios } from './axios';
8
2
  export type { EmitMetricOptions, MetricDefinition, MetricUnit } from './emf';
9
3
  export { emitMetric, emitMetrics } from './emf';
package/dist/index.js CHANGED
@@ -1,10 +1,4 @@
1
1
  "use strict";
2
- /**
3
- * unnbound-logger
4
- *
5
- * A structured logging library built on Pino with TypeScript support.
6
- * Provides consistent, well-typed logging across different operational contexts.
7
- */
8
2
  Object.defineProperty(exports, "__esModule", { value: true });
9
3
  exports.defaultTraceHeaderKey = exports.defaultMessageHeaderKey = exports.withTrace = exports.getTraceId = exports.withStorage = exports.storage = exports.startSpan = exports.traceMiddleware = exports.logger = exports.encode = exports.decode = exports.emitMetrics = exports.emitMetric = exports.traceAxios = void 0;
10
4
  var axios_1 = require("./axios");
@@ -1,3 +1,11 @@
1
- export declare const hidden: (data: object) => unknown;
1
+ interface InternalLogEntry {
2
+ _internal: true;
3
+ _hide?: true;
4
+ }
5
+ type LogEntry = object & {
6
+ _hide?: boolean;
7
+ };
8
+ export declare const hidden: (data: LogEntry) => boolean;
2
9
  /** @public - used by SDK consumers via dist/internal */
3
- export declare const internal: () => Record<string, unknown>;
10
+ export declare const internal: () => InternalLogEntry;
11
+ export {};
package/dist/internal.js CHANGED
@@ -5,7 +5,7 @@ const o = { _internal: true };
5
5
  // Sandbox-internal logs are hidden from customer-visible streams.
6
6
  if (process.env.UNNBOUND_IDLE_TIMEOUT)
7
7
  o._hide = true;
8
- const hidden = (data) => '_hide' in data && data._hide;
8
+ const hidden = (data) => data._hide === true;
9
9
  exports.hidden = hidden;
10
10
  /** @public - used by SDK consumers via dist/internal */
11
11
  const internal = () => o;
package/dist/logger.d.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import pino from 'pino';
2
- import type { DestinationStream } from 'pino';
2
+ import type { Bindings, DestinationStream } from 'pino';
3
3
  export interface ILogger {
4
- trace(object: object, message: string): void;
4
+ trace(object: Bindings, message: string): void;
5
5
  trace(message: string): void;
6
- debug(object: object, message: string): void;
6
+ debug(object: Bindings, message: string): void;
7
7
  debug(message: string): void;
8
- info(object: object, message: string): void;
8
+ info(object: Bindings, message: string): void;
9
9
  info(message: string): void;
10
- warn(object: object, message: string): void;
10
+ warn(object: Bindings, message: string): void;
11
11
  warn(message: string): void;
12
12
  error<O extends {
13
13
  err: unknown;
package/dist/logger.js CHANGED
@@ -9,9 +9,22 @@ const uuid_1 = require("uuid");
9
9
  const encode_1 = require("./encode");
10
10
  const internal_1 = require("./internal");
11
11
  const storage_1 = require("./storage");
12
- const levels = new Set(['debug', 'info', 'warn', 'error']);
13
- const workflowEnvironment = process.env.WORKFLOW_ENV ?? process.env.UNNBOUND_ENVIRONMENT ?? process.env.ENVIRONMENT;
12
+ // The Temper name wins while legacy fallbacks keep existing workflow images compatible.
13
+ const workflowEnvironment = process.env.TEMPER_ENVIRONMENT ??
14
+ process.env.WORKFLOW_ENV ??
15
+ process.env.UNNBOUND_ENVIRONMENT ??
16
+ process.env.ENVIRONMENT;
14
17
  const formatLevel = (level) => ({ level });
18
+ const isBindings = (value) => Object(value) === value;
19
+ const isLogLevel = (value) => value === 'debug' || value === 'info' || value === 'warn' || value === 'error';
20
+ const isStringAnnotation = (value) => {
21
+ try {
22
+ return String.prototype.valueOf.call(value) === value;
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ };
15
28
  const formatLog = (log, visited = new WeakSet()) => {
16
29
  // Prevent infinite loops from circular references
17
30
  if (visited.has(log))
@@ -35,17 +48,13 @@ const formatLog = (log, visited = new WeakSet()) => {
35
48
  // We can't encode immutable properties.
36
49
  if (!Object.getOwnPropertyDescriptor(log, key)?.writable)
37
50
  continue;
38
- if (typeof value === 'string') {
51
+ if (isStringAnnotation(value)) {
39
52
  log[key] = (0, encode_1.encode)(value);
40
53
  }
41
54
  else if (Array.isArray(value)) {
42
- log[key] = value.map((item) => typeof item === 'string'
43
- ? (0, encode_1.encode)(item)
44
- : item && typeof item === 'object'
45
- ? formatLog(item, visited)
46
- : item);
55
+ log[key] = value.map((item) => isStringAnnotation(item) ? (0, encode_1.encode)(item) : isBindings(item) ? formatLog(item, visited) : item);
47
56
  }
48
- else if (value !== null && typeof value === 'object') {
57
+ else if (isBindings(value)) {
49
58
  log[key] = formatLog(value, visited);
50
59
  }
51
60
  }
@@ -76,13 +85,14 @@ const loggerOptions = {
76
85
  logMethod(args, method) {
77
86
  const firstArg = args[0];
78
87
  let activeMethod = method;
79
- if (!!firstArg && typeof firstArg === 'object') {
88
+ if (isBindings(firstArg)) {
80
89
  // If the log entry is considered hidden, don't log it
81
90
  if ((0, internal_1.hidden)(firstArg))
82
91
  return;
83
92
  // Dynamic log level by allowing overwriting the log level
84
- if ('level' in firstArg && typeof firstArg.level === 'string' && levels.has(firstArg.level)) {
85
- activeMethod = this[firstArg.level];
93
+ const requestedLevel = firstArg.level;
94
+ if (isLogLevel(requestedLevel)) {
95
+ activeMethod = this[requestedLevel];
86
96
  firstArg.level = undefined;
87
97
  }
88
98
  }
@@ -75,6 +75,7 @@ const traceMiddleware = ({ ignoreTraceRoutes = types_1.defaultIgnoreTraceRoutes,
75
75
  const response = o
76
76
  ? {
77
77
  status: res.statusCode,
78
+ // SAFETY: Express OutgoingHttpHeaders values are stringified by the tracer
78
79
  headers: res.getHeaders(),
79
80
  body: res.locals.body,
80
81
  }
package/dist/span.d.ts CHANGED
@@ -2,10 +2,8 @@ import type { Maybe } from './types';
2
2
  export type LogPayloadGetterOptions<T> = Maybe<T>;
3
3
  type LogPayloadGetter<T> = object | ((o?: LogPayloadGetterOptions<T>) => object);
4
4
  /**
5
- * Starts a span that tracks the duration of a callback
6
- * @param spanName - The span name to use for logging
7
- * @param callback - The async callback to execute
8
- * @returns The result of the callback
5
+ * Logs `<spanName> started/completed/failed` with duration (ms); nested spans chain spanId
6
+ * "child parent". getter receives {result} or {error}.
9
7
  */
10
8
  export declare const startSpan: <T>(spanName: string, callback: () => Promise<T>, getter?: LogPayloadGetter<T>) => Promise<T>;
11
9
  export {};
package/dist/span.js CHANGED
@@ -6,10 +6,8 @@ const logger_1 = require("./logger");
6
6
  const storage_1 = require("./storage");
7
7
  const trace_1 = require("./trace");
8
8
  /**
9
- * Starts a span that tracks the duration of a callback
10
- * @param spanName - The span name to use for logging
11
- * @param callback - The async callback to execute
12
- * @returns The result of the callback
9
+ * Logs `<spanName> started/completed/failed` with duration (ms); nested spans chain spanId
10
+ * "child parent". getter receives {result} or {error}.
13
11
  */
14
12
  const startSpan = async (spanName, callback, getter) => {
15
13
  const spanId = (0, uuid_1.v4)();
@@ -34,7 +32,7 @@ const startSpan = async (spanName, callback, getter) => {
34
32
  };
35
33
  exports.startSpan = startSpan;
36
34
  const getLogPayload = (getter, o) => {
37
- if (typeof getter !== 'function')
35
+ if (!(getter instanceof Function))
38
36
  return getter;
39
37
  return getter(o);
40
38
  };
package/dist/storage.d.ts CHANGED
@@ -8,5 +8,5 @@ interface UnnboundStorage extends AsyncLocalStorage<object> {
8
8
  getStore<T>(): T | undefined;
9
9
  }
10
10
  export declare const storage: UnnboundStorage;
11
- export declare const withStorage: <T>(data: object, callback: () => Promise<T>) => Promise<T>;
11
+ export declare const withStorage: <T, Data extends object>(data: Data, callback: () => Promise<T>) => Promise<T>;
12
12
  export {};
package/dist/storage.js CHANGED
@@ -2,7 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.withStorage = exports.storage = void 0;
4
4
  const node_async_hooks_1 = require("node:async_hooks");
5
- exports.storage = new node_async_hooks_1.AsyncLocalStorage();
5
+ exports.storage =
6
+ // SAFETY: UnnboundStorage only adds a typed getStore helper over AsyncLocalStorage
7
+ new node_async_hooks_1.AsyncLocalStorage();
6
8
  const withStorage = async (data, callback) => {
7
9
  const previous = exports.storage.getStore();
8
10
  return exports.storage.run({ ...previous, ...data }, callback);
package/dist/trace.d.ts CHANGED
@@ -1,20 +1,7 @@
1
- /**
2
- * Generates a trace ID
3
- * @returns A trace ID
4
- */
5
1
  export declare const getTraceId: () => string;
6
- /**
7
- * Generates a message ID
8
- * @public - used by SDK consumers via dist/trace
9
- * @returns A message ID
10
- */
2
+ /** @public Consumed via dist/trace; keep exported. */
11
3
  export declare const getMessageId: () => string;
12
- /**
13
- * Runs a callback with a trace ID
14
- * @param callback - The callback to run
15
- * @param extra - Extra context to add to the trace
16
- * @returns The result of the callback
17
- */
4
+ /** traceId/messageId precedence: extra → enclosing store → fresh uuid. */
18
5
  export declare const withTrace: <T, E extends {
19
6
  traceId?: string;
20
7
  messageId?: string;
package/dist/trace.js CHANGED
@@ -3,25 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.withTrace = exports.getMessageId = exports.getTraceId = void 0;
4
4
  const uuid_1 = require("uuid");
5
5
  const storage_1 = require("./storage");
6
- /**
7
- * Generates a trace ID
8
- * @returns A trace ID
9
- */
10
6
  const getTraceId = () => (0, uuid_1.v4)();
11
7
  exports.getTraceId = getTraceId;
12
- /**
13
- * Generates a message ID
14
- * @public - used by SDK consumers via dist/trace
15
- * @returns A message ID
16
- */
8
+ /** @public Consumed via dist/trace; keep exported. */
17
9
  const getMessageId = () => (0, uuid_1.v4)();
18
10
  exports.getMessageId = getMessageId;
19
- /**
20
- * Runs a callback with a trace ID
21
- * @param callback - The callback to run
22
- * @param extra - Extra context to add to the trace
23
- * @returns The result of the callback
24
- */
11
+ /** traceId/messageId precedence: extra → enclosing store → fresh uuid. */
25
12
  const withTrace = (callback, extra) => {
26
13
  const previous = storage_1.storage.getStore();
27
14
  const traceId = extra?.traceId ?? previous?.traceId ?? (0, exports.getTraceId)();
package/dist/utils.d.ts CHANGED
@@ -1,19 +1,6 @@
1
- /**
2
- * Checks if a path matches any of the ignore patterns
3
- * @param path - The path to check
4
- * @param patterns - Array of glob patterns to match against
5
- * @returns boolean indicating if the path should be ignored
6
- */
1
+ /** patterns are globs (`*`, `?`) matched against the whole path. */
7
2
  export declare const shouldIgnorePath: (path: string, patterns: string[]) => boolean;
8
- /**
9
- * Safely parses JSON strings, returning the original data if parsing fails or if it's not a string.
10
- * @param data The data to potentially parse as JSON.
11
- * @returns Parsed JSON object or the original data.
12
- */
3
+ /** Non-string or invalid JSON → input returned unchanged. */
13
4
  export declare function safeJsonParse(data: any): any;
14
- /**
15
- * Normalizes IP addresses by removing IPv6 mapping prefix for IPv4 addresses.
16
- * @param ip The IP address to normalize.
17
- * @returns Normalized IP address string.
18
- */
5
+ /** Strips the IPv4-mapped IPv6 prefix (::ffff:). */
19
6
  export declare function normalizeIp(ip: string | undefined): string | undefined;
package/dist/utils.js CHANGED
@@ -3,31 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.shouldIgnorePath = void 0;
4
4
  exports.safeJsonParse = safeJsonParse;
5
5
  exports.normalizeIp = normalizeIp;
6
- /**
7
- * Checks if a path matches any of the ignore patterns
8
- * @param path - The path to check
9
- * @param patterns - Array of glob patterns to match against
10
- * @returns boolean indicating if the path should be ignored
11
- */
6
+ /** patterns are globs (`*`, `?`) matched against the whole path. */
12
7
  const shouldIgnorePath = (path, patterns) => {
13
8
  return patterns.some((pattern) => {
14
- // Convert glob pattern to regex
15
- const regexPattern = pattern
16
- .replace(/\./g, '\\.') // Escape dots
17
- .replace(/\*/g, '.*') // Convert * to .*
18
- .replace(/\?/g, '.'); // Convert ? to .
9
+ const regexPattern = pattern.replace(/\./g, '\\.').replace(/\*/g, '.*').replace(/\?/g, '.');
19
10
  const regex = new RegExp(`^${regexPattern}$`);
20
11
  return regex.test(path);
21
12
  });
22
13
  };
23
14
  exports.shouldIgnorePath = shouldIgnorePath;
24
- /**
25
- * Safely parses JSON strings, returning the original data if parsing fails or if it's not a string.
26
- * @param data The data to potentially parse as JSON.
27
- * @returns Parsed JSON object or the original data.
28
- */
15
+ /** Non-string or invalid JSON → input returned unchanged. */
29
16
  function safeJsonParse(data) {
30
- if (typeof data === 'string') {
17
+ if (String(data) === data) {
31
18
  try {
32
19
  return JSON.parse(data);
33
20
  }
@@ -37,15 +24,10 @@ function safeJsonParse(data) {
37
24
  }
38
25
  return data;
39
26
  }
40
- /**
41
- * Normalizes IP addresses by removing IPv6 mapping prefix for IPv4 addresses.
42
- * @param ip The IP address to normalize.
43
- * @returns Normalized IP address string.
44
- */
27
+ /** Strips the IPv4-mapped IPv6 prefix (::ffff:). */
45
28
  function normalizeIp(ip) {
46
29
  if (!ip)
47
30
  return ip;
48
- // Remove IPv4-mapped IPv6 prefix (::ffff:) to get clean IPv4 address
49
31
  if (ip.startsWith('::ffff:')) {
50
32
  return ip.substring(7);
51
33
  }
package/package.json CHANGED
@@ -1,15 +1,15 @@
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.1",
4
+ "version": "3.1.2",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
9
  "test": "vitest run src",
10
10
  "typecheck": "tsc --noEmit",
11
- "format": "biome format --write .",
12
- "format:check": "biome format .",
11
+ "format": "oxfmt --write .",
12
+ "format:check": "oxfmt --check .",
13
13
  "prepublishOnly": "npm run build",
14
14
  "start:example": "tsx watch examples/node-express.ts",
15
15
  "version:bump": "npm version patch"
@@ -35,7 +35,7 @@
35
35
  "url": "https://github.com/unnbounddev/unnbound-sdks/issues"
36
36
  },
37
37
  "dependencies": {
38
- "axios": "1.16.0",
38
+ "axios": "1.18.0",
39
39
  "express": "^4.0.0 || ^5.0.0",
40
40
  "pino": "^10.3.1",
41
41
  "uuid": "^11.1.1"
@@ -47,7 +47,7 @@
47
47
  "vitest": "^4.0.15"
48
48
  },
49
49
  "peerDependencies": {
50
- "axios": "^1.15.1",
50
+ "axios": "^1.18.0",
51
51
  "express": "^4.0.0 || ^5.0.0"
52
52
  },
53
53
  "peerDependenciesMeta": {