@webpieces/bunyan 0.3.307

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 ADDED
@@ -0,0 +1,44 @@
1
+ # @webpieces/bunyan
2
+
3
+ Node-only [bunyan](https://github.com/trentm/node-bunyan) backends for the webpieces
4
+ pluggable logging seam (`LoggerFactory` → `Logger` from `@webpieces/core-util`).
5
+
6
+ Two factories, both auto-enriching every line with the logged context keys registered in
7
+ `HeaderRegistry`:
8
+
9
+ - **`BunyanConsoleFactory`** — local dev: human-readable, greppable text to stdout,
10
+ `[LEVEL][time][ctx tags]: message` + multi-line error details.
11
+ - **`BunyanGcpFactory`** — GCP: streams to Cloud Logging via
12
+ [`@google-cloud/logging-bunyan`](https://github.com/googleapis/nodejs-logging-bunyan),
13
+ which owns the numeric-level→severity mapping and structured payload. Registered context
14
+ keys ride along as payload fields. This mirrors the tested-in-GCP `trytami` service.
15
+
16
+ ## Usage
17
+
18
+ ```ts
19
+ import { LogManager, HeaderRegistry } from '@webpieces/core-util';
20
+ import { BunyanGcpFactory, BunyanConsoleFactory } from '@webpieces/bunyan';
21
+ import { RequestContextReader } from '@webpieces/core-context';
22
+
23
+ const reader = new RequestContextReader();
24
+ const loggerFactory = process.env.K_SERVICE
25
+ ? new BunyanGcpFactory(reader)
26
+ : new BunyanConsoleFactory(reader);
27
+
28
+ // Typically you pass loggerFactory to setupRuntime(new RuntimeSetupOptions(loggerFactory, ...)),
29
+ // which calls HeaderRegistry.configure(...) then LogManager.setFactory(loggerFactory) for you.
30
+ ```
31
+
32
+ The `ContextReader` is a **constructor argument** (the node `RequestContextReader` lives in
33
+ `@webpieces/core-context`) so this package depends only on `@webpieces/core-util` — not on
34
+ any node context package.
35
+
36
+ `BunyanGcpFactory` sends to the Cloud Logging API and needs GCP Application Default
37
+ Credentials on the instance (automatic on Cloud Run), exactly as the source service runs.
38
+
39
+ ## Options
40
+
41
+ `new BunyanGcpFactory(reader, new BunyanFactoryOptions(level, serviceName))`:
42
+
43
+ - `level` — minimum webpieces level to emit (default `'info'`).
44
+ - `serviceName` — the bunyan logger `name` (default `'webpieces'`), surfaced in the payload.
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@webpieces/bunyan",
3
+ "version": "0.3.307",
4
+ "description": "Node-only bunyan LoggerFactory backends for webpieces: Console (local pretty) + GCP (@google-cloud/logging-bunyan), auto-enriched with HeaderRegistry context keys",
5
+ "type": "commonjs",
6
+ "main": "./src/index.js",
7
+ "types": "./src/index.d.ts",
8
+ "author": "Dean Hiller",
9
+ "license": "Apache-2.0",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/deanhiller/webpieces-ts.git",
13
+ "directory": "packages/logging/bunyan"
14
+ },
15
+ "keywords": [
16
+ "webpieces",
17
+ "bunyan",
18
+ "logging",
19
+ "gcp",
20
+ "cloud-logging"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "dependencies": {
26
+ "@webpieces/core-util": "0.3.307",
27
+ "bunyan": "1.8.15",
28
+ "@google-cloud/logging-bunyan": "5.1.0",
29
+ "@types/bunyan": "1.8.11"
30
+ }
31
+ }
@@ -0,0 +1,15 @@
1
+ import type { ContextReader } from '@webpieces/core-util';
2
+ import { BunyanFactoryBase } from './BunyanFactoryBase';
3
+ import { BunyanFactoryOptions } from './BunyanFactoryOptions';
4
+ /**
5
+ * BunyanConsoleFactory - the LOCAL developer backend. Human-readable, greppable
6
+ * text to stdout (`[LEVEL][time][ctx tags]: message`) with the registered context
7
+ * keys as tags — same enrichment as the GCP backend, different rendering. Mirrors
8
+ * the tested trytami local console stream.
9
+ *
10
+ * @param reader the environment's ContextReader (on a node server, a
11
+ * `RequestContextReader` from @webpieces/core-context).
12
+ */
13
+ export declare class BunyanConsoleFactory extends BunyanFactoryBase {
14
+ constructor(reader: ContextReader, opts?: BunyanFactoryOptions);
15
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BunyanConsoleFactory = void 0;
4
+ const BunyanFactoryBase_1 = require("./BunyanFactoryBase");
5
+ const BunyanFactoryOptions_1 = require("./BunyanFactoryOptions");
6
+ const streams_1 = require("./streams");
7
+ /**
8
+ * BunyanConsoleFactory - the LOCAL developer backend. Human-readable, greppable
9
+ * text to stdout (`[LEVEL][time][ctx tags]: message`) with the registered context
10
+ * keys as tags — same enrichment as the GCP backend, different rendering. Mirrors
11
+ * the tested trytami local console stream.
12
+ *
13
+ * @param reader the environment's ContextReader (on a node server, a
14
+ * `RequestContextReader` from @webpieces/core-context).
15
+ */
16
+ class BunyanConsoleFactory extends BunyanFactoryBase_1.BunyanFactoryBase {
17
+ constructor(reader, opts = new BunyanFactoryOptions_1.BunyanFactoryOptions()) {
18
+ super(reader, opts.serviceName, [(0, streams_1.createConsoleStream)(opts.level)]);
19
+ }
20
+ }
21
+ exports.BunyanConsoleFactory = BunyanConsoleFactory;
22
+ //# sourceMappingURL=BunyanConsoleFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BunyanConsoleFactory.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/BunyanConsoleFactory.ts"],"names":[],"mappings":";;;AACA,2DAAwD;AACxD,iEAA8D;AAC9D,uCAAgD;AAEhD;;;;;;;;GAQG;AACH,MAAa,oBAAqB,SAAQ,qCAAiB;IACvD,YAAY,MAAqB,EAAE,OAA6B,IAAI,2CAAoB,EAAE;QACtF,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,IAAA,6BAAmB,EAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC;CACJ;AAJD,oDAIC","sourcesContent":["import type { ContextReader } from '@webpieces/core-util';\nimport { BunyanFactoryBase } from './BunyanFactoryBase';\nimport { BunyanFactoryOptions } from './BunyanFactoryOptions';\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 * @param reader the environment's ContextReader (on a node server, a\n * `RequestContextReader` from @webpieces/core-context).\n */\nexport class BunyanConsoleFactory extends BunyanFactoryBase {\n constructor(reader: ContextReader, opts: BunyanFactoryOptions = new BunyanFactoryOptions()) {\n super(reader, opts.serviceName, [createConsoleStream(opts.level)]);\n }\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import Logger from 'bunyan';
2
+ import type { ContextReader, Logger as WpLogger, LoggerFactory } from '@webpieces/core-util';
3
+ /**
4
+ * BunyanFactoryBase - shared plumbing for the bunyan {@link LoggerFactory}
5
+ * backends. Builds ONE underlying bunyan logger with the caller-chosen stream,
6
+ * then hands out a cached {@link BunyanLogger} per name (each a bunyan child
7
+ * carrying `loggerName`, wrapping the shared ContextReader). Subclasses differ
8
+ * only in the stream they pass up (GCP vs local console).
9
+ */
10
+ export declare abstract class BunyanFactoryBase implements LoggerFactory {
11
+ private readonly reader;
12
+ private readonly base;
13
+ private readonly loggers;
14
+ protected constructor(reader: ContextReader, name: string, streams: Logger.Stream[]);
15
+ getLogger(name: string): WpLogger;
16
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BunyanFactoryBase = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const bunyan_1 = tslib_1.__importDefault(require("bunyan"));
6
+ const BunyanLogger_1 = require("./BunyanLogger");
7
+ /**
8
+ * BunyanFactoryBase - shared plumbing for the bunyan {@link LoggerFactory}
9
+ * backends. Builds ONE underlying bunyan logger with the caller-chosen stream,
10
+ * then hands out a cached {@link BunyanLogger} per name (each a bunyan child
11
+ * carrying `loggerName`, wrapping the shared ContextReader). Subclasses differ
12
+ * only in the stream they pass up (GCP vs local console).
13
+ */
14
+ class BunyanFactoryBase {
15
+ reader;
16
+ base;
17
+ loggers = new Map();
18
+ constructor(reader, name, streams) {
19
+ this.reader = reader;
20
+ this.base = bunyan_1.default.createLogger({ name, streams });
21
+ }
22
+ getLogger(name) {
23
+ let logger = this.loggers.get(name);
24
+ if (!logger) {
25
+ logger = new BunyanLogger_1.BunyanLogger(this.base.child({ loggerName: name }), this.reader);
26
+ this.loggers.set(name, logger);
27
+ }
28
+ return logger;
29
+ }
30
+ }
31
+ exports.BunyanFactoryBase = BunyanFactoryBase;
32
+ //# sourceMappingURL=BunyanFactoryBase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BunyanFactoryBase.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/BunyanFactoryBase.ts"],"names":[],"mappings":";;;;AAAA,4DAA4B;AAE5B,iDAA8C;AAE9C;;;;;;GAMG;AACH,MAAsB,iBAAiB;IAKd;IAJJ,IAAI,CAAS;IACb,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IAEvD,YACqB,MAAqB,EACtC,IAAY,EACZ,OAAwB;QAFP,WAAM,GAAN,MAAM,CAAe;QAItC,IAAI,CAAC,IAAI,GAAG,gBAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,SAAS,CAAC,IAAY;QAClB,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM,GAAG,IAAI,2BAAY,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAC9E,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;CACJ;AApBD,8CAoBC","sourcesContent":["import Logger from 'bunyan';\nimport type { ContextReader, Logger as WpLogger, LoggerFactory } from '@webpieces/core-util';\nimport { BunyanLogger } from './BunyanLogger';\n\n/**\n * BunyanFactoryBase - shared plumbing for the bunyan {@link LoggerFactory}\n * backends. Builds ONE underlying bunyan logger with the caller-chosen stream,\n * then hands out a cached {@link BunyanLogger} per name (each a bunyan child\n * carrying `loggerName`, wrapping the shared ContextReader). Subclasses differ\n * only in the stream they pass up (GCP vs local console).\n */\nexport abstract class BunyanFactoryBase implements LoggerFactory {\n private readonly base: Logger;\n private readonly loggers = new Map<string, WpLogger>();\n\n protected constructor(\n private readonly reader: ContextReader,\n name: string,\n streams: Logger.Stream[],\n ) {\n this.base = Logger.createLogger({ name, streams });\n }\n\n getLogger(name: string): WpLogger {\n let logger = this.loggers.get(name);\n if (!logger) {\n logger = new BunyanLogger(this.base.child({ loggerName: name }), this.reader);\n this.loggers.set(name, logger);\n }\n return logger;\n }\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import type { LogLevel } from '@webpieces/core-util';
2
+ /**
3
+ * BunyanFactoryOptions - tuning for the bunyan LoggerFactory backends.
4
+ *
5
+ * Data-only structure → a class, per CLAUDE.md. All fields optional; a bare
6
+ * `new BunyanGcpFactory(reader)` uses the defaults.
7
+ */
8
+ export declare class BunyanFactoryOptions {
9
+ /** Minimum webpieces level to emit. Defaults to 'info'. */
10
+ readonly level: LogLevel;
11
+ /**
12
+ * The bunyan logger `name` (surfaces as `name` in Cloud Logging's JSON
13
+ * payload). Defaults to 'webpieces'.
14
+ */
15
+ readonly serviceName: string;
16
+ constructor(
17
+ /** Minimum webpieces level to emit. Defaults to 'info'. */
18
+ level?: LogLevel,
19
+ /**
20
+ * The bunyan logger `name` (surfaces as `name` in Cloud Logging's JSON
21
+ * payload). Defaults to 'webpieces'.
22
+ */
23
+ serviceName?: string);
24
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BunyanFactoryOptions = void 0;
4
+ /**
5
+ * BunyanFactoryOptions - tuning for the bunyan LoggerFactory backends.
6
+ *
7
+ * Data-only structure → a class, per CLAUDE.md. All fields optional; a bare
8
+ * `new BunyanGcpFactory(reader)` uses the defaults.
9
+ */
10
+ class BunyanFactoryOptions {
11
+ level;
12
+ serviceName;
13
+ constructor(
14
+ /** Minimum webpieces level to emit. Defaults to 'info'. */
15
+ level = 'info',
16
+ /**
17
+ * The bunyan logger `name` (surfaces as `name` in Cloud Logging's JSON
18
+ * payload). Defaults to 'webpieces'.
19
+ */
20
+ serviceName = 'webpieces') {
21
+ this.level = level;
22
+ this.serviceName = serviceName;
23
+ }
24
+ }
25
+ exports.BunyanFactoryOptions = BunyanFactoryOptions;
26
+ //# sourceMappingURL=BunyanFactoryOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BunyanFactoryOptions.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/BunyanFactoryOptions.ts"],"names":[],"mappings":";;;AAEA;;;;;GAKG;AACH,MAAa,oBAAoB;IAGT;IAKA;IAPpB;IACI,2DAA2D;IAC3C,QAAkB,MAAM;IACxC;;;OAGG;IACa,cAAsB,WAAW;QALjC,UAAK,GAAL,KAAK,CAAmB;QAKxB,gBAAW,GAAX,WAAW,CAAsB;IAClD,CAAC;CACP;AAVD,oDAUC","sourcesContent":["import type { LogLevel } from '@webpieces/core-util';\n\n/**\n * BunyanFactoryOptions - tuning for the bunyan LoggerFactory backends.\n *\n * Data-only structure → a class, per CLAUDE.md. All fields optional; a bare\n * `new BunyanGcpFactory(reader)` uses the defaults.\n */\nexport class BunyanFactoryOptions {\n constructor(\n /** Minimum webpieces level to emit. Defaults to 'info'. */\n public readonly level: LogLevel = 'info',\n /**\n * The bunyan logger `name` (surfaces as `name` in Cloud Logging's JSON\n * payload). Defaults to 'webpieces'.\n */\n public readonly serviceName: string = 'webpieces',\n ) {}\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import type { ContextReader } from '@webpieces/core-util';
2
+ import { BunyanFactoryBase } from './BunyanFactoryBase';
3
+ import { BunyanFactoryOptions } from './BunyanFactoryOptions';
4
+ /**
5
+ * BunyanGcpFactory - the GCP backend. Streams to Cloud Logging via
6
+ * @google-cloud/logging-bunyan (which owns the numeric-level→severity mapping,
7
+ * msg→message, trace/httpRequest fields). Registered context keys ride along as
8
+ * structured payload fields. This matches the tested-in-GCP trytami service
9
+ * exactly. Requires GCP Application Default Credentials on the instance.
10
+ *
11
+ * @param reader the environment's ContextReader — on a node server pass
12
+ * `new RequestContextReader()` from @webpieces/core-context. Keeping it a
13
+ * constructor arg is why this package depends only on @webpieces/core-util.
14
+ */
15
+ export declare class BunyanGcpFactory extends BunyanFactoryBase {
16
+ constructor(reader: ContextReader, opts?: BunyanFactoryOptions);
17
+ }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BunyanGcpFactory = void 0;
4
+ const BunyanFactoryBase_1 = require("./BunyanFactoryBase");
5
+ const BunyanFactoryOptions_1 = require("./BunyanFactoryOptions");
6
+ const streams_1 = require("./streams");
7
+ /**
8
+ * BunyanGcpFactory - the GCP backend. Streams to Cloud Logging via
9
+ * @google-cloud/logging-bunyan (which owns the numeric-level→severity mapping,
10
+ * msg→message, trace/httpRequest fields). Registered context keys ride along as
11
+ * structured payload fields. This matches the tested-in-GCP trytami service
12
+ * exactly. Requires GCP Application Default Credentials on the instance.
13
+ *
14
+ * @param reader the environment's ContextReader — on a node server pass
15
+ * `new RequestContextReader()` from @webpieces/core-context. Keeping it a
16
+ * constructor arg is why this package depends only on @webpieces/core-util.
17
+ */
18
+ class BunyanGcpFactory extends BunyanFactoryBase_1.BunyanFactoryBase {
19
+ constructor(reader, opts = new BunyanFactoryOptions_1.BunyanFactoryOptions()) {
20
+ super(reader, opts.serviceName, [(0, streams_1.createGoogleCloudStream)(opts.level)]);
21
+ }
22
+ }
23
+ exports.BunyanGcpFactory = BunyanGcpFactory;
24
+ //# sourceMappingURL=BunyanGcpFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BunyanGcpFactory.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/BunyanGcpFactory.ts"],"names":[],"mappings":";;;AACA,2DAAwD;AACxD,iEAA8D;AAC9D,uCAAoD;AAEpD;;;;;;;;;;GAUG;AACH,MAAa,gBAAiB,SAAQ,qCAAiB;IACnD,YAAY,MAAqB,EAAE,OAA6B,IAAI,2CAAoB,EAAE;QACtF,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,IAAA,iCAAuB,EAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3E,CAAC;CACJ;AAJD,4CAIC","sourcesContent":["import type { ContextReader } from '@webpieces/core-util';\nimport { BunyanFactoryBase } from './BunyanFactoryBase';\nimport { BunyanFactoryOptions } from './BunyanFactoryOptions';\nimport { createGoogleCloudStream } from './streams';\n\n/**\n * BunyanGcpFactory - the GCP backend. Streams to Cloud Logging via\n * @google-cloud/logging-bunyan (which owns the numeric-level→severity mapping,\n * msg→message, trace/httpRequest fields). Registered context keys ride along as\n * structured payload fields. This matches the tested-in-GCP trytami service\n * exactly. Requires GCP Application Default Credentials on the instance.\n *\n * @param reader the environment's ContextReader — on a node server pass\n * `new RequestContextReader()` from @webpieces/core-context. Keeping it a\n * constructor arg is why this package depends only on @webpieces/core-util.\n */\nexport class BunyanGcpFactory extends BunyanFactoryBase {\n constructor(reader: ContextReader, opts: BunyanFactoryOptions = new BunyanFactoryOptions()) {\n super(reader, opts.serviceName, [createGoogleCloudStream(opts.level)]);\n }\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import type Logger from 'bunyan';
2
+ import type { ContextReader, Logger as WpLogger } from '@webpieces/core-util';
3
+ /**
4
+ * BunyanLogger - a webpieces {@link WpLogger} backed by a bunyan logger (one per
5
+ * name, created as a bunyan child carrying `loggerName`). On every call it merges
6
+ * the logged HeaderRegistry context keys (read through the ContextReader, secured
7
+ * values masked) into the bunyan fields object, and normalizes an optional Error
8
+ * into `err: { name, message, stack }` — matching the tested trytami AppLogger.
9
+ * The GCP stream then serializes those fields into the structured log payload.
10
+ */
11
+ export declare class BunyanLogger implements WpLogger {
12
+ private readonly bunyan;
13
+ private readonly reader;
14
+ private readonly headerMethods;
15
+ private loggedKeys?;
16
+ constructor(bunyan: Logger, reader: ContextReader);
17
+ trace(message: string, err?: Error): void;
18
+ debug(message: string, err?: Error): void;
19
+ info(message: string, err?: Error): void;
20
+ warn(message: string, err?: Error): void;
21
+ error(message: string, err?: Error): void;
22
+ private buildFields;
23
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BunyanLogger = void 0;
4
+ const core_util_1 = require("@webpieces/core-util");
5
+ const LoggedError_1 = require("./LoggedError");
6
+ // Above this serialized size the error is truncated, so one giant stack can't
7
+ // blow up a log line (ported from the tested trytami AppLogger).
8
+ const MAX_ERR_SERIALIZED = 100_000;
9
+ function truncateStack(stack, maxLines = 5) {
10
+ if (!stack) {
11
+ return '';
12
+ }
13
+ return stack.split('\n').slice(0, maxLines).join('\n');
14
+ }
15
+ function normalizeError(err) {
16
+ const full = new LoggedError_1.LoggedError(err.name, err.message, err.stack);
17
+ if (JSON.stringify({ err: full }).length <= MAX_ERR_SERIALIZED) {
18
+ return full;
19
+ }
20
+ return new LoggedError_1.LoggedError(`error too long: ${err.name}`, err.message.substring(0, 100), truncateStack(err.stack, 5));
21
+ }
22
+ /**
23
+ * BunyanLogger - a webpieces {@link WpLogger} backed by a bunyan logger (one per
24
+ * name, created as a bunyan child carrying `loggerName`). On every call it merges
25
+ * the logged HeaderRegistry context keys (read through the ContextReader, secured
26
+ * values masked) into the bunyan fields object, and normalizes an optional Error
27
+ * into `err: { name, message, stack }` — matching the tested trytami AppLogger.
28
+ * The GCP stream then serializes those fields into the structured log payload.
29
+ */
30
+ class BunyanLogger {
31
+ bunyan;
32
+ reader;
33
+ headerMethods = new core_util_1.HeaderMethods();
34
+ loggedKeys;
35
+ constructor(bunyan, reader) {
36
+ this.bunyan = bunyan;
37
+ this.reader = reader;
38
+ }
39
+ trace(message, err) {
40
+ this.bunyan.trace(this.buildFields(err), message);
41
+ }
42
+ debug(message, err) {
43
+ this.bunyan.debug(this.buildFields(err), message);
44
+ }
45
+ info(message, err) {
46
+ this.bunyan.info(this.buildFields(err), message);
47
+ }
48
+ warn(message, err) {
49
+ this.bunyan.warn(this.buildFields(err), message);
50
+ }
51
+ error(message, err) {
52
+ this.bunyan.error(this.buildFields(err), message);
53
+ }
54
+ // Registry keys are read LAZILY (first log) — a LoggerFactory is built before
55
+ // setupRuntime calls HeaderRegistry.configure.
56
+ buildFields(err) {
57
+ if (!this.loggedKeys) {
58
+ this.loggedKeys = core_util_1.HeaderRegistry.get().getLoggedKeys();
59
+ }
60
+ const fields = {};
61
+ const logMap = this.headerMethods.buildSecureMapForLogs(this.loggedKeys, this.reader);
62
+ logMap.forEach((value, name) => {
63
+ fields[name] = value;
64
+ });
65
+ if (err) {
66
+ fields['err'] = normalizeError(err);
67
+ }
68
+ return fields;
69
+ }
70
+ }
71
+ exports.BunyanLogger = BunyanLogger;
72
+ //# sourceMappingURL=BunyanLogger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BunyanLogger.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/BunyanLogger.ts"],"names":[],"mappings":"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","sourcesContent":["import type Logger from 'bunyan';\nimport { HeaderMethods, HeaderRegistry } from '@webpieces/core-util';\nimport type { ContextKey, ContextReader, Logger as WpLogger } from '@webpieces/core-util';\nimport { LoggedError } from './LoggedError';\n\n// Above this serialized size the error is truncated, so one giant stack can't\n// blow up a log line (ported from the tested trytami AppLogger).\nconst MAX_ERR_SERIALIZED = 100_000;\n\nfunction truncateStack(stack: string | undefined, maxLines = 5): string {\n if (!stack) {\n return '';\n }\n return stack.split('\\n').slice(0, maxLines).join('\\n');\n}\n\nfunction normalizeError(err: Error): LoggedError {\n const full = new LoggedError(err.name, err.message, err.stack);\n if (JSON.stringify({ err: full }).length <= MAX_ERR_SERIALIZED) {\n return full;\n }\n return new LoggedError(\n `error too long: ${err.name}`,\n err.message.substring(0, 100),\n truncateStack(err.stack, 5),\n );\n}\n\n/**\n * BunyanLogger - a webpieces {@link WpLogger} backed by a bunyan logger (one per\n * name, created as a bunyan child carrying `loggerName`). On every call it merges\n * the logged HeaderRegistry context keys (read through the ContextReader, secured\n * values masked) into the bunyan fields object, and normalizes an optional Error\n * into `err: { name, message, stack }` — matching the tested trytami AppLogger.\n * The GCP stream then serializes those fields into the structured log payload.\n */\nexport class BunyanLogger implements WpLogger {\n private readonly headerMethods = new HeaderMethods();\n private loggedKeys?: ContextKey[];\n\n constructor(\n private readonly bunyan: Logger,\n private readonly reader: ContextReader,\n ) {}\n\n trace(message: string, err?: Error): void {\n this.bunyan.trace(this.buildFields(err), message);\n }\n\n debug(message: string, err?: Error): void {\n this.bunyan.debug(this.buildFields(err), message);\n }\n\n info(message: string, err?: Error): void {\n this.bunyan.info(this.buildFields(err), message);\n }\n\n warn(message: string, err?: Error): void {\n this.bunyan.warn(this.buildFields(err), message);\n }\n\n error(message: string, err?: Error): void {\n this.bunyan.error(this.buildFields(err), message);\n }\n\n // Registry keys are read LAZILY (first log) — a LoggerFactory is built before\n // setupRuntime calls HeaderRegistry.configure.\n private buildFields(err?: Error): Record<string, string | LoggedError> {\n if (!this.loggedKeys) {\n this.loggedKeys = HeaderRegistry.get().getLoggedKeys();\n }\n const fields: Record<string, string | LoggedError> = {};\n const logMap = this.headerMethods.buildSecureMapForLogs(this.loggedKeys, this.reader);\n logMap.forEach((value: string, name: string) => {\n fields[name] = value;\n });\n if (err) {\n fields['err'] = normalizeError(err);\n }\n return fields;\n }\n}\n"]}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * LoggedError - the serialized shape of an Error as it appears in a bunyan log
3
+ * record's `err` field. Data-only structure → a class, per CLAUDE.md. Produced by
4
+ * BunyanLogger's error normalization and read back by the console formatter.
5
+ */
6
+ export declare class LoggedError {
7
+ readonly name: string;
8
+ readonly message: string;
9
+ readonly stack?: string | undefined;
10
+ constructor(name: string, message: string, stack?: string | undefined);
11
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LoggedError = void 0;
4
+ /**
5
+ * LoggedError - the serialized shape of an Error as it appears in a bunyan log
6
+ * record's `err` field. Data-only structure → a class, per CLAUDE.md. Produced by
7
+ * BunyanLogger's error normalization and read back by the console formatter.
8
+ */
9
+ class LoggedError {
10
+ name;
11
+ message;
12
+ stack;
13
+ constructor(name, message, stack) {
14
+ this.name = name;
15
+ this.message = message;
16
+ this.stack = stack;
17
+ }
18
+ }
19
+ exports.LoggedError = LoggedError;
20
+ //# sourceMappingURL=LoggedError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoggedError.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/LoggedError.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,MAAa,WAAW;IAEA;IACA;IACA;IAHpB,YACoB,IAAY,EACZ,OAAe,EACf,KAAc;QAFd,SAAI,GAAJ,IAAI,CAAQ;QACZ,YAAO,GAAP,OAAO,CAAQ;QACf,UAAK,GAAL,KAAK,CAAS;IAC/B,CAAC;CACP;AAND,kCAMC","sourcesContent":["/**\n * LoggedError - the serialized shape of an Error as it appears in a bunyan log\n * record's `err` field. Data-only structure → a class, per CLAUDE.md. Produced by\n * BunyanLogger's error normalization and read back by the console formatter.\n */\nexport class LoggedError {\n constructor(\n public readonly name: string,\n public readonly message: string,\n public readonly stack?: string,\n ) {}\n}\n"]}
package/src/index.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @webpieces/bunyan
3
+ *
4
+ * Node-only bunyan {@link LoggerFactory} backends for webpieces. Install one at
5
+ * startup via `LogManager.setFactory(...)`:
6
+ *
7
+ * ```ts
8
+ * import { BunyanGcpFactory, BunyanConsoleFactory } from '@webpieces/bunyan';
9
+ * import { RequestContextReader } from '@webpieces/core-context';
10
+ *
11
+ * const reader = new RequestContextReader();
12
+ * const loggerFactory = process.env.K_SERVICE
13
+ * ? new BunyanGcpFactory(reader) // Cloud Logging via @google-cloud/logging-bunyan
14
+ * : new BunyanConsoleFactory(reader); // local → pretty console
15
+ * // hand to setupRuntime(new RuntimeSetupOptions(loggerFactory, ...))
16
+ * ```
17
+ *
18
+ * Both backends auto-enrich every line with the logged context keys from
19
+ * HeaderRegistry. This package depends only on @webpieces/core-util (+ bunyan +
20
+ * @google-cloud/logging-bunyan); the node RequestContextReader is passed in.
21
+ *
22
+ * @packageDocumentation
23
+ */
24
+ export { BunyanGcpFactory } from './BunyanGcpFactory';
25
+ export { BunyanConsoleFactory } from './BunyanConsoleFactory';
26
+ export { BunyanFactoryOptions } from './BunyanFactoryOptions';
27
+ export { BunyanLogger } from './BunyanLogger';
28
+ export { createGoogleCloudStream, createConsoleStream } from './streams';
29
+ export { LEVEL_TO_BUNYAN, logLevelToBunyanLevel } from './levels';
package/src/index.js ADDED
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logLevelToBunyanLevel = exports.LEVEL_TO_BUNYAN = exports.createConsoleStream = exports.createGoogleCloudStream = exports.BunyanLogger = exports.BunyanFactoryOptions = exports.BunyanConsoleFactory = exports.BunyanGcpFactory = void 0;
4
+ /**
5
+ * @webpieces/bunyan
6
+ *
7
+ * Node-only bunyan {@link LoggerFactory} backends for webpieces. Install one at
8
+ * startup via `LogManager.setFactory(...)`:
9
+ *
10
+ * ```ts
11
+ * import { BunyanGcpFactory, BunyanConsoleFactory } from '@webpieces/bunyan';
12
+ * import { RequestContextReader } from '@webpieces/core-context';
13
+ *
14
+ * const reader = new RequestContextReader();
15
+ * const loggerFactory = process.env.K_SERVICE
16
+ * ? new BunyanGcpFactory(reader) // Cloud Logging via @google-cloud/logging-bunyan
17
+ * : new BunyanConsoleFactory(reader); // local → pretty console
18
+ * // hand to setupRuntime(new RuntimeSetupOptions(loggerFactory, ...))
19
+ * ```
20
+ *
21
+ * Both backends auto-enrich every line with the logged context keys from
22
+ * HeaderRegistry. This package depends only on @webpieces/core-util (+ bunyan +
23
+ * @google-cloud/logging-bunyan); the node RequestContextReader is passed in.
24
+ *
25
+ * @packageDocumentation
26
+ */
27
+ var BunyanGcpFactory_1 = require("./BunyanGcpFactory");
28
+ Object.defineProperty(exports, "BunyanGcpFactory", { enumerable: true, get: function () { return BunyanGcpFactory_1.BunyanGcpFactory; } });
29
+ var BunyanConsoleFactory_1 = require("./BunyanConsoleFactory");
30
+ Object.defineProperty(exports, "BunyanConsoleFactory", { enumerable: true, get: function () { return BunyanConsoleFactory_1.BunyanConsoleFactory; } });
31
+ var BunyanFactoryOptions_1 = require("./BunyanFactoryOptions");
32
+ Object.defineProperty(exports, "BunyanFactoryOptions", { enumerable: true, get: function () { return BunyanFactoryOptions_1.BunyanFactoryOptions; } });
33
+ var BunyanLogger_1 = require("./BunyanLogger");
34
+ Object.defineProperty(exports, "BunyanLogger", { enumerable: true, get: function () { return BunyanLogger_1.BunyanLogger; } });
35
+ var streams_1 = require("./streams");
36
+ Object.defineProperty(exports, "createGoogleCloudStream", { enumerable: true, get: function () { return streams_1.createGoogleCloudStream; } });
37
+ Object.defineProperty(exports, "createConsoleStream", { enumerable: true, get: function () { return streams_1.createConsoleStream; } });
38
+ var levels_1 = require("./levels");
39
+ Object.defineProperty(exports, "LEVEL_TO_BUNYAN", { enumerable: true, get: function () { return levels_1.LEVEL_TO_BUNYAN; } });
40
+ Object.defineProperty(exports, "logLevelToBunyanLevel", { enumerable: true, get: function () { return levels_1.logLevelToBunyanLevel; } });
41
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAC7B,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAC7B,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AACrB,qCAAyE;AAAhE,kHAAA,uBAAuB,OAAA;AAAE,8GAAA,mBAAmB,OAAA;AACrD,mCAAkE;AAAzD,yGAAA,eAAe,OAAA;AAAE,+GAAA,qBAAqB,OAAA","sourcesContent":["/**\n * @webpieces/bunyan\n *\n * Node-only bunyan {@link LoggerFactory} backends for webpieces. Install one at\n * startup via `LogManager.setFactory(...)`:\n *\n * ```ts\n * import { BunyanGcpFactory, BunyanConsoleFactory } from '@webpieces/bunyan';\n * import { RequestContextReader } from '@webpieces/core-context';\n *\n * const reader = new RequestContextReader();\n * const loggerFactory = process.env.K_SERVICE\n * ? new BunyanGcpFactory(reader) // Cloud Logging via @google-cloud/logging-bunyan\n * : new BunyanConsoleFactory(reader); // local → pretty console\n * // hand to setupRuntime(new RuntimeSetupOptions(loggerFactory, ...))\n * ```\n *\n * Both backends auto-enrich every line with the logged context keys from\n * HeaderRegistry. This package depends only on @webpieces/core-util (+ bunyan +\n * @google-cloud/logging-bunyan); the node RequestContextReader is passed in.\n *\n * @packageDocumentation\n */\nexport { BunyanGcpFactory } from './BunyanGcpFactory';\nexport { BunyanConsoleFactory } from './BunyanConsoleFactory';\nexport { BunyanFactoryOptions } from './BunyanFactoryOptions';\nexport { BunyanLogger } from './BunyanLogger';\nexport { createGoogleCloudStream, createConsoleStream } from './streams';\nexport { LEVEL_TO_BUNYAN, logLevelToBunyanLevel } from './levels';\n"]}
@@ -0,0 +1,9 @@
1
+ import type { LogLevel } from '@webpieces/core-util';
2
+ /**
3
+ * webpieces LogLevel → bunyan numeric level. webpieces has no `fatal`; its 5
4
+ * levels map onto bunyan TRACE(10)/DEBUG(20)/INFO(30)/WARN(40)/ERROR(50).
5
+ * @google-cloud/logging-bunyan turns these into GCP severities
6
+ * (10/20→DEBUG, 30→INFO, 40→WARNING, 50→ERROR).
7
+ */
8
+ export declare const LEVEL_TO_BUNYAN: Record<LogLevel, number>;
9
+ export declare function logLevelToBunyanLevel(level: LogLevel): number;
package/src/levels.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LEVEL_TO_BUNYAN = void 0;
4
+ exports.logLevelToBunyanLevel = logLevelToBunyanLevel;
5
+ const tslib_1 = require("tslib");
6
+ const bunyan_1 = tslib_1.__importDefault(require("bunyan"));
7
+ /**
8
+ * webpieces LogLevel → bunyan numeric level. webpieces has no `fatal`; its 5
9
+ * levels map onto bunyan TRACE(10)/DEBUG(20)/INFO(30)/WARN(40)/ERROR(50).
10
+ * @google-cloud/logging-bunyan turns these into GCP severities
11
+ * (10/20→DEBUG, 30→INFO, 40→WARNING, 50→ERROR).
12
+ */
13
+ exports.LEVEL_TO_BUNYAN = {
14
+ trace: bunyan_1.default.TRACE,
15
+ debug: bunyan_1.default.DEBUG,
16
+ info: bunyan_1.default.INFO,
17
+ warn: bunyan_1.default.WARN,
18
+ error: bunyan_1.default.ERROR,
19
+ };
20
+ function logLevelToBunyanLevel(level) {
21
+ return exports.LEVEL_TO_BUNYAN[level];
22
+ }
23
+ //# sourceMappingURL=levels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"levels.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/levels.ts"],"names":[],"mappings":";;;AAiBA,sDAEC;;AAnBD,4DAA4B;AAG5B;;;;;GAKG;AACU,QAAA,eAAe,GAA6B;IACrD,KAAK,EAAE,gBAAM,CAAC,KAAK;IACnB,KAAK,EAAE,gBAAM,CAAC,KAAK;IACnB,IAAI,EAAE,gBAAM,CAAC,IAAI;IACjB,IAAI,EAAE,gBAAM,CAAC,IAAI;IACjB,KAAK,EAAE,gBAAM,CAAC,KAAK;CACtB,CAAC;AAEF,SAAgB,qBAAqB,CAAC,KAAe;IACjD,OAAO,uBAAe,CAAC,KAAK,CAAC,CAAC;AAClC,CAAC","sourcesContent":["import Logger from 'bunyan';\nimport type { LogLevel } from '@webpieces/core-util';\n\n/**\n * webpieces LogLevel → bunyan numeric level. webpieces has no `fatal`; its 5\n * levels map onto bunyan TRACE(10)/DEBUG(20)/INFO(30)/WARN(40)/ERROR(50).\n * @google-cloud/logging-bunyan turns these into GCP severities\n * (10/20→DEBUG, 30→INFO, 40→WARNING, 50→ERROR).\n */\nexport const LEVEL_TO_BUNYAN: Record<LogLevel, number> = {\n trace: Logger.TRACE,\n debug: Logger.DEBUG,\n info: Logger.INFO,\n warn: Logger.WARN,\n error: Logger.ERROR,\n};\n\nexport function logLevelToBunyanLevel(level: LogLevel): number {\n return LEVEL_TO_BUNYAN[level];\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import Logger from 'bunyan';
2
+ import type { LogLevel } from '@webpieces/core-util';
3
+ /**
4
+ * The GCP stream: delegates ALL structured-JSON formatting (numeric level → GCP
5
+ * severity, msg→message, trace/httpRequest fields, stripping name/hostname/pid)
6
+ * to @google-cloud/logging-bunyan, exactly as the tested trytami service does.
7
+ * Sends to the Cloud Logging API (needs ADC on the instance).
8
+ */
9
+ export declare function createGoogleCloudStream(level: LogLevel): Logger.Stream;
10
+ /** The local dev stream: human-readable text to stdout via {@link writeConsole}. */
11
+ export declare function createConsoleStream(level: LogLevel): Logger.Stream;
package/src/streams.js ADDED
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createGoogleCloudStream = createGoogleCloudStream;
4
+ exports.createConsoleStream = createConsoleStream;
5
+ const tslib_1 = require("tslib");
6
+ const stream_1 = require("stream");
7
+ const bunyan_1 = tslib_1.__importDefault(require("bunyan"));
8
+ const logging_bunyan_1 = require("@google-cloud/logging-bunyan");
9
+ const levels_1 = require("./levels");
10
+ // bunyan record fields that are structural / rendered specially, so they are not
11
+ // shown as context tags in the local console line.
12
+ const BUNYAN_STD_FIELDS = new Set([
13
+ 'v',
14
+ 'level',
15
+ 'name',
16
+ 'hostname',
17
+ 'pid',
18
+ 'time',
19
+ 'msg',
20
+ 'src',
21
+ 'err',
22
+ 'loggerName',
23
+ ]);
24
+ function formatTime(iso) {
25
+ if (typeof iso !== 'string') {
26
+ return '';
27
+ }
28
+ // ISO 8601 "2026-07-08T12:34:56.789Z" → "12:34:56.789"
29
+ const timePart = iso.split('T')[1];
30
+ return timePart ? timePart.replace('Z', '') : iso;
31
+ }
32
+ /**
33
+ * Render one bunyan JSON line as a human-readable, greppable console line:
34
+ * `[LEVEL][time][ctx tags]: message` plus multi-line error details. Ported from
35
+ * the tested trytami writeConsole, generalized: every non-structural field
36
+ * (i.e. the injected context keys) becomes a `key:value` tag.
37
+ */
38
+ function writeConsole(line) {
39
+ const obj = JSON.parse(line);
40
+ const levelName = (bunyan_1.default.nameFromLevel[obj['level']] ?? 'info').toUpperCase().padEnd(5);
41
+ const time = formatTime(obj['time']);
42
+ const tags = [];
43
+ if (obj['loggerName']) {
44
+ tags.push(`logger:${String(obj['loggerName'])}`);
45
+ }
46
+ for (const key of Object.keys(obj)) {
47
+ if (BUNYAN_STD_FIELDS.has(key)) {
48
+ continue;
49
+ }
50
+ tags.push(`${key}:${String(obj[key])}`);
51
+ }
52
+ const tagStr = tags.length > 0 ? tags.join(', ') : 'no-context';
53
+ let message = `[${levelName}][${time}][${tagStr}]: ${String(obj['msg'] ?? '')}`;
54
+ const err = obj['err'];
55
+ if (err) {
56
+ message += `\nError Details:`;
57
+ message += `\n Message: ${err.message}`;
58
+ message += `\n Name: ${err.name}`;
59
+ if (err.stack) {
60
+ message += `\n Stack Trace:\n${err.stack}`;
61
+ }
62
+ }
63
+ // This IS a logging backend (the console sink); direct stdout is intentional.
64
+ console.log(message);
65
+ }
66
+ /**
67
+ * The GCP stream: delegates ALL structured-JSON formatting (numeric level → GCP
68
+ * severity, msg→message, trace/httpRequest fields, stripping name/hostname/pid)
69
+ * to @google-cloud/logging-bunyan, exactly as the tested trytami service does.
70
+ * Sends to the Cloud Logging API (needs ADC on the instance).
71
+ */
72
+ function createGoogleCloudStream(level) {
73
+ const loggingBunyan = new logging_bunyan_1.LoggingBunyan();
74
+ return loggingBunyan.stream((0, levels_1.logLevelToBunyanLevel)(level));
75
+ }
76
+ /** The local dev stream: human-readable text to stdout via {@link writeConsole}. */
77
+ function createConsoleStream(level) {
78
+ const writable = new stream_1.Writable({
79
+ write(chunk, _encoding, callback) {
80
+ writeConsole(chunk.toString());
81
+ callback();
82
+ },
83
+ });
84
+ return {
85
+ name: 'console',
86
+ level: (0, levels_1.logLevelToBunyanLevel)(level),
87
+ stream: writable,
88
+ };
89
+ }
90
+ //# sourceMappingURL=streams.js.map
@@ -0,0 +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 type { LogLevel } from '@webpieces/core-util';\nimport { logLevelToBunyanLevel } from './levels';\nimport { LoggedError } from './LoggedError';\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.\nconst BUNYAN_STD_FIELDS = new Set<string>([\n 'v',\n 'level',\n 'name',\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 */\nexport function createGoogleCloudStream(level: LogLevel): Logger.Stream {\n const loggingBunyan = new LoggingBunyan();\n return loggingBunyan.stream(logLevelToBunyanLevel(level));\n}\n\n/** The local dev stream: human-readable text to stdout via {@link writeConsole}. */\nexport function createConsoleStream(level: LogLevel): 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 level: logLevelToBunyanLevel(level),\n stream: writable,\n };\n}\n"]}