@webpieces/bunyan 0.4.394 → 0.4.396

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,9 +20,10 @@ import { LogManager, HeaderRegistry } from '@webpieces/core-util';
20
20
  import { ServiceInfo } from '@webpieces/core-util';
21
21
  import { BunyanGcpFactory, BunyanConsoleFactory } from '@webpieces/bunyan';
22
22
 
23
- // FIRST: name this service. Both factories read it in their CONSTRUCTOR, so this must come
24
- // before you build one — a forgotten call throws at startup rather than shipping unnamed logs.
25
- ServiceInfo.setName('my-service');
23
+ // FIRST: identify this service. Both factories read name+version in their CONSTRUCTOR, so this
24
+ // must come before you build one — a forgotten call throws at startup rather than shipping logs
25
+ // that cannot say which build emitted them.
26
+ ServiceInfo.setInfo('my-service', '2.1.0');
26
27
 
27
28
  const loggerFactory = process.env.K_SERVICE
28
29
  ? new BunyanGcpFactory()
@@ -42,10 +43,13 @@ Credentials on the instance (automatic on Cloud Run), exactly as the source serv
42
43
 
43
44
  There are none — both factories take no arguments.
44
45
 
45
- - **Service name** — from `ServiceInfo.setName(...)` (see above), NOT a factory option. It
46
- becomes bunyan's mandatory root-logger `name` and surfaces as `name` in the payload. It
47
- lives in `@webpieces/core-util` because it is a fact about the SERVICE, not about bunyan:
48
- the winston backend reads the same value, and so does `requestIdSource` (which records
49
- which service minted a request-id).
46
+ - **Service name + version** — from `ServiceInfo.setInfo(...)` (see above), NOT factory options.
47
+ The name becomes bunyan's mandatory root-logger `name` and surfaces as `name` in the payload;
48
+ the version rides as a bunyan base field and surfaces as `version`. They live in
49
+ `@webpieces/core-util` because they are facts about the SERVICE, not about bunyan: the winston
50
+ backend reads the same values, and `requestIdSource` reads the name (it records which service
51
+ minted a request-id).
52
+ - **`version` is opaque** — a git SHA, a semver tag, a CI build number, whatever identifies your
53
+ build. webpieces neither parses nor derives it; your app decides where it comes from.
50
54
  - **Level** — there is deliberately no knob. webpieces does not filter by level; bunyan
51
55
  filters at its own default.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/bunyan",
3
- "version": "0.4.394",
3
+ "version": "0.4.396",
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.394",
27
- "@webpieces/core-context": "0.4.394",
26
+ "@webpieces/core-util": "0.4.396",
27
+ "@webpieces/core-context": "0.4.396",
28
28
  "bunyan": "1.8.15",
29
29
  "@google-cloud/logging-bunyan": "5.1.0",
30
30
  "@types/bunyan": "1.8.11"
@@ -5,7 +5,7 @@ import { BunyanFactoryBase } from './BunyanFactoryBase';
5
5
  * keys as tags — same enrichment as the GCP backend, different rendering. Mirrors
6
6
  * the tested trytami local console stream.
7
7
  *
8
- * The service name comes from {@link ServiceInfo}, which startup must have named
8
+ * The service name + version come from {@link ServiceInfo}, which startup must have populated
9
9
  * BEFORE constructing this.
10
10
  */
11
11
  export declare class BunyanConsoleFactory extends BunyanFactoryBase {
@@ -9,7 +9,7 @@ const streams_1 = require("./streams");
9
9
  * keys as tags — same enrichment as the GCP backend, different rendering. Mirrors
10
10
  * the tested trytami local console stream.
11
11
  *
12
- * The service name comes from {@link ServiceInfo}, which startup must have named
12
+ * The service name + version come from {@link ServiceInfo}, which startup must have populated
13
13
  * BEFORE constructing this.
14
14
  */
15
15
  class BunyanConsoleFactory extends BunyanFactoryBase_1.BunyanFactoryBase {
@@ -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 comes from {@link ServiceInfo}, which startup must have named\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;;;;;;;;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"]}
@@ -11,9 +11,16 @@ import type { Logger as WpLogger, LoggerFactory } from '@webpieces/core-util';
11
11
  * The root logger's `name` is bunyan's ONE mandatory option (its constructor throws
12
12
  * `options.name (string) is required`), and it comes from {@link ServiceInfo} — so an
13
13
  * app names itself ONCE, in one place, for logging AND for requestIdSource. Reading it
14
- * here means a forgotten `ServiceInfo.setName(...)` throws OUR actionable error while
14
+ * here means a forgotten `ServiceInfo.setInfo(...)` throws OUR actionable error while
15
15
  * the process boots, rather than bunyan's opaque TypeError.
16
16
  *
17
+ * `version` rides alongside as a bunyan BASE FIELD (bunyan passes unknown top-level options straight
18
+ * onto every record — it strips only stream/streams/level/serializers/src, and its own format
19
+ * version is `v`, so there is no collision). This backend previously could not stamp a version at
20
+ * all: it lived on winston's factory options, which made "do my logs say which build emitted them?"
21
+ * depend on the logging library rather than on the service. Both backends now read the ONE
22
+ * {@link ServiceInfo}.
23
+ *
17
24
  * Per-logger names ride as `loggerName` on each child rather than `name`, because
18
25
  * bunyan REFUSES to let a child override `name` (`invalid options.name: child cannot
19
26
  * set logger name`).
@@ -16,9 +16,16 @@ const BunyanLogger_1 = require("./BunyanLogger");
16
16
  * The root logger's `name` is bunyan's ONE mandatory option (its constructor throws
17
17
  * `options.name (string) is required`), and it comes from {@link ServiceInfo} — so an
18
18
  * app names itself ONCE, in one place, for logging AND for requestIdSource. Reading it
19
- * here means a forgotten `ServiceInfo.setName(...)` throws OUR actionable error while
19
+ * here means a forgotten `ServiceInfo.setInfo(...)` throws OUR actionable error while
20
20
  * the process boots, rather than bunyan's opaque TypeError.
21
21
  *
22
+ * `version` rides alongside as a bunyan BASE FIELD (bunyan passes unknown top-level options straight
23
+ * onto every record — it strips only stream/streams/level/serializers/src, and its own format
24
+ * version is `v`, so there is no collision). This backend previously could not stamp a version at
25
+ * all: it lived on winston's factory options, which made "do my logs say which build emitted them?"
26
+ * depend on the logging library rather than on the service. Both backends now read the ONE
27
+ * {@link ServiceInfo}.
28
+ *
22
29
  * Per-logger names ride as `loggerName` on each child rather than `name`, because
23
30
  * bunyan REFUSES to let a child override `name` (`invalid options.name: child cannot
24
31
  * set logger name`).
@@ -27,7 +34,11 @@ class BunyanFactoryBase {
27
34
  base;
28
35
  loggers = new Map();
29
36
  constructor(streams) {
30
- this.base = bunyan_1.default.createLogger({ name: core_util_1.ServiceInfo.getName(), streams });
37
+ this.base = bunyan_1.default.createLogger({
38
+ name: core_util_1.ServiceInfo.getName(),
39
+ version: core_util_1.ServiceInfo.getVersion(),
40
+ streams,
41
+ });
31
42
  }
32
43
  getLogger(name) {
33
44
  let logger = this.loggers.get(name);
@@ -1 +1 @@
1
- {"version":3,"file":"BunyanFactoryBase.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/BunyanFactoryBase.ts"],"names":[],"mappings":";;;;AAAA,4DAA4B;AAE5B,oDAAmD;AACnD,iDAA8C;AAE9C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAsB,iBAAiB;IAClB,IAAI,CAAS;IACb,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IAEvD,YAAsB,OAAwB;QAC1C,IAAI,CAAC,IAAI,GAAG,gBAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,uBAAW,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9E,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,CAAC,CAAC;YACjE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;CACJ;AAhBD,8CAgBC","sourcesContent":["import Logger from 'bunyan';\nimport type { Logger as WpLogger, LoggerFactory } from '@webpieces/core-util';\nimport { ServiceInfo } 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`). Every logger reads the magic context DIRECTLY from\n * RequestContext, so nothing is threaded through here. Subclasses differ only in\n * the stream they pass up (GCP vs local console).\n *\n * The root logger's `name` is bunyan's ONE mandatory option (its constructor throws\n * `options.name (string) is required`), and it comes from {@link ServiceInfo} — so an\n * app names itself ONCE, in one place, for logging AND for requestIdSource. Reading it\n * here means a forgotten `ServiceInfo.setName(...)` throws OUR actionable error while\n * the process boots, rather than bunyan's opaque TypeError.\n *\n * Per-logger names ride as `loggerName` on each child rather than `name`, because\n * bunyan REFUSES to let a child override `name` (`invalid options.name: child cannot\n * set logger name`).\n */\nexport abstract class BunyanFactoryBase implements LoggerFactory {\n private readonly base: Logger;\n private readonly loggers = new Map<string, WpLogger>();\n\n protected constructor(streams: Logger.Stream[]) {\n this.base = Logger.createLogger({ name: ServiceInfo.getName(), 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 }));\n this.loggers.set(name, logger);\n }\n return logger;\n }\n}\n"]}
1
+ {"version":3,"file":"BunyanFactoryBase.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/BunyanFactoryBase.ts"],"names":[],"mappings":";;;;AAAA,4DAA4B;AAE5B,oDAAmD;AACnD,iDAA8C;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAsB,iBAAiB;IAClB,IAAI,CAAS;IACb,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IAEvD,YAAsB,OAAwB;QAC1C,IAAI,CAAC,IAAI,GAAG,gBAAM,CAAC,YAAY,CAAC;YAC5B,IAAI,EAAE,uBAAW,CAAC,OAAO,EAAE;YAC3B,OAAO,EAAE,uBAAW,CAAC,UAAU,EAAE;YACjC,OAAO;SACV,CAAC,CAAC;IACP,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,CAAC,CAAC;YACjE,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 { Logger as WpLogger, LoggerFactory } from '@webpieces/core-util';\nimport { ServiceInfo } 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`). Every logger reads the magic context DIRECTLY from\n * RequestContext, so nothing is threaded through here. Subclasses differ only in\n * the stream they pass up (GCP vs local console).\n *\n * The root logger's `name` is bunyan's ONE mandatory option (its constructor throws\n * `options.name (string) is required`), and it comes from {@link ServiceInfo} — so an\n * app names itself ONCE, in one place, for logging AND for requestIdSource. Reading it\n * here means a forgotten `ServiceInfo.setInfo(...)` throws OUR actionable error while\n * the process boots, rather than bunyan's opaque TypeError.\n *\n * `version` rides alongside as a bunyan BASE FIELD (bunyan passes unknown top-level options straight\n * onto every record — it strips only stream/streams/level/serializers/src, and its own format\n * version is `v`, so there is no collision). This backend previously could not stamp a version at\n * all: it lived on winston's factory options, which made \"do my logs say which build emitted them?\"\n * depend on the logging library rather than on the service. Both backends now read the ONE\n * {@link ServiceInfo}.\n *\n * Per-logger names ride as `loggerName` on each child rather than `name`, because\n * bunyan REFUSES to let a child override `name` (`invalid options.name: child cannot\n * set logger name`).\n */\nexport abstract class BunyanFactoryBase implements LoggerFactory {\n private readonly base: Logger;\n private readonly loggers = new Map<string, WpLogger>();\n\n protected constructor(streams: Logger.Stream[]) {\n this.base = Logger.createLogger({\n name: ServiceInfo.getName(),\n version: ServiceInfo.getVersion(),\n streams,\n });\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 }));\n this.loggers.set(name, logger);\n }\n return logger;\n }\n}\n"]}
@@ -7,7 +7,7 @@ import { BunyanFactoryBase } from './BunyanFactoryBase';
7
7
  * matches the tested-in-GCP trytami service exactly. Requires GCP Application
8
8
  * Default Credentials on the instance.
9
9
  *
10
- * The service name comes from {@link ServiceInfo}, which startup must have named
10
+ * The service name + version come from {@link ServiceInfo}, which startup must have populated
11
11
  * BEFORE constructing this.
12
12
  */
13
13
  export declare class BunyanGcpFactory extends BunyanFactoryBase {
@@ -11,7 +11,7 @@ const streams_1 = require("./streams");
11
11
  * matches the tested-in-GCP trytami service exactly. Requires GCP Application
12
12
  * Default Credentials on the instance.
13
13
  *
14
- * The service name comes from {@link ServiceInfo}, which startup must have named
14
+ * The service name + version come from {@link ServiceInfo}, which startup must have populated
15
15
  * BEFORE constructing this.
16
16
  */
17
17
  class BunyanGcpFactory extends BunyanFactoryBase_1.BunyanFactoryBase {
@@ -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 the tested-in-GCP trytami service exactly. Requires GCP Application\n * Default Credentials on the instance.\n *\n * The service name comes from {@link ServiceInfo}, which startup must have named\n * BEFORE constructing this.\n */\nexport class BunyanGcpFactory extends BunyanFactoryBase {\n constructor() {\n super([createGoogleCloudStream()]);\n }\n}\n"]}
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 the tested-in-GCP trytami 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/index.d.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  * import { ServiceInfo } from '@webpieces/core-util';
9
9
  * import { BunyanGcpFactory, BunyanConsoleFactory } from '@webpieces/bunyan';
10
10
  *
11
- * ServiceInfo.setName('my-service'); // FIRST — the factories read it in their constructor
11
+ * ServiceInfo.setInfo('my-service', '2.1.0'); // FIRST — the factories read it in their constructor
12
12
  * const loggerFactory = process.env.K_SERVICE
13
13
  * ? new BunyanGcpFactory() // Cloud Logging via @google-cloud/logging-bunyan
14
14
  * : new BunyanConsoleFactory(); // local → pretty console
@@ -16,10 +16,14 @@
16
16
  * ```
17
17
  *
18
18
  * BREAKING (was `new BunyanFactoryOptions('my-service')` passed to each factory): the service name
19
- * moved to `ServiceInfo.setName(...)` in @webpieces/core-util, because it is a fact about the
19
+ * moved to `ServiceInfo.setInfo(...)` in @webpieces/core-util, because it is a fact about the
20
20
  * SERVICE, not about bunyan — winston needs the same name, and so does `requestIdSource`. Migration:
21
- * delete the `BunyanFactoryOptions` import, call `ServiceInfo.setName(<the same string>)` before
22
- * building the factory, and drop the ctor argument. A forgotten call throws at startup.
21
+ * delete the `BunyanFactoryOptions` import, call `ServiceInfo.setInfo(<the same string>, <version>)`
22
+ * before building the factory, and drop the ctor argument. A forgotten call throws at startup.
23
+ *
24
+ * NEW: every line now also carries `version` (the second setInfo arg) — this backend could not stamp
25
+ * a build version before. It is opaque: a git SHA, a semver tag, a CI build number, whatever
26
+ * identifies your build.
23
27
  *
24
28
  * Both backends auto-enrich every line with the logged context keys, read
25
29
  * DIRECTLY from the active RequestContext (@webpieces/core-context) on each line —
package/src/index.js CHANGED
@@ -11,7 +11,7 @@ exports.logLevelToBunyanLevel = exports.LEVEL_TO_BUNYAN = exports.createConsoleS
11
11
  * import { ServiceInfo } from '@webpieces/core-util';
12
12
  * import { BunyanGcpFactory, BunyanConsoleFactory } from '@webpieces/bunyan';
13
13
  *
14
- * ServiceInfo.setName('my-service'); // FIRST — the factories read it in their constructor
14
+ * ServiceInfo.setInfo('my-service', '2.1.0'); // FIRST — the factories read it in their constructor
15
15
  * const loggerFactory = process.env.K_SERVICE
16
16
  * ? new BunyanGcpFactory() // Cloud Logging via @google-cloud/logging-bunyan
17
17
  * : new BunyanConsoleFactory(); // local → pretty console
@@ -19,10 +19,14 @@ exports.logLevelToBunyanLevel = exports.LEVEL_TO_BUNYAN = exports.createConsoleS
19
19
  * ```
20
20
  *
21
21
  * BREAKING (was `new BunyanFactoryOptions('my-service')` passed to each factory): the service name
22
- * moved to `ServiceInfo.setName(...)` in @webpieces/core-util, because it is a fact about the
22
+ * moved to `ServiceInfo.setInfo(...)` in @webpieces/core-util, because it is a fact about the
23
23
  * SERVICE, not about bunyan — winston needs the same name, and so does `requestIdSource`. Migration:
24
- * delete the `BunyanFactoryOptions` import, call `ServiceInfo.setName(<the same string>)` before
25
- * building the factory, and drop the ctor argument. A forgotten call throws at startup.
24
+ * delete the `BunyanFactoryOptions` import, call `ServiceInfo.setInfo(<the same string>, <version>)`
25
+ * before building the factory, and drop the ctor argument. A forgotten call throws at startup.
26
+ *
27
+ * NEW: every line now also carries `version` (the second setInfo arg) — this backend could not stamp
28
+ * a build version before. It is opaque: a git SHA, a semver tag, a CI build number, whatever
29
+ * identifies your build.
26
30
  *
27
31
  * Both backends auto-enrich every line with the logged context keys, read
28
32
  * DIRECTLY from the active RequestContext (@webpieces/core-context) on each line —
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,+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 { ServiceInfo } from '@webpieces/core-util';\n * import { BunyanGcpFactory, BunyanConsoleFactory } from '@webpieces/bunyan';\n *\n * ServiceInfo.setName('my-service'); // FIRST — the factories read it in their constructor\n * const loggerFactory = process.env.K_SERVICE\n * ? new BunyanGcpFactory() // Cloud Logging via @google-cloud/logging-bunyan\n * : new BunyanConsoleFactory(); // local → pretty console\n * // hand to setupRuntime(new RuntimeSetupOptions(loggerFactory, ...))\n * ```\n *\n * BREAKING (was `new BunyanFactoryOptions('my-service')` passed to each factory): the service name\n * moved to `ServiceInfo.setName(...)` in @webpieces/core-util, because it is a fact about the\n * SERVICE, not about bunyan — winston needs the same name, and so does `requestIdSource`. Migration:\n * delete the `BunyanFactoryOptions` import, call `ServiceInfo.setName(<the same string>)` before\n * building the factory, and drop the ctor argument. A forgotten call throws at startup.\n *\n * Both backends auto-enrich every line with the logged context keys, read\n * DIRECTLY from the active RequestContext (@webpieces/core-context) on each line —\n * no ContextReader is threaded in. There is no level knob: bunyan filters at its\n * own default.\n *\n * @packageDocumentation\n */\nexport { BunyanGcpFactory } from './BunyanGcpFactory';\nexport { BunyanConsoleFactory } from './BunyanConsoleFactory';\nexport { BunyanLogger } from './BunyanLogger';\nexport { createGoogleCloudStream, createConsoleStream } from './streams';\nexport { LEVEL_TO_BUNYAN, logLevelToBunyanLevel } from './levels';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/logging/bunyan/src/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,+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 { ServiceInfo } from '@webpieces/core-util';\n * import { BunyanGcpFactory, BunyanConsoleFactory } from '@webpieces/bunyan';\n *\n * ServiceInfo.setInfo('my-service', '2.1.0'); // FIRST — the factories read it in their constructor\n * const loggerFactory = process.env.K_SERVICE\n * ? new BunyanGcpFactory() // Cloud Logging via @google-cloud/logging-bunyan\n * : new BunyanConsoleFactory(); // local → pretty console\n * // hand to setupRuntime(new RuntimeSetupOptions(loggerFactory, ...))\n * ```\n *\n * BREAKING (was `new BunyanFactoryOptions('my-service')` passed to each factory): the service name\n * moved to `ServiceInfo.setInfo(...)` in @webpieces/core-util, because it is a fact about the\n * SERVICE, not about bunyan — winston needs the same name, and so does `requestIdSource`. Migration:\n * delete the `BunyanFactoryOptions` import, call `ServiceInfo.setInfo(<the same string>, <version>)`\n * before building the factory, and drop the ctor argument. A forgotten call throws at startup.\n *\n * NEW: every line now also carries `version` (the second setInfo arg) — this backend could not stamp\n * a build version before. It is opaque: a git SHA, a semver tag, a CI build number, whatever\n * identifies your build.\n *\n * Both backends auto-enrich every line with the logged context keys, read\n * DIRECTLY from the active RequestContext (@webpieces/core-context) on each line —\n * no ContextReader is threaded in. There is no level knob: bunyan filters at its\n * own default.\n *\n * @packageDocumentation\n */\nexport { BunyanGcpFactory } from './BunyanGcpFactory';\nexport { BunyanConsoleFactory } from './BunyanConsoleFactory';\nexport { BunyanLogger } from './BunyanLogger';\nexport { createGoogleCloudStream, createConsoleStream } from './streams';\nexport { LEVEL_TO_BUNYAN, logLevelToBunyanLevel } from './levels';\n"]}
package/src/streams.js CHANGED
@@ -8,10 +8,17 @@ const bunyan_1 = tslib_1.__importDefault(require("bunyan"));
8
8
  const logging_bunyan_1 = require("@google-cloud/logging-bunyan");
9
9
  // bunyan record fields that are structural / rendered specially, so they are not
10
10
  // shown as context tags in the local console line.
11
+ //
12
+ // `name` (the service) and `version` (the build) are ServiceInfo base fields riding on EVERY
13
+ // record. They earn their keep in GCP, where you filter across many services and deploys, but
14
+ // locally each service logs to its own place and you can check git yourself — so as a tag on every
15
+ // single line they are pure noise. Listing them here is the bunyan twin of winston's
16
+ // LOCAL_STRUCTURAL_KEYS; GCP still gets both (that stream does its own formatting).
11
17
  const BUNYAN_STD_FIELDS = new Set([
12
18
  'v',
13
19
  'level',
14
20
  'name',
21
+ 'version',
15
22
  'hostname',
16
23
  'pid',
17
24
  'time',
@@ -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';\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 *\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 return loggingBunyan.stream('info');\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';\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 * 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 return loggingBunyan.stream('info');\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"]}