@webpieces/winston 0.4.402 → 0.4.403

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/winston",
3
- "version": "0.4.402",
3
+ "version": "0.4.403",
4
4
  "description": "Node-only winston LoggerFactory backends for webpieces: Console (local pretty) + GCP (Cloud Run stdout JSON), auto-enriched with HeaderRegistry context keys",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -23,8 +23,8 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@webpieces/core-util": "0.4.402",
27
- "@webpieces/core-context": "0.4.402",
26
+ "@webpieces/core-util": "0.4.403",
27
+ "@webpieces/core-context": "0.4.403",
28
28
  "winston": "3.11.0",
29
29
  "logform": "2.7.0",
30
30
  "safe-stable-stringify": "2.5.0"
@@ -1,15 +1,23 @@
1
1
  import { WinstonFactoryBase } from './WinstonFactoryBase';
2
2
  /**
3
- * WinstonConsoleFactory - the LOCAL developer backend. Colorized single-line
4
- * pretty console output with the registered context keys as a bracketed prefix,
5
- * for human reading same enrichment as the GCP backend, different rendering.
6
- * Matches the tested onetablet/monorepo-nx1 local logger.
7
- *
8
- * The service name + version come from {@link ServiceInfo} (this constructor reads them), but
9
- * neither RENDERS locally: you already know which service you are running and can check git
10
- * yourself, so they would be noise on every line. They still ship to GCP via the sibling
3
+ * WinstonConsoleFactory - the LOCAL developer backend. Single-line, greppable pretty console output
4
+ * `[LEVEL][time][Controller.method][loggerName][ctx tags]: message` same enrichment as the GCP
5
+ * backend, different rendering, byte-identical to the bunyan console backend for the same record.
6
+ *
7
+ * PLAIN TEXT (no `format.colorize()`): the format is tuned for human reading AND `grep` (and AI
8
+ * reading), matching trytami's deliberate "no colors" choice ANSI escapes break greppability and
9
+ * bunyan's console has none, so color here would also break winston≡bunyan parity.
10
+ *
11
+ * `format.timestamp({format:'HH:mm:ss.SSS'})` supplies the `[time]` slot (`localPrettyFormat` reads
12
+ * `info.timestamp`); millisecond precision, no date (each line already lands in a dated file).
13
+ *
14
+ * `consoleFields`, when given, is the app-chosen ordered ALLOW-LIST of context keys to render locally
15
+ * (hides noise like `requestPath`); GCP still receives every logged key.
16
+ *
17
+ * The service name + version come from {@link ServiceInfo}, but neither RENDERS locally: you already
18
+ * know which service you are running and can check git yourself. They still ship to GCP via the sibling
11
19
  * {@link WinstonGcpFactory}. See LOCAL_STRUCTURAL_KEYS in ./format.
12
20
  */
13
21
  export declare class WinstonConsoleFactory extends WinstonFactoryBase {
14
- constructor();
22
+ constructor(consoleFields?: string[]);
15
23
  }
@@ -5,19 +5,27 @@ const winston_1 = require("winston");
5
5
  const WinstonFactoryBase_1 = require("./WinstonFactoryBase");
6
6
  const format_1 = require("./format");
7
7
  /**
8
- * WinstonConsoleFactory - the LOCAL developer backend. Colorized single-line
9
- * pretty console output with the registered context keys as a bracketed prefix,
10
- * for human reading same enrichment as the GCP backend, different rendering.
11
- * Matches the tested onetablet/monorepo-nx1 local logger.
8
+ * WinstonConsoleFactory - the LOCAL developer backend. Single-line, greppable pretty console output
9
+ * `[LEVEL][time][Controller.method][loggerName][ctx tags]: message` same enrichment as the GCP
10
+ * backend, different rendering, byte-identical to the bunyan console backend for the same record.
12
11
  *
13
- * The service name + version come from {@link ServiceInfo} (this constructor reads them), but
14
- * neither RENDERS locally: you already know which service you are running and can check git
15
- * yourself, so they would be noise on every line. They still ship to GCP via the sibling
12
+ * PLAIN TEXT (no `format.colorize()`): the format is tuned for human reading AND `grep` (and AI
13
+ * reading), matching trytami's deliberate "no colors" choice ANSI escapes break greppability and
14
+ * bunyan's console has none, so color here would also break winston≡bunyan parity.
15
+ *
16
+ * `format.timestamp({format:'HH:mm:ss.SSS'})` supplies the `[time]` slot (`localPrettyFormat` reads
17
+ * `info.timestamp`); millisecond precision, no date (each line already lands in a dated file).
18
+ *
19
+ * `consoleFields`, when given, is the app-chosen ordered ALLOW-LIST of context keys to render locally
20
+ * (hides noise like `requestPath`); GCP still receives every logged key.
21
+ *
22
+ * The service name + version come from {@link ServiceInfo}, but neither RENDERS locally: you already
23
+ * know which service you are running and can check git yourself. They still ship to GCP via the sibling
16
24
  * {@link WinstonGcpFactory}. See LOCAL_STRUCTURAL_KEYS in ./format.
17
25
  */
18
26
  class WinstonConsoleFactory extends WinstonFactoryBase_1.WinstonFactoryBase {
19
- constructor() {
20
- super(winston_1.format.combine((0, format_1.bigIntSafeFormat)(), (0, format_1.injectContextFormat)(), (0, format_1.severityFormat)(), winston_1.format.colorize(), (0, format_1.localPrettyFormat)()));
27
+ constructor(consoleFields) {
28
+ super(winston_1.format.combine((0, format_1.bigIntSafeFormat)(), (0, format_1.injectContextFormat)(), (0, format_1.severityFormat)(), winston_1.format.timestamp({ format: 'HH:mm:ss.SSS' }), (0, format_1.localPrettyFormat)(consoleFields)));
21
29
  }
22
30
  }
23
31
  exports.WinstonConsoleFactory = WinstonConsoleFactory;
@@ -1 +1 @@
1
- {"version":3,"file":"WinstonConsoleFactory.js","sourceRoot":"","sources":["../../../../../packages/logging/winston/src/WinstonConsoleFactory.ts"],"names":[],"mappings":";;;AAAA,qCAAiC;AACjC,6DAA0D;AAC1D,qCAAoG;AAEpG;;;;;;;;;;GAUG;AACH,MAAa,qBAAsB,SAAQ,uCAAkB;IACzD;QACI,KAAK,CACD,gBAAM,CAAC,OAAO,CACV,IAAA,yBAAgB,GAAE,EAClB,IAAA,4BAAmB,GAAE,EACrB,IAAA,uBAAc,GAAE,EAChB,gBAAM,CAAC,QAAQ,EAAE,EACjB,IAAA,0BAAiB,GAAE,CACtB,CACJ,CAAC;IACN,CAAC;CACJ;AAZD,sDAYC","sourcesContent":["import { format } from 'winston';\nimport { WinstonFactoryBase } from './WinstonFactoryBase';\nimport { bigIntSafeFormat, injectContextFormat, localPrettyFormat, severityFormat } from './format';\n\n/**\n * WinstonConsoleFactory - the LOCAL developer backend. Colorized single-line\n * pretty console output with the registered context keys as a bracketed prefix,\n * for human reading — same enrichment as the GCP backend, different rendering.\n * Matches the tested onetablet/monorepo-nx1 local logger.\n *\n * The service name + version come from {@link ServiceInfo} (this constructor reads them), but\n * neither RENDERS locally: you already know which service you are running and can check git\n * yourself, so they would be noise on every line. They still ship to GCP via the sibling\n * {@link WinstonGcpFactory}. See LOCAL_STRUCTURAL_KEYS in ./format.\n */\nexport class WinstonConsoleFactory extends WinstonFactoryBase {\n constructor() {\n super(\n format.combine(\n bigIntSafeFormat(),\n injectContextFormat(),\n severityFormat(),\n format.colorize(),\n localPrettyFormat(),\n ),\n );\n }\n}\n"]}
1
+ {"version":3,"file":"WinstonConsoleFactory.js","sourceRoot":"","sources":["../../../../../packages/logging/winston/src/WinstonConsoleFactory.ts"],"names":[],"mappings":";;;AAAA,qCAAiC;AACjC,6DAA0D;AAC1D,qCAAoG;AAEpG;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAa,qBAAsB,SAAQ,uCAAkB;IACzD,YAAY,aAAwB;QAChC,KAAK,CACD,gBAAM,CAAC,OAAO,CACV,IAAA,yBAAgB,GAAE,EAClB,IAAA,4BAAmB,GAAE,EACrB,IAAA,uBAAc,GAAE,EAChB,gBAAM,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,EAC5C,IAAA,0BAAiB,EAAC,aAAa,CAAC,CACnC,CACJ,CAAC;IACN,CAAC;CACJ;AAZD,sDAYC","sourcesContent":["import { format } from 'winston';\nimport { WinstonFactoryBase } from './WinstonFactoryBase';\nimport { bigIntSafeFormat, injectContextFormat, localPrettyFormat, severityFormat } from './format';\n\n/**\n * WinstonConsoleFactory - the LOCAL developer backend. Single-line, greppable pretty console output\n * `[LEVEL][time][Controller.method][loggerName][ctx tags]: message` same enrichment as the GCP\n * backend, different rendering, byte-identical to the bunyan console backend for the same record.\n *\n * PLAIN TEXT (no `format.colorize()`): the format is tuned for human reading AND `grep` (and AI\n * reading), matching trytami's deliberate \"no colors\" choice ANSI escapes break greppability and\n * bunyan's console has none, so color here would also break winston≡bunyan parity.\n *\n * `format.timestamp({format:'HH:mm:ss.SSS'})` supplies the `[time]` slot (`localPrettyFormat` reads\n * `info.timestamp`); millisecond precision, no date (each line already lands in a dated file).\n *\n * `consoleFields`, when given, is the app-chosen ordered ALLOW-LIST of context keys to render locally\n * (hides noise like `requestPath`); GCP still receives every logged key.\n *\n * The service name + version come from {@link ServiceInfo}, but neither RENDERS locally: you already\n * know which service you are running and can check git yourself. They still ship to GCP via the sibling\n * {@link WinstonGcpFactory}. See LOCAL_STRUCTURAL_KEYS in ./format.\n */\nexport class WinstonConsoleFactory extends WinstonFactoryBase {\n constructor(consoleFields?: string[]) {\n super(\n format.combine(\n bigIntSafeFormat(),\n injectContextFormat(),\n severityFormat(),\n format.timestamp({ format: 'HH:mm:ss.SSS' }),\n localPrettyFormat(consoleFields),\n ),\n );\n }\n}\n"]}
package/src/format.d.ts CHANGED
@@ -30,9 +30,15 @@ export declare function injectContextFormat(): Format;
30
30
  */
31
31
  export declare function severityFormat(): Format;
32
32
  /**
33
- * Local-only human format: `[loggerName] [requestId=… tenantId=…] level: message { …extra }`.
34
- * The registered context keys (already injected by injectContextFormat) render as
35
- * a bracketed prefix; anything else the caller attached renders as trailing JSON.
36
- * The set of context-key names is read lazily from the registry (first line).
33
+ * Local-only human format:
34
+ * `[LEVEL][time][Controller.method][loggerName][ctx tags]: message { …extra }` + Error Details block.
35
+ * Level FIRST, then time (the ordering the trytami format was tuned for). `controller`/`method` render
36
+ * as a compact bracket, `loggerName` as its own bracket, every other registered context key as a
37
+ * `key:value` tag, and anything else the caller attached as trailing JSON. Byte-identical to the bunyan
38
+ * backend for the same record + `fields`.
39
+ *
40
+ * `fields`, when given, is the app-chosen ordered ALLOW-LIST of context keys to render as tags (hides
41
+ * local noise like `requestPath`); when omitted, the registered logged keys render in registry order.
42
+ * The time value comes from the `format.timestamp({format:'HH:mm:ss.SSS'})` layer in WinstonConsoleFactory.
37
43
  */
38
- export declare function localPrettyFormat(): Format;
44
+ export declare function localPrettyFormat(fields?: string[]): Format;
package/src/format.js CHANGED
@@ -99,6 +99,21 @@ function severityFormat() {
99
99
  return info;
100
100
  })();
101
101
  }
102
+ // The two context keys rendered SPECIALLY (as the compact `[Controller.method]` bracket) instead of as
103
+ // `key:value` tags — they name WHICH CODE ran. Kept in sync with WebpiecesCoreHeaders.CONTROLLER / .METHOD.
104
+ const CONTROLLER_FIELD = 'controller';
105
+ const METHOD_FIELD = 'method';
106
+ // winston level name → the webpieces display level shown in the console line. Mirrors bunyan's
107
+ // Logger.nameFromLevel output so the two backends print IDENTICAL level tokens. webpieces `trace`
108
+ // rides winston `silly` (see WinstonLogger); `verbose` is winston's own extra rung, shown as DEBUG.
109
+ const WINSTON_LEVEL_TO_DISPLAY = {
110
+ silly: 'TRACE',
111
+ verbose: 'DEBUG',
112
+ debug: 'DEBUG',
113
+ info: 'INFO',
114
+ warn: 'WARN',
115
+ error: 'ERROR',
116
+ };
102
117
  // Fields that are rendered specially (or not at all) by the local pretty format,
103
118
  // so they must not leak into the trailing "extra" JSON blob.
104
119
  //
@@ -106,6 +121,11 @@ function severityFormat() {
106
121
  // they earn their keep in GCP, where you filter across many services and deploys, but locally each
107
122
  // service logs to its own place and you can check git yourself — so on every single line they are
108
123
  // pure noise. GCP still gets both (this set only affects localPrettyFormat).
124
+ //
125
+ // `timestamp` (its own `[time]` slot), `loggerName` (its own `[…]` bracket) and `controller`/`method`
126
+ // (the `[Controller.method]` bracket) are rendered specially. `errName`/`errMessage`/`errStack` (the
127
+ // Error spread from WinstonLogger) render as the multi-line "Error Details:" block. All are excluded
128
+ // here so none leak into the trailing JSON.
109
129
  const LOCAL_STRUCTURAL_KEYS = new Set([
110
130
  'level',
111
131
  'message',
@@ -113,45 +133,84 @@ const LOCAL_STRUCTURAL_KEYS = new Set([
113
133
  'svcName',
114
134
  'version',
115
135
  'loggerName',
136
+ 'timestamp',
137
+ CONTROLLER_FIELD,
138
+ METHOD_FIELD,
139
+ 'errName',
140
+ 'errMessage',
141
+ 'errStack',
116
142
  ]);
117
143
  /**
118
- * Local-only human format: `[loggerName] [requestId=… tenantId=…] level: message { …extra }`.
119
- * The registered context keys (already injected by injectContextFormat) render as
120
- * a bracketed prefix; anything else the caller attached renders as trailing JSON.
121
- * The set of context-key names is read lazily from the registry (first line).
144
+ * Local-only human format:
145
+ * `[LEVEL][time][Controller.method][loggerName][ctx tags]: message { …extra }` + Error Details block.
146
+ * Level FIRST, then time (the ordering the trytami format was tuned for). `controller`/`method` render
147
+ * as a compact bracket, `loggerName` as its own bracket, every other registered context key as a
148
+ * `key:value` tag, and anything else the caller attached as trailing JSON. Byte-identical to the bunyan
149
+ * backend for the same record + `fields`.
150
+ *
151
+ * `fields`, when given, is the app-chosen ordered ALLOW-LIST of context keys to render as tags (hides
152
+ * local noise like `requestPath`); when omitted, the registered logged keys render in registry order.
153
+ * The time value comes from the `format.timestamp({format:'HH:mm:ss.SSS'})` layer in WinstonConsoleFactory.
122
154
  */
123
- function localPrettyFormat() {
155
+ // webpieces-disable no-function-outside-class -- winston Format factory; whole file is winston Format factories
156
+ function localPrettyFormat(fields) {
124
157
  let contextNames;
125
158
  return winston_1.format.printf((info) => {
126
159
  if (!contextNames) {
127
- contextNames = new Set(core_util_1.HeaderRegistry.get().getLoggedKeys().map((k) => k.name));
160
+ contextNames = core_util_1.HeaderRegistry.get().getLoggedKeys().map((k) => k.name);
128
161
  }
129
- // Only STRING context values render in the bracket prefix. An object-valued key (the `api`
130
- // tag) would stringify to "[object Object]" here, so it is left out of the prefix and instead
131
- // falls into the trailing JSON blob below (readable), while GCP still gets it nested.
132
- const prefixBits = [];
133
- for (const name of contextNames) {
162
+ const levelName = (WINSTON_LEVEL_TO_DISPLAY[info.level] ?? info.level.toUpperCase()).padEnd(5);
163
+ const time = info['timestamp'] != null ? String(info['timestamp']) : '';
164
+ // The compact `[Controller.method]` bracket (or `[Controller]`, or '' when neither is present
165
+ // a startup / static / pre-route line). Empty controller drops the whole bracket.
166
+ const controller = info[CONTROLLER_FIELD];
167
+ const method = info[METHOD_FIELD];
168
+ const controllerMethod = typeof controller === 'string' && controller.length > 0
169
+ ? `[${controller}${typeof method === 'string' && method.length > 0 ? `.${method}` : ''}]`
170
+ : '';
171
+ const loggerBracket = info['loggerName'] ? `[${String(info['loggerName'])}]` : '';
172
+ // Ordered `key:value` context tags. `fields` (allow-list) wins; else the registered logged keys.
173
+ // Only STRING values render (an object-valued key like `api` is dropped here and falls into the
174
+ // trailing JSON blob below, while GCP still gets it nested). controller/method/loggerName are
175
+ // rendered in their own brackets, so they are skipped here.
176
+ const order = fields ?? contextNames;
177
+ const tags = [];
178
+ for (const name of order) {
179
+ if (name === CONTROLLER_FIELD || name === METHOD_FIELD || name === 'loggerName') {
180
+ continue;
181
+ }
134
182
  const value = info[name];
135
- if (value != null && typeof value === 'string') {
136
- prefixBits.push(`${name}=${value}`);
183
+ if (value != null && typeof value === 'string' && value.length > 0) {
184
+ tags.push(`${name}:${value}`);
137
185
  }
138
186
  }
139
- const prefix = prefixBits.length ? `[${prefixBits.join(' ')}] ` : '';
140
- const loggerName = info['loggerName'] ? `[${String(info['loggerName'])}] ` : '';
187
+ const tagStr = tags.length > 0 ? tags.join(', ') : 'no-context';
188
+ const contextNameSet = new Set(contextNames);
141
189
  const rest = {};
142
190
  for (const key of Object.keys(info)) {
143
191
  if (LOCAL_STRUCTURAL_KEYS.has(key)) {
144
192
  continue;
145
193
  }
146
- // A context key already shown in the bracket prefix (string value) is skipped; an
147
- // object-valued context key (api) was NOT shown there, so let it render as trailing JSON.
148
- if (contextNames.has(key) && typeof info[key] === 'string') {
194
+ // A registered context key already shown as a string tag is skipped; an object-valued
195
+ // context key (api) was NOT shown there, so let it render as trailing JSON.
196
+ if (contextNameSet.has(key) && typeof info[key] === 'string') {
149
197
  continue;
150
198
  }
151
199
  rest[key] = info[key];
152
200
  }
153
201
  const restStr = Object.keys(rest).length ? ` ${(0, safe_stable_stringify_1.stringify)(rest)}` : '';
154
- return `${loggerName}${prefix}${info.level}: ${info.message}${restStr}`;
202
+ let line = `[${levelName}][${time}]${controllerMethod}${loggerBracket}[${tagStr}]: ${info.message}${restStr}`;
203
+ // Multi-line error block, byte-identical to the bunyan backend. WinstonLogger spreads an Error
204
+ // into errName/errMessage/errStack; render them the same way trytami's writeConsole did.
205
+ if (info['errName'] != null || info['errMessage'] != null || info['errStack'] != null) {
206
+ line += `\nError Details:`;
207
+ line += `\n Message: ${String(info['errMessage'] ?? '')}`;
208
+ line += `\n Name: ${String(info['errName'] ?? '')}`;
209
+ if (info['errStack'] != null) {
210
+ line += `\n Stack Trace:\n${String(info['errStack'])}`;
211
+ }
212
+ }
213
+ return line;
155
214
  });
156
215
  }
157
216
  //# sourceMappingURL=format.js.map
package/src/format.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"format.js","sourceRoot":"","sources":["../../../../../packages/logging/winston/src/format.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The winston format layers that turn a raw webpieces log call into a\n * Cloud-Logging-ready structured record. Ported verbatim (behaviourally) from\n * the tested-in-GCP logger at\n * onetablet/monorepo-nx1 libraries/core-context/src/logger/format.ts, with the\n * one webpieces adaptation: context is read from the webpieces HeaderRegistry +\n * a ContextReader (rather than a hard-coded PLATFORM_HEADERS enum), so the exact\n * set of logged fields is whatever the app registered.\n *\n * Correlation rides the webpieces magic context (AsyncLocalStorage on the\n * server, via the ContextReader passed in) — NOT OpenTelemetry / trace-agent, so\n * nothing here imports a tracing agent.\n */\nimport { format } from 'winston';\nimport type { Format, TransformableInfo } from 'logform';\nimport { stringify as safeStringify } from 'safe-stable-stringify';\nimport { HeaderRegistry } from '@webpieces/core-util';\nimport type { ContextKey } from '@webpieces/core-util';\nimport { RequestContext } from '@webpieces/core-context';\n\n// JSON-shaped value (the widest thing a log field / replacer value can hold),\n// used instead of `any`/`unknown` which the code rules disallow.\ntype JsonValue = string | number | boolean | bigint | object | null | undefined;\n\n// winston level → GCP Cloud Logging severity. The Cloud Run / GKE logging agent\n// recognises top-level `severity` in stdout JSON; without this map it falls back\n// to \"DEFAULT\" which is unfilterable. webpieces `trace` maps onto winston `silly`\n// (see WinstonLogger), so both land at DEBUG severity.\nexport const LEVEL_TO_SEVERITY: Record<string, string> = {\n silly: 'DEBUG',\n verbose: 'DEBUG',\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARNING',\n error: 'ERROR',\n};\n\n/**\n * Round-trip the record through safe-stable-stringify so circular references\n * (HTTP client/response cycles, request/response objects, framework execution\n * contexts) become \"[Circular]\" instead of crashing the log emit, and bigints\n * serialize as strings (JSON.stringify can't, and the bare safe-stringify output\n * wouldn't round-trip through JSON.parse). Symbol keys winston relies on are\n * untouched (JSON ignores them), so Object.assign only rewrites string fields.\n */\nexport function bigIntSafeFormat(): Format {\n return format((info: TransformableInfo) => {\n return Object.assign(\n info,\n JSON.parse(\n // webpieces-disable no-any-unknown -- safe-stable-stringify's Replacer types the value as unknown\n safeStringify(info, (_key: string, value: unknown) => {\n if (typeof value === 'bigint') {\n return value.toString();\n }\n return value;\n })!,\n ),\n );\n })();\n}\n\n/**\n * Inject every logged HeaderRegistry key present in the active RequestContext frame\n * into the record under its `name` (→ top-level jsonPayload.<name> in GCP, filterable\n * as jsonPayload.requestId, jsonPayload.tenantId, …). Values are read DIRECTLY from\n * RequestContext, secured keys masked via {@link ContextKey.maskIfSecured} — no\n * ContextReader. Caller-supplied fields on the record win on conflict. Runs on EVERY\n * winston call, including winston's own handleExceptions/handleRejections lines that\n * bypass the WinstonLogger wrapper.\n *\n * This mirrors the (duplicated, on purpose) inline logic in BunyanLogger: it must run\n * ONLY when a winston backend is installed, never for the plain ConsoleLogger. A log\n * line with no active RequestContext just injects nothing (startup and background-job\n * lines are legitimately out of context).\n */\n// webpieces-disable no-function-outside-class -- winston format(fn) factory; whole file is winston Format factories\nexport function injectContextFormat(): Format {\n return format((info: TransformableInfo) => {\n // No active RequestContext (startup, a background job, or an in-process call the caller did\n // not wrap) simply injects nothing — an empty context is normal, not an error. The one place\n // a request-path wrap can legitimately be missing (the in-process client) is caught precisely\n // by ApiClientFactory.requireActiveContext(), which throws at the api boundary.\n if (RequestContext.isActive()) {\n // ONE loop, in HeaderRegistry.buildStructuredLogFields. Values may be OBJECTS (the `api`\n // tag), so an object-valued key nests into jsonPayload.<name> (winston JSON-serializes the\n // whole record) rather than being dropped by the string-only buildLogFields. Caller-supplied\n // fields win on conflict.\n RequestContext.buildStructuredLogFields().forEach((value: string | object, name: string) => {\n if (info[name] === undefined) {\n info[name] = value;\n }\n });\n }\n return info;\n })();\n}\n\n/**\n * Map the winston level onto a top-level `severity` field that the Cloud Logging\n * agent lifts onto the LogEntry.\n */\nexport function severityFormat(): Format {\n return format((info: TransformableInfo) => {\n info['severity'] = LEVEL_TO_SEVERITY[info.level] || info.level.toUpperCase();\n return info;\n })();\n}\n\n// Fields that are rendered specially (or not at all) by the local pretty format,\n// so they must not leak into the trailing \"extra\" JSON blob.\n//\n// `svcName` + `version` (the ServiceInfo defaultMeta fields) are here to be rendered NOT AT ALL:\n// they earn their keep in GCP, where you filter across many services and deploys, but locally each\n// service logs to its own place and you can check git yourself — so on every single line they are\n// pure noise. GCP still gets both (this set only affects localPrettyFormat).\nconst LOCAL_STRUCTURAL_KEYS = new Set<string>([\n 'level',\n 'message',\n 'severity',\n 'svcName',\n 'version',\n 'loggerName',\n]);\n\n/**\n * Local-only human format: `[loggerName] [requestId=… tenantId=…] level: message { …extra }`.\n * The registered context keys (already injected by injectContextFormat) render as\n * a bracketed prefix; anything else the caller attached renders as trailing JSON.\n * The set of context-key names is read lazily from the registry (first line).\n */\nexport function localPrettyFormat(): Format {\n let contextNames: Set<string> | undefined;\n return format.printf((info: TransformableInfo) => {\n if (!contextNames) {\n contextNames = new Set(HeaderRegistry.get().getLoggedKeys().map((k: ContextKey) => k.name));\n }\n // Only STRING context values render in the bracket prefix. An object-valued key (the `api`\n // tag) would stringify to \"[object Object]\" here, so it is left out of the prefix and instead\n // falls into the trailing JSON blob below (readable), while GCP still gets it nested.\n const prefixBits: string[] = [];\n for (const name of contextNames) {\n const value = info[name];\n if (value != null && typeof value === 'string') {\n prefixBits.push(`${name}=${value}`);\n }\n }\n const prefix = prefixBits.length ? `[${prefixBits.join(' ')}] ` : '';\n const loggerName = info['loggerName'] ? `[${String(info['loggerName'])}] ` : '';\n\n const rest: Record<string, JsonValue> = {};\n for (const key of Object.keys(info)) {\n if (LOCAL_STRUCTURAL_KEYS.has(key)) {\n continue;\n }\n // A context key already shown in the bracket prefix (string value) is skipped; an\n // object-valued context key (api) was NOT shown there, so let it render as trailing JSON.\n if (contextNames.has(key) && typeof info[key] === 'string') {\n continue;\n }\n rest[key] = info[key] as JsonValue;\n }\n const restStr = Object.keys(rest).length ? ` ${safeStringify(rest)}` : '';\n\n return `${loggerName}${prefix}${info.level}: ${info.message}${restStr}`;\n });\n}\n"]}
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../../../../packages/logging/winston/src/format.ts"],"names":[],"mappings":";;;AA6CA,4CAeC;AAiBD,kDAmBC;AAMD,wCAKC;AA2DD,8CAkEC;AAxOD;;;;;;;;;;;;GAYG;AACH,qCAAiC;AAEjC,iEAAmE;AACnE,oDAAsD;AAEtD,0DAAyD;AAMzD,gFAAgF;AAChF,iFAAiF;AACjF,kFAAkF;AAClF,uDAAuD;AAC1C,QAAA,iBAAiB,GAA2B;IACrD,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,OAAO;IAChB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,OAAO;CACjB,CAAC;AAEF;;;;;;;GAOG;AACH,SAAgB,gBAAgB;IAC5B,OAAO,IAAA,gBAAM,EAAC,CAAC,IAAuB,EAAE,EAAE;QACtC,OAAO,MAAM,CAAC,MAAM,CAChB,IAAI,EACJ,IAAI,CAAC,KAAK;QACN,kGAAkG;QAClG,IAAA,iCAAa,EAAC,IAAI,EAAE,CAAC,IAAY,EAAE,KAAc,EAAE,EAAE;YACjD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC5B,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;YAC5B,CAAC;YACD,OAAO,KAAK,CAAC;QACjB,CAAC,CAAE,CACN,CACJ,CAAC;IACN,CAAC,CAAC,EAAE,CAAC;AACT,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,oHAAoH;AACpH,SAAgB,mBAAmB;IAC/B,OAAO,IAAA,gBAAM,EAAC,CAAC,IAAuB,EAAE,EAAE;QACtC,4FAA4F;QAC5F,6FAA6F;QAC7F,8FAA8F;QAC9F,gFAAgF;QAChF,IAAI,6BAAc,CAAC,QAAQ,EAAE,EAAE,CAAC;YAC5B,yFAAyF;YACzF,2FAA2F;YAC3F,6FAA6F;YAC7F,0BAA0B;YAC1B,6BAAc,CAAC,wBAAwB,EAAE,CAAC,OAAO,CAAC,CAAC,KAAsB,EAAE,IAAY,EAAE,EAAE;gBACvF,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;oBAC3B,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;gBACvB,CAAC;YACL,CAAC,CAAC,CAAC;QACP,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC,CAAC,EAAE,CAAC;AACT,CAAC;AAED;;;GAGG;AACH,SAAgB,cAAc;IAC1B,OAAO,IAAA,gBAAM,EAAC,CAAC,IAAuB,EAAE,EAAE;QACtC,IAAI,CAAC,UAAU,CAAC,GAAG,yBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QAC7E,OAAO,IAAI,CAAC;IAChB,CAAC,CAAC,EAAE,CAAC;AACT,CAAC;AAED,uGAAuG;AACvG,4GAA4G;AAC5G,MAAM,gBAAgB,GAAG,YAAY,CAAC;AACtC,MAAM,YAAY,GAAG,QAAQ,CAAC;AAE9B,+FAA+F;AAC/F,kGAAkG;AAClG,oGAAoG;AACpG,MAAM,wBAAwB,GAA2B;IACrD,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,OAAO;IAChB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;CACjB,CAAC;AAEF,iFAAiF;AACjF,6DAA6D;AAC7D,EAAE;AACF,iGAAiG;AACjG,mGAAmG;AACnG,kGAAkG;AAClG,6EAA6E;AAC7E,EAAE;AACF,sGAAsG;AACtG,qGAAqG;AACrG,qGAAqG;AACrG,4CAA4C;AAC5C,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAS;IAC1C,OAAO;IACP,SAAS;IACT,UAAU;IACV,SAAS;IACT,SAAS;IACT,YAAY;IACZ,WAAW;IACX,gBAAgB;IAChB,YAAY;IACZ,SAAS;IACT,YAAY;IACZ,UAAU;CACb,CAAC,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,gHAAgH;AAChH,SAAgB,iBAAiB,CAAC,MAAiB;IAC/C,IAAI,YAAkC,CAAC;IACvC,OAAO,gBAAM,CAAC,MAAM,CAAC,CAAC,IAAuB,EAAE,EAAE;QAC7C,IAAI,CAAC,YAAY,EAAE,CAAC;YAChB,YAAY,GAAG,0BAAc,CAAC,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACvF,CAAC;QACD,MAAM,SAAS,GAAG,CAAC,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC/F,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAExE,gGAAgG;QAChG,kFAAkF;QAClF,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC;QAClC,MAAM,gBAAgB,GAClB,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;YACnD,CAAC,CAAC,IAAI,UAAU,GAAG,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG;YACzF,CAAC,CAAC,EAAE,CAAC;QACb,MAAM,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAElF,iGAAiG;QACjG,gGAAgG;QAChG,8FAA8F;QAC9F,4DAA4D;QAC5D,MAAM,KAAK,GAAG,MAAM,IAAI,YAAY,CAAC;QACrC,MAAM,IAAI,GAAa,EAAE,CAAC;QAC1B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,IAAI,IAAI,KAAK,gBAAgB,IAAI,IAAI,KAAK,YAAY,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;gBAC9E,SAAS;YACb,CAAC;YACD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;YACzB,IAAI,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACjE,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC;YAClC,CAAC;QACL,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;QAEhE,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;QAC7C,MAAM,IAAI,GAA8B,EAAE,CAAC;QAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,IAAI,qBAAqB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACjC,SAAS;YACb,CAAC;YACD,sFAAsF;YACtF,4EAA4E;YAC5E,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;gBAC3D,SAAS;YACb,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAc,CAAC;QACvC,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,IAAA,iCAAa,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAE1E,IAAI,IAAI,GAAG,IAAI,SAAS,KAAK,IAAI,IAAI,gBAAgB,GAAG,aAAa,IAAI,MAAM,MAAM,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,CAAC;QAE9G,+FAA+F;QAC/F,yFAAyF;QACzF,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC;YACpF,IAAI,IAAI,kBAAkB,CAAC;YAC3B,IAAI,IAAI,gBAAgB,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAC3D,IAAI,IAAI,aAAa,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YACrD,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC3B,IAAI,IAAI,qBAAqB,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;YAC5D,CAAC;QACL,CAAC;QAED,OAAO,IAAI,CAAC;IAChB,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["/**\n * The winston format layers that turn a raw webpieces log call into a\n * Cloud-Logging-ready structured record. Ported verbatim (behaviourally) from\n * the tested-in-GCP logger at\n * onetablet/monorepo-nx1 libraries/core-context/src/logger/format.ts, with the\n * one webpieces adaptation: context is read from the webpieces HeaderRegistry +\n * a ContextReader (rather than a hard-coded PLATFORM_HEADERS enum), so the exact\n * set of logged fields is whatever the app registered.\n *\n * Correlation rides the webpieces magic context (AsyncLocalStorage on the\n * server, via the ContextReader passed in) — NOT OpenTelemetry / trace-agent, so\n * nothing here imports a tracing agent.\n */\nimport { format } from 'winston';\nimport type { Format, TransformableInfo } from 'logform';\nimport { stringify as safeStringify } from 'safe-stable-stringify';\nimport { HeaderRegistry } from '@webpieces/core-util';\nimport type { ContextKey } from '@webpieces/core-util';\nimport { RequestContext } from '@webpieces/core-context';\n\n// JSON-shaped value (the widest thing a log field / replacer value can hold),\n// used instead of `any`/`unknown` which the code rules disallow.\ntype JsonValue = string | number | boolean | bigint | object | null | undefined;\n\n// winston level → GCP Cloud Logging severity. The Cloud Run / GKE logging agent\n// recognises top-level `severity` in stdout JSON; without this map it falls back\n// to \"DEFAULT\" which is unfilterable. webpieces `trace` maps onto winston `silly`\n// (see WinstonLogger), so both land at DEBUG severity.\nexport const LEVEL_TO_SEVERITY: Record<string, string> = {\n silly: 'DEBUG',\n verbose: 'DEBUG',\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARNING',\n error: 'ERROR',\n};\n\n/**\n * Round-trip the record through safe-stable-stringify so circular references\n * (HTTP client/response cycles, request/response objects, framework execution\n * contexts) become \"[Circular]\" instead of crashing the log emit, and bigints\n * serialize as strings (JSON.stringify can't, and the bare safe-stringify output\n * wouldn't round-trip through JSON.parse). Symbol keys winston relies on are\n * untouched (JSON ignores them), so Object.assign only rewrites string fields.\n */\nexport function bigIntSafeFormat(): Format {\n return format((info: TransformableInfo) => {\n return Object.assign(\n info,\n JSON.parse(\n // webpieces-disable no-any-unknown -- safe-stable-stringify's Replacer types the value as unknown\n safeStringify(info, (_key: string, value: unknown) => {\n if (typeof value === 'bigint') {\n return value.toString();\n }\n return value;\n })!,\n ),\n );\n })();\n}\n\n/**\n * Inject every logged HeaderRegistry key present in the active RequestContext frame\n * into the record under its `name` (→ top-level jsonPayload.<name> in GCP, filterable\n * as jsonPayload.requestId, jsonPayload.tenantId, …). Values are read DIRECTLY from\n * RequestContext, secured keys masked via {@link ContextKey.maskIfSecured} — no\n * ContextReader. Caller-supplied fields on the record win on conflict. Runs on EVERY\n * winston call, including winston's own handleExceptions/handleRejections lines that\n * bypass the WinstonLogger wrapper.\n *\n * This mirrors the (duplicated, on purpose) inline logic in BunyanLogger: it must run\n * ONLY when a winston backend is installed, never for the plain ConsoleLogger. A log\n * line with no active RequestContext just injects nothing (startup and background-job\n * lines are legitimately out of context).\n */\n// webpieces-disable no-function-outside-class -- winston format(fn) factory; whole file is winston Format factories\nexport function injectContextFormat(): Format {\n return format((info: TransformableInfo) => {\n // No active RequestContext (startup, a background job, or an in-process call the caller did\n // not wrap) simply injects nothing — an empty context is normal, not an error. The one place\n // a request-path wrap can legitimately be missing (the in-process client) is caught precisely\n // by ApiClientFactory.requireActiveContext(), which throws at the api boundary.\n if (RequestContext.isActive()) {\n // ONE loop, in HeaderRegistry.buildStructuredLogFields. Values may be OBJECTS (the `api`\n // tag), so an object-valued key nests into jsonPayload.<name> (winston JSON-serializes the\n // whole record) rather than being dropped by the string-only buildLogFields. Caller-supplied\n // fields win on conflict.\n RequestContext.buildStructuredLogFields().forEach((value: string | object, name: string) => {\n if (info[name] === undefined) {\n info[name] = value;\n }\n });\n }\n return info;\n })();\n}\n\n/**\n * Map the winston level onto a top-level `severity` field that the Cloud Logging\n * agent lifts onto the LogEntry.\n */\nexport function severityFormat(): Format {\n return format((info: TransformableInfo) => {\n info['severity'] = LEVEL_TO_SEVERITY[info.level] || info.level.toUpperCase();\n return info;\n })();\n}\n\n// The two context keys rendered SPECIALLY (as the compact `[Controller.method]` bracket) instead of as\n// `key:value` tags — they name WHICH CODE ran. Kept in sync with WebpiecesCoreHeaders.CONTROLLER / .METHOD.\nconst CONTROLLER_FIELD = 'controller';\nconst METHOD_FIELD = 'method';\n\n// winston level name → the webpieces display level shown in the console line. Mirrors bunyan's\n// Logger.nameFromLevel output so the two backends print IDENTICAL level tokens. webpieces `trace`\n// rides winston `silly` (see WinstonLogger); `verbose` is winston's own extra rung, shown as DEBUG.\nconst WINSTON_LEVEL_TO_DISPLAY: Record<string, string> = {\n silly: 'TRACE',\n verbose: 'DEBUG',\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n};\n\n// Fields that are rendered specially (or not at all) by the local pretty format,\n// so they must not leak into the trailing \"extra\" JSON blob.\n//\n// `svcName` + `version` (the ServiceInfo defaultMeta fields) are here to be rendered NOT AT ALL:\n// they earn their keep in GCP, where you filter across many services and deploys, but locally each\n// service logs to its own place and you can check git yourself — so on every single line they are\n// pure noise. GCP still gets both (this set only affects localPrettyFormat).\n//\n// `timestamp` (its own `[time]` slot), `loggerName` (its own `[…]` bracket) and `controller`/`method`\n// (the `[Controller.method]` bracket) are rendered specially. `errName`/`errMessage`/`errStack` (the\n// Error spread from WinstonLogger) render as the multi-line \"Error Details:\" block. All are excluded\n// here so none leak into the trailing JSON.\nconst LOCAL_STRUCTURAL_KEYS = new Set<string>([\n 'level',\n 'message',\n 'severity',\n 'svcName',\n 'version',\n 'loggerName',\n 'timestamp',\n CONTROLLER_FIELD,\n METHOD_FIELD,\n 'errName',\n 'errMessage',\n 'errStack',\n]);\n\n/**\n * Local-only human format:\n * `[LEVEL][time][Controller.method][loggerName][ctx tags]: message { …extra }` + Error Details block.\n * Level FIRST, then time (the ordering the trytami format was tuned for). `controller`/`method` render\n * as a compact bracket, `loggerName` as its own bracket, every other registered context key as a\n * `key:value` tag, and anything else the caller attached as trailing JSON. Byte-identical to the bunyan\n * backend for the same record + `fields`.\n *\n * `fields`, when given, is the app-chosen ordered ALLOW-LIST of context keys to render as tags (hides\n * local noise like `requestPath`); when omitted, the registered logged keys render in registry order.\n * The time value comes from the `format.timestamp({format:'HH:mm:ss.SSS'})` layer in WinstonConsoleFactory.\n */\n// webpieces-disable no-function-outside-class -- winston Format factory; whole file is winston Format factories\nexport function localPrettyFormat(fields?: string[]): Format {\n let contextNames: string[] | undefined;\n return format.printf((info: TransformableInfo) => {\n if (!contextNames) {\n contextNames = HeaderRegistry.get().getLoggedKeys().map((k: ContextKey) => k.name);\n }\n const levelName = (WINSTON_LEVEL_TO_DISPLAY[info.level] ?? info.level.toUpperCase()).padEnd(5);\n const time = info['timestamp'] != null ? String(info['timestamp']) : '';\n\n // The compact `[Controller.method]` bracket (or `[Controller]`, or '' when neither is present —\n // a startup / static / pre-route line). Empty controller drops the whole bracket.\n const controller = info[CONTROLLER_FIELD];\n const method = info[METHOD_FIELD];\n const controllerMethod =\n typeof controller === 'string' && controller.length > 0\n ? `[${controller}${typeof method === 'string' && method.length > 0 ? `.${method}` : ''}]`\n : '';\n const loggerBracket = info['loggerName'] ? `[${String(info['loggerName'])}]` : '';\n\n // Ordered `key:value` context tags. `fields` (allow-list) wins; else the registered logged keys.\n // Only STRING values render (an object-valued key like `api` is dropped here and falls into the\n // trailing JSON blob below, while GCP still gets it nested). controller/method/loggerName are\n // rendered in their own brackets, so they are skipped here.\n const order = fields ?? contextNames;\n const tags: string[] = [];\n for (const name of order) {\n if (name === CONTROLLER_FIELD || name === METHOD_FIELD || name === 'loggerName') {\n continue;\n }\n const value = info[name];\n if (value != null && typeof value === 'string' && value.length > 0) {\n tags.push(`${name}:${value}`);\n }\n }\n const tagStr = tags.length > 0 ? tags.join(', ') : 'no-context';\n\n const contextNameSet = new Set(contextNames);\n const rest: Record<string, JsonValue> = {};\n for (const key of Object.keys(info)) {\n if (LOCAL_STRUCTURAL_KEYS.has(key)) {\n continue;\n }\n // A registered context key already shown as a string tag is skipped; an object-valued\n // context key (api) was NOT shown there, so let it render as trailing JSON.\n if (contextNameSet.has(key) && typeof info[key] === 'string') {\n continue;\n }\n rest[key] = info[key] as JsonValue;\n }\n const restStr = Object.keys(rest).length ? ` ${safeStringify(rest)}` : '';\n\n let line = `[${levelName}][${time}]${controllerMethod}${loggerBracket}[${tagStr}]: ${info.message}${restStr}`;\n\n // Multi-line error block, byte-identical to the bunyan backend. WinstonLogger spreads an Error\n // into errName/errMessage/errStack; render them the same way trytami's writeConsole did.\n if (info['errName'] != null || info['errMessage'] != null || info['errStack'] != null) {\n line += `\\nError Details:`;\n line += `\\n Message: ${String(info['errMessage'] ?? '')}`;\n line += `\\n Name: ${String(info['errName'] ?? '')}`;\n if (info['errStack'] != null) {\n line += `\\n Stack Trace:\\n${String(info['errStack'])}`;\n }\n }\n\n return line;\n });\n}\n"]}