@diveinto/obs 1.0.2 → 1.0.3
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/dist/config.d.ts +9 -3
- package/dist/config.js +37 -9
- package/dist/setup.js +12 -2
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -71,11 +71,17 @@ type Resolved = Required<Pick<ObsConfig, "service" | "envPrefix" | "defaultVarDi
|
|
|
71
71
|
};
|
|
72
72
|
export declare function configure(cfg: ObsConfig): void;
|
|
73
73
|
/**
|
|
74
|
-
* The live
|
|
75
|
-
*
|
|
76
|
-
*
|
|
74
|
+
* The live configuration, or a fallback.
|
|
75
|
+
*
|
|
76
|
+
* Falling back rather than throwing is deliberate: a log line emitted before
|
|
77
|
+
* `configure()` (an import-time warning, say) should still come out, named
|
|
78
|
+
* honestly, instead of taking the process down. The fallback is NOT cached,
|
|
79
|
+
* so a later `configure()` still wins, and `isConfigured()` lets the file
|
|
80
|
+
* sinks refuse to invent a path from it.
|
|
77
81
|
*/
|
|
78
82
|
export declare function obsConfig(): Resolved;
|
|
83
|
+
/** Whether `configure()` has actually run. See `setupLogging()`. */
|
|
84
|
+
export declare function isConfigured(): boolean;
|
|
79
85
|
/** Test seam: forget the configuration so a test can set a different one. */
|
|
80
86
|
export declare function resetConfigForTests(): void;
|
|
81
87
|
/** Read one of this service's own prefixed variables. */
|
package/dist/config.js
CHANGED
|
@@ -14,7 +14,26 @@
|
|
|
14
14
|
* service name is a no-op and a call with a DIFFERENT one is a mistake worth
|
|
15
15
|
* hearing about.
|
|
16
16
|
*/
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The configuration lives on `globalThis`, NOT in a module-level variable.
|
|
19
|
+
*
|
|
20
|
+
* This is not defensiveness, it is a scar. Next can run `instrumentation.ts`
|
|
21
|
+
* in a DIFFERENT module context from the route handlers, so a module-level
|
|
22
|
+
* `let` set by `configure()` reads as unset in half the process. When that
|
|
23
|
+
* happened, the lazy sink initialiser re-ran `setupLogging()` in the second
|
|
24
|
+
* context, got the fallback configuration, tried to open
|
|
25
|
+
* `/var/lib/unconfigured/logs/...`, and self-disabled both file sinks. Every
|
|
26
|
+
* request log went to stdout only and the `.jsonl` and `.log` files silently
|
|
27
|
+
* stopped receiving anything, which is exactly the surface operators grep.
|
|
28
|
+
*
|
|
29
|
+
* `globalThis` is shared across those contexts, which is the same reason
|
|
30
|
+
* @vercel/otel puts its provider there and why `otelEnabled()` reads the
|
|
31
|
+
* environment rather than a flag.
|
|
32
|
+
*/
|
|
33
|
+
const SLOT = Symbol.for("diveinto.obs.config");
|
|
34
|
+
function slot() {
|
|
35
|
+
return globalThis;
|
|
36
|
+
}
|
|
18
37
|
function resolve(cfg) {
|
|
19
38
|
const service = cfg.service.trim();
|
|
20
39
|
if (!service)
|
|
@@ -35,24 +54,33 @@ function resolve(cfg) {
|
|
|
35
54
|
}
|
|
36
55
|
export function configure(cfg) {
|
|
37
56
|
const next = resolve(cfg);
|
|
38
|
-
|
|
57
|
+
const held = slot()[SLOT];
|
|
58
|
+
if (held && held.service !== next.service) {
|
|
39
59
|
// Two services in one process is not a thing this library supports, and
|
|
40
60
|
// the symptom (logs filed under the wrong service) is miserable to chase.
|
|
41
|
-
throw new Error(`[obs] already configured as '${
|
|
61
|
+
throw new Error(`[obs] already configured as '${held.service}'; cannot reconfigure as '${next.service}'`);
|
|
42
62
|
}
|
|
43
|
-
|
|
63
|
+
slot()[SLOT] = next;
|
|
44
64
|
}
|
|
45
65
|
/**
|
|
46
|
-
* The live
|
|
47
|
-
*
|
|
48
|
-
*
|
|
66
|
+
* The live configuration, or a fallback.
|
|
67
|
+
*
|
|
68
|
+
* Falling back rather than throwing is deliberate: a log line emitted before
|
|
69
|
+
* `configure()` (an import-time warning, say) should still come out, named
|
|
70
|
+
* honestly, instead of taking the process down. The fallback is NOT cached,
|
|
71
|
+
* so a later `configure()` still wins, and `isConfigured()` lets the file
|
|
72
|
+
* sinks refuse to invent a path from it.
|
|
49
73
|
*/
|
|
50
74
|
export function obsConfig() {
|
|
51
|
-
return
|
|
75
|
+
return slot()[SLOT] ?? resolve({ service: "unconfigured", envPrefix: "OBS" });
|
|
76
|
+
}
|
|
77
|
+
/** Whether `configure()` has actually run. See `setupLogging()`. */
|
|
78
|
+
export function isConfigured() {
|
|
79
|
+
return slot()[SLOT] !== undefined;
|
|
52
80
|
}
|
|
53
81
|
/** Test seam: forget the configuration so a test can set a different one. */
|
|
54
82
|
export function resetConfigForTests() {
|
|
55
|
-
|
|
83
|
+
delete slot()[SLOT];
|
|
56
84
|
}
|
|
57
85
|
/** Read one of this service's own prefixed variables. */
|
|
58
86
|
export function envVar(name, env = process.env) {
|
package/dist/setup.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { join } from "node:path";
|
|
25
25
|
import { logEvent } from "./api.js";
|
|
26
|
-
import { obsConfig } from "./config.js";
|
|
26
|
+
import { isConfigured, obsConfig } from "./config.js";
|
|
27
27
|
import { makeOtelLogSink, otelEnabled } from "./otel.js";
|
|
28
28
|
import { renderJson, renderPretty } from "./render.js";
|
|
29
29
|
import { configureSinks, makeFileSink, makeStdoutSink, setSinkInitializer } from "./sinks.js";
|
|
@@ -116,8 +116,18 @@ export function setupLogging() {
|
|
|
116
116
|
// The two rotating files are self-hosted only. They are format-locked
|
|
117
117
|
// (<stem>.log = pretty, <stem>.jsonl = json) regardless of LOG_FORMAT, so
|
|
118
118
|
// each file is always what its name promises.
|
|
119
|
+
//
|
|
120
|
+
// Belt and braces after the globalThis fix: if this ever runs before
|
|
121
|
+
// configure() again, write to stdout and say so, rather than deriving a
|
|
122
|
+
// path from the fallback name. The old behaviour opened
|
|
123
|
+
// /var/lib/unconfigured/logs/, failed, self-disabled both files, and left
|
|
124
|
+
// an operator with a silently empty .jsonl.
|
|
119
125
|
const serverless = isServerless();
|
|
120
|
-
if (!serverless) {
|
|
126
|
+
if (!serverless && !isConfigured()) {
|
|
127
|
+
process.stderr.write("[obs] setupLogging() ran before configure(); writing to stdout only. " +
|
|
128
|
+
"Call configure() or createObs() first, from instrumentation.ts.\n");
|
|
129
|
+
}
|
|
130
|
+
if (!serverless && isConfigured()) {
|
|
121
131
|
const targets = resolveFileTargets();
|
|
122
132
|
if (targets.text) {
|
|
123
133
|
sinks.push(makeFileSink(targets.text.path, "pretty", renderPretty, targets.text.rotation, targets.text.retention));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diveinto/obs",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Structured logging, tracing and metrics for Next.js and Node services: one record shape, rotating files, W3C trace context, optional OpenTelemetry export",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|