@webpieces/core-util 0.4.395 → 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/package.json +1 -1
- package/src/http/ServiceInfo.d.ts +57 -25
- package/src/http/ServiceInfo.js +83 -31
- package/src/http/ServiceInfo.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ServiceInfo - the ONE answer to "what service am I?", for every part of
|
|
2
|
+
* ServiceInfo - the ONE answer to "what service am I, and which build of it?", for every part of
|
|
3
|
+
* webpieces that needs it.
|
|
3
4
|
*
|
|
4
5
|
* Configured like {@link HeaderRegistry} / {@link ClientRegistry} / LogManager — populated once at
|
|
5
6
|
* startup, then globally accessible with NO DI wiring. Browser-safe (no `process.env`, no node-only
|
|
@@ -7,49 +8,68 @@
|
|
|
7
8
|
*
|
|
8
9
|
* ```ts
|
|
9
10
|
* // startup, FIRST — before the logger factory is constructed (it reads this):
|
|
10
|
-
* ServiceInfo.
|
|
11
|
+
* ServiceInfo.setInfo('my-service', '2.1.0');
|
|
11
12
|
* ```
|
|
12
13
|
*
|
|
13
|
-
* WHY THIS EXISTS.
|
|
14
|
-
*
|
|
15
|
-
* - bunyan REQUIRES a root-logger name
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* WHY THIS EXISTS. Both facts used to be artifacts of WHICH LOGGING LIBRARY YOU PICKED rather than
|
|
15
|
+
* facts about the service:
|
|
16
|
+
* - the name lived on `BunyanFactoryOptions`, because bunyan REQUIRES a root-logger name
|
|
17
|
+
* (`options.name (string) is required` — bunyan's own TypeError). winston has no such concept, so
|
|
18
|
+
* a winston app shipped its logs UNNAMED.
|
|
19
|
+
* - the version lived on `WinstonFactoryOptions` as `svcGitHash`, so a bunyan app could not stamp a
|
|
20
|
+
* version AT ALL — and the name presumed a git SHA, which not every project deploys from.
|
|
18
21
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* - the
|
|
22
|
+
* Switching backends silently changed which fields your logs carried. Several unrelated readers
|
|
23
|
+
* need these same two facts, so they belong to the framework, not to a backend:
|
|
24
|
+
* - the bunyan backend, to satisfy bunyan's mandatory root-logger `name`, and to stamp `version`;
|
|
25
|
+
* - the winston backend, to stamp `svcName` + `version`;
|
|
22
26
|
* - {@link WebpiecesCoreHeaders.REQUEST_ID_SOURCE}, to record WHO minted a request id.
|
|
23
27
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* {@link
|
|
28
|
+
* VERSION IS OPAQUE. It is whatever string identifies THIS build — a git SHA, a semver tag, a CI
|
|
29
|
+
* build number. webpieces neither parses nor derives it; the app decides where it comes from (a
|
|
30
|
+
* generated file, an env var, a Docker build arg) and passes it here.
|
|
31
|
+
*
|
|
32
|
+
* FAIL FAST, AT STARTUP. {@link getName} / {@link getVersion} throw, and the logger factories +
|
|
33
|
+
* `setupRuntime` call them while the process is booting — so a forgotten `setInfo` kills the deploy
|
|
34
|
+
* (the revision never goes healthy) instead of quietly shipping logs that cannot say which build
|
|
35
|
+
* emitted them. Nothing on the REQUEST path may throw over this: a missing log field must never 500
|
|
36
|
+
* live traffic, so per-request readers use {@link tryGetName} / {@link tryGetVersion} and simply
|
|
37
|
+
* omit the field.
|
|
29
38
|
*/
|
|
30
39
|
export declare class ServiceInfo {
|
|
31
40
|
/** This service's name. Process-global; set once at startup. */
|
|
32
41
|
private static svcName;
|
|
42
|
+
/** This build's version — an opaque app-chosen string. Process-global; set once at startup. */
|
|
43
|
+
private static svcVersion;
|
|
33
44
|
/**
|
|
34
|
-
*
|
|
35
|
-
* reads
|
|
45
|
+
* Identify this service. Call it FIRST at startup — before constructing the logger factory,
|
|
46
|
+
* which reads both values during its own constructor.
|
|
36
47
|
*
|
|
37
|
-
* LAST CALL WINS, deliberately. A real deployment
|
|
38
|
-
* legitimately boot TWO services back-to-back (see the app-example e2e two-server flow), so a
|
|
39
|
-
* "one process = one
|
|
40
|
-
* factory captures
|
|
41
|
-
*
|
|
48
|
+
* LAST CALL WINS, deliberately. A real deployment identifies itself once, but an in-process test
|
|
49
|
+
* can legitimately boot TWO services back-to-back (see the app-example e2e two-server flow), so a
|
|
50
|
+
* "one process = one service" rule would reject a case that genuinely exists. Since a logger
|
|
51
|
+
* factory captures both values in its own constructor, each server built that way still keeps
|
|
52
|
+
* what was set when IT was built.
|
|
42
53
|
*
|
|
43
|
-
* @
|
|
54
|
+
* @param name - this service's name, e.g. 'my-service'.
|
|
55
|
+
* @param version - the opaque identifier of THIS build (git SHA, semver, CI build number).
|
|
56
|
+
* @throws Error on a blank name or version — that is always a bug, never a use case.
|
|
44
57
|
*/
|
|
45
|
-
static
|
|
58
|
+
static setInfo(name: string, version: string): void;
|
|
46
59
|
/**
|
|
47
60
|
* This service's name, REQUIRED. For STARTUP callers only (the logger factories, setupRuntime) —
|
|
48
61
|
* they run while booting, so throwing here fails the deploy rather than live traffic.
|
|
49
62
|
*
|
|
50
|
-
* @throws Error if {@link
|
|
63
|
+
* @throws Error if {@link setInfo} was never called.
|
|
51
64
|
*/
|
|
52
65
|
static getName(): string;
|
|
66
|
+
/**
|
|
67
|
+
* This build's version, REQUIRED. For STARTUP callers only (the logger factories, setupRuntime),
|
|
68
|
+
* for the same reason as {@link getName}.
|
|
69
|
+
*
|
|
70
|
+
* @throws Error if {@link setInfo} was never called.
|
|
71
|
+
*/
|
|
72
|
+
static getVersion(): string;
|
|
53
73
|
/**
|
|
54
74
|
* This service's name, or undefined when unset. For the REQUEST path, which must NOT throw: a
|
|
55
75
|
* server that booted has already passed the {@link getName} check in `setupRuntime`, so a real
|
|
@@ -57,6 +77,18 @@ export declare class ServiceInfo {
|
|
|
57
77
|
* undefined — and there, omitting a log field beats exploding.
|
|
58
78
|
*/
|
|
59
79
|
static tryGetName(): string | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* This build's version, or undefined when unset. For the REQUEST path, which must NOT throw —
|
|
82
|
+
* same reasoning as {@link tryGetName}.
|
|
83
|
+
*/
|
|
84
|
+
static tryGetVersion(): string | undefined;
|
|
60
85
|
/** Reset — for tests, mirroring {@link ClientRegistry.clear}. */
|
|
61
86
|
static clear(): void;
|
|
87
|
+
/**
|
|
88
|
+
* The one actionable "you forgot to call setInfo" message. Shared by {@link getName} and
|
|
89
|
+
* {@link getVersion}: setInfo sets BOTH, so either being missing has the identical cause and
|
|
90
|
+
* the identical fix — telling the caller only about the half they happened to read first would
|
|
91
|
+
* send them back for a second round.
|
|
92
|
+
*/
|
|
93
|
+
private static notSetMessage;
|
|
62
94
|
}
|
package/src/http/ServiceInfo.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ServiceInfo = void 0;
|
|
4
4
|
/**
|
|
5
|
-
* ServiceInfo - the ONE answer to "what service am I?", for every part of
|
|
5
|
+
* ServiceInfo - the ONE answer to "what service am I, and which build of it?", for every part of
|
|
6
|
+
* webpieces that needs it.
|
|
6
7
|
*
|
|
7
8
|
* Configured like {@link HeaderRegistry} / {@link ClientRegistry} / LogManager — populated once at
|
|
8
9
|
* startup, then globally accessible with NO DI wiring. Browser-safe (no `process.env`, no node-only
|
|
@@ -10,65 +11,92 @@ exports.ServiceInfo = void 0;
|
|
|
10
11
|
*
|
|
11
12
|
* ```ts
|
|
12
13
|
* // startup, FIRST — before the logger factory is constructed (it reads this):
|
|
13
|
-
* ServiceInfo.
|
|
14
|
+
* ServiceInfo.setInfo('my-service', '2.1.0');
|
|
14
15
|
* ```
|
|
15
16
|
*
|
|
16
|
-
* WHY THIS EXISTS.
|
|
17
|
-
*
|
|
18
|
-
* - bunyan REQUIRES a root-logger name
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* WHY THIS EXISTS. Both facts used to be artifacts of WHICH LOGGING LIBRARY YOU PICKED rather than
|
|
18
|
+
* facts about the service:
|
|
19
|
+
* - the name lived on `BunyanFactoryOptions`, because bunyan REQUIRES a root-logger name
|
|
20
|
+
* (`options.name (string) is required` — bunyan's own TypeError). winston has no such concept, so
|
|
21
|
+
* a winston app shipped its logs UNNAMED.
|
|
22
|
+
* - the version lived on `WinstonFactoryOptions` as `svcGitHash`, so a bunyan app could not stamp a
|
|
23
|
+
* version AT ALL — and the name presumed a git SHA, which not every project deploys from.
|
|
21
24
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* - the
|
|
25
|
+
* Switching backends silently changed which fields your logs carried. Several unrelated readers
|
|
26
|
+
* need these same two facts, so they belong to the framework, not to a backend:
|
|
27
|
+
* - the bunyan backend, to satisfy bunyan's mandatory root-logger `name`, and to stamp `version`;
|
|
28
|
+
* - the winston backend, to stamp `svcName` + `version`;
|
|
25
29
|
* - {@link WebpiecesCoreHeaders.REQUEST_ID_SOURCE}, to record WHO minted a request id.
|
|
26
30
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* {@link
|
|
31
|
+
* VERSION IS OPAQUE. It is whatever string identifies THIS build — a git SHA, a semver tag, a CI
|
|
32
|
+
* build number. webpieces neither parses nor derives it; the app decides where it comes from (a
|
|
33
|
+
* generated file, an env var, a Docker build arg) and passes it here.
|
|
34
|
+
*
|
|
35
|
+
* FAIL FAST, AT STARTUP. {@link getName} / {@link getVersion} throw, and the logger factories +
|
|
36
|
+
* `setupRuntime` call them while the process is booting — so a forgotten `setInfo` kills the deploy
|
|
37
|
+
* (the revision never goes healthy) instead of quietly shipping logs that cannot say which build
|
|
38
|
+
* emitted them. Nothing on the REQUEST path may throw over this: a missing log field must never 500
|
|
39
|
+
* live traffic, so per-request readers use {@link tryGetName} / {@link tryGetVersion} and simply
|
|
40
|
+
* omit the field.
|
|
32
41
|
*/
|
|
33
42
|
class ServiceInfo {
|
|
34
43
|
/** This service's name. Process-global; set once at startup. */
|
|
35
44
|
static svcName;
|
|
45
|
+
/** This build's version — an opaque app-chosen string. Process-global; set once at startup. */
|
|
46
|
+
static svcVersion;
|
|
36
47
|
/**
|
|
37
|
-
*
|
|
38
|
-
* reads
|
|
48
|
+
* Identify this service. Call it FIRST at startup — before constructing the logger factory,
|
|
49
|
+
* which reads both values during its own constructor.
|
|
39
50
|
*
|
|
40
|
-
* LAST CALL WINS, deliberately. A real deployment
|
|
41
|
-
* legitimately boot TWO services back-to-back (see the app-example e2e two-server flow), so a
|
|
42
|
-
* "one process = one
|
|
43
|
-
* factory captures
|
|
44
|
-
*
|
|
51
|
+
* LAST CALL WINS, deliberately. A real deployment identifies itself once, but an in-process test
|
|
52
|
+
* can legitimately boot TWO services back-to-back (see the app-example e2e two-server flow), so a
|
|
53
|
+
* "one process = one service" rule would reject a case that genuinely exists. Since a logger
|
|
54
|
+
* factory captures both values in its own constructor, each server built that way still keeps
|
|
55
|
+
* what was set when IT was built.
|
|
45
56
|
*
|
|
46
|
-
* @
|
|
57
|
+
* @param name - this service's name, e.g. 'my-service'.
|
|
58
|
+
* @param version - the opaque identifier of THIS build (git SHA, semver, CI build number).
|
|
59
|
+
* @throws Error on a blank name or version — that is always a bug, never a use case.
|
|
47
60
|
*/
|
|
48
61
|
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected
|
|
49
|
-
static
|
|
62
|
+
static setInfo(name, version) {
|
|
50
63
|
if (!name || !name.trim()) {
|
|
51
|
-
throw new Error('ServiceInfo.
|
|
64
|
+
throw new Error('ServiceInfo.setInfo(...) requires a non-blank service name.');
|
|
65
|
+
}
|
|
66
|
+
if (!version || !version.trim()) {
|
|
67
|
+
throw new Error('ServiceInfo.setInfo(...) requires a non-blank version. It is opaque to webpieces — ' +
|
|
68
|
+
'a git SHA, a semver tag, a CI build number — but every deployed build must be able ' +
|
|
69
|
+
'to say which build it is.');
|
|
52
70
|
}
|
|
53
71
|
ServiceInfo.svcName = name;
|
|
72
|
+
ServiceInfo.svcVersion = version;
|
|
54
73
|
}
|
|
55
74
|
/**
|
|
56
75
|
* This service's name, REQUIRED. For STARTUP callers only (the logger factories, setupRuntime) —
|
|
57
76
|
* they run while booting, so throwing here fails the deploy rather than live traffic.
|
|
58
77
|
*
|
|
59
|
-
* @throws Error if {@link
|
|
78
|
+
* @throws Error if {@link setInfo} was never called.
|
|
60
79
|
*/
|
|
61
80
|
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected
|
|
62
81
|
static getName() {
|
|
63
82
|
if (!ServiceInfo.svcName) {
|
|
64
|
-
throw new Error(
|
|
65
|
-
'BEFORE constructing the logger factory (it reads the name in its constructor):\n' +
|
|
66
|
-
" ServiceInfo.setName('my-service');\n" +
|
|
67
|
-
'It names every log line and records which service minted a request id ' +
|
|
68
|
-
'(requestIdSource).');
|
|
83
|
+
throw new Error(ServiceInfo.notSetMessage());
|
|
69
84
|
}
|
|
70
85
|
return ServiceInfo.svcName;
|
|
71
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* This build's version, REQUIRED. For STARTUP callers only (the logger factories, setupRuntime),
|
|
89
|
+
* for the same reason as {@link getName}.
|
|
90
|
+
*
|
|
91
|
+
* @throws Error if {@link setInfo} was never called.
|
|
92
|
+
*/
|
|
93
|
+
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected
|
|
94
|
+
static getVersion() {
|
|
95
|
+
if (!ServiceInfo.svcVersion) {
|
|
96
|
+
throw new Error(ServiceInfo.notSetMessage());
|
|
97
|
+
}
|
|
98
|
+
return ServiceInfo.svcVersion;
|
|
99
|
+
}
|
|
72
100
|
/**
|
|
73
101
|
* This service's name, or undefined when unset. For the REQUEST path, which must NOT throw: a
|
|
74
102
|
* server that booted has already passed the {@link getName} check in `setupRuntime`, so a real
|
|
@@ -79,10 +107,34 @@ class ServiceInfo {
|
|
|
79
107
|
static tryGetName() {
|
|
80
108
|
return ServiceInfo.svcName;
|
|
81
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* This build's version, or undefined when unset. For the REQUEST path, which must NOT throw —
|
|
112
|
+
* same reasoning as {@link tryGetName}.
|
|
113
|
+
*/
|
|
114
|
+
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected
|
|
115
|
+
static tryGetVersion() {
|
|
116
|
+
return ServiceInfo.svcVersion;
|
|
117
|
+
}
|
|
82
118
|
/** Reset — for tests, mirroring {@link ClientRegistry.clear}. */
|
|
83
119
|
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected
|
|
84
120
|
static clear() {
|
|
85
121
|
ServiceInfo.svcName = undefined;
|
|
122
|
+
ServiceInfo.svcVersion = undefined;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The one actionable "you forgot to call setInfo" message. Shared by {@link getName} and
|
|
126
|
+
* {@link getVersion}: setInfo sets BOTH, so either being missing has the identical cause and
|
|
127
|
+
* the identical fix — telling the caller only about the half they happened to read first would
|
|
128
|
+
* send them back for a second round.
|
|
129
|
+
*/
|
|
130
|
+
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected
|
|
131
|
+
static notSetMessage() {
|
|
132
|
+
return ('ServiceInfo.setInfo(...) has not been called. Identify this service at startup, ' +
|
|
133
|
+
'BEFORE constructing the logger factory (it reads name+version in its constructor):\n' +
|
|
134
|
+
" ServiceInfo.setInfo('my-service', '2.1.0');\n" +
|
|
135
|
+
'The name+version stamp every log line (so you can tell WHICH BUILD emitted a line), ' +
|
|
136
|
+
'and the name records which service minted a request id (requestIdSource). The version ' +
|
|
137
|
+
'is opaque — a git SHA, a semver tag, a CI build number, whatever identifies your build.');
|
|
86
138
|
}
|
|
87
139
|
}
|
|
88
140
|
exports.ServiceInfo = ServiceInfo;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServiceInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ServiceInfo.ts"],"names":[],"mappings":";;;AAAA
|
|
1
|
+
{"version":3,"file":"ServiceInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ServiceInfo.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAa,WAAW;IACpB,gEAAgE;IACxD,MAAM,CAAC,OAAO,CAAqB;IAE3C,+FAA+F;IACvF,MAAM,CAAC,UAAU,CAAqB;IAE9C;;;;;;;;;;;;;OAaG;IACH,4JAA4J;IAC5J,MAAM,CAAC,OAAO,CAAC,IAAY,EAAE,OAAe;QACxC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACX,qFAAqF;gBACrF,qFAAqF;gBACrF,2BAA2B,CAC9B,CAAC;QACN,CAAC;QACD,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;QAC3B,WAAW,CAAC,UAAU,GAAG,OAAO,CAAC;IACrC,CAAC;IAED;;;;;OAKG;IACH,4JAA4J;IAC5J,MAAM,CAAC,OAAO;QACV,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,aAAa,EAAE,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,WAAW,CAAC,OAAO,CAAC;IAC/B,CAAC;IAED;;;;;OAKG;IACH,4JAA4J;IAC5J,MAAM,CAAC,UAAU;QACb,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,aAAa,EAAE,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,WAAW,CAAC,UAAU,CAAC;IAClC,CAAC;IAED;;;;;OAKG;IACH,4JAA4J;IAC5J,MAAM,CAAC,UAAU;QACb,OAAO,WAAW,CAAC,OAAO,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACH,4JAA4J;IAC5J,MAAM,CAAC,aAAa;QAChB,OAAO,WAAW,CAAC,UAAU,CAAC;IAClC,CAAC;IAED,iEAAiE;IACjE,4JAA4J;IAC5J,MAAM,CAAC,KAAK;QACR,WAAW,CAAC,OAAO,GAAG,SAAS,CAAC;QAChC,WAAW,CAAC,UAAU,GAAG,SAAS,CAAC;IACvC,CAAC;IAED;;;;;OAKG;IACH,4JAA4J;IACpJ,MAAM,CAAC,aAAa;QACxB,OAAO,CACH,kFAAkF;YAClF,sFAAsF;YACtF,mDAAmD;YACnD,sFAAsF;YACtF,wFAAwF;YACxF,yFAAyF,CAC5F,CAAC;IACN,CAAC;CACJ;AA7GD,kCA6GC","sourcesContent":["/**\n * ServiceInfo - the ONE answer to \"what service am I, and which build of it?\", for every part of\n * webpieces that needs it.\n *\n * Configured like {@link HeaderRegistry} / {@link ClientRegistry} / LogManager — populated once at\n * startup, then globally accessible with NO DI wiring. Browser-safe (no `process.env`, no node-only\n * deps), which is why it lives in core-util beside {@link ClientRegistry}.\n *\n * ```ts\n * // startup, FIRST — before the logger factory is constructed (it reads this):\n * ServiceInfo.setInfo('my-service', '2.1.0');\n * ```\n *\n * WHY THIS EXISTS. Both facts used to be artifacts of WHICH LOGGING LIBRARY YOU PICKED rather than\n * facts about the service:\n * - the name lived on `BunyanFactoryOptions`, because bunyan REQUIRES a root-logger name\n * (`options.name (string) is required` — bunyan's own TypeError). winston has no such concept, so\n * a winston app shipped its logs UNNAMED.\n * - the version lived on `WinstonFactoryOptions` as `svcGitHash`, so a bunyan app could not stamp a\n * version AT ALL — and the name presumed a git SHA, which not every project deploys from.\n *\n * Switching backends silently changed which fields your logs carried. Several unrelated readers\n * need these same two facts, so they belong to the framework, not to a backend:\n * - the bunyan backend, to satisfy bunyan's mandatory root-logger `name`, and to stamp `version`;\n * - the winston backend, to stamp `svcName` + `version`;\n * - {@link WebpiecesCoreHeaders.REQUEST_ID_SOURCE}, to record WHO minted a request id.\n *\n * VERSION IS OPAQUE. It is whatever string identifies THIS build — a git SHA, a semver tag, a CI\n * build number. webpieces neither parses nor derives it; the app decides where it comes from (a\n * generated file, an env var, a Docker build arg) and passes it here.\n *\n * FAIL FAST, AT STARTUP. {@link getName} / {@link getVersion} throw, and the logger factories +\n * `setupRuntime` call them while the process is booting — so a forgotten `setInfo` kills the deploy\n * (the revision never goes healthy) instead of quietly shipping logs that cannot say which build\n * emitted them. Nothing on the REQUEST path may throw over this: a missing log field must never 500\n * live traffic, so per-request readers use {@link tryGetName} / {@link tryGetVersion} and simply\n * omit the field.\n */\nexport class ServiceInfo {\n /** This service's name. Process-global; set once at startup. */\n private static svcName: string | undefined;\n\n /** This build's version — an opaque app-chosen string. Process-global; set once at startup. */\n private static svcVersion: string | undefined;\n\n /**\n * Identify this service. Call it FIRST at startup — before constructing the logger factory,\n * which reads both values during its own constructor.\n *\n * LAST CALL WINS, deliberately. A real deployment identifies itself once, but an in-process test\n * can legitimately boot TWO services back-to-back (see the app-example e2e two-server flow), so a\n * \"one process = one service\" rule would reject a case that genuinely exists. Since a logger\n * factory captures both values in its own constructor, each server built that way still keeps\n * what was set when IT was built.\n *\n * @param name - this service's name, e.g. 'my-service'.\n * @param version - the opaque identifier of THIS build (git SHA, semver, CI build number).\n * @throws Error on a blank name or version — that is always a bug, never a use case.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n static setInfo(name: string, version: string): void {\n if (!name || !name.trim()) {\n throw new Error('ServiceInfo.setInfo(...) requires a non-blank service name.');\n }\n if (!version || !version.trim()) {\n throw new Error(\n 'ServiceInfo.setInfo(...) requires a non-blank version. It is opaque to webpieces — ' +\n 'a git SHA, a semver tag, a CI build number — but every deployed build must be able ' +\n 'to say which build it is.',\n );\n }\n ServiceInfo.svcName = name;\n ServiceInfo.svcVersion = version;\n }\n\n /**\n * This service's name, REQUIRED. For STARTUP callers only (the logger factories, setupRuntime) —\n * they run while booting, so throwing here fails the deploy rather than live traffic.\n *\n * @throws Error if {@link setInfo} was never called.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n static getName(): string {\n if (!ServiceInfo.svcName) {\n throw new Error(ServiceInfo.notSetMessage());\n }\n return ServiceInfo.svcName;\n }\n\n /**\n * This build's version, REQUIRED. For STARTUP callers only (the logger factories, setupRuntime),\n * for the same reason as {@link getName}.\n *\n * @throws Error if {@link setInfo} was never called.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n static getVersion(): string {\n if (!ServiceInfo.svcVersion) {\n throw new Error(ServiceInfo.notSetMessage());\n }\n return ServiceInfo.svcVersion;\n }\n\n /**\n * This service's name, or undefined when unset. For the REQUEST path, which must NOT throw: a\n * server that booted has already passed the {@link getName} check in `setupRuntime`, so a real\n * request always finds a name here. Only a unit test driving the context directly can see\n * undefined — and there, omitting a log field beats exploding.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n static tryGetName(): string | undefined {\n return ServiceInfo.svcName;\n }\n\n /**\n * This build's version, or undefined when unset. For the REQUEST path, which must NOT throw —\n * same reasoning as {@link tryGetName}.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n static tryGetVersion(): string | undefined {\n return ServiceInfo.svcVersion;\n }\n\n /** Reset — for tests, mirroring {@link ClientRegistry.clear}. */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n static clear(): void {\n ServiceInfo.svcName = undefined;\n ServiceInfo.svcVersion = undefined;\n }\n\n /**\n * The one actionable \"you forgot to call setInfo\" message. Shared by {@link getName} and\n * {@link getVersion}: setInfo sets BOTH, so either being missing has the identical cause and\n * the identical fix — telling the caller only about the half they happened to read first would\n * send them back for a second round.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n private static notSetMessage(): string {\n return (\n 'ServiceInfo.setInfo(...) has not been called. Identify this service at startup, ' +\n 'BEFORE constructing the logger factory (it reads name+version in its constructor):\\n' +\n \" ServiceInfo.setInfo('my-service', '2.1.0');\\n\" +\n 'The name+version stamp every log line (so you can tell WHICH BUILD emitted a line), ' +\n 'and the name records which service minted a request id (requestIdSource). The version ' +\n 'is opaque — a git SHA, a semver tag, a CI build number, whatever identifies your build.'\n );\n }\n}\n"]}
|