@webpieces/winston 0.4.612 → 0.4.614

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,8 +10,8 @@ Two factories, both auto-enriching every line with the logged context keys regis
10
10
  `[loggerName] [requestId=… tenantId=…] level: message {…extra}`.
11
11
  - **`WinstonGcpFactory`** — Cloud Run / GKE: flat JSON to **stdout**, scraped by the logging
12
12
  agent. A level→`severity` map is the only GCP glue (no `@google-cloud` transport);
13
- registered context keys land at top-level `jsonPayload.<name>`. This mirrors the
14
- tested-in-GCP logger in `onetablet/monorepo-nx1`.
13
+ registered context keys land at top-level `jsonPayload.<name>`. This mirrors a
14
+ production-tested GCP logger.
15
15
 
16
16
  ## Usage
17
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/winston",
3
- "version": "0.4.612",
3
+ "version": "0.4.614",
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.612",
27
- "@webpieces/core-context": "0.4.612",
26
+ "@webpieces/core-util": "0.4.614",
27
+ "@webpieces/core-context": "0.4.614",
28
28
  "winston": "3.11.0",
29
29
  "logform": "2.7.0",
30
30
  "safe-stable-stringify": "2.5.0"
@@ -5,7 +5,7 @@ import { WinstonFactoryBase } from './WinstonFactoryBase';
5
5
  * backend, different rendering, byte-identical to the bunyan console backend for the same record.
6
6
  *
7
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
8
+ * reading), matching the production-tested logger's deliberate "no colors" choice — ANSI escapes break greppability and
9
9
  * bunyan's console has none, so color here would also break winston≡bunyan parity.
10
10
  *
11
11
  * `format.timestamp({format:'HH:mm:ss.SSS'})` supplies the `[time]` slot (`localPrettyFormat` reads
@@ -10,7 +10,7 @@ const format_1 = require("./format");
10
10
  * backend, different rendering, byte-identical to the bunyan console backend for the same record.
11
11
  *
12
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
13
+ * reading), matching the production-tested logger's deliberate "no colors" choice — ANSI escapes break greppability and
14
14
  * bunyan's console has none, so color here would also break winston≡bunyan parity.
15
15
  *
16
16
  * `format.timestamp({format:'HH:mm:ss.SSS'})` supplies the `[time]` slot (`localPrettyFormat` reads
@@ -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;;;;;;;;;;;;;;;;;;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"]}
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 the production-tested logger'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"]}
@@ -5,8 +5,8 @@ import { WinstonFactoryBase } from './WinstonFactoryBase';
5
5
  * onto the LogEntry and every registered context key lands at top-level
6
6
  * jsonPayload.<name> (requestId, tenantId, …), filterable directly. There is NO
7
7
  * @google-cloud transport — correlation rides the webpieces context, read
8
- * DIRECTLY from RequestContext on each line. This matches the tested-in-GCP
9
- * onetablet/monorepo-nx1 core logger exactly.
8
+ * DIRECTLY from RequestContext on each line. This matches a production-tested GCP
9
+ * core logger exactly.
10
10
  *
11
11
  * The service name + version come from {@link ServiceInfo}, which startup must have populated
12
12
  * (this constructor reads them); they are NOT factory options.
@@ -11,8 +11,8 @@ const format_1 = require("./format");
11
11
  * onto the LogEntry and every registered context key lands at top-level
12
12
  * jsonPayload.<name> (requestId, tenantId, …), filterable directly. There is NO
13
13
  * @google-cloud transport — correlation rides the webpieces context, read
14
- * DIRECTLY from RequestContext on each line. This matches the tested-in-GCP
15
- * onetablet/monorepo-nx1 core logger exactly.
14
+ * DIRECTLY from RequestContext on each line. This matches a production-tested GCP
15
+ * core logger exactly.
16
16
  *
17
17
  * The service name + version come from {@link ServiceInfo}, which startup must have populated
18
18
  * (this constructor reads them); they are NOT factory options.
@@ -1 +1 @@
1
- {"version":3,"file":"WinstonGcpFactory.js","sourceRoot":"","sources":["../../../../../packages/logging/winston/src/WinstonGcpFactory.ts"],"names":[],"mappings":";;;AAAA,qCAAiC;AACjC,6DAA0D;AAC1D,yEAAsE;AACtE,qCAAiF;AAEjF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAa,iBAAkB,SAAQ,uCAAkB;IACrD;QACI,KAAK,CACD,gBAAM,CAAC,OAAO,CACV,IAAA,yBAAgB,GAAE,EAClB,IAAA,4BAAmB,GAAE,EACrB,IAAA,uBAAc,GAAE,EAChB,gBAAM,CAAC,IAAI,EAAE,CAChB,EACD,IAAI,mDAAwB,EAAE,CACjC,CAAC;IACN,CAAC;CACJ;AAZD,8CAYC","sourcesContent":["import { format } from 'winston';\nimport { WinstonFactoryBase } from './WinstonFactoryBase';\nimport { ChunkingConsoleTransport } from './ChunkingConsoleTransport';\nimport { bigIntSafeFormat, injectContextFormat, severityFormat } from './format';\n\n/**\n * WinstonGcpFactory - the GCP/Cloud Run backend. Emits flat JSON to stdout; the\n * Cloud Run / GKE logging agent natively parses it — `severity` + `message` lift\n * onto the LogEntry and every registered context key lands at top-level\n * jsonPayload.<name> (requestId, tenantId, …), filterable directly. There is NO\n * @google-cloud transport — correlation rides the webpieces context, read\n * DIRECTLY from RequestContext on each line. This matches the tested-in-GCP\n * onetablet/monorepo-nx1 core logger exactly.\n *\n * The service name + version come from {@link ServiceInfo}, which startup must have populated\n * (this constructor reads them); they are NOT factory options.\n *\n * Writes through a {@link ChunkingConsoleTransport} rather than a plain Console, because THIS is the\n * backend with a size limit: Cloud Logging caps a LogEntry at 256 KiB and — critically — an\n * oversized jsonPayload entry is DROPPED, not truncated, with no error raised anywhere. A big\n * response body or stack trace would simply never appear. The transport splits such a record into\n * several complete, parseable records sharing a `jsonPayload.logChunk.uid`.\n */\nexport class WinstonGcpFactory extends WinstonFactoryBase {\n constructor() {\n super(\n format.combine(\n bigIntSafeFormat(),\n injectContextFormat(),\n severityFormat(),\n format.json(),\n ),\n new ChunkingConsoleTransport(),\n );\n }\n}\n"]}
1
+ {"version":3,"file":"WinstonGcpFactory.js","sourceRoot":"","sources":["../../../../../packages/logging/winston/src/WinstonGcpFactory.ts"],"names":[],"mappings":";;;AAAA,qCAAiC;AACjC,6DAA0D;AAC1D,yEAAsE;AACtE,qCAAiF;AAEjF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAa,iBAAkB,SAAQ,uCAAkB;IACrD;QACI,KAAK,CACD,gBAAM,CAAC,OAAO,CACV,IAAA,yBAAgB,GAAE,EAClB,IAAA,4BAAmB,GAAE,EACrB,IAAA,uBAAc,GAAE,EAChB,gBAAM,CAAC,IAAI,EAAE,CAChB,EACD,IAAI,mDAAwB,EAAE,CACjC,CAAC;IACN,CAAC;CACJ;AAZD,8CAYC","sourcesContent":["import { format } from 'winston';\nimport { WinstonFactoryBase } from './WinstonFactoryBase';\nimport { ChunkingConsoleTransport } from './ChunkingConsoleTransport';\nimport { bigIntSafeFormat, injectContextFormat, severityFormat } from './format';\n\n/**\n * WinstonGcpFactory - the GCP/Cloud Run backend. Emits flat JSON to stdout; the\n * Cloud Run / GKE logging agent natively parses it — `severity` + `message` lift\n * onto the LogEntry and every registered context key lands at top-level\n * jsonPayload.<name> (requestId, tenantId, …), filterable directly. There is NO\n * @google-cloud transport — correlation rides the webpieces context, read\n * DIRECTLY from RequestContext on each line. This matches a production-tested GCP\n * core logger exactly.\n *\n * The service name + version come from {@link ServiceInfo}, which startup must have populated\n * (this constructor reads them); they are NOT factory options.\n *\n * Writes through a {@link ChunkingConsoleTransport} rather than a plain Console, because THIS is the\n * backend with a size limit: Cloud Logging caps a LogEntry at 256 KiB and — critically — an\n * oversized jsonPayload entry is DROPPED, not truncated, with no error raised anywhere. A big\n * response body or stack trace would simply never appear. The transport splits such a record into\n * several complete, parseable records sharing a `jsonPayload.logChunk.uid`.\n */\nexport class WinstonGcpFactory extends WinstonFactoryBase {\n constructor() {\n super(\n format.combine(\n bigIntSafeFormat(),\n injectContextFormat(),\n severityFormat(),\n format.json(),\n ),\n new ChunkingConsoleTransport(),\n );\n }\n}\n"]}
@@ -11,7 +11,7 @@ export declare const LEVEL_TO_WINSTON: Record<LogLevel, string>;
11
11
  * (one per name, created as a winston child carrying `loggerName`). Context
12
12
  * enrichment is done by the shared winston formats (see format.ts), so this
13
13
  * wrapper only maps the 5 webpieces levels and spreads an optional Error into
14
- * `errName`/`errMessage`/`errStack` — matching the tested monorepo-nx behaviour.
14
+ * `errName`/`errMessage`/`errStack` — matching the production-tested logger's behaviour.
15
15
  */
16
16
  export declare class WinstonLogger implements Logger {
17
17
  private readonly winston;
@@ -18,7 +18,7 @@ exports.LEVEL_TO_WINSTON = {
18
18
  * (one per name, created as a winston child carrying `loggerName`). Context
19
19
  * enrichment is done by the shared winston formats (see format.ts), so this
20
20
  * wrapper only maps the 5 webpieces levels and spreads an optional Error into
21
- * `errName`/`errMessage`/`errStack` — matching the tested monorepo-nx behaviour.
21
+ * `errName`/`errMessage`/`errStack` — matching the production-tested logger's behaviour.
22
22
  */
23
23
  class WinstonLogger {
24
24
  winston;
@@ -1 +1 @@
1
- {"version":3,"file":"WinstonLogger.js","sourceRoot":"","sources":["../../../../../packages/logging/winston/src/WinstonLogger.ts"],"names":[],"mappings":";;;AAGA;;;;GAIG;AACU,QAAA,gBAAgB,GAA6B;IACtD,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;CACjB,CAAC;AAEF;;;;;;GAMG;AACH,MAAa,aAAa;IACO;IAA7B,YAA6B,OAAoB;QAApB,YAAO,GAAP,OAAO,CAAa;IAAG,CAAC;IAErD,KAAK,CAAC,OAAe,EAAE,GAAW;QAC9B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAW;QAC9B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAW;QAC7B,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAW;QAC7B,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAW;QAC9B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC;IAEO,IAAI,CAAC,KAAe,EAAE,OAAe,EAAE,GAAW;QACtD,MAAM,YAAY,GAAG,wBAAgB,CAAC,KAAK,CAAC,CAAC;QAC7C,IAAI,GAAG,EAAE,CAAC;YACN,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,EAAE;gBACpC,OAAO,EAAE,GAAG,CAAC,IAAI;gBACjB,UAAU,EAAE,GAAG,CAAC,OAAO;gBACvB,QAAQ,EAAE,GAAG,CAAC,KAAK;aACtB,CAAC,CAAC;QACP,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAC5C,CAAC;IACL,CAAC;CACJ;AAnCD,sCAmCC","sourcesContent":["import type { Logger, LogLevel } from '@webpieces/core-util';\nimport type { Logger as WinstonBase } from 'winston';\n\n/**\n * webpieces LogLevel → winston level name. webpieces `trace` has no native\n * winston counterpart, so it maps onto winston `silly` (the finest npm level);\n * both resolve to GCP severity DEBUG via LEVEL_TO_SEVERITY.\n */\nexport const LEVEL_TO_WINSTON: Record<LogLevel, string> = {\n trace: 'silly',\n debug: 'debug',\n info: 'info',\n warn: 'warn',\n error: 'error',\n};\n\n/**\n * WinstonLogger - a webpieces {@link Logger} backed by a winston logger instance\n * (one per name, created as a winston child carrying `loggerName`). Context\n * enrichment is done by the shared winston formats (see format.ts), so this\n * wrapper only maps the 5 webpieces levels and spreads an optional Error into\n * `errName`/`errMessage`/`errStack` — matching the tested monorepo-nx behaviour.\n */\nexport class WinstonLogger implements Logger {\n constructor(private readonly winston: WinstonBase) {}\n\n trace(message: string, err?: Error): void {\n this.emit('trace', message, err);\n }\n\n debug(message: string, err?: Error): void {\n this.emit('debug', message, err);\n }\n\n info(message: string, err?: Error): void {\n this.emit('info', message, err);\n }\n\n warn(message: string, err?: Error): void {\n this.emit('warn', message, err);\n }\n\n error(message: string, err?: Error): void {\n this.emit('error', message, err);\n }\n\n private emit(level: LogLevel, message: string, err?: Error): void {\n const winstonLevel = LEVEL_TO_WINSTON[level];\n if (err) {\n this.winston.log(winstonLevel, message, {\n errName: err.name,\n errMessage: err.message,\n errStack: err.stack,\n });\n } else {\n this.winston.log(winstonLevel, message);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"WinstonLogger.js","sourceRoot":"","sources":["../../../../../packages/logging/winston/src/WinstonLogger.ts"],"names":[],"mappings":";;;AAGA;;;;GAIG;AACU,QAAA,gBAAgB,GAA6B;IACtD,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;CACjB,CAAC;AAEF;;;;;;GAMG;AACH,MAAa,aAAa;IACO;IAA7B,YAA6B,OAAoB;QAApB,YAAO,GAAP,OAAO,CAAa;IAAG,CAAC;IAErD,KAAK,CAAC,OAAe,EAAE,GAAW;QAC9B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAW;QAC9B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAW;QAC7B,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAW;QAC7B,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAW;QAC9B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC;IAEO,IAAI,CAAC,KAAe,EAAE,OAAe,EAAE,GAAW;QACtD,MAAM,YAAY,GAAG,wBAAgB,CAAC,KAAK,CAAC,CAAC;QAC7C,IAAI,GAAG,EAAE,CAAC;YACN,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,EAAE;gBACpC,OAAO,EAAE,GAAG,CAAC,IAAI;gBACjB,UAAU,EAAE,GAAG,CAAC,OAAO;gBACvB,QAAQ,EAAE,GAAG,CAAC,KAAK;aACtB,CAAC,CAAC;QACP,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAC5C,CAAC;IACL,CAAC;CACJ;AAnCD,sCAmCC","sourcesContent":["import type { Logger, LogLevel } from '@webpieces/core-util';\nimport type { Logger as WinstonBase } from 'winston';\n\n/**\n * webpieces LogLevel → winston level name. webpieces `trace` has no native\n * winston counterpart, so it maps onto winston `silly` (the finest npm level);\n * both resolve to GCP severity DEBUG via LEVEL_TO_SEVERITY.\n */\nexport const LEVEL_TO_WINSTON: Record<LogLevel, string> = {\n trace: 'silly',\n debug: 'debug',\n info: 'info',\n warn: 'warn',\n error: 'error',\n};\n\n/**\n * WinstonLogger - a webpieces {@link Logger} backed by a winston logger instance\n * (one per name, created as a winston child carrying `loggerName`). Context\n * enrichment is done by the shared winston formats (see format.ts), so this\n * wrapper only maps the 5 webpieces levels and spreads an optional Error into\n * `errName`/`errMessage`/`errStack` — matching the production-tested logger's behaviour.\n */\nexport class WinstonLogger implements Logger {\n constructor(private readonly winston: WinstonBase) {}\n\n trace(message: string, err?: Error): void {\n this.emit('trace', message, err);\n }\n\n debug(message: string, err?: Error): void {\n this.emit('debug', message, err);\n }\n\n info(message: string, err?: Error): void {\n this.emit('info', message, err);\n }\n\n warn(message: string, err?: Error): void {\n this.emit('warn', message, err);\n }\n\n error(message: string, err?: Error): void {\n this.emit('error', message, err);\n }\n\n private emit(level: LogLevel, message: string, err?: Error): void {\n const winstonLevel = LEVEL_TO_WINSTON[level];\n if (err) {\n this.winston.log(winstonLevel, message, {\n errName: err.name,\n errMessage: err.message,\n errStack: err.stack,\n });\n } else {\n this.winston.log(winstonLevel, message);\n }\n }\n}\n"]}
package/src/format.d.ts CHANGED
@@ -32,7 +32,7 @@ export declare function severityFormat(): Format;
32
32
  /**
33
33
  * Local-only human format:
34
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
35
+ * Level FIRST, then time (the ordering the production-tested format was tuned for). `controller`/`method` render
36
36
  * as a compact bracket, `loggerName` as its own bracket, every other registered context key as a
37
37
  * `key:value` tag, and anything else the caller attached as trailing JSON. Byte-identical to the bunyan
38
38
  * backend for the same record + `fields`.
package/src/format.js CHANGED
@@ -8,8 +8,7 @@ exports.localPrettyFormat = localPrettyFormat;
8
8
  /**
9
9
  * The winston format layers that turn a raw webpieces log call into a
10
10
  * Cloud-Logging-ready structured record. Ported verbatim (behaviourally) from
11
- * the tested-in-GCP logger at
12
- * onetablet/monorepo-nx1 libraries/core-context/src/logger/format.ts, with the
11
+ * a production-tested GCP logger's format module, with the
13
12
  * one webpieces adaptation: context is read from the webpieces HeaderRegistry +
14
13
  * a ContextReader (rather than a hard-coded PLATFORM_HEADERS enum), so the exact
15
14
  * set of logged fields is whatever the app registered.
@@ -141,7 +140,7 @@ const LOCAL_STRUCTURAL_KEYS = new Set([
141
140
  /**
142
141
  * Local-only human format:
143
142
  * `[LEVEL][time][Controller.method][loggerName][ctx tags]: message { …extra }` + Error Details block.
144
- * Level FIRST, then time (the ordering the trytami format was tuned for). `controller`/`method` render
143
+ * Level FIRST, then time (the ordering the production-tested format was tuned for). `controller`/`method` render
145
144
  * as a compact bracket, `loggerName` as its own bracket, every other registered context key as a
146
145
  * `key:value` tag, and anything else the caller attached as trailing JSON. Byte-identical to the bunyan
147
146
  * backend for the same record + `fields`.
@@ -201,7 +200,7 @@ function localPrettyFormat(fields) {
201
200
  const restStr = Object.keys(rest).length ? ` ${(0, safe_stable_stringify_1.stringify)(rest)}` : '';
202
201
  let line = `[${levelName}][${time}]${controllerMethod}${loggerBracket}[${tagStr}]: ${info.message}${restStr}`;
203
202
  // 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.
203
+ // into errName/errMessage/errStack; render them the same way the production-tested logger's writeConsole did.
205
204
  if (info['errName'] != null || info['errMessage'] != null || info['errStack'] != null) {
206
205
  line += `\nError Details:`;
207
206
  line += `\n Message: ${String(info['errMessage'] ?? '')}`;
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, ApiCallLogName } from '@webpieces/core-util';\nimport type { ContextKey, AnyContextKey } 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.maskForLogs} — 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 // Called UNCONDITIONALLY (no active-context guard): buildStructuredLogFields returns the build\n // `version` even with NO active RequestContext — so startup, background-job, and unwrapped-call\n // lines carry `version` too — plus the logged context keys when one IS active. An empty context\n // is normal, not an error; the one place a request-path wrap can legitimately be missing (the\n // in-process client) is still caught precisely by ApiClientFactory.requireActiveContext(), which\n // throws at the api boundary. Values may be OBJECTS (the `api` tag), so an object-valued key\n // nests into jsonPayload.<name> (winston JSON-serializes the whole record) rather than being\n // dropped by the string-only buildLogFields. Caller-supplied 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 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: AnyContextKey) => 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 // LogApiCall lines render as a self-describing [API.{side}.{phase}] bracket instead of the opaque\n // [LogApiCall]; every other line keeps its plain [loggerName] bracket. (info indexes to `unknown`.)\n const loggerBracket = ApiCallLogName.bracket(\n info['loggerName'] as string | undefined, info['api'] as object | undefined);\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"]}
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 * a production-tested GCP logger's format module, 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, ApiCallLogName } from '@webpieces/core-util';\nimport type { ContextKey, AnyContextKey } 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.maskForLogs} — 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 // Called UNCONDITIONALLY (no active-context guard): buildStructuredLogFields returns the build\n // `version` even with NO active RequestContext — so startup, background-job, and unwrapped-call\n // lines carry `version` too — plus the logged context keys when one IS active. An empty context\n // is normal, not an error; the one place a request-path wrap can legitimately be missing (the\n // in-process client) is still caught precisely by ApiClientFactory.requireActiveContext(), which\n // throws at the api boundary. Values may be OBJECTS (the `api` tag), so an object-valued key\n // nests into jsonPayload.<name> (winston JSON-serializes the whole record) rather than being\n // dropped by the string-only buildLogFields. Caller-supplied 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 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 production-tested 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: AnyContextKey) => 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 // LogApiCall lines render as a self-describing [API.{side}.{phase}] bracket instead of the opaque\n // [LogApiCall]; every other line keeps its plain [loggerName] bracket. (info indexes to `unknown`.)\n const loggerBracket = ApiCallLogName.bracket(\n info['loggerName'] as string | undefined, info['api'] as object | undefined);\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 the production-tested logger'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"]}