@lunora/container 1.0.0-alpha.13 → 1.0.0-alpha.15
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/otel.d.mts +27 -1
- package/dist/otel.d.ts +27 -1
- package/dist/otel.mjs +83 -11
- package/package.json +2 -2
package/dist/otel.d.mts
CHANGED
|
@@ -61,6 +61,20 @@ interface ContainerLogInput {
|
|
|
61
61
|
* @experimental
|
|
62
62
|
*/
|
|
63
63
|
interface ContainerTelemetryOptions {
|
|
64
|
+
/**
|
|
65
|
+
* Value of the `deployment.environment` resource attribute. Falls back to
|
|
66
|
+
* the `DEPLOYMENT_ENVIRONMENT` / `ENVIRONMENT` / `NODE_ENV` env vars **only
|
|
67
|
+
* when {@link ContainerTelemetryOptions.detectResources} is `true`** —
|
|
68
|
+
* unlike `serviceName`, env detection here is opt-in so a stray `NODE_ENV`
|
|
69
|
+
* never silently labels a deployment.
|
|
70
|
+
*/
|
|
71
|
+
deploymentEnvironment?: string;
|
|
72
|
+
/**
|
|
73
|
+
* When `true`, auto-detect OTLP resource attributes from the container
|
|
74
|
+
* environment (`HOSTNAME`, `KUBERNETES_*`, `SERVICE_VERSION`, etc.).
|
|
75
|
+
* Explicit options and `resourceAttributes` win on collision.
|
|
76
|
+
*/
|
|
77
|
+
detectResources?: boolean;
|
|
64
78
|
/** Base OTLP collector endpoint; defaults to the `LUNORA_OTLP_ENDPOINT` env var. */
|
|
65
79
|
endpoint?: string;
|
|
66
80
|
/** Injectable `fetch` (tests / non-global runtimes). Defaults to `globalThis.fetch`. */
|
|
@@ -69,8 +83,17 @@ interface ContainerTelemetryOptions {
|
|
|
69
83
|
headers?: Record<string, string>;
|
|
70
84
|
/** Called with any send failure so the caller can surface it; the export itself always swallows. */
|
|
71
85
|
onError?: (error: unknown) => void;
|
|
86
|
+
/** Additional resource attributes merged onto every signal. */
|
|
87
|
+
resourceAttributes?: Record<string, ContainerAttributeValue>;
|
|
72
88
|
/** `service.name` resource attribute; defaults to the `LUNORA_SERVICE_NAME` env var then `"lunora-container"`. */
|
|
73
89
|
serviceName?: string;
|
|
90
|
+
/**
|
|
91
|
+
* `service.version` resource attribute. Falls back to `SERVICE_VERSION` /
|
|
92
|
+
* `CF_VERSION_METADATA` / `VERCEL_GIT_COMMIT_SHA` / `GITHUB_SHA` /
|
|
93
|
+
* `COMMIT_SHA` env vars **only when
|
|
94
|
+
* {@link ContainerTelemetryOptions.detectResources} is `true`**.
|
|
95
|
+
*/
|
|
96
|
+
serviceVersion?: string;
|
|
74
97
|
/** Per-POST timeout in ms; a collector that never responds aborts after this so a stuck send can't stall `flush()`. Defaults to {@link DEFAULT_TIMEOUT_MS} (10s). */
|
|
75
98
|
timeoutMs?: number;
|
|
76
99
|
/** Bearer token sent as an `Authorization: Bearer` header; defaults to the `LUNORA_OTLP_TOKEN` env var. */
|
|
@@ -128,7 +151,10 @@ interface ContainerTelemetry {
|
|
|
128
151
|
* With no endpoint resolvable the returned exporter is disabled (`enabled ===
|
|
129
152
|
* false`): `emitSpan`/`emitLog` no-op and `trace` still runs its work but records
|
|
130
153
|
* nothing — so the same code runs unchanged locally and in the cloud.
|
|
131
|
-
* @param options Exporter options
|
|
154
|
+
* @param options Exporter options. Connection fields (`endpoint`, `token`,
|
|
155
|
+
* `serviceName`, `traceparent`) always fall back to their `LUNORA_*` env var;
|
|
156
|
+
* resource fields (`serviceVersion`, `deploymentEnvironment`) only do so under
|
|
157
|
+
* `detectResources: true`.
|
|
132
158
|
* @experimental
|
|
133
159
|
*/
|
|
134
160
|
declare const createContainerTelemetry: (options?: ContainerTelemetryOptions) => ContainerTelemetry;
|
package/dist/otel.d.ts
CHANGED
|
@@ -61,6 +61,20 @@ interface ContainerLogInput {
|
|
|
61
61
|
* @experimental
|
|
62
62
|
*/
|
|
63
63
|
interface ContainerTelemetryOptions {
|
|
64
|
+
/**
|
|
65
|
+
* Value of the `deployment.environment` resource attribute. Falls back to
|
|
66
|
+
* the `DEPLOYMENT_ENVIRONMENT` / `ENVIRONMENT` / `NODE_ENV` env vars **only
|
|
67
|
+
* when {@link ContainerTelemetryOptions.detectResources} is `true`** —
|
|
68
|
+
* unlike `serviceName`, env detection here is opt-in so a stray `NODE_ENV`
|
|
69
|
+
* never silently labels a deployment.
|
|
70
|
+
*/
|
|
71
|
+
deploymentEnvironment?: string;
|
|
72
|
+
/**
|
|
73
|
+
* When `true`, auto-detect OTLP resource attributes from the container
|
|
74
|
+
* environment (`HOSTNAME`, `KUBERNETES_*`, `SERVICE_VERSION`, etc.).
|
|
75
|
+
* Explicit options and `resourceAttributes` win on collision.
|
|
76
|
+
*/
|
|
77
|
+
detectResources?: boolean;
|
|
64
78
|
/** Base OTLP collector endpoint; defaults to the `LUNORA_OTLP_ENDPOINT` env var. */
|
|
65
79
|
endpoint?: string;
|
|
66
80
|
/** Injectable `fetch` (tests / non-global runtimes). Defaults to `globalThis.fetch`. */
|
|
@@ -69,8 +83,17 @@ interface ContainerTelemetryOptions {
|
|
|
69
83
|
headers?: Record<string, string>;
|
|
70
84
|
/** Called with any send failure so the caller can surface it; the export itself always swallows. */
|
|
71
85
|
onError?: (error: unknown) => void;
|
|
86
|
+
/** Additional resource attributes merged onto every signal. */
|
|
87
|
+
resourceAttributes?: Record<string, ContainerAttributeValue>;
|
|
72
88
|
/** `service.name` resource attribute; defaults to the `LUNORA_SERVICE_NAME` env var then `"lunora-container"`. */
|
|
73
89
|
serviceName?: string;
|
|
90
|
+
/**
|
|
91
|
+
* `service.version` resource attribute. Falls back to `SERVICE_VERSION` /
|
|
92
|
+
* `CF_VERSION_METADATA` / `VERCEL_GIT_COMMIT_SHA` / `GITHUB_SHA` /
|
|
93
|
+
* `COMMIT_SHA` env vars **only when
|
|
94
|
+
* {@link ContainerTelemetryOptions.detectResources} is `true`**.
|
|
95
|
+
*/
|
|
96
|
+
serviceVersion?: string;
|
|
74
97
|
/** Per-POST timeout in ms; a collector that never responds aborts after this so a stuck send can't stall `flush()`. Defaults to {@link DEFAULT_TIMEOUT_MS} (10s). */
|
|
75
98
|
timeoutMs?: number;
|
|
76
99
|
/** Bearer token sent as an `Authorization: Bearer` header; defaults to the `LUNORA_OTLP_TOKEN` env var. */
|
|
@@ -128,7 +151,10 @@ interface ContainerTelemetry {
|
|
|
128
151
|
* With no endpoint resolvable the returned exporter is disabled (`enabled ===
|
|
129
152
|
* false`): `emitSpan`/`emitLog` no-op and `trace` still runs its work but records
|
|
130
153
|
* nothing — so the same code runs unchanged locally and in the cloud.
|
|
131
|
-
* @param options Exporter options
|
|
154
|
+
* @param options Exporter options. Connection fields (`endpoint`, `token`,
|
|
155
|
+
* `serviceName`, `traceparent`) always fall back to their `LUNORA_*` env var;
|
|
156
|
+
* resource fields (`serviceVersion`, `deploymentEnvironment`) only do so under
|
|
157
|
+
* `detectResources: true`.
|
|
132
158
|
* @experimental
|
|
133
159
|
*/
|
|
134
160
|
declare const createContainerTelemetry: (options?: ContainerTelemetryOptions) => ContainerTelemetry;
|
package/dist/otel.mjs
CHANGED
|
@@ -35,7 +35,7 @@ const parseTraceparent = (header) => {
|
|
|
35
35
|
version === "00" && parts.length !== 4 || traceId === void 0 || parentSpanId === void 0 || flags === void 0 || flags.length !== 2 || !HEX_ONLY.test(flags) || traceId.length !== 32 || parentSpanId.length !== 16 || !HEX_ONLY.test(traceId) || !HEX_ONLY.test(parentSpanId) || traceId === "00000000000000000000000000000000" || parentSpanId === "0000000000000000") {
|
|
36
36
|
return void 0;
|
|
37
37
|
}
|
|
38
|
-
return { parentSpanId, traceId };
|
|
38
|
+
return { parentSpanId, sampled: (Number.parseInt(flags, 16) & 1) === 1, traceId };
|
|
39
39
|
};
|
|
40
40
|
const encodeAttribute = (key, value) => {
|
|
41
41
|
if (typeof value === "boolean") {
|
|
@@ -79,31 +79,82 @@ const mergeHeaders = (defaults, overrides, token) => {
|
|
|
79
79
|
}
|
|
80
80
|
return merged;
|
|
81
81
|
};
|
|
82
|
-
const
|
|
82
|
+
const buildResourceAttributes = (serviceName, extra) => {
|
|
83
|
+
const merged = { "service.name": serviceName };
|
|
84
|
+
for (const [key, value] of Object.entries(extra ?? {})) {
|
|
85
|
+
merged[key] = value;
|
|
86
|
+
}
|
|
87
|
+
return Object.entries(merged).map(([key, value]) => encodeAttribute(key, value));
|
|
88
|
+
};
|
|
89
|
+
const wrapResourceSpans = (span, scopeName, serviceName, resourceAttributes) => {
|
|
83
90
|
return {
|
|
84
91
|
resourceSpans: [
|
|
85
92
|
{
|
|
86
|
-
resource: { attributes:
|
|
93
|
+
resource: { attributes: buildResourceAttributes(serviceName, resourceAttributes) },
|
|
87
94
|
scopeSpans: [{ scope: { name: scopeName }, spans: [span] }]
|
|
88
95
|
}
|
|
89
96
|
]
|
|
90
97
|
};
|
|
91
98
|
};
|
|
92
|
-
const wrapResourceLogs = (logRecord, scopeName, serviceName) => {
|
|
99
|
+
const wrapResourceLogs = (logRecord, scopeName, serviceName, resourceAttributes) => {
|
|
93
100
|
return {
|
|
94
101
|
resourceLogs: [
|
|
95
102
|
{
|
|
96
|
-
resource: { attributes:
|
|
103
|
+
resource: { attributes: buildResourceAttributes(serviceName, resourceAttributes) },
|
|
97
104
|
scopeLogs: [{ logRecords: [logRecord], scope: { name: scopeName } }]
|
|
98
105
|
}
|
|
99
106
|
]
|
|
100
107
|
};
|
|
101
108
|
};
|
|
102
109
|
|
|
110
|
+
const detectServiceResource = (read) => {
|
|
111
|
+
const detected = {};
|
|
112
|
+
const serviceVersion = read("SERVICE_VERSION") ?? read("CF_VERSION_METADATA") ?? read("VERCEL_GIT_COMMIT_SHA") ?? read("GITHUB_SHA") ?? read("COMMIT_SHA");
|
|
113
|
+
if (serviceVersion !== void 0) {
|
|
114
|
+
detected["service.version"] = serviceVersion;
|
|
115
|
+
}
|
|
116
|
+
const deploymentEnvironment = read("DEPLOYMENT_ENVIRONMENT") ?? read("ENVIRONMENT") ?? read("NODE_ENV");
|
|
117
|
+
if (deploymentEnvironment !== void 0) {
|
|
118
|
+
detected["deployment.environment"] = deploymentEnvironment;
|
|
119
|
+
}
|
|
120
|
+
return detected;
|
|
121
|
+
};
|
|
122
|
+
const detectHostResource = (read, pid) => {
|
|
123
|
+
const detected = {};
|
|
124
|
+
const hostName = read("HOSTNAME") ?? read("COMPUTERNAME");
|
|
125
|
+
if (hostName !== void 0) {
|
|
126
|
+
detected["host.name"] = hostName;
|
|
127
|
+
}
|
|
128
|
+
const podName = read("KUBERNETES_POD_NAME") ?? read("HOSTNAME");
|
|
129
|
+
if (podName !== void 0 && read("KUBERNETES_SERVICE_HOST") !== void 0) {
|
|
130
|
+
detected["k8s.pod.name"] = podName;
|
|
131
|
+
}
|
|
132
|
+
if (pid !== void 0 && Number.isFinite(pid)) {
|
|
133
|
+
detected["process.pid"] = pid;
|
|
134
|
+
}
|
|
135
|
+
return detected;
|
|
136
|
+
};
|
|
137
|
+
const mergeResourceAttributes = (...bags) => {
|
|
138
|
+
const merged = {};
|
|
139
|
+
for (const bag of bags) {
|
|
140
|
+
if (bag === void 0) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
for (const [key, value] of Object.entries(bag)) {
|
|
144
|
+
merged[key] = value;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return merged;
|
|
148
|
+
};
|
|
149
|
+
|
|
103
150
|
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
104
151
|
const readEnv = (name) => {
|
|
105
152
|
return process.env[name];
|
|
106
153
|
};
|
|
154
|
+
const detectContainerResource = () => {
|
|
155
|
+
const pid = typeof process.pid !== "number" ? void 0 : process.pid;
|
|
156
|
+
return mergeResourceAttributes(detectServiceResource(readEnv), detectHostResource(readEnv, pid));
|
|
157
|
+
};
|
|
107
158
|
const resolveFetch = (injected) => {
|
|
108
159
|
if (injected !== void 0) {
|
|
109
160
|
return injected;
|
|
@@ -113,7 +164,7 @@ const resolveFetch = (injected) => {
|
|
|
113
164
|
}
|
|
114
165
|
return void 0;
|
|
115
166
|
};
|
|
116
|
-
const traceBody = (span, serviceName, parent) => {
|
|
167
|
+
const traceBody = (span, serviceName, parent, resource) => {
|
|
117
168
|
const attributes = encodeAttributes(span.attributes);
|
|
118
169
|
if (span.error?.type !== void 0) {
|
|
119
170
|
attributes.push(encodeAttribute("error.type", span.error.type));
|
|
@@ -133,9 +184,18 @@ const traceBody = (span, serviceName, parent) => {
|
|
|
133
184
|
status: span.error === void 0 ? { code: 1 } : { code: 2, message: span.error.message },
|
|
134
185
|
traceId: parent?.traceId ?? otlpRandomHex(16)
|
|
135
186
|
};
|
|
136
|
-
|
|
187
|
+
if (span.error) {
|
|
188
|
+
otlpSpan.events = [
|
|
189
|
+
{
|
|
190
|
+
attributes: [encodeAttribute("exception.type", span.error.type ?? "Error"), encodeAttribute("exception.message", span.error.message)],
|
|
191
|
+
name: "exception",
|
|
192
|
+
timeUnixNano: otlpUnixNano(span.endMs)
|
|
193
|
+
}
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
return wrapResourceSpans(otlpSpan, "@lunora/container", serviceName, resource);
|
|
137
197
|
};
|
|
138
|
-
const logBody = (log, serviceName, nowMs) => {
|
|
198
|
+
const logBody = (log, serviceName, nowMs, resource) => {
|
|
139
199
|
const level = log.level ?? "info";
|
|
140
200
|
const record = {
|
|
141
201
|
attributes: encodeAttributes(log.attributes),
|
|
@@ -144,7 +204,7 @@ const logBody = (log, serviceName, nowMs) => {
|
|
|
144
204
|
severityText: level.toUpperCase(),
|
|
145
205
|
timeUnixNano: otlpUnixNano(log.ts ?? nowMs)
|
|
146
206
|
};
|
|
147
|
-
return wrapResourceLogs(record, "@lunora/container", serviceName);
|
|
207
|
+
return wrapResourceLogs(record, "@lunora/container", serviceName, resource);
|
|
148
208
|
};
|
|
149
209
|
const createContainerTelemetry = (options = {}) => {
|
|
150
210
|
const endpoint = options.endpoint ?? readEnv("LUNORA_OTLP_ENDPOINT");
|
|
@@ -155,6 +215,18 @@ const createContainerTelemetry = (options = {}) => {
|
|
|
155
215
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
156
216
|
const fetchImpl = resolveFetch(options.fetch);
|
|
157
217
|
const headers = mergeHeaders({ "content-type": "application/json" }, options.headers, token);
|
|
218
|
+
const detectedAttributes = options.detectResources === true ? detectContainerResource() : void 0;
|
|
219
|
+
const staticAttributes = {};
|
|
220
|
+
if (options.serviceVersion !== void 0) {
|
|
221
|
+
staticAttributes["service.version"] = options.serviceVersion;
|
|
222
|
+
}
|
|
223
|
+
if (options.deploymentEnvironment !== void 0) {
|
|
224
|
+
staticAttributes["deployment.environment"] = options.deploymentEnvironment;
|
|
225
|
+
}
|
|
226
|
+
if (options.resourceAttributes !== void 0) {
|
|
227
|
+
Object.assign(staticAttributes, options.resourceAttributes);
|
|
228
|
+
}
|
|
229
|
+
const resource = mergeResourceAttributes(detectedAttributes, staticAttributes);
|
|
158
230
|
let base = endpoint ?? "";
|
|
159
231
|
while (base.endsWith("/")) {
|
|
160
232
|
base = base.slice(0, -1);
|
|
@@ -190,13 +262,13 @@ const createContainerTelemetry = (options = {}) => {
|
|
|
190
262
|
if (!enabled) {
|
|
191
263
|
return;
|
|
192
264
|
}
|
|
193
|
-
send(tracesUrl, traceBody(span, serviceName, parent));
|
|
265
|
+
send(tracesUrl, traceBody(span, serviceName, parent, resource));
|
|
194
266
|
};
|
|
195
267
|
const emitLog = (log) => {
|
|
196
268
|
if (!enabled) {
|
|
197
269
|
return;
|
|
198
270
|
}
|
|
199
|
-
send(logsUrl, logBody(log, serviceName, Date.now()));
|
|
271
|
+
send(logsUrl, logBody(log, serviceName, Date.now(), resource));
|
|
200
272
|
};
|
|
201
273
|
const trace = async (name, run, attributes) => {
|
|
202
274
|
const startMs = Date.now();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/container",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.15",
|
|
4
4
|
"description": "Cloudflare Containers for Lunora: defineContainer, generated Container DO classes, and the ctx.containers action surface",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"access": "public"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
59
|
+
"@lunora/errors": "1.0.0-alpha.7"
|
|
60
60
|
},
|
|
61
61
|
"engines": {
|
|
62
62
|
"node": "^22.15.0 || >=24.11.0"
|