@webpieces/bunyan 0.4.401 → 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/bunyan",
3
- "version": "0.4.401",
3
+ "version": "0.4.403",
4
4
  "description": "Node-only bunyan LoggerFactory backends for webpieces: Console (local pretty) + GCP (@google-cloud/logging-bunyan), 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.401",
27
- "@webpieces/core-context": "0.4.401",
26
+ "@webpieces/core-util": "0.4.403",
27
+ "@webpieces/core-context": "0.4.403",
28
28
  "bunyan": "1.8.15",
29
29
  "@google-cloud/logging-bunyan": "5.1.0",
30
30
  "@types/bunyan": "1.8.11"
@@ -1,13 +1,16 @@
1
1
  import { BunyanFactoryBase } from './BunyanFactoryBase';
2
2
  /**
3
3
  * BunyanConsoleFactory - the LOCAL developer backend. Human-readable, greppable
4
- * text to stdout (`[LEVEL][time][ctx tags]: message`) with the registered context
5
- * keys as tags — same enrichment as the GCP backend, different rendering. Mirrors
4
+ * text to stdout (`[LEVEL][time][Controller.method][loggerName][ctx tags]: message`) with the
5
+ * registered context keys as tags — same enrichment as the GCP backend, different rendering. Mirrors
6
6
  * the tested trytami local console stream.
7
7
  *
8
+ * `consoleFields`, when given, is the app-chosen ordered ALLOW-LIST of context keys to render in the
9
+ * console line (hides local noise like `requestPath`); GCP still receives every logged key.
10
+ *
8
11
  * The service name + version come from {@link ServiceInfo}, which startup must have populated
9
12
  * BEFORE constructing this.
10
13
  */
11
14
  export declare class BunyanConsoleFactory extends BunyanFactoryBase {
12
- constructor();
15
+ constructor(consoleFields?: string[]);
13
16
  }
@@ -5,16 +5,19 @@ const BunyanFactoryBase_1 = require("./BunyanFactoryBase");
5
5
  const streams_1 = require("./streams");
6
6
  /**
7
7
  * BunyanConsoleFactory - the LOCAL developer backend. Human-readable, greppable
8
- * text to stdout (`[LEVEL][time][ctx tags]: message`) with the registered context
9
- * keys as tags — same enrichment as the GCP backend, different rendering. Mirrors
8
+ * text to stdout (`[LEVEL][time][Controller.method][loggerName][ctx tags]: message`) with the
9
+ * registered context keys as tags — same enrichment as the GCP backend, different rendering. Mirrors
10
10
  * the tested trytami local console stream.
11
11
  *
12
+ * `consoleFields`, when given, is the app-chosen ordered ALLOW-LIST of context keys to render in the
13
+ * console line (hides local noise like `requestPath`); GCP still receives every logged key.
14
+ *
12
15
  * The service name + version come from {@link ServiceInfo}, which startup must have populated
13
16
  * BEFORE constructing this.
14
17
  */
15
18
  class BunyanConsoleFactory extends BunyanFactoryBase_1.BunyanFactoryBase {
16
- constructor() {
17
- super([(0, streams_1.createConsoleStream)()]);
19
+ constructor(consoleFields) {
20
+ super([(0, streams_1.createConsoleStream)(consoleFields)]);
18
21
  }
19
22
  }
20
23
  exports.BunyanConsoleFactory = BunyanConsoleFactory;
@@ -1 +1 @@
1
- {"version":3,"file":"BunyanConsoleFactory.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/BunyanConsoleFactory.ts"],"names":[],"mappings":";;;AAAA,2DAAwD;AACxD,uCAAgD;AAEhD;;;;;;;;GAQG;AACH,MAAa,oBAAqB,SAAQ,qCAAiB;IACvD;QACI,KAAK,CAAC,CAAC,IAAA,6BAAmB,GAAE,CAAC,CAAC,CAAC;IACnC,CAAC;CACJ;AAJD,oDAIC","sourcesContent":["import { BunyanFactoryBase } from './BunyanFactoryBase';\nimport { createConsoleStream } from './streams';\n\n/**\n * BunyanConsoleFactory - the LOCAL developer backend. Human-readable, greppable\n * text to stdout (`[LEVEL][time][ctx tags]: message`) with the registered context\n * keys as tags — same enrichment as the GCP backend, different rendering. Mirrors\n * the tested trytami local console stream.\n *\n * The service name + version come from {@link ServiceInfo}, which startup must have populated\n * BEFORE constructing this.\n */\nexport class BunyanConsoleFactory extends BunyanFactoryBase {\n constructor() {\n super([createConsoleStream()]);\n }\n}\n"]}
1
+ {"version":3,"file":"BunyanConsoleFactory.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/BunyanConsoleFactory.ts"],"names":[],"mappings":";;;AAAA,2DAAwD;AACxD,uCAAgD;AAEhD;;;;;;;;;;;GAWG;AACH,MAAa,oBAAqB,SAAQ,qCAAiB;IACvD,YAAY,aAAwB;QAChC,KAAK,CAAC,CAAC,IAAA,6BAAmB,EAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;CACJ;AAJD,oDAIC","sourcesContent":["import { BunyanFactoryBase } from './BunyanFactoryBase';\nimport { createConsoleStream } from './streams';\n\n/**\n * BunyanConsoleFactory - the LOCAL developer backend. Human-readable, greppable\n * text to stdout (`[LEVEL][time][Controller.method][loggerName][ctx tags]: message`) with the\n * registered context keys as tags — same enrichment as the GCP backend, different rendering. Mirrors\n * the tested trytami local console stream.\n *\n * `consoleFields`, when given, is the app-chosen ordered ALLOW-LIST of context keys to render in the\n * console line (hides local noise like `requestPath`); GCP still receives every logged key.\n *\n * The service name + version come from {@link ServiceInfo}, which startup must have populated\n * BEFORE constructing this.\n */\nexport class BunyanConsoleFactory extends BunyanFactoryBase {\n constructor(consoleFields?: string[]) {\n super([createConsoleStream(consoleFields)]);\n }\n}\n"]}
package/src/streams.d.ts CHANGED
@@ -17,5 +17,9 @@ export declare function createGoogleCloudStream(): Logger.Stream;
17
17
  /**
18
18
  * The local dev stream: human-readable text to stdout via {@link writeConsole}.
19
19
  * No level is set — bunyan filters at its own default ('info').
20
+ *
21
+ * `consoleFields`, when given, is the app-chosen ordered ALLOW-LIST of context keys to render in the
22
+ * console line (hides noise like `requestPath` locally while GCP still gets every logged key). When
23
+ * omitted, every non-structural context key renders in record order.
20
24
  */
21
- export declare function createConsoleStream(): Logger.Stream;
25
+ export declare function createConsoleStream(consoleFields?: string[]): Logger.Stream;
package/src/streams.js CHANGED
@@ -7,6 +7,11 @@ const stream_1 = require("stream");
7
7
  const bunyan_1 = tslib_1.__importDefault(require("bunyan"));
8
8
  const logging_bunyan_1 = require("@google-cloud/logging-bunyan");
9
9
  const ChunkingRawStream_1 = require("./ChunkingRawStream");
10
+ // The two context keys rendered SPECIALLY (as the compact `[Controller.method]` bracket) rather than
11
+ // as `key:value` tags — they name WHICH CODE ran, the thing you grep for. Kept in sync with
12
+ // WebpiecesCoreHeaders.CONTROLLER / .METHOD by name. They are excluded from the generic tag loop.
13
+ const CONTROLLER_FIELD = 'controller';
14
+ const METHOD_FIELD = 'method';
10
15
  // bunyan record fields that are structural / rendered specially, so they are not
11
16
  // shown as context tags in the local console line.
12
17
  //
@@ -15,6 +20,9 @@ const ChunkingRawStream_1 = require("./ChunkingRawStream");
15
20
  // locally each service logs to its own place and you can check git yourself — so as a tag on every
16
21
  // single line they are pure noise. Listing them here is the bunyan twin of winston's
17
22
  // LOCAL_STRUCTURAL_KEYS; GCP still gets both (that stream does its own formatting).
23
+ //
24
+ // `loggerName` (its own `[…]` bracket) and `controller`/`method` (the `[Controller.method]` bracket)
25
+ // are rendered specially too, so they are excluded here from the generic `key:value` tag loop.
18
26
  const BUNYAN_STD_FIELDS = new Set([
19
27
  'v',
20
28
  'level',
@@ -27,37 +35,88 @@ const BUNYAN_STD_FIELDS = new Set([
27
35
  'src',
28
36
  'err',
29
37
  'loggerName',
38
+ CONTROLLER_FIELD,
39
+ METHOD_FIELD,
30
40
  ]);
41
+ // "HH:MM:SS.mmm" in LOCAL time — matching the winston backend (fecha 'HH:mm:ss.SSS') and the trytami
42
+ // format this was tuned for (`Date.toTimeString()`, also local). Parsing the ISO to a Date and reading
43
+ // local fields (rather than splitting the UTC ISO string) is what keeps the two backends byte-identical;
44
+ // milliseconds are zero-padded to 3 digits (trytami's raw `getMilliseconds()` rendered 5ms as ".5").
45
+ // webpieces-disable no-function-outside-class -- bunyan render helper; whole file is bunyan stream/render factories
31
46
  function formatTime(iso) {
32
47
  if (typeof iso !== 'string') {
33
48
  return '';
34
49
  }
35
- // ISO 8601 "2026-07-08T12:34:56.789Z" → "12:34:56.789"
36
- const timePart = iso.split('T')[1];
37
- return timePart ? timePart.replace('Z', '') : iso;
50
+ const d = new Date(iso);
51
+ if (Number.isNaN(d.getTime())) {
52
+ return iso;
53
+ }
54
+ const p2 = (n) => String(n).padStart(2, '0');
55
+ const p3 = (n) => String(n).padStart(3, '0');
56
+ return `${p2(d.getHours())}:${p2(d.getMinutes())}:${p2(d.getSeconds())}.${p3(d.getMilliseconds())}`;
57
+ }
58
+ // The compact `[Controller.method]` bracket (or `[Controller]`, or '' when neither is present, e.g. a
59
+ // startup / static / pre-route line). Empty controller drops the whole bracket.
60
+ // webpieces-disable no-function-outside-class -- bunyan render helper; whole file is bunyan stream/render factories
61
+ function formatControllerMethod(controller, method) {
62
+ if (typeof controller !== 'string' || controller.length === 0) {
63
+ return '';
64
+ }
65
+ const suffix = typeof method === 'string' && method.length > 0 ? `.${method}` : '';
66
+ return `[${controller}${suffix}]`;
67
+ }
68
+ // Build the ordered `key:value` context tags. When `fields` is given it is an app-chosen ALLOW-LIST:
69
+ // only those keys render, in that order (specials skipped). When absent, every non-structural string
70
+ // field renders in record order. Empty/object-valued fields are dropped either way.
71
+ // webpieces-disable no-function-outside-class -- bunyan render helper; whole file is bunyan stream/render factories
72
+ function buildTags(obj, fields) {
73
+ const tags = [];
74
+ const push = (key) => {
75
+ const value = obj[key];
76
+ if (typeof value === 'object' || value == null) {
77
+ return;
78
+ }
79
+ const str = String(value);
80
+ if (str.length === 0) {
81
+ return;
82
+ }
83
+ tags.push(`${key}:${str}`);
84
+ };
85
+ if (fields) {
86
+ for (const key of fields) {
87
+ if (key === CONTROLLER_FIELD || key === METHOD_FIELD || key === 'loggerName') {
88
+ continue;
89
+ }
90
+ push(key);
91
+ }
92
+ }
93
+ else {
94
+ for (const key of Object.keys(obj)) {
95
+ if (BUNYAN_STD_FIELDS.has(key)) {
96
+ continue;
97
+ }
98
+ push(key);
99
+ }
100
+ }
101
+ return tags;
38
102
  }
39
103
  /**
40
104
  * Render one bunyan JSON line as a human-readable, greppable console line:
41
- * `[LEVEL][time][ctx tags]: message` plus multi-line error details. Ported from
42
- * the tested trytami writeConsole, generalized: every non-structural field
43
- * (i.e. the injected context keys) becomes a `key:value` tag.
105
+ * `[LEVEL][time][Controller.method][loggerName][ctx tags]: message` plus multi-line error details.
106
+ * Level FIRST, then time (the ordering the trytami format was tuned for). `controller`/`method` render
107
+ * as a compact bracket, `loggerName` as its own bracket, and every other injected context key becomes a
108
+ * `key:value` tag (optionally filtered/ordered by the app `fields` allow-list).
44
109
  */
45
- function writeConsole(line) {
110
+ // webpieces-disable no-function-outside-class -- bunyan render helper; whole file is bunyan stream/render factories
111
+ function writeConsole(line, fields) {
46
112
  const obj = JSON.parse(line);
47
113
  const levelName = (bunyan_1.default.nameFromLevel[obj['level']] ?? 'info').toUpperCase().padEnd(5);
48
114
  const time = formatTime(obj['time']);
49
- const tags = [];
50
- if (obj['loggerName']) {
51
- tags.push(`logger:${String(obj['loggerName'])}`);
52
- }
53
- for (const key of Object.keys(obj)) {
54
- if (BUNYAN_STD_FIELDS.has(key)) {
55
- continue;
56
- }
57
- tags.push(`${key}:${String(obj[key])}`);
58
- }
115
+ const controllerMethod = formatControllerMethod(obj[CONTROLLER_FIELD], obj[METHOD_FIELD]);
116
+ const loggerBracket = obj['loggerName'] ? `[${String(obj['loggerName'])}]` : '';
117
+ const tags = buildTags(obj, fields);
59
118
  const tagStr = tags.length > 0 ? tags.join(', ') : 'no-context';
60
- let message = `[${levelName}][${time}][${tagStr}]: ${String(obj['msg'] ?? '')}`;
119
+ let message = `[${levelName}][${time}]${controllerMethod}${loggerBracket}[${tagStr}]: ${String(obj['msg'] ?? '')}`;
61
120
  const err = obj['err'];
62
121
  if (err) {
63
122
  message += `\nError Details:`;
@@ -99,12 +158,16 @@ function createGoogleCloudStream() {
99
158
  /**
100
159
  * The local dev stream: human-readable text to stdout via {@link writeConsole}.
101
160
  * No level is set — bunyan filters at its own default ('info').
161
+ *
162
+ * `consoleFields`, when given, is the app-chosen ordered ALLOW-LIST of context keys to render in the
163
+ * console line (hides noise like `requestPath` locally while GCP still gets every logged key). When
164
+ * omitted, every non-structural context key renders in record order.
102
165
  */
103
166
  // webpieces-disable no-function-outside-class -- bunyan Stream factory; whole file is bunyan stream/render factories
104
- function createConsoleStream() {
167
+ function createConsoleStream(consoleFields) {
105
168
  const writable = new stream_1.Writable({
106
169
  write(chunk, _encoding, callback) {
107
- writeConsole(chunk.toString());
170
+ writeConsole(chunk.toString(), consoleFields);
108
171
  callback();
109
172
  },
110
173
  });
@@ -1 +1 @@
1
- {"version":3,"file":"streams.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/streams.ts"],"names":[],"mappings":"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","sourcesContent":["import { Writable } from 'stream';\nimport Logger from 'bunyan';\nimport { LoggingBunyan } from '@google-cloud/logging-bunyan';\nimport { LoggedError } from './LoggedError';\nimport { ChunkingRawStream } from './ChunkingRawStream';\n\n// A parsed bunyan JSON record: standard fields plus arbitrary injected context\n// tags. Values are whatever JSON holds.\ntype JsonValue = string | number | boolean | object | null;\ntype BunyanRecord = Record<string, JsonValue>;\n\n// bunyan record fields that are structural / rendered specially, so they are not\n// shown as context tags in the local console line.\n//\n// `name` (the service) and `version` (the build) are ServiceInfo base fields riding on EVERY\n// record. They earn their keep in GCP, where you filter across many services and deploys, but\n// locally each service logs to its own place and you can check git yourself — so as a tag on every\n// single line they are pure noise. Listing them here is the bunyan twin of winston's\n// LOCAL_STRUCTURAL_KEYS; GCP still gets both (that stream does its own formatting).\nconst BUNYAN_STD_FIELDS = new Set<string>([\n 'v',\n 'level',\n 'name',\n 'version',\n 'hostname',\n 'pid',\n 'time',\n 'msg',\n 'src',\n 'err',\n 'loggerName',\n]);\n\nfunction formatTime(iso: JsonValue): string {\n if (typeof iso !== 'string') {\n return '';\n }\n // ISO 8601 \"2026-07-08T12:34:56.789Z\" → \"12:34:56.789\"\n const timePart = iso.split('T')[1];\n return timePart ? timePart.replace('Z', '') : iso;\n}\n\n/**\n * Render one bunyan JSON line as a human-readable, greppable console line:\n * `[LEVEL][time][ctx tags]: message` plus multi-line error details. Ported from\n * the tested trytami writeConsole, generalized: every non-structural field\n * (i.e. the injected context keys) becomes a `key:value` tag.\n */\nfunction writeConsole(line: string): void {\n const obj: BunyanRecord = JSON.parse(line);\n const levelName = (Logger.nameFromLevel[obj['level'] as number] ?? 'info').toUpperCase().padEnd(5);\n const time = formatTime(obj['time']);\n\n const tags: string[] = [];\n if (obj['loggerName']) {\n tags.push(`logger:${String(obj['loggerName'])}`);\n }\n for (const key of Object.keys(obj)) {\n if (BUNYAN_STD_FIELDS.has(key)) {\n continue;\n }\n tags.push(`${key}:${String(obj[key])}`);\n }\n const tagStr = tags.length > 0 ? tags.join(', ') : 'no-context';\n\n let message = `[${levelName}][${time}][${tagStr}]: ${String(obj['msg'] ?? '')}`;\n\n const err = obj['err'] as LoggedError | undefined;\n if (err) {\n message += `\\nError Details:`;\n message += `\\n Message: ${err.message}`;\n message += `\\n Name: ${err.name}`;\n if (err.stack) {\n message += `\\n Stack Trace:\\n${err.stack}`;\n }\n }\n\n // This IS a logging backend (the console sink); direct stdout is intentional.\n console.log(message);\n}\n\n/**\n * The GCP stream: delegates ALL structured-JSON formatting (numeric level → GCP\n * severity, msg→message, trace/httpRequest fields, stripping name/hostname/pid)\n * to @google-cloud/logging-bunyan, exactly as the tested trytami service does.\n * Sends to the Cloud Logging API (needs ADC on the instance).\n *\n * Records pass through a {@link ChunkingRawStream} first, because Cloud Logging caps a LogEntry at\n * 256 KiB and rejects the whole `entries.write` call when one entry exceeds it — so a single fat\n * response body or stack trace can take a batch of good entries down with it. Oversized records are\n * SPLIT into several complete records sharing a `logChunk.uid` rather than lost or truncated.\n *\n * We do NOT filter by level — that is bunyan's job. The stream is created at\n * bunyan's default level ('info'); there is no webpieces level knob.\n */\n// webpieces-disable no-function-outside-class -- bunyan Stream factory; whole file is bunyan stream/render factories\nexport function createGoogleCloudStream(): Logger.Stream {\n const loggingBunyan = new LoggingBunyan();\n // stream('info') returns { level, type: 'raw', stream: loggingBunyan } — we keep bunyan's raw\n // contract and only interpose on the stream it writes records to.\n const gcp = loggingBunyan.stream('info');\n return {\n level: gcp.level,\n type: 'raw',\n stream: new ChunkingRawStream(loggingBunyan),\n };\n}\n\n/**\n * The local dev stream: human-readable text to stdout via {@link writeConsole}.\n * No level is set — bunyan filters at its own default ('info').\n */\n// webpieces-disable no-function-outside-class -- bunyan Stream factory; whole file is bunyan stream/render factories\nexport function createConsoleStream(): Logger.Stream {\n const writable = new Writable({\n write(chunk: Buffer | string, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void {\n writeConsole(chunk.toString());\n callback();\n },\n });\n return {\n name: 'console',\n stream: writable,\n };\n}\n"]}
1
+ {"version":3,"file":"streams.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/streams.ts"],"names":[],"mappings":"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","sourcesContent":["import { Writable } from 'stream';\nimport Logger from 'bunyan';\nimport { LoggingBunyan } from '@google-cloud/logging-bunyan';\nimport { LoggedError } from './LoggedError';\nimport { ChunkingRawStream } from './ChunkingRawStream';\n\n// A parsed bunyan JSON record: standard fields plus arbitrary injected context\n// tags. Values are whatever JSON holds.\ntype JsonValue = string | number | boolean | object | null;\ntype BunyanRecord = Record<string, JsonValue>;\n\n// The two context keys rendered SPECIALLY (as the compact `[Controller.method]` bracket) rather than\n// as `key:value` tags — they name WHICH CODE ran, the thing you grep for. Kept in sync with\n// WebpiecesCoreHeaders.CONTROLLER / .METHOD by name. They are excluded from the generic tag loop.\nconst CONTROLLER_FIELD = 'controller';\nconst METHOD_FIELD = 'method';\n\n// bunyan record fields that are structural / rendered specially, so they are not\n// shown as context tags in the local console line.\n//\n// `name` (the service) and `version` (the build) are ServiceInfo base fields riding on EVERY\n// record. They earn their keep in GCP, where you filter across many services and deploys, but\n// locally each service logs to its own place and you can check git yourself — so as a tag on every\n// single line they are pure noise. Listing them here is the bunyan twin of winston's\n// LOCAL_STRUCTURAL_KEYS; GCP still gets both (that stream does its own formatting).\n//\n// `loggerName` (its own `[…]` bracket) and `controller`/`method` (the `[Controller.method]` bracket)\n// are rendered specially too, so they are excluded here from the generic `key:value` tag loop.\nconst BUNYAN_STD_FIELDS = new Set<string>([\n 'v',\n 'level',\n 'name',\n 'version',\n 'hostname',\n 'pid',\n 'time',\n 'msg',\n 'src',\n 'err',\n 'loggerName',\n CONTROLLER_FIELD,\n METHOD_FIELD,\n]);\n\n// \"HH:MM:SS.mmm\" in LOCAL time — matching the winston backend (fecha 'HH:mm:ss.SSS') and the trytami\n// format this was tuned for (`Date.toTimeString()`, also local). Parsing the ISO to a Date and reading\n// local fields (rather than splitting the UTC ISO string) is what keeps the two backends byte-identical;\n// milliseconds are zero-padded to 3 digits (trytami's raw `getMilliseconds()` rendered 5ms as \".5\").\n// webpieces-disable no-function-outside-class -- bunyan render helper; whole file is bunyan stream/render factories\nfunction formatTime(iso: JsonValue): string {\n if (typeof iso !== 'string') {\n return '';\n }\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) {\n return iso;\n }\n const p2 = (n: number): string => String(n).padStart(2, '0');\n const p3 = (n: number): string => String(n).padStart(3, '0');\n return `${p2(d.getHours())}:${p2(d.getMinutes())}:${p2(d.getSeconds())}.${p3(d.getMilliseconds())}`;\n}\n\n// The compact `[Controller.method]` bracket (or `[Controller]`, or '' when neither is present, e.g. a\n// startup / static / pre-route line). Empty controller drops the whole bracket.\n// webpieces-disable no-function-outside-class -- bunyan render helper; whole file is bunyan stream/render factories\nfunction formatControllerMethod(controller: JsonValue, method: JsonValue): string {\n if (typeof controller !== 'string' || controller.length === 0) {\n return '';\n }\n const suffix = typeof method === 'string' && method.length > 0 ? `.${method}` : '';\n return `[${controller}${suffix}]`;\n}\n\n// Build the ordered `key:value` context tags. When `fields` is given it is an app-chosen ALLOW-LIST:\n// only those keys render, in that order (specials skipped). When absent, every non-structural string\n// field renders in record order. Empty/object-valued fields are dropped either way.\n// webpieces-disable no-function-outside-class -- bunyan render helper; whole file is bunyan stream/render factories\nfunction buildTags(obj: BunyanRecord, fields?: string[]): string[] {\n const tags: string[] = [];\n const push = (key: string): void => {\n const value = obj[key];\n if (typeof value === 'object' || value == null) {\n return;\n }\n const str = String(value);\n if (str.length === 0) {\n return;\n }\n tags.push(`${key}:${str}`);\n };\n if (fields) {\n for (const key of fields) {\n if (key === CONTROLLER_FIELD || key === METHOD_FIELD || key === 'loggerName') {\n continue;\n }\n push(key);\n }\n } else {\n for (const key of Object.keys(obj)) {\n if (BUNYAN_STD_FIELDS.has(key)) {\n continue;\n }\n push(key);\n }\n }\n return tags;\n}\n\n/**\n * Render one bunyan JSON line as a human-readable, greppable console line:\n * `[LEVEL][time][Controller.method][loggerName][ctx tags]: message` plus multi-line error details.\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, and every other injected context key becomes a\n * `key:value` tag (optionally filtered/ordered by the app `fields` allow-list).\n */\n// webpieces-disable no-function-outside-class -- bunyan render helper; whole file is bunyan stream/render factories\nfunction writeConsole(line: string, fields?: string[]): void {\n const obj: BunyanRecord = JSON.parse(line);\n const levelName = (Logger.nameFromLevel[obj['level'] as number] ?? 'info').toUpperCase().padEnd(5);\n const time = formatTime(obj['time']);\n\n const controllerMethod = formatControllerMethod(obj[CONTROLLER_FIELD], obj[METHOD_FIELD]);\n const loggerBracket = obj['loggerName'] ? `[${String(obj['loggerName'])}]` : '';\n\n const tags = buildTags(obj, fields);\n const tagStr = tags.length > 0 ? tags.join(', ') : 'no-context';\n\n let message = `[${levelName}][${time}]${controllerMethod}${loggerBracket}[${tagStr}]: ${String(obj['msg'] ?? '')}`;\n\n const err = obj['err'] as LoggedError | undefined;\n if (err) {\n message += `\\nError Details:`;\n message += `\\n Message: ${err.message}`;\n message += `\\n Name: ${err.name}`;\n if (err.stack) {\n message += `\\n Stack Trace:\\n${err.stack}`;\n }\n }\n\n // This IS a logging backend (the console sink); direct stdout is intentional.\n console.log(message);\n}\n\n/**\n * The GCP stream: delegates ALL structured-JSON formatting (numeric level → GCP\n * severity, msg→message, trace/httpRequest fields, stripping name/hostname/pid)\n * to @google-cloud/logging-bunyan, exactly as the tested trytami service does.\n * Sends to the Cloud Logging API (needs ADC on the instance).\n *\n * Records pass through a {@link ChunkingRawStream} first, because Cloud Logging caps a LogEntry at\n * 256 KiB and rejects the whole `entries.write` call when one entry exceeds it — so a single fat\n * response body or stack trace can take a batch of good entries down with it. Oversized records are\n * SPLIT into several complete records sharing a `logChunk.uid` rather than lost or truncated.\n *\n * We do NOT filter by level — that is bunyan's job. The stream is created at\n * bunyan's default level ('info'); there is no webpieces level knob.\n */\n// webpieces-disable no-function-outside-class -- bunyan Stream factory; whole file is bunyan stream/render factories\nexport function createGoogleCloudStream(): Logger.Stream {\n const loggingBunyan = new LoggingBunyan();\n // stream('info') returns { level, type: 'raw', stream: loggingBunyan } — we keep bunyan's raw\n // contract and only interpose on the stream it writes records to.\n const gcp = loggingBunyan.stream('info');\n return {\n level: gcp.level,\n type: 'raw',\n stream: new ChunkingRawStream(loggingBunyan),\n };\n}\n\n/**\n * The local dev stream: human-readable text to stdout via {@link writeConsole}.\n * No level is set — bunyan filters at its own default ('info').\n *\n * `consoleFields`, when given, is the app-chosen ordered ALLOW-LIST of context keys to render in the\n * console line (hides noise like `requestPath` locally while GCP still gets every logged key). When\n * omitted, every non-structural context key renders in record order.\n */\n// webpieces-disable no-function-outside-class -- bunyan Stream factory; whole file is bunyan stream/render factories\nexport function createConsoleStream(consoleFields?: string[]): Logger.Stream {\n const writable = new Writable({\n write(chunk: Buffer | string, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void {\n writeConsole(chunk.toString(), consoleFields);\n callback();\n },\n });\n return {\n name: 'console',\n stream: writable,\n };\n}\n"]}