@sebspark/otel 2.0.7 â 2.0.9
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/index.d.mts +74 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +740 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +12 -12
- package/dist/index.d.ts +0 -69
- package/dist/index.js +0 -965
- package/dist/index.js.map +0 -1
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
import { DiagConsoleLogger, DiagLogLevel, SpanStatusCode, SpanStatusCode as SpanStatusCode$1, context, context as context$1, diag, metrics, trace } from "@opentelemetry/api";
|
|
2
|
+
import { SeverityNumber, logs } from "@opentelemetry/api-logs";
|
|
3
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
4
|
+
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
|
5
|
+
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
|
6
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
7
|
+
import { BatchLogRecordProcessor, LoggerProvider, SimpleLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
|
8
|
+
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
9
|
+
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
|
|
10
|
+
import { ExportResultCode } from "@opentelemetry/core";
|
|
11
|
+
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
|
12
|
+
import fss from "fast-safe-stringify";
|
|
13
|
+
import kleur from "kleur";
|
|
14
|
+
import { containerDetector } from "@opentelemetry/resource-detector-container";
|
|
15
|
+
import { gcpDetector } from "@opentelemetry/resource-detector-gcp";
|
|
16
|
+
import { detectResources, envDetector, osDetector, processDetector, resourceFromAttributes, serviceInstanceIdDetector } from "@opentelemetry/resources";
|
|
17
|
+
|
|
18
|
+
//#region src/instrumentations.ts
|
|
19
|
+
let _http;
|
|
20
|
+
let _express;
|
|
21
|
+
let _grpc;
|
|
22
|
+
let _redis;
|
|
23
|
+
let _dns;
|
|
24
|
+
let _net;
|
|
25
|
+
let _fs;
|
|
26
|
+
let _undici;
|
|
27
|
+
let _socketIo;
|
|
28
|
+
const instrumentations = {
|
|
29
|
+
get http() {
|
|
30
|
+
if (!_http) _http = import("@opentelemetry/instrumentation-http").then(({ HttpInstrumentation }) => new HttpInstrumentation());
|
|
31
|
+
return _http;
|
|
32
|
+
},
|
|
33
|
+
get express() {
|
|
34
|
+
if (!_express) _express = import("@opentelemetry/instrumentation-express").then(({ ExpressInstrumentation }) => new ExpressInstrumentation());
|
|
35
|
+
return _express;
|
|
36
|
+
},
|
|
37
|
+
get grpc() {
|
|
38
|
+
if (!_grpc) _grpc = import("@opentelemetry/instrumentation-grpc").then(({ GrpcInstrumentation }) => new GrpcInstrumentation());
|
|
39
|
+
return _grpc;
|
|
40
|
+
},
|
|
41
|
+
get redis() {
|
|
42
|
+
if (!_redis) _redis = import("@opentelemetry/instrumentation-redis").then(({ RedisInstrumentation }) => new RedisInstrumentation());
|
|
43
|
+
return _redis;
|
|
44
|
+
},
|
|
45
|
+
get dns() {
|
|
46
|
+
if (!_dns) _dns = import("@opentelemetry/instrumentation-dns").then(({ DnsInstrumentation }) => new DnsInstrumentation());
|
|
47
|
+
return _dns;
|
|
48
|
+
},
|
|
49
|
+
get net() {
|
|
50
|
+
if (!_net) _net = import("@opentelemetry/instrumentation-net").then(({ NetInstrumentation }) => new NetInstrumentation());
|
|
51
|
+
return _net;
|
|
52
|
+
},
|
|
53
|
+
get fs() {
|
|
54
|
+
if (!_fs) _fs = import("@opentelemetry/instrumentation-fs").then(({ FsInstrumentation }) => new FsInstrumentation());
|
|
55
|
+
return _fs;
|
|
56
|
+
},
|
|
57
|
+
get undici() {
|
|
58
|
+
if (!_undici) _undici = import("@opentelemetry/instrumentation-undici").then(({ UndiciInstrumentation }) => new UndiciInstrumentation());
|
|
59
|
+
return _undici;
|
|
60
|
+
},
|
|
61
|
+
get socketIo() {
|
|
62
|
+
if (!_socketIo) _socketIo = import("@opentelemetry/instrumentation-socket.io").then(({ SocketIoInstrumentation }) => new SocketIoInstrumentation());
|
|
63
|
+
return _socketIo;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/consts.ts
|
|
69
|
+
const LOG_SEVERITY_MAP = {
|
|
70
|
+
TRACE: 1,
|
|
71
|
+
DEBUG: 5,
|
|
72
|
+
INFO: 9,
|
|
73
|
+
NOTICE: 10,
|
|
74
|
+
WARNING: 13,
|
|
75
|
+
WARN: 13,
|
|
76
|
+
ERROR: 17,
|
|
77
|
+
FATAL: 21,
|
|
78
|
+
CRITICAL: 21,
|
|
79
|
+
ALERT: 22,
|
|
80
|
+
EMERGENCY: 23
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/loggers/formatters/style.ts
|
|
85
|
+
const colors = {
|
|
86
|
+
gray: kleur.gray,
|
|
87
|
+
dim: kleur.dim,
|
|
88
|
+
cyan: kleur.cyan,
|
|
89
|
+
white: kleur.white,
|
|
90
|
+
green: kleur.green,
|
|
91
|
+
yellow: kleur.yellow,
|
|
92
|
+
red: kleur.red,
|
|
93
|
+
magenta: kleur.magenta
|
|
94
|
+
};
|
|
95
|
+
const levelColorMap = {
|
|
96
|
+
DEBUG: kleur.magenta,
|
|
97
|
+
INFO: kleur.green,
|
|
98
|
+
WARN: kleur.yellow,
|
|
99
|
+
ERROR: kleur.red,
|
|
100
|
+
FATAL: kleur.red
|
|
101
|
+
};
|
|
102
|
+
const levelIconMap = {
|
|
103
|
+
DEBUG: "đ",
|
|
104
|
+
INFO: "âšī¸ ",
|
|
105
|
+
WARN: "â ī¸ ",
|
|
106
|
+
ERROR: "â",
|
|
107
|
+
FATAL: "đ"
|
|
108
|
+
};
|
|
109
|
+
const statusLabelMap = {
|
|
110
|
+
0: "UNSET",
|
|
111
|
+
1: "OK",
|
|
112
|
+
2: "ERROR"
|
|
113
|
+
};
|
|
114
|
+
const statusColorMap = {
|
|
115
|
+
0: colors.gray,
|
|
116
|
+
1: colors.green,
|
|
117
|
+
2: colors.red
|
|
118
|
+
};
|
|
119
|
+
const kindColorMap = {
|
|
120
|
+
0: colors.white,
|
|
121
|
+
1: colors.cyan,
|
|
122
|
+
2: colors.yellow,
|
|
123
|
+
3: colors.magenta,
|
|
124
|
+
4: colors.green
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/loggers/formatters/shared.ts
|
|
129
|
+
const stringify = fss.default.stableStringify;
|
|
130
|
+
function formatTimestamp(time) {
|
|
131
|
+
const date = new Date(hrTimeToMillis(time));
|
|
132
|
+
return colors.dim(date.toISOString().slice(11, 23));
|
|
133
|
+
}
|
|
134
|
+
function formatService(resource) {
|
|
135
|
+
const name = resource.attributes[ATTR_SERVICE_NAME] ?? "unknown-service";
|
|
136
|
+
const version = resource.attributes[ATTR_SERVICE_VERSION] ?? "1.0.0";
|
|
137
|
+
return colors.gray(`[${name}@${version}]`);
|
|
138
|
+
}
|
|
139
|
+
function formatLevel(record) {
|
|
140
|
+
const text = (record.severityText ?? "INFO").toUpperCase();
|
|
141
|
+
const colorFn = levelColorMap[text] ?? colors.white;
|
|
142
|
+
return `${levelIconMap[text] ?? "âĸ"} ${colorFn(text.padEnd(5))}`;
|
|
143
|
+
}
|
|
144
|
+
function formatScope(resource, instrumentationScope) {
|
|
145
|
+
const component = resource.attributes["component.name"];
|
|
146
|
+
const { name, version } = instrumentationScope;
|
|
147
|
+
const scopeLabel = component || (name && name !== "unknown" ? name : void 0);
|
|
148
|
+
if (!scopeLabel) return "";
|
|
149
|
+
const versionLabel = version ? `@${version}` : "";
|
|
150
|
+
return colors.cyan(`${scopeLabel}${versionLabel} `);
|
|
151
|
+
}
|
|
152
|
+
function formatMessage(record) {
|
|
153
|
+
return typeof record.body === "string" ? record.body : stringify(record.body);
|
|
154
|
+
}
|
|
155
|
+
function formatAttributes(attrs) {
|
|
156
|
+
const keys = Object.keys(attrs).filter((k) => !k.startsWith("service.") && !k.startsWith("serviceContext."));
|
|
157
|
+
if (keys.length === 0) return "";
|
|
158
|
+
const formatted = keys.map((k) => {
|
|
159
|
+
const val = attrs[k];
|
|
160
|
+
return `${k}=${typeof val === "object" ? stringify(val) : val}`;
|
|
161
|
+
});
|
|
162
|
+
return ` ${colors.gray(formatted.join(" "))}`;
|
|
163
|
+
}
|
|
164
|
+
function hrTimeToMillis(hrTime) {
|
|
165
|
+
return hrTime[0] * 1e3 + Math.floor(hrTime[1] / 1e6);
|
|
166
|
+
}
|
|
167
|
+
function calculateDuration(span) {
|
|
168
|
+
const start = hrTimeToMillis(span.startTime);
|
|
169
|
+
const end = hrTimeToMillis(span.endTime);
|
|
170
|
+
return Math.max(0, Math.round(end - start));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/loggers/formatters/log-record.ts
|
|
175
|
+
function formatLogRecord(record) {
|
|
176
|
+
const timestamp = formatTimestamp(record.hrTime);
|
|
177
|
+
return `${formatService(record.resource)} ${timestamp} ${formatLevel(record)} ${formatScope(record.resource, record.instrumentationScope)}${formatMessage(record)}${formatAttributes(record.attributes)}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/loggers/formatters/metrics.ts
|
|
182
|
+
function formatMetrics(resourceMetrics) {
|
|
183
|
+
const { resource, scopeMetrics } = resourceMetrics;
|
|
184
|
+
return scopeMetrics.map((scopeMetric) => formatScopeMetric(scopeMetric, resource)).join("\n");
|
|
185
|
+
}
|
|
186
|
+
function formatScopeMetric(scopeMetric, resource) {
|
|
187
|
+
return scopeMetric.metrics.map((metric) => formatMetricData(metric, resource, scopeMetric.scope)).join("\n");
|
|
188
|
+
}
|
|
189
|
+
function formatMetricData(metric, resource, scope) {
|
|
190
|
+
const scopeStr = formatScope(resource, scope);
|
|
191
|
+
const serviceStr = formatService(resource);
|
|
192
|
+
const lines = [];
|
|
193
|
+
for (const dp of metric.dataPoints) {
|
|
194
|
+
const ts = formatTimestamp(dp.startTime);
|
|
195
|
+
const value = extractMetricValue(dp);
|
|
196
|
+
const attrs = formatAttributes(dp.attributes ?? {});
|
|
197
|
+
lines.push(`${serviceStr} ${ts} đ ${scopeStr}${colors.white(metric.descriptor.name)} ${colors.dim(value)}${attrs}`);
|
|
198
|
+
}
|
|
199
|
+
return lines.join("\n");
|
|
200
|
+
}
|
|
201
|
+
function extractMetricValue(dp) {
|
|
202
|
+
const value = dp.value;
|
|
203
|
+
if (typeof value === "number") return value.toString();
|
|
204
|
+
if (isHistogramLike(value)) return value.sum.toString();
|
|
205
|
+
return "[complex]";
|
|
206
|
+
}
|
|
207
|
+
function isHistogramLike(val) {
|
|
208
|
+
return typeof val === "object" && val !== null && "sum" in val && typeof val.sum === "number";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
//#endregion
|
|
212
|
+
//#region src/loggers/formatters/span.ts
|
|
213
|
+
const LABEL_WIDTH = 20;
|
|
214
|
+
const DESCRIPTION_MAX_WIDTH = 16;
|
|
215
|
+
const BAR_MIN_WIDTH = 1;
|
|
216
|
+
const BAR_MAX_WIDTH = 20;
|
|
217
|
+
function formatSpans(spans) {
|
|
218
|
+
const rootSpan = spans[0];
|
|
219
|
+
const rootStart = hrTimeToMillis(rootSpan.startTime);
|
|
220
|
+
const totalDuration = hrTimeToMillis(rootSpan.endTime) - rootStart;
|
|
221
|
+
const lines = [`${formatService(rootSpan.resource)} ${formatTimestamp(rootSpan.startTime)} ${colors.gray(`[${rootSpan.spanContext().traceId}]`)}`];
|
|
222
|
+
for (const span of spans) {
|
|
223
|
+
const offset = hrTimeToMillis(span.startTime) - rootStart;
|
|
224
|
+
const depth = computeDepth(span, spans);
|
|
225
|
+
lines.push(formatSpan(span, {
|
|
226
|
+
offsetMs: offset,
|
|
227
|
+
totalDurationMs: totalDuration,
|
|
228
|
+
depth
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
return lines.join("\n");
|
|
232
|
+
}
|
|
233
|
+
function formatSpan(span, opts) {
|
|
234
|
+
const label = formatLabel(span, opts.depth);
|
|
235
|
+
const bar = buildBar(span, opts?.offsetMs, opts?.totalDurationMs);
|
|
236
|
+
const barColor = span.status.code === SpanStatusCode$1.OK ? colors.green : span.status.code === SpanStatusCode$1.ERROR ? colors.red : colors.gray;
|
|
237
|
+
const desc = formatDescription(span);
|
|
238
|
+
const status = formatStatus(span);
|
|
239
|
+
const duration = formatDuration(span, opts?.offsetMs);
|
|
240
|
+
const spanId = colors.gray(`[${span.spanContext().spanId}]`);
|
|
241
|
+
return `${label} ${barColor(bar)} ${desc} ${status} ${duration} ${spanId}`;
|
|
242
|
+
}
|
|
243
|
+
function formatLabel(span, depth) {
|
|
244
|
+
return `${" ".repeat(depth)}ââ ${span.name}`.padEnd(LABEL_WIDTH);
|
|
245
|
+
}
|
|
246
|
+
function buildBar(span, offsetMs, totalDurationMs) {
|
|
247
|
+
const duration = calculateDuration(span);
|
|
248
|
+
if (typeof offsetMs !== "number" || typeof totalDurationMs !== "number" || totalDurationMs === 0) {
|
|
249
|
+
const capped = Math.min(duration, 1e3);
|
|
250
|
+
const barLength = Math.max(BAR_MIN_WIDTH, Math.round(capped / 1e3 * BAR_MAX_WIDTH));
|
|
251
|
+
return "â".repeat(barLength).padEnd(BAR_MAX_WIDTH + 2);
|
|
252
|
+
}
|
|
253
|
+
const offsetRatio = Math.max(0, Math.min(offsetMs / totalDurationMs, 1));
|
|
254
|
+
const durationRatio = Math.max(0, Math.min(duration / totalDurationMs, 1));
|
|
255
|
+
const offsetChars = Math.floor(offsetRatio * BAR_MAX_WIDTH);
|
|
256
|
+
const barChars = Math.max(BAR_MIN_WIDTH, Math.round(durationRatio * BAR_MAX_WIDTH));
|
|
257
|
+
return (" ".repeat(offsetChars) + "â".repeat(barChars)).padEnd(BAR_MAX_WIDTH + 2);
|
|
258
|
+
}
|
|
259
|
+
function formatDescription(span) {
|
|
260
|
+
for (const keys of [
|
|
261
|
+
["http.method", "http.target"],
|
|
262
|
+
["http.route"],
|
|
263
|
+
["http.url"],
|
|
264
|
+
["graphql.operation.name"],
|
|
265
|
+
["graphql.operation.type"],
|
|
266
|
+
["graphql.document"],
|
|
267
|
+
["ws.event"],
|
|
268
|
+
["ws.message_type"],
|
|
269
|
+
["ws.url"],
|
|
270
|
+
["db.system", "db.statement"],
|
|
271
|
+
["db.operation"],
|
|
272
|
+
["db.statement"],
|
|
273
|
+
["db.operation"],
|
|
274
|
+
["db.name"],
|
|
275
|
+
["db.operation"],
|
|
276
|
+
["db.statement"],
|
|
277
|
+
["messaging.operation"],
|
|
278
|
+
["messaging.destination"],
|
|
279
|
+
["messaging.gcp_pubsub.topic"],
|
|
280
|
+
["faas.invoked_name"],
|
|
281
|
+
["faas.trigger"],
|
|
282
|
+
["otel.description"]
|
|
283
|
+
]) {
|
|
284
|
+
const parts = keys.map((k) => span.attributes[k]).filter((v) => v !== void 0 && v !== null).map((v) => String(v));
|
|
285
|
+
if (parts.length > 0) return truncate(parts.join(" "), DESCRIPTION_MAX_WIDTH - 1).padEnd(DESCRIPTION_MAX_WIDTH);
|
|
286
|
+
}
|
|
287
|
+
return "".padEnd(DESCRIPTION_MAX_WIDTH);
|
|
288
|
+
}
|
|
289
|
+
function formatStatus(span) {
|
|
290
|
+
const code = span.status.code;
|
|
291
|
+
const label = statusLabelMap[code] ?? "UNSET";
|
|
292
|
+
return (statusColorMap[code] ?? colors.gray)(label).padEnd(6);
|
|
293
|
+
}
|
|
294
|
+
function formatDuration(span, offsetMs) {
|
|
295
|
+
const duration = calculateDuration(span);
|
|
296
|
+
const format = (ms) => ms >= 1e3 ? `${(ms / 1e3).toFixed(2)} s` : `${ms} ms`;
|
|
297
|
+
return `(${format(offsetMs || 0)}â${format(duration)})`.padEnd(16);
|
|
298
|
+
}
|
|
299
|
+
function truncate(input, maxLength) {
|
|
300
|
+
const str = String(input ?? "");
|
|
301
|
+
return str.length > maxLength ? `${str.slice(0, maxLength - 1)}âĻ` : str;
|
|
302
|
+
}
|
|
303
|
+
function computeDepth(span, allSpans) {
|
|
304
|
+
let depth = 0;
|
|
305
|
+
let currentParentId = span.parentSpanContext?.spanId;
|
|
306
|
+
while (currentParentId) {
|
|
307
|
+
const parentSpan = allSpans.find((s) => s.spanContext().spanId === currentParentId);
|
|
308
|
+
if (!parentSpan) break;
|
|
309
|
+
depth += 1;
|
|
310
|
+
currentParentId = parentSpan.parentSpanContext?.spanId;
|
|
311
|
+
}
|
|
312
|
+
return depth;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/loggers/console-metric-pretty-exporter.ts
|
|
317
|
+
var ConsoleMetricPrettyExporter = class {
|
|
318
|
+
patterns;
|
|
319
|
+
constructor() {
|
|
320
|
+
this.patterns = (process.env.METRIC_FILTER ?? "").split(",").map((e) => e.trim()).filter(Boolean).map(globToRegex);
|
|
321
|
+
}
|
|
322
|
+
filterMetrics(resourceMetrics) {
|
|
323
|
+
if (this.patterns.length === 0) return void 0;
|
|
324
|
+
const filteredScopes = resourceMetrics.scopeMetrics.map((scopeMetric) => {
|
|
325
|
+
const filteredMetrics = scopeMetric.metrics.filter((metric) => this.patterns.some((pattern) => pattern.test(metric.descriptor.name)));
|
|
326
|
+
if (filteredMetrics.length === 0) return void 0;
|
|
327
|
+
return {
|
|
328
|
+
...scopeMetric,
|
|
329
|
+
metrics: filteredMetrics
|
|
330
|
+
};
|
|
331
|
+
}).filter((s) => s !== void 0);
|
|
332
|
+
if (filteredScopes.length === 0) return void 0;
|
|
333
|
+
return {
|
|
334
|
+
...resourceMetrics,
|
|
335
|
+
scopeMetrics: filteredScopes
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
export(metrics$1, resultCallback) {
|
|
339
|
+
const filtered = this.filterMetrics(metrics$1);
|
|
340
|
+
if (filtered) console.log(formatMetrics(filtered));
|
|
341
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
342
|
+
}
|
|
343
|
+
shutdown() {
|
|
344
|
+
return Promise.resolve();
|
|
345
|
+
}
|
|
346
|
+
forceFlush() {
|
|
347
|
+
return Promise.resolve();
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
function globToRegex(glob) {
|
|
351
|
+
const regex = `^${glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`;
|
|
352
|
+
return new RegExp(regex);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
//#endregion
|
|
356
|
+
//#region src/loggers/console-log-pretty-exporter.ts
|
|
357
|
+
var ConsoleLogPrettyExporter = class {
|
|
358
|
+
logThreshold;
|
|
359
|
+
constructor() {
|
|
360
|
+
const defaultLogLevel = "INFO";
|
|
361
|
+
this.logThreshold = LOG_SEVERITY_MAP[process.env.LOG_LEVEL?.toUpperCase() ?? defaultLogLevel] ?? LOG_SEVERITY_MAP[defaultLogLevel];
|
|
362
|
+
}
|
|
363
|
+
export(logs$1, resultCallback) {
|
|
364
|
+
this._sendLogRecords(logs$1, resultCallback);
|
|
365
|
+
}
|
|
366
|
+
shutdown() {
|
|
367
|
+
return Promise.resolve();
|
|
368
|
+
}
|
|
369
|
+
forceFlush() {
|
|
370
|
+
return Promise.resolve();
|
|
371
|
+
}
|
|
372
|
+
_sendLogRecords(logRecords, done) {
|
|
373
|
+
for (const record of logRecords) if ((record.severityNumber ?? 0) >= this.logThreshold) {
|
|
374
|
+
const formatted = formatLogRecord(record);
|
|
375
|
+
const severity = record.severityNumber || SeverityNumber.UNSPECIFIED;
|
|
376
|
+
if (severity >= SeverityNumber.ERROR) console.error(formatted);
|
|
377
|
+
else if (severity >= SeverityNumber.WARN) console.warn(formatted);
|
|
378
|
+
else if (severity >= SeverityNumber.INFO) console.info(formatted);
|
|
379
|
+
else if (severity >= SeverityNumber.DEBUG) console.debug(formatted);
|
|
380
|
+
else console.trace(formatted);
|
|
381
|
+
}
|
|
382
|
+
done?.({ code: ExportResultCode.SUCCESS });
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
//#endregion
|
|
387
|
+
//#region src/loggers/console-span-pretty-exporter.ts
|
|
388
|
+
var ConsoleSpanPrettyExporter = class {
|
|
389
|
+
allowedStatuses;
|
|
390
|
+
constructor() {
|
|
391
|
+
const env = process.env.SPAN_LEVEL?.toUpperCase();
|
|
392
|
+
if (!env) this.allowedStatuses = new Set([
|
|
393
|
+
SpanStatusCode$1.UNSET,
|
|
394
|
+
SpanStatusCode$1.OK,
|
|
395
|
+
SpanStatusCode$1.ERROR
|
|
396
|
+
]);
|
|
397
|
+
else {
|
|
398
|
+
const map = {
|
|
399
|
+
UNSET: SpanStatusCode$1.UNSET,
|
|
400
|
+
OK: SpanStatusCode$1.OK,
|
|
401
|
+
ERROR: SpanStatusCode$1.ERROR
|
|
402
|
+
};
|
|
403
|
+
this.allowedStatuses = new Set(env.split(",").map((s) => s.trim()).map((s) => map[s]).filter((v) => typeof v === "number"));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
shouldExport(spans) {
|
|
407
|
+
if (this.allowedStatuses.size === 3) return true;
|
|
408
|
+
return spans.some((span) => this.allowedStatuses.has(span.status.code));
|
|
409
|
+
}
|
|
410
|
+
export(spans, resultCallback) {
|
|
411
|
+
if (this.shouldExport(spans)) console.log(formatSpans(spans));
|
|
412
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
413
|
+
}
|
|
414
|
+
shutdown() {
|
|
415
|
+
return Promise.resolve();
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
//#endregion
|
|
420
|
+
//#region src/loggers/tree-span-processor.ts
|
|
421
|
+
var TreeSpanProcessor = class {
|
|
422
|
+
exporter;
|
|
423
|
+
orphans = /* @__PURE__ */ new Map();
|
|
424
|
+
constructor(exporter) {
|
|
425
|
+
this.exporter = exporter;
|
|
426
|
+
}
|
|
427
|
+
onStart() {}
|
|
428
|
+
onEnd(span) {
|
|
429
|
+
const parentId = span.parentSpanContext?.spanId;
|
|
430
|
+
if (parentId) {
|
|
431
|
+
const siblings = this.orphans.get(parentId) || [];
|
|
432
|
+
this.orphans.set(parentId, [...siblings, span]);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const sorted = [span, ...this.getChildrenRecursively(span)].sort((s1, s2) => {
|
|
436
|
+
const [sec1, nano1] = s1.startTime;
|
|
437
|
+
const [sec2, nano2] = s2.startTime;
|
|
438
|
+
if (sec1 !== sec2) return sec1 - sec2;
|
|
439
|
+
return nano1 - nano2;
|
|
440
|
+
});
|
|
441
|
+
this.exporter.export(sorted, () => {});
|
|
442
|
+
}
|
|
443
|
+
getChildrenRecursively(span) {
|
|
444
|
+
const spanId = span.spanContext().spanId;
|
|
445
|
+
const children = this.orphans.get(spanId) || [];
|
|
446
|
+
this.orphans.delete(spanId);
|
|
447
|
+
const result = [...children];
|
|
448
|
+
for (const child of children) result.push(...this.getChildrenRecursively(child));
|
|
449
|
+
return result;
|
|
450
|
+
}
|
|
451
|
+
shutdown() {
|
|
452
|
+
return this.exporter.shutdown();
|
|
453
|
+
}
|
|
454
|
+
async forceFlush() {
|
|
455
|
+
await this.exporter.forceFlush?.();
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
//#endregion
|
|
460
|
+
//#region src/providers.ts
|
|
461
|
+
const getLogProvider = (resource, otlpEndpoint) => {
|
|
462
|
+
if (otlpEndpoint) {
|
|
463
|
+
const processors = [new BatchLogRecordProcessor(new OTLPLogExporter({ url: `${otlpEndpoint}/v1/logs` }))];
|
|
464
|
+
if (process.env.LOG_LEVEL) processors.push(new SimpleLogRecordProcessor(new ConsoleLogPrettyExporter()));
|
|
465
|
+
return new LoggerProvider({
|
|
466
|
+
resource,
|
|
467
|
+
processors
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
return new LoggerProvider({
|
|
471
|
+
resource,
|
|
472
|
+
processors: [new SimpleLogRecordProcessor(new ConsoleLogPrettyExporter())]
|
|
473
|
+
});
|
|
474
|
+
};
|
|
475
|
+
const getSpanProcessor = (otlpEndpoint) => {
|
|
476
|
+
const exporter = otlpEndpoint ? new OTLPTraceExporter({ url: `${otlpEndpoint}/v1/traces` }) : new ConsoleSpanPrettyExporter();
|
|
477
|
+
return otlpEndpoint ? new BatchSpanProcessor(exporter) : new TreeSpanProcessor(exporter);
|
|
478
|
+
};
|
|
479
|
+
const getMetricReader = (otlpEndpoint) => {
|
|
480
|
+
return new PeriodicExportingMetricReader({ exporter: otlpEndpoint ? new OTLPMetricExporter({ url: `${otlpEndpoint}/v1/metrics` }) : new ConsoleMetricPrettyExporter() });
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
//#endregion
|
|
484
|
+
//#region src/otel-context.ts
|
|
485
|
+
/**
|
|
486
|
+
* Extracts telemetry context from environment (cloud/k8s aware),
|
|
487
|
+
* with support for subservice/component override.
|
|
488
|
+
*/
|
|
489
|
+
function detectTelemetryContext(componentNameOverride) {
|
|
490
|
+
const { OTEL_SERVICE_NAME, OTEL_SERVICE_VERSION, K_SERVICE, K_REVISION, K_CONFIGURATION, KUBERNETES_SERVICE_HOST, POD_NAME, POD_NAMESPACE, GCP_PROJECT, CLOUD_PROVIDER } = process.env;
|
|
491
|
+
const systemName = OTEL_SERVICE_NAME || "unknown-service";
|
|
492
|
+
const systemVersion = OTEL_SERVICE_VERSION || "1.0.0";
|
|
493
|
+
const componentName = componentNameOverride || void 0;
|
|
494
|
+
return {
|
|
495
|
+
systemName,
|
|
496
|
+
systemVersion,
|
|
497
|
+
componentName,
|
|
498
|
+
resourceAttributes: {
|
|
499
|
+
[ATTR_SERVICE_NAME]: systemName,
|
|
500
|
+
[ATTR_SERVICE_VERSION]: systemVersion,
|
|
501
|
+
"serviceContext.service": systemName,
|
|
502
|
+
"serviceContext.version": systemVersion,
|
|
503
|
+
...K_SERVICE && { "cloud.run.service": K_SERVICE },
|
|
504
|
+
...K_REVISION && { "cloud.run.revision": K_REVISION },
|
|
505
|
+
...K_CONFIGURATION && { "cloud.run.configuration": K_CONFIGURATION },
|
|
506
|
+
...POD_NAME && { "k8s.pod_name": POD_NAME },
|
|
507
|
+
...POD_NAMESPACE && { "k8s.namespace_name": POD_NAMESPACE },
|
|
508
|
+
...KUBERNETES_SERVICE_HOST && { "cloud.orchestrator": "kubernetes" },
|
|
509
|
+
...GCP_PROJECT && { "cloud.account.id": GCP_PROJECT },
|
|
510
|
+
...CLOUD_PROVIDER && { "cloud.provider": CLOUD_PROVIDER },
|
|
511
|
+
...componentName && { "component.name": componentName }
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
//#endregion
|
|
517
|
+
//#region src/resource.ts
|
|
518
|
+
const getResource = async () => {
|
|
519
|
+
const baseRes = await detectResources({ detectors: [
|
|
520
|
+
containerDetector,
|
|
521
|
+
envDetector,
|
|
522
|
+
gcpDetector,
|
|
523
|
+
osDetector,
|
|
524
|
+
processDetector,
|
|
525
|
+
serviceInstanceIdDetector
|
|
526
|
+
] });
|
|
527
|
+
if (baseRes.waitForAsyncAttributes) await baseRes.waitForAsyncAttributes();
|
|
528
|
+
const { resourceAttributes } = detectTelemetryContext();
|
|
529
|
+
const customRes = resourceFromAttributes(resourceAttributes);
|
|
530
|
+
const resource = baseRes.merge(customRes);
|
|
531
|
+
if (resource.waitForAsyncAttributes) await resource.waitForAsyncAttributes();
|
|
532
|
+
return resource;
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
//#endregion
|
|
536
|
+
//#region src/otel.ts
|
|
537
|
+
diag.disable();
|
|
538
|
+
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ERROR);
|
|
539
|
+
let initialization;
|
|
540
|
+
let _isInitialized = false;
|
|
541
|
+
async function initialize(...instrumentations$1) {
|
|
542
|
+
if (!initialization) {
|
|
543
|
+
initialization = _initialize(await Promise.all(instrumentations$1));
|
|
544
|
+
initialization.then(() => {
|
|
545
|
+
_isInitialized = true;
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
return initialization;
|
|
549
|
+
}
|
|
550
|
+
function isInitialized() {
|
|
551
|
+
return _isInitialized;
|
|
552
|
+
}
|
|
553
|
+
async function _initialize(instrumentations$1) {
|
|
554
|
+
try {
|
|
555
|
+
const serviceName = process.env.OTEL_SERVICE_NAME ?? "unknown-service";
|
|
556
|
+
const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
557
|
+
const resource = await getResource();
|
|
558
|
+
context$1.disable();
|
|
559
|
+
logs.disable();
|
|
560
|
+
trace.disable();
|
|
561
|
+
metrics.disable();
|
|
562
|
+
const logProvider = getLogProvider(resource, otlpEndpoint);
|
|
563
|
+
logs.setGlobalLoggerProvider(logProvider);
|
|
564
|
+
const sdk = new NodeSDK({
|
|
565
|
+
spanProcessor: getSpanProcessor(otlpEndpoint),
|
|
566
|
+
metricReader: getMetricReader(otlpEndpoint),
|
|
567
|
+
instrumentations: instrumentations$1,
|
|
568
|
+
resource
|
|
569
|
+
});
|
|
570
|
+
await sdk.start();
|
|
571
|
+
console.log(`[otel] Telemetry initialized for "${serviceName}"`);
|
|
572
|
+
process.on("SIGTERM", async () => {
|
|
573
|
+
console.log("[otel] Shutting down...");
|
|
574
|
+
await Promise.all([sdk.shutdown(), logProvider.shutdown()]);
|
|
575
|
+
console.log("[otel] Shutdown complete.");
|
|
576
|
+
process.exit(0);
|
|
577
|
+
});
|
|
578
|
+
} catch (err) {
|
|
579
|
+
console.error("[otel] Startup error:", err);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
//#endregion
|
|
584
|
+
//#region src/logger.ts
|
|
585
|
+
function getLogger(serviceOverride, extraAttrs = {}) {
|
|
586
|
+
const { systemName, systemVersion, resourceAttributes } = detectTelemetryContext(serviceOverride);
|
|
587
|
+
const defaultAttrs = {
|
|
588
|
+
...resourceAttributes,
|
|
589
|
+
...extraAttrs
|
|
590
|
+
};
|
|
591
|
+
function emit(severityText, body, attrs = {}) {
|
|
592
|
+
if (!isInitialized() && process.env.NODE_ENV !== "test") {
|
|
593
|
+
console.warn("OTEL must be initialized before using logger");
|
|
594
|
+
console.log(`[${severityText}] ${body}`);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const logger = logs.getLogger(systemName, systemVersion);
|
|
598
|
+
const spanContext = trace.getSpan(context$1.active())?.spanContext();
|
|
599
|
+
logger.emit({
|
|
600
|
+
severityText,
|
|
601
|
+
severityNumber: LOG_SEVERITY_MAP[severityText],
|
|
602
|
+
body,
|
|
603
|
+
attributes: {
|
|
604
|
+
...defaultAttrs,
|
|
605
|
+
...spanContext && {
|
|
606
|
+
trace_id: spanContext.traceId,
|
|
607
|
+
span_id: spanContext.spanId
|
|
608
|
+
},
|
|
609
|
+
...attrs
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
debug: (msg, attrs) => emit("DEBUG", msg, attrs),
|
|
615
|
+
info: (msg, attrs) => emit("INFO", msg, attrs),
|
|
616
|
+
notice: (msg, attrs) => emit("NOTICE", msg, attrs),
|
|
617
|
+
warn: (msg, attrs) => emit("WARNING", msg, attrs),
|
|
618
|
+
error: (msg, errOrAttrs, maybeAttrs = {}) => {
|
|
619
|
+
let body;
|
|
620
|
+
let attrs;
|
|
621
|
+
if (errOrAttrs instanceof Error) {
|
|
622
|
+
body = `${msg}: ${errOrAttrs.stack || errOrAttrs.message}`;
|
|
623
|
+
attrs = maybeAttrs;
|
|
624
|
+
} else {
|
|
625
|
+
body = msg instanceof Error ? msg.stack || msg.message : msg;
|
|
626
|
+
attrs = errOrAttrs || {};
|
|
627
|
+
}
|
|
628
|
+
emit("ERROR", body, attrs);
|
|
629
|
+
},
|
|
630
|
+
critical: (msg, attrs) => emit("CRITICAL", msg, attrs),
|
|
631
|
+
alert: (msg, attrs) => emit("ALERT", msg, attrs),
|
|
632
|
+
emergency: (msg, attrs) => emit("EMERGENCY", msg, attrs)
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
//#endregion
|
|
637
|
+
//#region src/metrics.ts
|
|
638
|
+
function getMeter(componentNameOverride) {
|
|
639
|
+
if (!isInitialized() && process.env.NODE_ENV !== "test") console.warn("OTEL must be initialized before using getMeter()");
|
|
640
|
+
const { componentName, systemName, systemVersion } = detectTelemetryContext(componentNameOverride);
|
|
641
|
+
return metrics.getMeter(componentName ?? systemName, systemVersion);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
//#endregion
|
|
645
|
+
//#region src/tracer.ts
|
|
646
|
+
/**
|
|
647
|
+
* Returns an OpenTelemetry tracer bound to the current service.
|
|
648
|
+
* Includes `withTrace()` and `withTraceSync()` helpers for span-wrapped execution.
|
|
649
|
+
*
|
|
650
|
+
* @param serviceOverride - Optional override for service name
|
|
651
|
+
* @returns Tracer with helpers
|
|
652
|
+
*/
|
|
653
|
+
function getTracer(componentNameOverride) {
|
|
654
|
+
if (!isInitialized() && process.env.NODE_ENV !== "test") console.warn("OTEL must be initialized before calling getTracer()");
|
|
655
|
+
const { componentName, systemName, systemVersion } = detectTelemetryContext(componentNameOverride);
|
|
656
|
+
const tracer = trace.getTracer(componentName ?? systemName, systemVersion);
|
|
657
|
+
/**
|
|
658
|
+
* Runs a function inside a new span (async variant).
|
|
659
|
+
* Automatically handles span status and nesting.
|
|
660
|
+
*/
|
|
661
|
+
const withTrace = async (name, spanOptionsSpanOrFunc, spanOrFunc, func) => {
|
|
662
|
+
const { options, parent, fn } = extractArgs(spanOptionsSpanOrFunc, spanOrFunc, func);
|
|
663
|
+
const parentContext = parent ? trace.setSpan(context$1.active(), parent) : context$1.active();
|
|
664
|
+
const span = tracer.startSpan(name, options, parentContext);
|
|
665
|
+
return await context$1.with(trace.setSpan(parentContext, span), async () => {
|
|
666
|
+
try {
|
|
667
|
+
const result = await fn(span);
|
|
668
|
+
span.setStatus({ code: SpanStatusCode$1.OK });
|
|
669
|
+
return result;
|
|
670
|
+
} catch (err) {
|
|
671
|
+
const error = err;
|
|
672
|
+
span.setStatus({
|
|
673
|
+
code: SpanStatusCode$1.ERROR,
|
|
674
|
+
message: error.message
|
|
675
|
+
});
|
|
676
|
+
span.recordException?.(error);
|
|
677
|
+
throw err;
|
|
678
|
+
} finally {
|
|
679
|
+
span.end();
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
};
|
|
683
|
+
/**
|
|
684
|
+
* Runs a synchronous function inside a new span.
|
|
685
|
+
* Automatically handles span status and nesting.
|
|
686
|
+
*/
|
|
687
|
+
const withTraceSync = (name, spanOptionsSpanOrFunc, spanOrFunc, func) => {
|
|
688
|
+
const { options, parent, fn } = extractArgs(spanOptionsSpanOrFunc, spanOrFunc, func);
|
|
689
|
+
const parentContext = parent ? trace.setSpan(context$1.active(), parent) : context$1.active();
|
|
690
|
+
const span = tracer.startSpan(name, options, parentContext);
|
|
691
|
+
return context$1.with(trace.setSpan(parentContext, span), () => {
|
|
692
|
+
try {
|
|
693
|
+
const result = fn(span);
|
|
694
|
+
span.setStatus({ code: SpanStatusCode$1.OK });
|
|
695
|
+
return result;
|
|
696
|
+
} catch (err) {
|
|
697
|
+
const error = err;
|
|
698
|
+
span.setStatus({
|
|
699
|
+
code: SpanStatusCode$1.ERROR,
|
|
700
|
+
message: error.message
|
|
701
|
+
});
|
|
702
|
+
span.recordException?.(error);
|
|
703
|
+
throw err;
|
|
704
|
+
} finally {
|
|
705
|
+
span.end();
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
};
|
|
709
|
+
tracer.withTrace = withTrace;
|
|
710
|
+
tracer.withTraceSync = withTraceSync;
|
|
711
|
+
return tracer;
|
|
712
|
+
}
|
|
713
|
+
function extractArgs(spanOptionsSpanOrFunc, spanOrFunc, func) {
|
|
714
|
+
let options = {};
|
|
715
|
+
let parent;
|
|
716
|
+
let fn;
|
|
717
|
+
if (isFunction(spanOptionsSpanOrFunc)) fn = spanOptionsSpanOrFunc;
|
|
718
|
+
else if (isFunction(spanOrFunc)) {
|
|
719
|
+
const spanOrSpanOptions = spanOptionsSpanOrFunc;
|
|
720
|
+
if (isSpanOptions(spanOrSpanOptions)) options = spanOrSpanOptions;
|
|
721
|
+
else parent = spanOrSpanOptions;
|
|
722
|
+
fn = spanOrFunc;
|
|
723
|
+
} else {
|
|
724
|
+
options = spanOptionsSpanOrFunc;
|
|
725
|
+
parent = spanOrFunc;
|
|
726
|
+
fn = func;
|
|
727
|
+
}
|
|
728
|
+
return {
|
|
729
|
+
options,
|
|
730
|
+
parent,
|
|
731
|
+
fn
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
const isFunction = (value) => typeof value === "function";
|
|
735
|
+
const isSpan = (value) => value !== null && value !== void 0 && isFunction(value.spanContext) && isFunction(value.end);
|
|
736
|
+
const isSpanOptions = (value) => value !== null && value !== void 0 && (!!value.startTime || !!value.attributes || !!value.kind) && !isSpan(value);
|
|
737
|
+
|
|
738
|
+
//#endregion
|
|
739
|
+
export { SpanStatusCode, context, getLogger, getMeter, getTracer, initialize, instrumentations, isInitialized };
|
|
740
|
+
//# sourceMappingURL=index.mjs.map
|