@bymax-one/nest-logger 1.4.1 → 1.4.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.
@@ -2,6 +2,7 @@
2
2
 
3
3
  var common = require('@nestjs/common');
4
4
  var core = require('@nestjs/core');
5
+ var util = require('util');
5
6
  var rxjs = require('rxjs');
6
7
  var pino = require('pino');
7
8
  var module$1 = require('module');
@@ -29,7 +30,7 @@ var DEFAULT_HTTP = {
29
30
  isEnabled: false,
30
31
  shouldCaptureExceptions: true,
31
32
  shouldGenerateRequestId: true,
32
- excludePaths: [/^\/health$/, /^\/metrics$/],
33
+ excludePaths: [/^\/health$/, /^\/health\/live$/, /^\/health\/ready$/, /^\/metrics$/],
33
34
  tenantIdHeader: "x-tenant-id"
34
35
  };
35
36
  var DEFAULT_OTEL = {
@@ -114,7 +115,36 @@ var LOG_LEVEL_PRIORITY = Object.freeze([
114
115
  ]);
115
116
 
116
117
  // src/server/config/validate-options.ts
117
- function validateOptions(options) {
118
+ function reject(path, rule, got) {
119
+ const received = got === void 0 ? "" : `. Got: ${describeValue(got)}`;
120
+ throw new Error(`[BymaxLoggerModule] options.${path} ${rule}${received}`);
121
+ }
122
+ function describeValue(value) {
123
+ if (value === null) return "null";
124
+ if (Array.isArray(value)) return "an array";
125
+ if (typeof value === "object") return "an object";
126
+ if (typeof value === "function") return "a function";
127
+ if (typeof value === "string") return JSON.stringify(value);
128
+ return String(value);
129
+ }
130
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
131
+ var isNonEmptyString = (value) => typeof value === "string" && value.length > 0;
132
+ function checkBoolean(path, value) {
133
+ if (value !== void 0 && typeof value !== "boolean") {
134
+ reject(path, "must be a boolean", value);
135
+ }
136
+ }
137
+ function checkNonEmptyString(path, value) {
138
+ if (value !== void 0 && !isNonEmptyString(value)) {
139
+ reject(path, "must be a non-empty string", value);
140
+ }
141
+ }
142
+ function checkGroup(path, value) {
143
+ if (value === void 0) return void 0;
144
+ if (!isRecord(value)) reject(path, "must be an object", value);
145
+ return value;
146
+ }
147
+ function validateCore(options) {
118
148
  if (!options.service) {
119
149
  throw new Error("[BymaxLoggerModule] options.service is required");
120
150
  }
@@ -131,9 +161,15 @@ function validateOptions(options) {
131
161
  )}. Got: ${String(options.level)}`
132
162
  );
133
163
  }
164
+ const size = options.maxEntrySizeBytes;
165
+ if (size !== void 0 && !Number.isFinite(size)) {
166
+ reject("maxEntrySizeBytes", "must be a finite number", size);
167
+ }
134
168
  if (options.maxEntrySizeBytes !== void 0 && options.maxEntrySizeBytes <= 0) {
135
169
  throw new Error("[BymaxLoggerModule] options.maxEntrySizeBytes must be > 0");
136
170
  }
171
+ }
172
+ function validateClosedSets(options) {
137
173
  if (options.redactStrategy !== void 0 && options.redactStrategy !== "names" && options.redactStrategy !== "paths") {
138
174
  throw new Error(
139
175
  `[BymaxLoggerModule] options.redactStrategy must be 'names' or 'paths'. Got: ${String(
@@ -163,6 +199,109 @@ function validateOptions(options) {
163
199
  );
164
200
  }
165
201
  }
202
+ function validateCustomization(options) {
203
+ checkBoolean("isGlobal", options.isGlobal);
204
+ checkBoolean("shouldDisableDefaultRedact", options.shouldDisableDefaultRedact);
205
+ if (options.redactCensor !== void 0 && typeof options.redactCensor !== "string") {
206
+ reject("redactCensor", "must be a string", options.redactCensor);
207
+ }
208
+ if (options.timestamp !== void 0 && typeof options.timestamp !== "function") {
209
+ reject("timestamp", "must be a function", options.timestamp);
210
+ }
211
+ const paths = options.redactPaths;
212
+ if (paths !== void 0) {
213
+ if (!Array.isArray(paths)) reject("redactPaths", "must be an array of strings", paths);
214
+ paths.forEach((entry, index) => {
215
+ if (!isNonEmptyString(entry)) {
216
+ reject(`redactPaths[${String(index)}]`, "must be a non-empty string", entry);
217
+ }
218
+ });
219
+ }
220
+ const serializers = options.serializers;
221
+ if (serializers !== void 0) {
222
+ if (!isRecord(serializers)) reject("serializers", "must be an object", serializers);
223
+ for (const [key, serializer] of Object.entries(serializers)) {
224
+ if (typeof serializer !== "function") {
225
+ reject(`serializers.${key}`, "must be a function", serializer);
226
+ }
227
+ }
228
+ }
229
+ }
230
+ var UNREADABLE = /* @__PURE__ */ Symbol("unreadable");
231
+ function readMember(destination, key) {
232
+ try {
233
+ return Reflect.get(destination, key);
234
+ } catch {
235
+ return UNREADABLE;
236
+ }
237
+ }
238
+ function validateDestination(entry, index) {
239
+ const path = `destinations[${String(index)}]`;
240
+ if (!isRecord(entry)) {
241
+ reject(path, "must be an object implementing ILogDestination", entry);
242
+ }
243
+ const write = readMember(entry, "write");
244
+ if (write !== UNREADABLE && typeof write !== "function") {
245
+ reject(path, "must have a write(payload) method");
246
+ }
247
+ const name = readMember(entry, "name");
248
+ if (name !== UNREADABLE && !isNonEmptyString(name)) {
249
+ reject(path, "must have a non-empty string name", name);
250
+ }
251
+ for (const hook of ["onInit", "onShutdown", "onRegistryReady"]) {
252
+ const value = readMember(entry, hook);
253
+ if (value !== UNREADABLE && value !== void 0 && typeof value !== "function") {
254
+ reject(`${path}.${hook}`, "must be a function", value);
255
+ }
256
+ }
257
+ }
258
+ function validateDestinations(options) {
259
+ const destinations = options.destinations;
260
+ if (destinations === void 0) return;
261
+ if (!Array.isArray(destinations)) {
262
+ reject("destinations", "must be an array of ILogDestination", destinations);
263
+ }
264
+ destinations.forEach((entry, index) => validateDestination(entry, index));
265
+ }
266
+ function validateHttp(options) {
267
+ const http = checkGroup("http", options.http);
268
+ if (http === void 0) return;
269
+ checkBoolean("http.isEnabled", http["isEnabled"]);
270
+ checkBoolean("http.shouldCaptureExceptions", http["shouldCaptureExceptions"]);
271
+ checkBoolean("http.shouldGenerateRequestId", http["shouldGenerateRequestId"]);
272
+ const excludePaths = http["excludePaths"];
273
+ if (excludePaths !== void 0) {
274
+ if (!Array.isArray(excludePaths)) {
275
+ reject("http.excludePaths", "must be an array of RegExp", excludePaths);
276
+ }
277
+ excludePaths.forEach((pattern, index) => {
278
+ if (!util.types.isRegExp(pattern)) {
279
+ reject(`http.excludePaths[${String(index)}]`, "must be a RegExp", pattern);
280
+ }
281
+ });
282
+ }
283
+ checkNonEmptyString("http.tenantIdHeader", http["tenantIdHeader"]);
284
+ }
285
+ function validateOtel(options) {
286
+ const otel = checkGroup("otel", options.otel);
287
+ if (otel === void 0) return;
288
+ checkBoolean("otel.shouldAutoInjectTraceContext", otel["shouldAutoInjectTraceContext"]);
289
+ const format = otel["fieldFormat"];
290
+ if (format !== void 0 && format !== "camelCase" && format !== "snake_case") {
291
+ reject("otel.fieldFormat", "must be 'camelCase' or 'snake_case'", format);
292
+ }
293
+ checkNonEmptyString("otel.traceIdField", otel["traceIdField"]);
294
+ checkNonEmptyString("otel.spanIdField", otel["spanIdField"]);
295
+ checkNonEmptyString("otel.traceFlagsField", otel["traceFlagsField"]);
296
+ }
297
+ function validateOptions(options) {
298
+ validateCore(options);
299
+ validateClosedSets(options);
300
+ validateCustomization(options);
301
+ validateDestinations(options);
302
+ validateHttp(options);
303
+ validateOtel(options);
304
+ }
166
305
 
167
306
  // src/server/constants/injection-tokens.constants.ts
168
307
  var LOGGER_OPTIONS_TOKEN = /* @__PURE__ */ Symbol("BYMAX_LOGGER_OPTIONS");
@@ -1609,8 +1748,11 @@ function buildPinoInstance(options, logContext, destinations, health) {
1609
1748
  options.resourceFormat,
1610
1749
  extraServiceFields(options.service)
1611
1750
  ),
1612
- // Pino requires the timestamp fn to emit the `,"time":"..."` fragment.
1613
- timestamp: () => `,"time":"${options.timestamp()}"`,
1751
+ // Pino requires the timestamp fn to emit the `,"time":<json>` fragment, and
1752
+ // inserts it into the line verbatim. The value comes from consumer code, so it
1753
+ // is encoded as a JSON string here: a quote, a backslash or a newline in it must
1754
+ // neither break the line nor add fields to it.
1755
+ timestamp: () => `,"time":${JSON.stringify(String(options.timestamp()))}`,
1614
1756
  formatters: {
1615
1757
  // Emit the level as a string label instead of the numeric code — easier
1616
1758
  // for log aggregators. Trace context is injected via `mixin`, never via
@@ -2468,10 +2610,18 @@ var PrettyDevDestination = class {
2468
2610
  * Lazily resolve `pino-pretty` and build the colorized transform that renders
2469
2611
  * each entry to `process.stdout`.
2470
2612
  *
2613
+ * @throws Error When `NODE_ENV` is `production`: this sink is development
2614
+ * output, so it refuses rather than put ANSI colour into the log stream.
2471
2615
  * @throws Error When `pino-pretty` is not installed — with an actionable
2472
2616
  * message telling the consumer to install it or drop this destination.
2473
2617
  */
2474
2618
  async onInit() {
2619
+ if (process.env["NODE_ENV"] === "production") {
2620
+ this.initFailed = true;
2621
+ throw new Error(
2622
+ "[PrettyDevDestination] refused in production (NODE_ENV=production): it renders ANSI colour through a main-thread transform. Use DefaultStdoutDestination, or register PrettyDevDestination only outside production."
2623
+ );
2624
+ }
2475
2625
  try {
2476
2626
  const { build } = await import('pino-pretty');
2477
2627
  this.stream = build({
@@ -2598,8 +2748,8 @@ var PrettyDevDestination = class {
2598
2748
  this.flushBuffer(writeRawToStdout);
2599
2749
  return;
2600
2750
  }
2601
- await new Promise((resolve, reject) => {
2602
- stream.once("error", reject);
2751
+ await new Promise((resolve, reject2) => {
2752
+ stream.once("error", reject2);
2603
2753
  stream.end(resolve);
2604
2754
  });
2605
2755
  }
@@ -3033,6 +3183,7 @@ exports.InjectLogger = InjectLogger;
3033
3183
  exports.LOGGER_DESTINATIONS_TOKEN = LOGGER_DESTINATIONS_TOKEN;
3034
3184
  exports.LOGGER_OPTIONS_TOKEN = LOGGER_OPTIONS_TOKEN;
3035
3185
  exports.LOGGER_PINO_INSTANCE_TOKEN = LOGGER_PINO_INSTANCE_TOKEN;
3186
+ exports.LOGGER_REDACTOR_TOKEN = LOGGER_REDACTOR_TOKEN;
3036
3187
  exports.LOG_CONTEXT_METADATA_KEY = LOG_CONTEXT_METADATA_KEY;
3037
3188
  exports.LOG_CONTEXT_TOKEN = LOG_CONTEXT_TOKEN;
3038
3189
  exports.LOG_KEYS_CONVENTION_REGEX = LOG_KEYS_CONVENTION_REGEX;
@@ -3041,7 +3192,9 @@ exports.LogPerformance = LogPerformance;
3041
3192
  exports.PINO_LEVEL_NAMES = PINO_LEVEL_NAMES;
3042
3193
  exports.PINO_LEVEL_NUMBERS = PINO_LEVEL_NUMBERS;
3043
3194
  exports.PrettyDevDestination = PrettyDevDestination;
3195
+ exports.REDACT_COMMON_FIELDS = REDACT_COMMON_FIELDS;
3044
3196
  exports.RESERVED_LOG_KEYS = RESERVED_LOG_KEYS;
3045
3197
  exports.RESERVED_LOG_KEYS_NOT_EMITTED = RESERVED_LOG_KEYS_NOT_EMITTED;
3046
3198
  exports.applyAccessLog = applyAccessLog;
3047
3199
  exports.applyRequestIdMiddleware = applyRequestIdMiddleware;
3200
+ exports.isValidTraceId = isValidTraceId;
@@ -52,7 +52,9 @@ interface ILogDestination {
52
52
  readonly name: string;
53
53
  /**
54
54
  * Minimum log level this destination accepts. Entries below this level
55
- * are not written. Undefined means "accept everything".
55
+ * are not written. The effective level is the STRICTER of this and the module
56
+ * `level`, so it can only narrow what the logger emits, never widen it.
57
+ * Undefined means "every entry the module level lets through".
56
58
  */
57
59
  readonly minLevel?: LogLevel;
58
60
  /**
@@ -64,8 +66,13 @@ interface ILogDestination {
64
66
  * - Buffer and flush periodically (e.g., HTTP batching)
65
67
  * - Return a Promise for async I/O
66
68
  *
67
- * Errors thrown here are caught by the library; the destination may be
68
- * temporarily skipped if it consistently fails.
69
+ * Errors thrown here, and rejections of a returned promise, are caught by the
70
+ * library: the entry is dropped for this destination only, and one failure line
71
+ * is written to stderr per failed entry. There is no retry and no circuit
72
+ * breaker, so a destination that initialized and keeps failing keeps receiving
73
+ * entries and keeps being reported; one that needs to back off does so itself.
74
+ * (A destination whose `onInit()` failed is a different case: it receives no
75
+ * entries at all.)
69
76
  *
70
77
  * @param payload — Newline-terminated JSON entry, UTF-8 encoded.
71
78
  */
@@ -250,30 +257,35 @@ interface EmittedDeploymentResource {
250
257
  * Every field is optional; defaults are applied by `applyDefaults`.
251
258
  */
252
259
  interface HttpOptions {
253
- /** Mount the HTTP interceptor + filter. Default: false. */
260
+ /**
261
+ * Mount the HTTP interceptor + filter. Default: false. The filter is registered by
262
+ * `forRoot` only: `forRootAsync` registers the interceptor and leaves the filter to
263
+ * the consumer (`{ provide: APP_FILTER, useClass: HttpExceptionFilter }`).
264
+ */
254
265
  isEnabled?: boolean;
255
- /** Capture unhandled HTTP exceptions and emit `HTTP_EXCEPTION_UNHANDLED`. Default: true. */
266
+ /**
267
+ * Capture unhandled HTTP exceptions and emit `HTTP_EXCEPTION_UNHANDLED`. Default: true.
268
+ * Read by `forRoot` only; it has no effect on `forRootAsync`, which never registers the
269
+ * filter.
270
+ */
256
271
  shouldCaptureExceptions?: boolean;
257
272
  /** Generate `requestId` when the request header is absent. Default: true. */
258
273
  shouldGenerateRequestId?: boolean;
259
274
  /**
260
275
  * Paths that bypass HTTP logging (health checks, metrics). Defaults:
261
- * `/health`, `/metrics`.
262
- *
263
- * The defaults do NOT cover `@bymax-one/nest-core`, and that is the common case
264
- * worth knowing before you rely on them. It serves liveness and readiness at
265
- * `/health/live` and `/health/ready`, and no bare `/health` at all, so
266
- * `/^\/health$/` excludes a route that does not exist while both probes are
267
- * logged. Measured on one deployment: 8272 of 8274 HTTP entries were the
268
- * liveness probe. Set `excludePaths` yourself when wiring the two together —
269
- * `[/^\/health\/(live|ready)$/, /^\/metrics$/]` matches the stock layout.
270
- *
271
- * The defaults are not widened to include those subpaths because both prefixes
272
- * are CONFIGURABLE there (`DEFAULT_HEALTH_PATH`, `DEFAULT_METRICS_PATH`): any
273
- * default naming specific subpaths would be less wrong rather than correct, and
274
- * a prefix pattern would silently swallow a consumer's own `/health/*` route
275
- * that they did want logged. Excluding a path is a decision about which requests
276
- * disappear from the record, so it stays explicit.
276
+ * `/health`, `/health/live`, `/health/ready`, `/metrics`.
277
+ *
278
+ * `/health/live` and `/health/ready` are the liveness and readiness probes of
279
+ * `@bymax-one/nest-core`, which serves no bare `/health`; left logged, a probe hit
280
+ * every few seconds drowns the record (measured on one deployment: 8272 of 8274
281
+ * HTTP entries were the liveness probe). Each default is an exact, anchored path,
282
+ * not a prefix, so a consumer's own `/health/other` route is still logged.
283
+ *
284
+ * The prefixes are CONFIGURABLE in nest-core (`DEFAULT_HEALTH_PATH`,
285
+ * `DEFAULT_METRICS_PATH`): when they are changed, or when the probes should be
286
+ * logged, set `excludePaths` yourself. A value REPLACES the defaults rather than
287
+ * adding to them; `[/^\/health$/, /^\/metrics$/]` excludes only `/health` and
288
+ * `/metrics`.
277
289
  *
278
290
  * SECURITY: each pattern is `.test()`-ed against the (attacker-controllable)
279
291
  * request path on every request. Supply only anchored, linear-time regexes
@@ -1171,6 +1183,8 @@ declare class PrettyDevDestination implements ILogDestination {
1171
1183
  * Lazily resolve `pino-pretty` and build the colorized transform that renders
1172
1184
  * each entry to `process.stdout`.
1173
1185
  *
1186
+ * @throws Error When `NODE_ENV` is `production`: this sink is development
1187
+ * output, so it refuses rather than put ANSI colour into the log stream.
1174
1188
  * @throws Error When `pino-pretty` is not installed — with an actionable
1175
1189
  * message telling the consumer to install it or drop this destination.
1176
1190
  */
@@ -1773,6 +1787,11 @@ declare const LOG_CONTEXT_METADATA_KEY = "bymax_logger:log_context";
1773
1787
  * Set the log-context label for a class. All loggers injected within the class
1774
1788
  * are intended to adopt this label (auto-application requires a wired interceptor).
1775
1789
  *
1790
+ * @deprecated Nothing in this package reads the metadata, so the label is never
1791
+ * applied: no interceptor is wired. Inject a logger bound to the label with
1792
+ * `@InjectLogger(PaymentsService.name)` instead. Do not call `setContext()` on
1793
+ * the shared logger — it is a singleton, so that overwrites the label for every
1794
+ * other holder.
1776
1795
  * @param name - The context label (typically the class name).
1777
1796
  * @returns A NestJS metadata decorator carrying the label.
1778
1797
  * @example
@@ -1816,9 +1835,31 @@ declare const LOGGER_OPTIONS_TOKEN: unique symbol;
1816
1835
  declare const LOGGER_PINO_INSTANCE_TOKEN: unique symbol;
1817
1836
  /** Injection token for the resolved destinations array (read-only at runtime). */
1818
1837
  declare const LOGGER_DESTINATIONS_TOKEN: unique symbol;
1838
+ /**
1839
+ * Injection token for the redaction function backing the DEFAULT PII coverage.
1840
+ *
1841
+ * Exposed as its own provider because `PinoLoggerService.child()` has to apply
1842
+ * it BEFORE handing bindings to Pino: child bindings are pre-serialized into the
1843
+ * instance's `chindings` fragment and never reach `formatters.log`, so the
1844
+ * factory's hook cannot see them.
1845
+ */
1846
+ declare const LOGGER_REDACTOR_TOKEN: unique symbol;
1819
1847
  /** Injection token for the per-request AsyncLocalStorage-backed log context. */
1820
1848
  declare const LOG_CONTEXT_TOKEN: unique symbol;
1821
1849
 
1850
+ /**
1851
+ * The canonical sensitive-field-name set.
1852
+ *
1853
+ * Under the default `redactStrategy: 'names'` this list IS the redaction
1854
+ * contract: a value is censored when its key name appears here, at any depth.
1855
+ * Under the legacy `'paths'` strategy the same names are expanded to depths 1–4
1856
+ * by {@link depth} to build {@link DEFAULT_REDACT_PATHS}.
1857
+ *
1858
+ * Append-only: a name may be added in a minor release; removing one requires a
1859
+ * major, because a field that silently stopped being redacted is a leak no
1860
+ * consumer would notice.
1861
+ */
1862
+ declare const REDACT_COMMON_FIELDS: readonly string[];
1822
1863
  /**
1823
1864
  * The legacy `fast-redact` path expansion of {@link REDACT_COMMON_FIELDS}:
1824
1865
  * every name at the record root plus wildcard depths 1–4, followed by the
@@ -1837,6 +1878,17 @@ declare const LOG_CONTEXT_TOKEN: unique symbol;
1837
1878
  */
1838
1879
  declare const DEFAULT_REDACT_PATHS: readonly string[];
1839
1880
 
1881
+ /**
1882
+ * Check whether a trace ID is a valid, non-zero W3C Trace Context value.
1883
+ *
1884
+ * Enforces 32 lowercase hex characters (rejecting non-hex input that could
1885
+ * carry log-injection payloads) and rejects the all-zeros "no trace" sentinel.
1886
+ *
1887
+ * @param traceId - The candidate trace ID.
1888
+ * @returns `true` when the ID is 32 lowercase hex chars and not all zeros.
1889
+ */
1890
+ declare function isValidTraceId(traceId: string): boolean;
1891
+
1840
1892
  /**
1841
1893
  * Pino numeric level mapping. Frozen at runtime so destinations and the
1842
1894
  * NestJS bridge can rely on a stable wire-format contract.
@@ -1944,4 +1996,5 @@ interface LogEntry {
1944
1996
  */
1945
1997
  declare const LOG_KEYS_CONVENTION_REGEX: RegExp;
1946
1998
 
1947
- export { BymaxLoggerModule, type BymaxLoggerModuleAsyncOptions, type BymaxLoggerModuleOptions, type BymaxLoggerModuleOptionsFactory, DEFAULT_REDACT_PATHS, DefaultStdoutDestination, type EmittedDeploymentResource, type EmittedServiceResource, HttpAccessLogMiddleware, HttpExceptionFilter, HttpLoggingInterceptor, type HttpOptions, type ILogDestination, type IUnexpectedErrorSink, type IncomingHeaders, InjectLogger, LOGGER_DESTINATIONS_TOKEN, LOGGER_OPTIONS_TOKEN, LOGGER_PINO_INSTANCE_TOKEN, LOG_CONTEXT_METADATA_KEY, LOG_CONTEXT_TOKEN, LOG_KEYS_CONVENTION_REGEX, LogContext, type LogContext$1 as LogContextBag, LogContextService, type LogEntry, type LogLevel, LogPerformance, type LoggableRequest, type LoggableResponse, type NextHandler, type OtelOptions, PINO_LEVEL_NAMES, PINO_LEVEL_NUMBERS, PinoLoggerService, PinoUnexpectedErrorSink, PrettyDevDestination, type PrettyViewOptions, RESERVED_LOG_KEYS, RESERVED_LOG_KEYS_NOT_EMITTED, RequestIdMiddleware, type ReservedLogKey, type ResolvedBymaxLoggerModuleOptions, type ResolvedServiceMetadata, type ServiceMetadata, type UnexpectedErrorReport, applyAccessLog, applyRequestIdMiddleware };
1999
+ export { BymaxLoggerModule, DEFAULT_REDACT_PATHS, DefaultStdoutDestination, HttpAccessLogMiddleware, HttpExceptionFilter, HttpLoggingInterceptor, InjectLogger, LOGGER_DESTINATIONS_TOKEN, LOGGER_OPTIONS_TOKEN, LOGGER_PINO_INSTANCE_TOKEN, LOGGER_REDACTOR_TOKEN, LOG_CONTEXT_METADATA_KEY, LOG_CONTEXT_TOKEN, LOG_KEYS_CONVENTION_REGEX, LogContext, LogContextService, LogPerformance, PINO_LEVEL_NAMES, PINO_LEVEL_NUMBERS, PinoLoggerService, PinoUnexpectedErrorSink, PrettyDevDestination, REDACT_COMMON_FIELDS, RESERVED_LOG_KEYS, RESERVED_LOG_KEYS_NOT_EMITTED, RequestIdMiddleware, applyAccessLog, applyRequestIdMiddleware, isValidTraceId };
2000
+ export type { BymaxLoggerModuleAsyncOptions, BymaxLoggerModuleOptions, BymaxLoggerModuleOptionsFactory, EmittedDeploymentResource, EmittedServiceResource, HttpOptions, ILogDestination, IUnexpectedErrorSink, IncomingHeaders, LogContext$1 as LogContextBag, LogEntry, LogLevel, LoggableRequest, LoggableResponse, NextHandler, OtelOptions, PrettyViewOptions, ReservedLogKey, ResolvedBymaxLoggerModuleOptions, ResolvedServiceMetadata, ServiceMetadata, UnexpectedErrorReport };
@@ -52,7 +52,9 @@ interface ILogDestination {
52
52
  readonly name: string;
53
53
  /**
54
54
  * Minimum log level this destination accepts. Entries below this level
55
- * are not written. Undefined means "accept everything".
55
+ * are not written. The effective level is the STRICTER of this and the module
56
+ * `level`, so it can only narrow what the logger emits, never widen it.
57
+ * Undefined means "every entry the module level lets through".
56
58
  */
57
59
  readonly minLevel?: LogLevel;
58
60
  /**
@@ -64,8 +66,13 @@ interface ILogDestination {
64
66
  * - Buffer and flush periodically (e.g., HTTP batching)
65
67
  * - Return a Promise for async I/O
66
68
  *
67
- * Errors thrown here are caught by the library; the destination may be
68
- * temporarily skipped if it consistently fails.
69
+ * Errors thrown here, and rejections of a returned promise, are caught by the
70
+ * library: the entry is dropped for this destination only, and one failure line
71
+ * is written to stderr per failed entry. There is no retry and no circuit
72
+ * breaker, so a destination that initialized and keeps failing keeps receiving
73
+ * entries and keeps being reported; one that needs to back off does so itself.
74
+ * (A destination whose `onInit()` failed is a different case: it receives no
75
+ * entries at all.)
69
76
  *
70
77
  * @param payload — Newline-terminated JSON entry, UTF-8 encoded.
71
78
  */
@@ -250,30 +257,35 @@ interface EmittedDeploymentResource {
250
257
  * Every field is optional; defaults are applied by `applyDefaults`.
251
258
  */
252
259
  interface HttpOptions {
253
- /** Mount the HTTP interceptor + filter. Default: false. */
260
+ /**
261
+ * Mount the HTTP interceptor + filter. Default: false. The filter is registered by
262
+ * `forRoot` only: `forRootAsync` registers the interceptor and leaves the filter to
263
+ * the consumer (`{ provide: APP_FILTER, useClass: HttpExceptionFilter }`).
264
+ */
254
265
  isEnabled?: boolean;
255
- /** Capture unhandled HTTP exceptions and emit `HTTP_EXCEPTION_UNHANDLED`. Default: true. */
266
+ /**
267
+ * Capture unhandled HTTP exceptions and emit `HTTP_EXCEPTION_UNHANDLED`. Default: true.
268
+ * Read by `forRoot` only; it has no effect on `forRootAsync`, which never registers the
269
+ * filter.
270
+ */
256
271
  shouldCaptureExceptions?: boolean;
257
272
  /** Generate `requestId` when the request header is absent. Default: true. */
258
273
  shouldGenerateRequestId?: boolean;
259
274
  /**
260
275
  * Paths that bypass HTTP logging (health checks, metrics). Defaults:
261
- * `/health`, `/metrics`.
262
- *
263
- * The defaults do NOT cover `@bymax-one/nest-core`, and that is the common case
264
- * worth knowing before you rely on them. It serves liveness and readiness at
265
- * `/health/live` and `/health/ready`, and no bare `/health` at all, so
266
- * `/^\/health$/` excludes a route that does not exist while both probes are
267
- * logged. Measured on one deployment: 8272 of 8274 HTTP entries were the
268
- * liveness probe. Set `excludePaths` yourself when wiring the two together —
269
- * `[/^\/health\/(live|ready)$/, /^\/metrics$/]` matches the stock layout.
270
- *
271
- * The defaults are not widened to include those subpaths because both prefixes
272
- * are CONFIGURABLE there (`DEFAULT_HEALTH_PATH`, `DEFAULT_METRICS_PATH`): any
273
- * default naming specific subpaths would be less wrong rather than correct, and
274
- * a prefix pattern would silently swallow a consumer's own `/health/*` route
275
- * that they did want logged. Excluding a path is a decision about which requests
276
- * disappear from the record, so it stays explicit.
276
+ * `/health`, `/health/live`, `/health/ready`, `/metrics`.
277
+ *
278
+ * `/health/live` and `/health/ready` are the liveness and readiness probes of
279
+ * `@bymax-one/nest-core`, which serves no bare `/health`; left logged, a probe hit
280
+ * every few seconds drowns the record (measured on one deployment: 8272 of 8274
281
+ * HTTP entries were the liveness probe). Each default is an exact, anchored path,
282
+ * not a prefix, so a consumer's own `/health/other` route is still logged.
283
+ *
284
+ * The prefixes are CONFIGURABLE in nest-core (`DEFAULT_HEALTH_PATH`,
285
+ * `DEFAULT_METRICS_PATH`): when they are changed, or when the probes should be
286
+ * logged, set `excludePaths` yourself. A value REPLACES the defaults rather than
287
+ * adding to them; `[/^\/health$/, /^\/metrics$/]` excludes only `/health` and
288
+ * `/metrics`.
277
289
  *
278
290
  * SECURITY: each pattern is `.test()`-ed against the (attacker-controllable)
279
291
  * request path on every request. Supply only anchored, linear-time regexes
@@ -1171,6 +1183,8 @@ declare class PrettyDevDestination implements ILogDestination {
1171
1183
  * Lazily resolve `pino-pretty` and build the colorized transform that renders
1172
1184
  * each entry to `process.stdout`.
1173
1185
  *
1186
+ * @throws Error When `NODE_ENV` is `production`: this sink is development
1187
+ * output, so it refuses rather than put ANSI colour into the log stream.
1174
1188
  * @throws Error When `pino-pretty` is not installed — with an actionable
1175
1189
  * message telling the consumer to install it or drop this destination.
1176
1190
  */
@@ -1773,6 +1787,11 @@ declare const LOG_CONTEXT_METADATA_KEY = "bymax_logger:log_context";
1773
1787
  * Set the log-context label for a class. All loggers injected within the class
1774
1788
  * are intended to adopt this label (auto-application requires a wired interceptor).
1775
1789
  *
1790
+ * @deprecated Nothing in this package reads the metadata, so the label is never
1791
+ * applied: no interceptor is wired. Inject a logger bound to the label with
1792
+ * `@InjectLogger(PaymentsService.name)` instead. Do not call `setContext()` on
1793
+ * the shared logger — it is a singleton, so that overwrites the label for every
1794
+ * other holder.
1776
1795
  * @param name - The context label (typically the class name).
1777
1796
  * @returns A NestJS metadata decorator carrying the label.
1778
1797
  * @example
@@ -1816,9 +1835,31 @@ declare const LOGGER_OPTIONS_TOKEN: unique symbol;
1816
1835
  declare const LOGGER_PINO_INSTANCE_TOKEN: unique symbol;
1817
1836
  /** Injection token for the resolved destinations array (read-only at runtime). */
1818
1837
  declare const LOGGER_DESTINATIONS_TOKEN: unique symbol;
1838
+ /**
1839
+ * Injection token for the redaction function backing the DEFAULT PII coverage.
1840
+ *
1841
+ * Exposed as its own provider because `PinoLoggerService.child()` has to apply
1842
+ * it BEFORE handing bindings to Pino: child bindings are pre-serialized into the
1843
+ * instance's `chindings` fragment and never reach `formatters.log`, so the
1844
+ * factory's hook cannot see them.
1845
+ */
1846
+ declare const LOGGER_REDACTOR_TOKEN: unique symbol;
1819
1847
  /** Injection token for the per-request AsyncLocalStorage-backed log context. */
1820
1848
  declare const LOG_CONTEXT_TOKEN: unique symbol;
1821
1849
 
1850
+ /**
1851
+ * The canonical sensitive-field-name set.
1852
+ *
1853
+ * Under the default `redactStrategy: 'names'` this list IS the redaction
1854
+ * contract: a value is censored when its key name appears here, at any depth.
1855
+ * Under the legacy `'paths'` strategy the same names are expanded to depths 1–4
1856
+ * by {@link depth} to build {@link DEFAULT_REDACT_PATHS}.
1857
+ *
1858
+ * Append-only: a name may be added in a minor release; removing one requires a
1859
+ * major, because a field that silently stopped being redacted is a leak no
1860
+ * consumer would notice.
1861
+ */
1862
+ declare const REDACT_COMMON_FIELDS: readonly string[];
1822
1863
  /**
1823
1864
  * The legacy `fast-redact` path expansion of {@link REDACT_COMMON_FIELDS}:
1824
1865
  * every name at the record root plus wildcard depths 1–4, followed by the
@@ -1837,6 +1878,17 @@ declare const LOG_CONTEXT_TOKEN: unique symbol;
1837
1878
  */
1838
1879
  declare const DEFAULT_REDACT_PATHS: readonly string[];
1839
1880
 
1881
+ /**
1882
+ * Check whether a trace ID is a valid, non-zero W3C Trace Context value.
1883
+ *
1884
+ * Enforces 32 lowercase hex characters (rejecting non-hex input that could
1885
+ * carry log-injection payloads) and rejects the all-zeros "no trace" sentinel.
1886
+ *
1887
+ * @param traceId - The candidate trace ID.
1888
+ * @returns `true` when the ID is 32 lowercase hex chars and not all zeros.
1889
+ */
1890
+ declare function isValidTraceId(traceId: string): boolean;
1891
+
1840
1892
  /**
1841
1893
  * Pino numeric level mapping. Frozen at runtime so destinations and the
1842
1894
  * NestJS bridge can rely on a stable wire-format contract.
@@ -1944,4 +1996,5 @@ interface LogEntry {
1944
1996
  */
1945
1997
  declare const LOG_KEYS_CONVENTION_REGEX: RegExp;
1946
1998
 
1947
- export { BymaxLoggerModule, type BymaxLoggerModuleAsyncOptions, type BymaxLoggerModuleOptions, type BymaxLoggerModuleOptionsFactory, DEFAULT_REDACT_PATHS, DefaultStdoutDestination, type EmittedDeploymentResource, type EmittedServiceResource, HttpAccessLogMiddleware, HttpExceptionFilter, HttpLoggingInterceptor, type HttpOptions, type ILogDestination, type IUnexpectedErrorSink, type IncomingHeaders, InjectLogger, LOGGER_DESTINATIONS_TOKEN, LOGGER_OPTIONS_TOKEN, LOGGER_PINO_INSTANCE_TOKEN, LOG_CONTEXT_METADATA_KEY, LOG_CONTEXT_TOKEN, LOG_KEYS_CONVENTION_REGEX, LogContext, type LogContext$1 as LogContextBag, LogContextService, type LogEntry, type LogLevel, LogPerformance, type LoggableRequest, type LoggableResponse, type NextHandler, type OtelOptions, PINO_LEVEL_NAMES, PINO_LEVEL_NUMBERS, PinoLoggerService, PinoUnexpectedErrorSink, PrettyDevDestination, type PrettyViewOptions, RESERVED_LOG_KEYS, RESERVED_LOG_KEYS_NOT_EMITTED, RequestIdMiddleware, type ReservedLogKey, type ResolvedBymaxLoggerModuleOptions, type ResolvedServiceMetadata, type ServiceMetadata, type UnexpectedErrorReport, applyAccessLog, applyRequestIdMiddleware };
1999
+ export { BymaxLoggerModule, DEFAULT_REDACT_PATHS, DefaultStdoutDestination, HttpAccessLogMiddleware, HttpExceptionFilter, HttpLoggingInterceptor, InjectLogger, LOGGER_DESTINATIONS_TOKEN, LOGGER_OPTIONS_TOKEN, LOGGER_PINO_INSTANCE_TOKEN, LOGGER_REDACTOR_TOKEN, LOG_CONTEXT_METADATA_KEY, LOG_CONTEXT_TOKEN, LOG_KEYS_CONVENTION_REGEX, LogContext, LogContextService, LogPerformance, PINO_LEVEL_NAMES, PINO_LEVEL_NUMBERS, PinoLoggerService, PinoUnexpectedErrorSink, PrettyDevDestination, REDACT_COMMON_FIELDS, RESERVED_LOG_KEYS, RESERVED_LOG_KEYS_NOT_EMITTED, RequestIdMiddleware, applyAccessLog, applyRequestIdMiddleware, isValidTraceId };
2000
+ export type { BymaxLoggerModuleAsyncOptions, BymaxLoggerModuleOptions, BymaxLoggerModuleOptionsFactory, EmittedDeploymentResource, EmittedServiceResource, HttpOptions, ILogDestination, IUnexpectedErrorSink, IncomingHeaders, LogContext$1 as LogContextBag, LogEntry, LogLevel, LoggableRequest, LoggableResponse, NextHandler, OtelOptions, PrettyViewOptions, ReservedLogKey, ResolvedBymaxLoggerModuleOptions, ResolvedServiceMetadata, ServiceMetadata, UnexpectedErrorReport };