@webpieces/bunyan 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 +1 -1
- package/package.json +3 -3
- package/src/BunyanConsoleFactory.d.ts +1 -1
- package/src/BunyanConsoleFactory.js +1 -1
- package/src/BunyanConsoleFactory.js.map +1 -1
- package/src/BunyanGcpFactory.d.ts +1 -1
- package/src/BunyanGcpFactory.js +1 -1
- package/src/BunyanGcpFactory.js.map +1 -1
- package/src/BunyanLogger.d.ts +1 -1
- package/src/BunyanLogger.js +1 -1
- package/src/BunyanLogger.js.map +1 -1
- package/src/streams.d.ts +1 -1
- package/src/streams.js +4 -4
- package/src/streams.js.map +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Two factories, both auto-enriching every line with the logged context keys regis
|
|
|
11
11
|
- **`BunyanGcpFactory`** — GCP: streams to Cloud Logging via
|
|
12
12
|
[`@google-cloud/logging-bunyan`](https://github.com/googleapis/nodejs-logging-bunyan),
|
|
13
13
|
which owns the numeric-level→severity mapping and structured payload. Registered context
|
|
14
|
-
keys ride along as payload fields. This mirrors
|
|
14
|
+
keys ride along as payload fields. This mirrors a production-tested GCP service.
|
|
15
15
|
|
|
16
16
|
## Usage
|
|
17
17
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/bunyan",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.614",
|
|
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.
|
|
27
|
-
"@webpieces/core-context": "0.4.
|
|
26
|
+
"@webpieces/core-util": "0.4.614",
|
|
27
|
+
"@webpieces/core-context": "0.4.614",
|
|
28
28
|
"bunyan": "1.8.15",
|
|
29
29
|
"@google-cloud/logging-bunyan": "5.1.0",
|
|
30
30
|
"@types/bunyan": "1.8.11"
|
|
@@ -3,7 +3,7 @@ import { BunyanFactoryBase } from './BunyanFactoryBase';
|
|
|
3
3
|
* BunyanConsoleFactory - the LOCAL developer backend. Human-readable, greppable
|
|
4
4
|
* text to stdout (`[LEVEL][time][Controller.method][loggerName][ctx tags]: message`) with the
|
|
5
5
|
* registered context keys as tags — same enrichment as the GCP backend, different rendering. Mirrors
|
|
6
|
-
*
|
|
6
|
+
* a production-tested local console stream.
|
|
7
7
|
*
|
|
8
8
|
* `consoleFields`, when given, is the app-chosen ordered ALLOW-LIST of context keys to render in the
|
|
9
9
|
* console line (hides local noise like `requestPath`); GCP still receives every logged key.
|
|
@@ -7,7 +7,7 @@ const streams_1 = require("./streams");
|
|
|
7
7
|
* BunyanConsoleFactory - the LOCAL developer backend. Human-readable, greppable
|
|
8
8
|
* text to stdout (`[LEVEL][time][Controller.method][loggerName][ctx tags]: message`) with the
|
|
9
9
|
* registered context keys as tags — same enrichment as the GCP backend, different rendering. Mirrors
|
|
10
|
-
*
|
|
10
|
+
* a production-tested local console stream.
|
|
11
11
|
*
|
|
12
12
|
* `consoleFields`, when given, is the app-chosen ordered ALLOW-LIST of context keys to render in the
|
|
13
13
|
* console line (hides local noise like `requestPath`); GCP still receives every logged key.
|
|
@@ -1 +1 @@
|
|
|
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 *
|
|
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 * a production-tested 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"]}
|
|
@@ -4,7 +4,7 @@ import { BunyanFactoryBase } from './BunyanFactoryBase';
|
|
|
4
4
|
* @google-cloud/logging-bunyan (which owns the numeric-level→severity mapping,
|
|
5
5
|
* msg→message, trace/httpRequest fields). The logged context keys ride along as
|
|
6
6
|
* structured payload fields, read straight from RequestContext on each line. This
|
|
7
|
-
* matches
|
|
7
|
+
* matches a production-tested GCP service exactly. Requires GCP Application
|
|
8
8
|
* Default Credentials on the instance.
|
|
9
9
|
*
|
|
10
10
|
* The service name + version come from {@link ServiceInfo}, which startup must have populated
|
package/src/BunyanGcpFactory.js
CHANGED
|
@@ -8,7 +8,7 @@ const streams_1 = require("./streams");
|
|
|
8
8
|
* @google-cloud/logging-bunyan (which owns the numeric-level→severity mapping,
|
|
9
9
|
* msg→message, trace/httpRequest fields). The logged context keys ride along as
|
|
10
10
|
* structured payload fields, read straight from RequestContext on each line. This
|
|
11
|
-
* matches
|
|
11
|
+
* matches a production-tested GCP service exactly. Requires GCP Application
|
|
12
12
|
* Default Credentials on the instance.
|
|
13
13
|
*
|
|
14
14
|
* The service name + version come from {@link ServiceInfo}, which startup must have populated
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BunyanGcpFactory.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/BunyanGcpFactory.ts"],"names":[],"mappings":";;;AAAA,2DAAwD;AACxD,uCAAoD;AAEpD;;;;;;;;;;GAUG;AACH,MAAa,gBAAiB,SAAQ,qCAAiB;IACnD;QACI,KAAK,CAAC,CAAC,IAAA,iCAAuB,GAAE,CAAC,CAAC,CAAC;IACvC,CAAC;CACJ;AAJD,4CAIC","sourcesContent":["import { BunyanFactoryBase } from './BunyanFactoryBase';\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). The logged context keys ride along as\n * structured payload fields, read straight from RequestContext on each line. This\n * matches
|
|
1
|
+
{"version":3,"file":"BunyanGcpFactory.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/BunyanGcpFactory.ts"],"names":[],"mappings":";;;AAAA,2DAAwD;AACxD,uCAAoD;AAEpD;;;;;;;;;;GAUG;AACH,MAAa,gBAAiB,SAAQ,qCAAiB;IACnD;QACI,KAAK,CAAC,CAAC,IAAA,iCAAuB,GAAE,CAAC,CAAC,CAAC;IACvC,CAAC;CACJ;AAJD,4CAIC","sourcesContent":["import { BunyanFactoryBase } from './BunyanFactoryBase';\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). The logged context keys ride along as\n * structured payload fields, read straight from RequestContext on each line. This\n * matches a production-tested GCP service exactly. Requires GCP Application\n * Default Credentials on the instance.\n *\n * The service name + version come from {@link ServiceInfo}, which startup must have populated\n * BEFORE constructing this.\n */\nexport class BunyanGcpFactory extends BunyanFactoryBase {\n constructor() {\n super([createGoogleCloudStream()]);\n }\n}\n"]}
|
package/src/BunyanLogger.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { Logger as WpLogger } from '@webpieces/core-util';
|
|
|
5
5
|
* name, created as a bunyan child carrying `loggerName`). On every call it reads
|
|
6
6
|
* the logged HeaderRegistry keys DIRECTLY from the active {@link RequestContext}
|
|
7
7
|
* (secured values masked) into the bunyan fields object, and normalizes an optional
|
|
8
|
-
* Error into `err: { name, message, stack }` — matching
|
|
8
|
+
* Error into `err: { name, message, stack }` — matching a production-tested AppLogger.
|
|
9
9
|
* The GCP stream then serializes those fields into the structured log payload.
|
|
10
10
|
*
|
|
11
11
|
* This context-reading is INLINED here (and duplicated in the winston logger) on
|
package/src/BunyanLogger.js
CHANGED
|
@@ -21,7 +21,7 @@ function normalizeError(err) {
|
|
|
21
21
|
* name, created as a bunyan child carrying `loggerName`). On every call it reads
|
|
22
22
|
* the logged HeaderRegistry keys DIRECTLY from the active {@link RequestContext}
|
|
23
23
|
* (secured values masked) into the bunyan fields object, and normalizes an optional
|
|
24
|
-
* Error into `err: { name, message, stack }` — matching
|
|
24
|
+
* Error into `err: { name, message, stack }` — matching a production-tested AppLogger.
|
|
25
25
|
* The GCP stream then serializes those fields into the structured log payload.
|
|
26
26
|
*
|
|
27
27
|
* This context-reading is INLINED here (and duplicated in the winston logger) on
|
package/src/BunyanLogger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BunyanLogger.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/BunyanLogger.ts"],"names":[],"mappings":";;;AAEA,0DAAyD;AACzD,+CAA4C;AAE5C;;;;;;;;;GASG;AACH,SAAS,cAAc,CAAC,GAAU;IAC9B,OAAO,IAAI,yBAAW,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAa,YAAY;IACQ;IAA7B,YAA6B,MAAc;QAAd,WAAM,GAAN,MAAM,CAAQ;IAAG,CAAC;IAE/C,KAAK,CAAC,OAAe,EAAE,GAAW;QAC9B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAW;QAC9B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAW;QAC7B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACrD,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAW;QAC7B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAW;QAC9B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAEO,WAAW,CAAC,GAAW;QAC3B,MAAM,MAAM,GAAkD,EAAE,CAAC;QACjE,iGAAiG;QACjG,gGAAgG;QAChG,iGAAiG;QACjG,qEAAqE;QACrE,6FAA6F;QAC7F,gGAAgG;QAChG,sFAAsF;QACtF,6BAAc,CAAC,wBAAwB,EAAE,CAAC,OAAO,CAAC,CAAC,KAAsB,EAAE,IAAY,EAAE,EAAE;YACvF,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QACzB,CAAC,CAAC,CAAC;QACH,IAAI,GAAG,EAAE,CAAC;YACN,MAAM,CAAC,KAAK,CAAC,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;CACJ;AAxCD,oCAwCC","sourcesContent":["import type Logger from 'bunyan';\nimport type { Logger as WpLogger } from '@webpieces/core-util';\nimport { RequestContext } from '@webpieces/core-context';\nimport { LoggedError } from './LoggedError';\n\n/**\n * The error, whole. There is deliberately NO size guard here any more.\n *\n * This used to truncate an error over 100KB down to 5 stack frames and a 100-character message, to\n * keep one giant stack from blowing past Cloud Logging's per-entry limit. That traded the wrong\n * thing away: a stack trace big enough to trip the limit is precisely the one worth reading, and it\n * arrived pre-shredded. The GCP stream now SPLITS an oversized record across several complete\n * records instead (see ChunkingRawStream), so the whole stack survives and nothing is lost — which\n * makes truncating here strictly harmful.\n */\nfunction normalizeError(err: Error): LoggedError {\n return new LoggedError(err.name, err.message, err.stack);\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 reads\n * the logged HeaderRegistry keys DIRECTLY from the active {@link RequestContext}\n * (secured values masked) into the bunyan fields object, and normalizes an optional\n * Error into `err: { name, message, stack }` — matching
|
|
1
|
+
{"version":3,"file":"BunyanLogger.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/BunyanLogger.ts"],"names":[],"mappings":";;;AAEA,0DAAyD;AACzD,+CAA4C;AAE5C;;;;;;;;;GASG;AACH,SAAS,cAAc,CAAC,GAAU;IAC9B,OAAO,IAAI,yBAAW,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAa,YAAY;IACQ;IAA7B,YAA6B,MAAc;QAAd,WAAM,GAAN,MAAM,CAAQ;IAAG,CAAC;IAE/C,KAAK,CAAC,OAAe,EAAE,GAAW;QAC9B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAW;QAC9B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAW;QAC7B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACrD,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAW;QAC7B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAW;QAC9B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAEO,WAAW,CAAC,GAAW;QAC3B,MAAM,MAAM,GAAkD,EAAE,CAAC;QACjE,iGAAiG;QACjG,gGAAgG;QAChG,iGAAiG;QACjG,qEAAqE;QACrE,6FAA6F;QAC7F,gGAAgG;QAChG,sFAAsF;QACtF,6BAAc,CAAC,wBAAwB,EAAE,CAAC,OAAO,CAAC,CAAC,KAAsB,EAAE,IAAY,EAAE,EAAE;YACvF,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QACzB,CAAC,CAAC,CAAC;QACH,IAAI,GAAG,EAAE,CAAC;YACN,MAAM,CAAC,KAAK,CAAC,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;CACJ;AAxCD,oCAwCC","sourcesContent":["import type Logger from 'bunyan';\nimport type { Logger as WpLogger } from '@webpieces/core-util';\nimport { RequestContext } from '@webpieces/core-context';\nimport { LoggedError } from './LoggedError';\n\n/**\n * The error, whole. There is deliberately NO size guard here any more.\n *\n * This used to truncate an error over 100KB down to 5 stack frames and a 100-character message, to\n * keep one giant stack from blowing past Cloud Logging's per-entry limit. That traded the wrong\n * thing away: a stack trace big enough to trip the limit is precisely the one worth reading, and it\n * arrived pre-shredded. The GCP stream now SPLITS an oversized record across several complete\n * records instead (see ChunkingRawStream), so the whole stack survives and nothing is lost — which\n * makes truncating here strictly harmful.\n */\nfunction normalizeError(err: Error): LoggedError {\n return new LoggedError(err.name, err.message, err.stack);\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 reads\n * the logged HeaderRegistry keys DIRECTLY from the active {@link RequestContext}\n * (secured values masked) into the bunyan fields object, and normalizes an optional\n * Error into `err: { name, message, stack }` — matching a production-tested AppLogger.\n * The GCP stream then serializes those fields into the structured log payload.\n *\n * This context-reading is INLINED here (and duplicated in the winston logger) on\n * purpose: it must run ONLY when a bunyan/winston backend is installed, never for\n * the plain ConsoleLogger — so it does not belong on RequestContext.\n */\nexport class BunyanLogger implements WpLogger {\n constructor(private readonly bunyan: Logger) {}\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 private buildFields(err?: Error): Record<string, string | object | LoggedError> {\n const fields: Record<string, string | object | LoggedError> = {};\n // Called UNCONDITIONALLY (no active-context short-circuit): buildStructuredLogFields returns the\n // build `version` even with NO active RequestContext — plus the logged context keys when one IS\n // active. Startup, background-job, and unwrapped-call lines are thus stamped with `version` too;\n // an empty context is normal, not an error. (The in-process client's\n // ApiClientFactory.requireActiveContext() still throws precisely when a request-path wrap is\n // genuinely missing.) Values may be OBJECTS (the `api` tag), so an object-valued key nests into\n // the structured payload rather than being dropped by the string-only buildLogFields.\n RequestContext.buildStructuredLogFields().forEach((value: string | object, name: string) => {\n fields[name] = value;\n });\n if (err) {\n fields['err'] = normalizeError(err);\n }\n return fields;\n }\n}\n"]}
|
package/src/streams.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import Logger from 'bunyan';
|
|
|
2
2
|
/**
|
|
3
3
|
* The GCP stream: delegates ALL structured-JSON formatting (numeric level → GCP
|
|
4
4
|
* severity, msg→message, trace/httpRequest fields, stripping name/hostname/pid)
|
|
5
|
-
* to @google-cloud/logging-bunyan, exactly as the tested
|
|
5
|
+
* to @google-cloud/logging-bunyan, exactly as the production-tested service does.
|
|
6
6
|
* Sends to the Cloud Logging API (needs ADC on the instance).
|
|
7
7
|
*
|
|
8
8
|
* Records pass through a {@link ChunkingRawStream} first, because Cloud Logging caps a LogEntry at
|
package/src/streams.js
CHANGED
|
@@ -42,10 +42,10 @@ const BUNYAN_STD_FIELDS = new Set([
|
|
|
42
42
|
CONTROLLER_FIELD,
|
|
43
43
|
METHOD_FIELD,
|
|
44
44
|
]);
|
|
45
|
-
// "HH:MM:SS.mmm" in LOCAL time — matching the winston backend (fecha 'HH:mm:ss.SSS') and the
|
|
45
|
+
// "HH:MM:SS.mmm" in LOCAL time — matching the winston backend (fecha 'HH:mm:ss.SSS') and the production-tested
|
|
46
46
|
// format this was tuned for (`Date.toTimeString()`, also local). Parsing the ISO to a Date and reading
|
|
47
47
|
// local fields (rather than splitting the UTC ISO string) is what keeps the two backends byte-identical;
|
|
48
|
-
// milliseconds are zero-padded to 3 digits (
|
|
48
|
+
// milliseconds are zero-padded to 3 digits (that logger's raw `getMilliseconds()` rendered 5ms as ".5").
|
|
49
49
|
// webpieces-disable no-function-outside-class -- bunyan render helper; whole file is bunyan stream/render factories
|
|
50
50
|
function formatTime(iso) {
|
|
51
51
|
if (typeof iso !== 'string') {
|
|
@@ -107,7 +107,7 @@ function buildTags(obj, fields) {
|
|
|
107
107
|
/**
|
|
108
108
|
* Render one bunyan JSON line as a human-readable, greppable console line:
|
|
109
109
|
* `[LEVEL][time][Controller.method][loggerName][ctx tags]: message` plus multi-line error details.
|
|
110
|
-
* Level FIRST, then time (the ordering the
|
|
110
|
+
* Level FIRST, then time (the ordering the production-tested format was tuned for). `controller`/`method` render
|
|
111
111
|
* as a compact bracket, `loggerName` as its own bracket, and every other injected context key becomes a
|
|
112
112
|
* `key:value` tag (optionally filtered/ordered by the app `fields` allow-list).
|
|
113
113
|
*/
|
|
@@ -138,7 +138,7 @@ function writeConsole(line, fields) {
|
|
|
138
138
|
/**
|
|
139
139
|
* The GCP stream: delegates ALL structured-JSON formatting (numeric level → GCP
|
|
140
140
|
* severity, msg→message, trace/httpRequest fields, stripping name/hostname/pid)
|
|
141
|
-
* to @google-cloud/logging-bunyan, exactly as the tested
|
|
141
|
+
* to @google-cloud/logging-bunyan, exactly as the production-tested service does.
|
|
142
142
|
* Sends to the Cloud Logging API (needs ADC on the instance).
|
|
143
143
|
*
|
|
144
144
|
* Records pass through a {@link ChunkingRawStream} first, because Cloud Logging caps a LogEntry at
|
package/src/streams.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streams.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/streams.ts"],"names":[],"mappings":";;AAoKA,0DAUC;AAWD,kDAWC;;AApMD,mCAAkC;AAClC,4DAA4B;AAC5B,iEAA6D;AAC7D,oDAAsD;AAEtD,2DAAwD;AAOxD,qGAAqG;AACrG,4FAA4F;AAC5F,kGAAkG;AAClG,MAAM,gBAAgB,GAAG,YAAY,CAAC;AACtC,MAAM,YAAY,GAAG,QAAQ,CAAC;AAE9B,iFAAiF;AACjF,mDAAmD;AACnD,EAAE;AACF,iGAAiG;AACjG,sGAAsG;AACtG,6FAA6F;AAC7F,qGAAqG;AACrG,uGAAuG;AACvG,qFAAqF;AACrF,oFAAoF;AACpF,EAAE;AACF,qGAAqG;AACrG,+FAA+F;AAC/F,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS;IACtC,GAAG;IACH,OAAO;IACP,MAAM;IACN,SAAS;IACT,SAAS;IACT,UAAU;IACV,KAAK;IACL,MAAM;IACN,KAAK;IACL,KAAK;IACL,KAAK;IACL,YAAY;IACZ,gBAAgB;IAChB,YAAY;CACf,CAAC,CAAC;AAEH,qGAAqG;AACrG,uGAAuG;AACvG,yGAAyG;AACzG,qGAAqG;AACrG,oHAAoH;AACpH,SAAS,UAAU,CAAC,GAAc;IAC9B,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACd,CAAC;IACD,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IACxB,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC;IACf,CAAC;IACD,MAAM,EAAE,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC7D,MAAM,EAAE,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC7D,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,EAAE,CAAC;AACxG,CAAC;AAED,sGAAsG;AACtG,gFAAgF;AAChF,oHAAoH;AACpH,SAAS,sBAAsB,CAAC,UAAqB,EAAE,MAAiB;IACpE,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5D,OAAO,EAAE,CAAC;IACd,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACnF,OAAO,IAAI,UAAU,GAAG,MAAM,GAAG,CAAC;AACtC,CAAC;AAED,qGAAqG;AACrG,qGAAqG;AACrG,oFAAoF;AACpF,oHAAoH;AACpH,SAAS,SAAS,CAAC,GAAiB,EAAE,MAAiB;IACnD,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,CAAC,GAAW,EAAQ,EAAE;QAC/B,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;YAC7C,OAAO;QACX,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnB,OAAO;QACX,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC;IAC/B,CAAC,CAAC;IACF,IAAI,MAAM,EAAE,CAAC;QACT,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;YACvB,IAAI,GAAG,KAAK,gBAAgB,IAAI,GAAG,KAAK,YAAY,IAAI,GAAG,KAAK,YAAY,EAAE,CAAC;gBAC3E,SAAS;YACb,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,CAAC;QACd,CAAC;IACL,CAAC;SAAM,CAAC;QACJ,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACjC,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC7B,SAAS;YACb,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,CAAC;QACd,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,oHAAoH;AACpH,SAAS,YAAY,CAAC,IAAY,EAAE,MAAiB;IACjD,MAAM,GAAG,GAAiB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,CAAC,gBAAM,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAW,CAAC,IAAI,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACnG,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAErC,MAAM,gBAAgB,GAAG,sBAAsB,CAAC,GAAG,CAAC,gBAAgB,CAAC,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;IAC1F,kGAAkG;IAClG,uEAAuE;IACvE,MAAM,aAAa,GAAG,0BAAc,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IAE5E,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;IAEhE,IAAI,OAAO,GAAG,IAAI,SAAS,KAAK,IAAI,IAAI,gBAAgB,GAAG,aAAa,IAAI,MAAM,MAAM,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;IAEnH,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAA4B,CAAC;IAClD,IAAI,GAAG,EAAE,CAAC;QACN,OAAO,IAAI,kBAAkB,CAAC;QAC9B,OAAO,IAAI,gBAAgB,GAAG,CAAC,OAAO,EAAE,CAAC;QACzC,OAAO,IAAI,aAAa,GAAG,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YACZ,OAAO,IAAI,qBAAqB,GAAG,CAAC,KAAK,EAAE,CAAC;QAChD,CAAC;IACL,CAAC;IAED,8EAA8E;IAC9E,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACzB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,qHAAqH;AACrH,SAAgB,uBAAuB;IACnC,MAAM,aAAa,GAAG,IAAI,8BAAa,EAAE,CAAC;IAC1C,8FAA8F;IAC9F,kEAAkE;IAClE,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACzC,OAAO;QACH,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,IAAI,EAAE,KAAK;QACX,MAAM,EAAE,IAAI,qCAAiB,CAAC,aAAa,CAAC;KAC/C,CAAC;AACN,CAAC;AAED;;;;;;;GAOG;AACH,qHAAqH;AACrH,SAAgB,mBAAmB,CAAC,aAAwB;IACxD,MAAM,QAAQ,GAAG,IAAI,iBAAQ,CAAC;QAC1B,KAAK,CAAC,KAAsB,EAAE,SAAyB,EAAE,QAAwC;YAC7F,YAAY,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,aAAa,CAAC,CAAC;YAC9C,QAAQ,EAAE,CAAC;QACf,CAAC;KACJ,CAAC,CAAC;IACH,OAAO;QACH,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,QAAQ;KACnB,CAAC;AACN,CAAC","sourcesContent":["import { Writable } from 'stream';\nimport Logger from 'bunyan';\nimport { LoggingBunyan } from '@google-cloud/logging-bunyan';\nimport { ApiCallLogName } from '@webpieces/core-util';\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// `svcName` (the service) and `version` (the build) are ServiceInfo facts riding on EVERY record\n// (stamped per-record by RequestContext.buildStructuredLogFields — the same map winston reads, so the\n// two backends are symmetrical). `name` is bunyan's own mandatory root-logger field (a fixed\n// placeholder). All three earn their keep in GCP, where you filter across many services and deploys,\n// but 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 them (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 'svcName',\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 // 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.\n const loggerBracket = ApiCallLogName.bracket(obj['loggerName'], obj['api']);\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"]}
|
|
1
|
+
{"version":3,"file":"streams.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/streams.ts"],"names":[],"mappings":";;AAoKA,0DAUC;AAWD,kDAWC;;AApMD,mCAAkC;AAClC,4DAA4B;AAC5B,iEAA6D;AAC7D,oDAAsD;AAEtD,2DAAwD;AAOxD,qGAAqG;AACrG,4FAA4F;AAC5F,kGAAkG;AAClG,MAAM,gBAAgB,GAAG,YAAY,CAAC;AACtC,MAAM,YAAY,GAAG,QAAQ,CAAC;AAE9B,iFAAiF;AACjF,mDAAmD;AACnD,EAAE;AACF,iGAAiG;AACjG,sGAAsG;AACtG,6FAA6F;AAC7F,qGAAqG;AACrG,uGAAuG;AACvG,qFAAqF;AACrF,oFAAoF;AACpF,EAAE;AACF,qGAAqG;AACrG,+FAA+F;AAC/F,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS;IACtC,GAAG;IACH,OAAO;IACP,MAAM;IACN,SAAS;IACT,SAAS;IACT,UAAU;IACV,KAAK;IACL,MAAM;IACN,KAAK;IACL,KAAK;IACL,KAAK;IACL,YAAY;IACZ,gBAAgB;IAChB,YAAY;CACf,CAAC,CAAC;AAEH,+GAA+G;AAC/G,uGAAuG;AACvG,yGAAyG;AACzG,yGAAyG;AACzG,oHAAoH;AACpH,SAAS,UAAU,CAAC,GAAc;IAC9B,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACd,CAAC;IACD,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IACxB,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC;IACf,CAAC;IACD,MAAM,EAAE,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC7D,MAAM,EAAE,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC7D,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,EAAE,CAAC;AACxG,CAAC;AAED,sGAAsG;AACtG,gFAAgF;AAChF,oHAAoH;AACpH,SAAS,sBAAsB,CAAC,UAAqB,EAAE,MAAiB;IACpE,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5D,OAAO,EAAE,CAAC;IACd,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACnF,OAAO,IAAI,UAAU,GAAG,MAAM,GAAG,CAAC;AACtC,CAAC;AAED,qGAAqG;AACrG,qGAAqG;AACrG,oFAAoF;AACpF,oHAAoH;AACpH,SAAS,SAAS,CAAC,GAAiB,EAAE,MAAiB;IACnD,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,CAAC,GAAW,EAAQ,EAAE;QAC/B,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;YAC7C,OAAO;QACX,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnB,OAAO;QACX,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC;IAC/B,CAAC,CAAC;IACF,IAAI,MAAM,EAAE,CAAC;QACT,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;YACvB,IAAI,GAAG,KAAK,gBAAgB,IAAI,GAAG,KAAK,YAAY,IAAI,GAAG,KAAK,YAAY,EAAE,CAAC;gBAC3E,SAAS;YACb,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,CAAC;QACd,CAAC;IACL,CAAC;SAAM,CAAC;QACJ,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACjC,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC7B,SAAS;YACb,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,CAAC;QACd,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,oHAAoH;AACpH,SAAS,YAAY,CAAC,IAAY,EAAE,MAAiB;IACjD,MAAM,GAAG,GAAiB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,CAAC,gBAAM,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAW,CAAC,IAAI,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACnG,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAErC,MAAM,gBAAgB,GAAG,sBAAsB,CAAC,GAAG,CAAC,gBAAgB,CAAC,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;IAC1F,kGAAkG;IAClG,uEAAuE;IACvE,MAAM,aAAa,GAAG,0BAAc,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IAE5E,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;IAEhE,IAAI,OAAO,GAAG,IAAI,SAAS,KAAK,IAAI,IAAI,gBAAgB,GAAG,aAAa,IAAI,MAAM,MAAM,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;IAEnH,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAA4B,CAAC;IAClD,IAAI,GAAG,EAAE,CAAC;QACN,OAAO,IAAI,kBAAkB,CAAC;QAC9B,OAAO,IAAI,gBAAgB,GAAG,CAAC,OAAO,EAAE,CAAC;QACzC,OAAO,IAAI,aAAa,GAAG,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YACZ,OAAO,IAAI,qBAAqB,GAAG,CAAC,KAAK,EAAE,CAAC;QAChD,CAAC;IACL,CAAC;IAED,8EAA8E;IAC9E,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACzB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,qHAAqH;AACrH,SAAgB,uBAAuB;IACnC,MAAM,aAAa,GAAG,IAAI,8BAAa,EAAE,CAAC;IAC1C,8FAA8F;IAC9F,kEAAkE;IAClE,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACzC,OAAO;QACH,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,IAAI,EAAE,KAAK;QACX,MAAM,EAAE,IAAI,qCAAiB,CAAC,aAAa,CAAC;KAC/C,CAAC;AACN,CAAC;AAED;;;;;;;GAOG;AACH,qHAAqH;AACrH,SAAgB,mBAAmB,CAAC,aAAwB;IACxD,MAAM,QAAQ,GAAG,IAAI,iBAAQ,CAAC;QAC1B,KAAK,CAAC,KAAsB,EAAE,SAAyB,EAAE,QAAwC;YAC7F,YAAY,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,aAAa,CAAC,CAAC;YAC9C,QAAQ,EAAE,CAAC;QACf,CAAC;KACJ,CAAC,CAAC;IACH,OAAO;QACH,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,QAAQ;KACnB,CAAC;AACN,CAAC","sourcesContent":["import { Writable } from 'stream';\nimport Logger from 'bunyan';\nimport { LoggingBunyan } from '@google-cloud/logging-bunyan';\nimport { ApiCallLogName } from '@webpieces/core-util';\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// `svcName` (the service) and `version` (the build) are ServiceInfo facts riding on EVERY record\n// (stamped per-record by RequestContext.buildStructuredLogFields — the same map winston reads, so the\n// two backends are symmetrical). `name` is bunyan's own mandatory root-logger field (a fixed\n// placeholder). All three earn their keep in GCP, where you filter across many services and deploys,\n// but 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 them (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 'svcName',\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 production-tested\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 (that logger'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 production-tested 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 // 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.\n const loggerBracket = ApiCallLogName.bracket(obj['loggerName'], obj['api']);\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 production-tested 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"]}
|