@bymax-one/nest-logger 1.2.0 → 1.2.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/CHANGELOG.md +160 -1
- package/README.md +238 -19
- package/dist/server/index.cjs +315 -40
- package/dist/server/index.d.cts +164 -14
- package/dist/server/index.d.ts +164 -14
- package/dist/server/index.mjs +327 -49
- package/dist/shared/index.d.cts +113 -3
- package/dist/shared/index.d.ts +113 -3
- package/package.json +1 -1
package/dist/server/index.cjs
CHANGED
|
@@ -65,6 +65,14 @@ function applyDefaults(options) {
|
|
|
65
65
|
shouldUseAsNestLogger: options.shouldUseAsNestLogger ?? true,
|
|
66
66
|
redactPaths: Object.freeze([...options.redactPaths ?? []]),
|
|
67
67
|
redactStrategy: options.redactStrategy ?? "names",
|
|
68
|
+
// `'nested'` keeps the shape every existing consumer already queries.
|
|
69
|
+
resourceFormat: options.resourceFormat ?? "nested",
|
|
70
|
+
// `??` rather than `||`: `false` is a meaningful value here (emit nothing)
|
|
71
|
+
// and `||` would silently replace it with the default.
|
|
72
|
+
eventNameField: options.eventNameField ?? "event.name",
|
|
73
|
+
// `'pino'` by default: the semconv attributes are additive value, but the
|
|
74
|
+
// legacy shape is what existing dashboards and alerts already query.
|
|
75
|
+
errorFormat: options.errorFormat ?? "pino",
|
|
68
76
|
redactCensor: options.redactCensor ?? "[REDACTED]",
|
|
69
77
|
shouldDisableDefaultRedact: options.shouldDisableDefaultRedact ?? false,
|
|
70
78
|
destinations: Object.freeze([...options.destinations ?? []]),
|
|
@@ -134,6 +142,27 @@ function validateOptions(options) {
|
|
|
134
142
|
)}`
|
|
135
143
|
);
|
|
136
144
|
}
|
|
145
|
+
if (options.resourceFormat !== void 0 && options.resourceFormat !== "nested" && options.resourceFormat !== "flat") {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`[BymaxLoggerModule] options.resourceFormat must be 'nested' or 'flat'. Got: ${String(
|
|
148
|
+
options.resourceFormat
|
|
149
|
+
)}`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (options.errorFormat !== void 0 && options.errorFormat !== "pino" && options.errorFormat !== "semconv" && options.errorFormat !== "both") {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`[BymaxLoggerModule] options.errorFormat must be 'pino', 'semconv' or 'both'. Got: ${String(
|
|
155
|
+
options.errorFormat
|
|
156
|
+
)}`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
if (options.eventNameField !== void 0 && options.eventNameField !== false && (typeof options.eventNameField !== "string" || options.eventNameField.length === 0)) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`[BymaxLoggerModule] options.eventNameField must be a non-empty string or false. Got: ${String(
|
|
162
|
+
options.eventNameField
|
|
163
|
+
)}`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
137
166
|
}
|
|
138
167
|
|
|
139
168
|
// src/server/constants/injection-tokens.constants.ts
|
|
@@ -183,6 +212,23 @@ var PROTOTYPE_POLLUTING_KEYS = /* @__PURE__ */ new Set([
|
|
|
183
212
|
"prototype"
|
|
184
213
|
]);
|
|
185
214
|
|
|
215
|
+
// src/server/utils/escape-log-text.util.ts
|
|
216
|
+
var LINE_TERMINATORS = /* @__PURE__ */ new Set(["\r", "\n", "\x85", "\u2028", "\u2029"]);
|
|
217
|
+
var UNSAFE_IN_SINGLE_LINE = /[\u0000-\u0008\u000A-\u001F\u007F-\u009F\u2028\u2029]/g;
|
|
218
|
+
var UNSAFE_IN_MULTILINE = /[\u0000-\u0008\u000B-\u001F\u007F-\u009F\u2028\u2029]/g;
|
|
219
|
+
function toEscapeSequence(char) {
|
|
220
|
+
return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
|
|
221
|
+
}
|
|
222
|
+
function toSingleLineMessage(text) {
|
|
223
|
+
return text.replace(
|
|
224
|
+
UNSAFE_IN_SINGLE_LINE,
|
|
225
|
+
(char) => LINE_TERMINATORS.has(char) ? "\\n" : toEscapeSequence(char)
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
function escapeControlCharacters(text) {
|
|
229
|
+
return text.replace(UNSAFE_IN_MULTILINE, toEscapeSequence);
|
|
230
|
+
}
|
|
231
|
+
|
|
186
232
|
// src/server/services/pino-logger.service.ts
|
|
187
233
|
var OWNED_PAYLOAD_KEYS = ["logKey", "userId", "context"];
|
|
188
234
|
function withoutOwnedKeys(metadata) {
|
|
@@ -206,7 +252,7 @@ function withoutOwnedKeys(metadata) {
|
|
|
206
252
|
}
|
|
207
253
|
function safeErrorMessage(error) {
|
|
208
254
|
try {
|
|
209
|
-
return String(error.message);
|
|
255
|
+
return toSingleLineMessage(String(error.message));
|
|
210
256
|
} catch {
|
|
211
257
|
return "Unreadable error message";
|
|
212
258
|
}
|
|
@@ -248,7 +294,7 @@ exports.PinoLoggerService = class PinoLoggerService {
|
|
|
248
294
|
*/
|
|
249
295
|
error(message, ...optionalParams) {
|
|
250
296
|
if (message instanceof Error) {
|
|
251
|
-
const payload2 = { err:
|
|
297
|
+
const payload2 = { err: message };
|
|
252
298
|
assignIfDefined(payload2, "context", this.resolveContext(optionalParams));
|
|
253
299
|
this.pino.error(payload2, safeErrorMessage(message));
|
|
254
300
|
return;
|
|
@@ -258,7 +304,10 @@ exports.PinoLoggerService = class PinoLoggerService {
|
|
|
258
304
|
const payload = {};
|
|
259
305
|
assignIfDefined(payload, "context", context);
|
|
260
306
|
assignIfDefined(payload, "stack", stack);
|
|
261
|
-
this.pino.error(
|
|
307
|
+
this.pino.error(
|
|
308
|
+
payload,
|
|
309
|
+
toSingleLineMessage(typeof message === "string" ? message : String(message))
|
|
310
|
+
);
|
|
262
311
|
}
|
|
263
312
|
/**
|
|
264
313
|
* NestJS `warn` (warn level).
|
|
@@ -335,7 +384,12 @@ exports.PinoLoggerService = class PinoLoggerService {
|
|
|
335
384
|
const payload = {
|
|
336
385
|
...withoutOwnedKeys(metadata),
|
|
337
386
|
logKey,
|
|
338
|
-
|
|
387
|
+
// The RAW value, not a pre-serialized copy. Pre-serializing was what let
|
|
388
|
+
// Pino's standard `err` serializer re-derive `type` from the resulting
|
|
389
|
+
// plain object's constructor and emit `"Object"` for every typed
|
|
390
|
+
// exception — and it discarded the cause chain on the way. The configured
|
|
391
|
+
// serializer owns the shape now.
|
|
392
|
+
err: error
|
|
339
393
|
};
|
|
340
394
|
assignIfDefined(payload, "userId", userId);
|
|
341
395
|
assignIfDefined(payload, "context", this.context);
|
|
@@ -387,23 +441,6 @@ exports.PinoLoggerService = class PinoLoggerService {
|
|
|
387
441
|
onApplicationShutdown() {
|
|
388
442
|
}
|
|
389
443
|
// ─── Private ──────────────────────────────────────────────────────────────
|
|
390
|
-
/**
|
|
391
|
-
* Serialize an Error into a plain, log-safe object matching Pino's built-in
|
|
392
|
-
* `err` serializer shape (`type`, `message`, `stack`).
|
|
393
|
-
*
|
|
394
|
-
* Reads `error.name` (NOT `error.constructor.name`) for `type`: native
|
|
395
|
-
* subclasses set `name` to their class name, and a sanitized plain-object error
|
|
396
|
-
* (from `sanitizeError`) carries the real class name on `name` — whereas its
|
|
397
|
-
* `constructor.name` would be the unhelpful `'Object'`, breaking `err.type`
|
|
398
|
-
* dashboards for every exception logged through `HttpExceptionFilter`.
|
|
399
|
-
*/
|
|
400
|
-
serializeError(error) {
|
|
401
|
-
try {
|
|
402
|
-
return { type: error.name, message: error.message, stack: error.stack };
|
|
403
|
-
} catch {
|
|
404
|
-
return { type: "SanitizeFailed", message: "Failed to read the thrown value" };
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
444
|
/** Resolve the context: last string param wins, else the instance context. */
|
|
408
445
|
resolveContext(optionalParams) {
|
|
409
446
|
const last = optionalParams[optionalParams.length - 1];
|
|
@@ -414,17 +451,18 @@ exports.PinoLoggerService = class PinoLoggerService {
|
|
|
414
451
|
const payload = { ...withoutOwnedKeys(metadata), logKey };
|
|
415
452
|
assignIfDefined(payload, "userId", userId);
|
|
416
453
|
assignIfDefined(payload, "context", this.context);
|
|
454
|
+
const line = toSingleLineMessage(message);
|
|
417
455
|
if (level === "info") {
|
|
418
|
-
this.pino.info(payload,
|
|
456
|
+
this.pino.info(payload, line);
|
|
419
457
|
} else {
|
|
420
|
-
this.pino.warn(payload,
|
|
458
|
+
this.pino.warn(payload, line);
|
|
421
459
|
}
|
|
422
460
|
}
|
|
423
461
|
/** Emit a NestJS-style variadic log, dispatching by level without computed indexing. */
|
|
424
462
|
emitNestStyle(level, message, optionalParams) {
|
|
425
463
|
const payload = {};
|
|
426
464
|
assignIfDefined(payload, "context", this.resolveContext(optionalParams));
|
|
427
|
-
const msg = typeof message === "string" ? message : String(message);
|
|
465
|
+
const msg = toSingleLineMessage(typeof message === "string" ? message : String(message));
|
|
428
466
|
switch (level) {
|
|
429
467
|
case "info":
|
|
430
468
|
this.pino.info(payload, msg);
|
|
@@ -519,9 +557,16 @@ function sanitizeError(err, options) {
|
|
|
519
557
|
const maxCauseDepth = DEFAULT_MAX_CAUSE_DEPTH;
|
|
520
558
|
return toSanitizedError(err, /* @__PURE__ */ new WeakSet(), 0, maxCauseDepth);
|
|
521
559
|
}
|
|
560
|
+
function isErrorLike(value) {
|
|
561
|
+
if (typeof value !== "object" || value === null) {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
const candidate = value;
|
|
565
|
+
return typeof candidate.name === "string" && typeof candidate.message === "string";
|
|
566
|
+
}
|
|
522
567
|
function toSanitizedError(value, seen, depth2, maxDepth) {
|
|
523
568
|
try {
|
|
524
|
-
if (!(value
|
|
569
|
+
if (!isErrorLike(value)) {
|
|
525
570
|
return { name: "UnknownError", message: String(value) };
|
|
526
571
|
}
|
|
527
572
|
seen.add(value);
|
|
@@ -559,7 +604,9 @@ function readAggregatedErrors(value) {
|
|
|
559
604
|
return Array.isArray(candidate) ? candidate : void 0;
|
|
560
605
|
}
|
|
561
606
|
function scrubStack(stack) {
|
|
562
|
-
return
|
|
607
|
+
return escapeControlCharacters(
|
|
608
|
+
stack.split("\n").filter((line) => !line.includes("node_modules")).join("\n")
|
|
609
|
+
);
|
|
563
610
|
}
|
|
564
611
|
function isObject(value) {
|
|
565
612
|
return typeof value === "object" && value !== null;
|
|
@@ -923,15 +970,18 @@ var DEFAULT_REDACT_PATHS = [
|
|
|
923
970
|
...REDACT_COMMON_FIELDS.flatMap((field) => depth(field)),
|
|
924
971
|
...REDACT_ABSOLUTE_PATHS
|
|
925
972
|
];
|
|
926
|
-
function
|
|
973
|
+
function loadTraceApiFrom(anchor) {
|
|
927
974
|
try {
|
|
928
|
-
const
|
|
929
|
-
const mod =
|
|
975
|
+
const requireFrom = module$1.createRequire(anchor());
|
|
976
|
+
const mod = requireFrom("@opentelemetry/api");
|
|
930
977
|
return mod.trace;
|
|
931
978
|
} catch {
|
|
932
979
|
return void 0;
|
|
933
980
|
}
|
|
934
981
|
}
|
|
982
|
+
function detectOtelTraceApi() {
|
|
983
|
+
return loadTraceApiFrom(() => __filename) ?? loadTraceApiFrom(() => path.join(process.cwd(), "noop.cjs"));
|
|
984
|
+
}
|
|
935
985
|
function isValidTraceId(traceId) {
|
|
936
986
|
return /^[0-9a-f]{32}$/.test(traceId) && !/^0+$/.test(traceId);
|
|
937
987
|
}
|
|
@@ -1127,6 +1177,127 @@ function createNameRedactor(fieldNames, censor) {
|
|
|
1127
1177
|
};
|
|
1128
1178
|
}
|
|
1129
1179
|
|
|
1180
|
+
// src/server/utils/resolve-resource.util.ts
|
|
1181
|
+
var ATTR_NAMESPACE = "service.namespace";
|
|
1182
|
+
var ATTR_VERSION = "service.version";
|
|
1183
|
+
var ATTR_NAME = "service.name";
|
|
1184
|
+
var ATTR_INSTANCE_ID = "service.instance.id";
|
|
1185
|
+
var ATTR_ENVIRONMENT = "deployment.environment.name";
|
|
1186
|
+
function parseResourceAttributes(raw) {
|
|
1187
|
+
const parsed = {};
|
|
1188
|
+
if (raw === void 0) {
|
|
1189
|
+
return parsed;
|
|
1190
|
+
}
|
|
1191
|
+
for (const pair of raw.split(",")) {
|
|
1192
|
+
const separator = pair.indexOf("=");
|
|
1193
|
+
if (separator <= 0) {
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
const key = pair.slice(0, separator).trim();
|
|
1197
|
+
const rawValue = pair.slice(separator + 1).trim();
|
|
1198
|
+
if (rawValue.length === 0) {
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
let value;
|
|
1202
|
+
try {
|
|
1203
|
+
value = decodeURIComponent(rawValue);
|
|
1204
|
+
} catch {
|
|
1205
|
+
value = rawValue;
|
|
1206
|
+
}
|
|
1207
|
+
Object.defineProperty(parsed, key, { value, enumerable: true, configurable: true });
|
|
1208
|
+
}
|
|
1209
|
+
return parsed;
|
|
1210
|
+
}
|
|
1211
|
+
function attribute(attributes, key) {
|
|
1212
|
+
const value = Object.hasOwn(attributes, key) ? Reflect.get(attributes, key) : void 0;
|
|
1213
|
+
return typeof value === "string" ? value : void 0;
|
|
1214
|
+
}
|
|
1215
|
+
function firstNonEmpty(...candidates) {
|
|
1216
|
+
const found = candidates.find(
|
|
1217
|
+
(candidate) => typeof candidate === "string" && candidate.length > 0
|
|
1218
|
+
);
|
|
1219
|
+
return found;
|
|
1220
|
+
}
|
|
1221
|
+
function resolveServiceMetadata(service, env = process.env) {
|
|
1222
|
+
const attributes = parseResourceAttributes(env["OTEL_RESOURCE_ATTRIBUTES"]);
|
|
1223
|
+
const resolved = {
|
|
1224
|
+
// The spec's precedence: OTEL_SERVICE_NAME beats service.name in the
|
|
1225
|
+
// attribute list. Explicit configuration still beats both.
|
|
1226
|
+
name: firstNonEmpty(service.name, env["OTEL_SERVICE_NAME"], attribute(attributes, ATTR_NAME)) ?? "",
|
|
1227
|
+
version: firstNonEmpty(service.version, attribute(attributes, ATTR_VERSION)) ?? ""
|
|
1228
|
+
};
|
|
1229
|
+
const namespace = firstNonEmpty(service.namespace, attribute(attributes, ATTR_NAMESPACE));
|
|
1230
|
+
if (namespace !== void 0) {
|
|
1231
|
+
resolved.namespace = namespace;
|
|
1232
|
+
}
|
|
1233
|
+
const instanceId = firstNonEmpty(service.instanceId, attribute(attributes, ATTR_INSTANCE_ID));
|
|
1234
|
+
if (instanceId !== void 0) {
|
|
1235
|
+
resolved.instanceId = instanceId;
|
|
1236
|
+
}
|
|
1237
|
+
const environment = firstNonEmpty(
|
|
1238
|
+
service.environment,
|
|
1239
|
+
attribute(attributes, ATTR_ENVIRONMENT),
|
|
1240
|
+
env["NODE_ENV"]
|
|
1241
|
+
);
|
|
1242
|
+
if (environment !== void 0) {
|
|
1243
|
+
resolved.environment = environment;
|
|
1244
|
+
}
|
|
1245
|
+
return resolved;
|
|
1246
|
+
}
|
|
1247
|
+
function buildResourceBindings(resolved, format, extras = {}) {
|
|
1248
|
+
if (format === "flat") {
|
|
1249
|
+
const flat = {
|
|
1250
|
+
[ATTR_NAME]: resolved.name,
|
|
1251
|
+
[ATTR_VERSION]: resolved.version
|
|
1252
|
+
};
|
|
1253
|
+
for (const [key, value] of Object.entries(extras)) {
|
|
1254
|
+
Reflect.set(flat, `service.${key}`, value);
|
|
1255
|
+
}
|
|
1256
|
+
if (resolved.namespace !== void 0) {
|
|
1257
|
+
Reflect.set(flat, ATTR_NAMESPACE, resolved.namespace);
|
|
1258
|
+
}
|
|
1259
|
+
if (resolved.instanceId !== void 0) {
|
|
1260
|
+
Reflect.set(flat, ATTR_INSTANCE_ID, resolved.instanceId);
|
|
1261
|
+
}
|
|
1262
|
+
if (resolved.environment !== void 0) {
|
|
1263
|
+
Reflect.set(flat, ATTR_ENVIRONMENT, resolved.environment);
|
|
1264
|
+
}
|
|
1265
|
+
return flat;
|
|
1266
|
+
}
|
|
1267
|
+
const service = {
|
|
1268
|
+
...extras,
|
|
1269
|
+
name: resolved.name,
|
|
1270
|
+
version: resolved.version
|
|
1271
|
+
};
|
|
1272
|
+
if (resolved.namespace !== void 0) {
|
|
1273
|
+
service["namespace"] = resolved.namespace;
|
|
1274
|
+
}
|
|
1275
|
+
if (resolved.instanceId !== void 0) {
|
|
1276
|
+
service["instance"] = { id: resolved.instanceId };
|
|
1277
|
+
}
|
|
1278
|
+
const bindings = { service };
|
|
1279
|
+
if (resolved.environment !== void 0) {
|
|
1280
|
+
bindings["deployment"] = { environment: { name: resolved.environment } };
|
|
1281
|
+
}
|
|
1282
|
+
return bindings;
|
|
1283
|
+
}
|
|
1284
|
+
var OWNED_SERVICE_KEYS = /* @__PURE__ */ new Set([
|
|
1285
|
+
"name",
|
|
1286
|
+
"version",
|
|
1287
|
+
"namespace",
|
|
1288
|
+
"instanceId",
|
|
1289
|
+
"environment"
|
|
1290
|
+
]);
|
|
1291
|
+
function extraServiceFields(service) {
|
|
1292
|
+
const extras = {};
|
|
1293
|
+
for (const [key, value] of Object.entries({ ...service })) {
|
|
1294
|
+
if (!OWNED_SERVICE_KEYS.has(key)) {
|
|
1295
|
+
Reflect.set(extras, key, value);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
return extras;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1130
1301
|
// src/server/utils/truncate-large-entries.ts
|
|
1131
1302
|
var PREVIEW_LENGTH = 200;
|
|
1132
1303
|
function createSizeBoundedSerializer(baseSerializer, maxBytes) {
|
|
@@ -1168,6 +1339,85 @@ function leafNameOf(path) {
|
|
|
1168
1339
|
}
|
|
1169
1340
|
return leaf;
|
|
1170
1341
|
}
|
|
1342
|
+
var DERIVED_ERROR_FIELDS = /* @__PURE__ */ new Set([
|
|
1343
|
+
"name",
|
|
1344
|
+
"message",
|
|
1345
|
+
"stack",
|
|
1346
|
+
"cause",
|
|
1347
|
+
"errors"
|
|
1348
|
+
]);
|
|
1349
|
+
function serializeErrorValue(value_) {
|
|
1350
|
+
const sanitized = sanitizeError(value_);
|
|
1351
|
+
const serialized = {
|
|
1352
|
+
// `type` rather than `name`: the field consumers already query.
|
|
1353
|
+
type: sanitized.name,
|
|
1354
|
+
message: sanitized.message
|
|
1355
|
+
};
|
|
1356
|
+
if (sanitized.stack !== void 0) {
|
|
1357
|
+
serialized["stack"] = sanitized.stack;
|
|
1358
|
+
}
|
|
1359
|
+
if (sanitized.cause !== void 0) {
|
|
1360
|
+
serialized["cause"] = sanitized.cause;
|
|
1361
|
+
}
|
|
1362
|
+
if (sanitized.errors !== void 0) {
|
|
1363
|
+
serialized["errors"] = sanitized.errors;
|
|
1364
|
+
}
|
|
1365
|
+
try {
|
|
1366
|
+
const source = (
|
|
1367
|
+
// Stryker disable next-line ConditionalExpression: equivalent BY CONSTRUCTION, not untested — the never-throw catch below makes the mutant observationally identical: with the null check flipped to true, `Object.entries(null)` throws, the catch swallows it, and the emitted envelope is byte-for-byte the same. The guard stays because routing the ordinary null case through exception control flow would trade an explicit branch for a hidden one.
|
|
1368
|
+
typeof value_ === "object" && value_ !== null && !Array.isArray(value_) ? value_ : {}
|
|
1369
|
+
);
|
|
1370
|
+
for (const [key, value] of Object.entries(source)) {
|
|
1371
|
+
if (!Object.hasOwn(serialized, key) && !DERIVED_ERROR_FIELDS.has(key)) {
|
|
1372
|
+
Reflect.set(serialized, key, value);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
} catch {
|
|
1376
|
+
}
|
|
1377
|
+
return serialized;
|
|
1378
|
+
}
|
|
1379
|
+
function withSemconvException(record, format) {
|
|
1380
|
+
if (format === "pino") {
|
|
1381
|
+
return record;
|
|
1382
|
+
}
|
|
1383
|
+
try {
|
|
1384
|
+
const candidate = record["err"];
|
|
1385
|
+
if (typeof candidate?.name === "string") {
|
|
1386
|
+
record["exception.type"] = candidate.name;
|
|
1387
|
+
record["error.type"] = candidate.name;
|
|
1388
|
+
}
|
|
1389
|
+
if (typeof candidate?.message === "string") {
|
|
1390
|
+
record["exception.message"] = candidate.message;
|
|
1391
|
+
}
|
|
1392
|
+
if (typeof candidate?.stack === "string") {
|
|
1393
|
+
record["exception.stacktrace"] = scrubStack(candidate.stack);
|
|
1394
|
+
}
|
|
1395
|
+
if (format === "semconv") {
|
|
1396
|
+
const withoutErr = {};
|
|
1397
|
+
for (const [key, value] of Object.entries(record)) {
|
|
1398
|
+
if (key !== "err") {
|
|
1399
|
+
Reflect.set(withoutErr, key, value);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
return withoutErr;
|
|
1403
|
+
}
|
|
1404
|
+
} catch {
|
|
1405
|
+
}
|
|
1406
|
+
return record;
|
|
1407
|
+
}
|
|
1408
|
+
function toEventName(logKey) {
|
|
1409
|
+
return logKey.toLowerCase().replaceAll("_", ".");
|
|
1410
|
+
}
|
|
1411
|
+
function withEventName(record, field) {
|
|
1412
|
+
if (field === false) {
|
|
1413
|
+
return record;
|
|
1414
|
+
}
|
|
1415
|
+
const logKey = record["logKey"];
|
|
1416
|
+
if (typeof logKey === "string" && logKey.length > 0) {
|
|
1417
|
+
Reflect.set(record, field, toEventName(logKey));
|
|
1418
|
+
}
|
|
1419
|
+
return record;
|
|
1420
|
+
}
|
|
1171
1421
|
var PASS_THROUGH = (value) => value;
|
|
1172
1422
|
function resolveNameRedactor(options) {
|
|
1173
1423
|
if (options.redactStrategy !== "names") {
|
|
@@ -1194,7 +1444,7 @@ function buildPinoInstance(options, logContext, destinations) {
|
|
|
1194
1444
|
const redact = resolveNameRedactor(options);
|
|
1195
1445
|
const bound = (serializer) => createSizeBoundedSerializer((input) => redact(serializer(input)), maxBytes);
|
|
1196
1446
|
const serializers = {
|
|
1197
|
-
err: bound(
|
|
1447
|
+
err: bound(serializeErrorValue),
|
|
1198
1448
|
...Object.fromEntries(
|
|
1199
1449
|
Object.entries(options.serializers).map(
|
|
1200
1450
|
([key, serializer]) => [key, bound(serializer)]
|
|
@@ -1205,7 +1455,14 @@ function buildPinoInstance(options, logContext, destinations) {
|
|
|
1205
1455
|
const pinoOptions = {
|
|
1206
1456
|
level: options.level,
|
|
1207
1457
|
...redactOption === void 0 ? {} : { redact: redactOption },
|
|
1208
|
-
|
|
1458
|
+
// Resource identity, resolved ONCE here rather than per entry: Pino
|
|
1459
|
+
// pre-serializes `base` into the instance's chindings, so the cost of the
|
|
1460
|
+
// extra attributes is one string built at construction, not work per log.
|
|
1461
|
+
base: buildResourceBindings(
|
|
1462
|
+
resolveServiceMetadata(options.service),
|
|
1463
|
+
options.resourceFormat,
|
|
1464
|
+
extraServiceFields(options.service)
|
|
1465
|
+
),
|
|
1209
1466
|
// Pino requires the timestamp fn to emit the `,"time":"..."` fragment.
|
|
1210
1467
|
timestamp: () => `,"time":"${options.timestamp()}"`,
|
|
1211
1468
|
formatters: {
|
|
@@ -1228,7 +1485,10 @@ function buildPinoInstance(options, logContext, destinations) {
|
|
|
1228
1485
|
// `base` and child bindings are NOT visible at this hook — they are
|
|
1229
1486
|
// library-owned (`service`, the `@InjectLogger` context) and carry nothing
|
|
1230
1487
|
// sensitive, which `pino-factory.spec.ts` pins.
|
|
1231
|
-
log: (record) =>
|
|
1488
|
+
log: (record) => withSemconvException(
|
|
1489
|
+
withEventName(redact(record, true), options.eventNameField),
|
|
1490
|
+
options.errorFormat
|
|
1491
|
+
)
|
|
1232
1492
|
},
|
|
1233
1493
|
serializers,
|
|
1234
1494
|
mixin: createTraceContextMixin(logContext, options.otel),
|
|
@@ -1327,6 +1587,14 @@ var DestinationRegistry = class {
|
|
|
1327
1587
|
}
|
|
1328
1588
|
);
|
|
1329
1589
|
}
|
|
1590
|
+
if (this.options.otel.shouldAutoInjectTraceContext && detectOtelTraceApi() === void 0) {
|
|
1591
|
+
this.logger.warnStructured(
|
|
1592
|
+
RESERVED_LOG_KEYS.LOGGER_BOOTSTRAP_WARNING,
|
|
1593
|
+
"Trace-context auto-injection is enabled but @opentelemetry/api could not be resolved \u2014 traceId and spanId will be absent from every entry",
|
|
1594
|
+
void 0,
|
|
1595
|
+
{ reason: "OTEL_API_UNAVAILABLE", shouldAutoInjectTraceContext: true }
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1330
1598
|
}
|
|
1331
1599
|
/**
|
|
1332
1600
|
* Flush and close active destinations in REVERSE registration order, so a
|
|
@@ -1705,8 +1973,9 @@ var HttpAccessLogMiddleware = class {
|
|
|
1705
1973
|
* `emitDecoratorMetadata`, so implicit class-type injection would not resolve.
|
|
1706
1974
|
* @param options - Resolved module options (read for `http.excludePaths`).
|
|
1707
1975
|
*/
|
|
1708
|
-
constructor(logger, options) {
|
|
1976
|
+
constructor(logger, logContext, options) {
|
|
1709
1977
|
this.logger = logger;
|
|
1978
|
+
this.logContext = logContext;
|
|
1710
1979
|
this.isEnabled = options.http.isEnabled;
|
|
1711
1980
|
this.excludePaths = options.http.excludePaths;
|
|
1712
1981
|
}
|
|
@@ -1745,12 +2014,17 @@ var HttpAccessLogMiddleware = class {
|
|
|
1745
2014
|
ip: req.ip,
|
|
1746
2015
|
userAgent: readUserAgent(req)
|
|
1747
2016
|
});
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
2017
|
+
const emitTerminal = () => {
|
|
2018
|
+
this.logTerminal(req, res, method, url, Date.now() - start);
|
|
2019
|
+
};
|
|
2020
|
+
const bound = async_hooks.AsyncResource.bind(emitTerminal);
|
|
2021
|
+
res.on("close", () => {
|
|
2022
|
+
if (this.logContext.getStore() !== void 0) {
|
|
2023
|
+
emitTerminal();
|
|
2024
|
+
} else {
|
|
2025
|
+
bound();
|
|
2026
|
+
}
|
|
2027
|
+
});
|
|
1754
2028
|
next();
|
|
1755
2029
|
}
|
|
1756
2030
|
/**
|
|
@@ -1809,7 +2083,8 @@ var HttpAccessLogMiddleware = class {
|
|
|
1809
2083
|
HttpAccessLogMiddleware = __decorateClass([
|
|
1810
2084
|
common.Injectable(),
|
|
1811
2085
|
__decorateParam(0, common.Inject(exports.PinoLoggerService)),
|
|
1812
|
-
__decorateParam(1, common.Inject(
|
|
2086
|
+
__decorateParam(1, common.Inject(exports.LogContextService)),
|
|
2087
|
+
__decorateParam(2, common.Inject(LOGGER_OPTIONS_TOKEN))
|
|
1813
2088
|
], HttpAccessLogMiddleware);
|
|
1814
2089
|
var REQUEST_ID_HEADER = "x-request-id";
|
|
1815
2090
|
var MAX_CORRELATION_ID_LENGTH = 256;
|