@clickhouse/clickstack-node 0.0.0-stage → 1.0.0
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/LICENSE +203 -0
- package/README.md +282 -2
- package/dist/_virtual/_rolldown/runtime.cjs +22 -0
- package/dist/body/http.cjs +115 -0
- package/dist/body/http.mjs +115 -0
- package/dist/body/rules.cjs +225 -0
- package/dist/body/rules.mjs +220 -0
- package/dist/body/undici.cjs +88 -0
- package/dist/body/undici.mjs +87 -0
- package/dist/compose.cjs +107 -0
- package/dist/compose.mjs +107 -0
- package/dist/config.cjs +27 -0
- package/dist/config.d.cts +61 -0
- package/dist/config.d.mts +61 -0
- package/dist/config.mjs +27 -0
- package/dist/env.cjs +88 -0
- package/dist/env.mjs +84 -0
- package/dist/exception.cjs +54 -0
- package/dist/exception.mjs +54 -0
- package/dist/hook.cjs +47 -0
- package/dist/hook.mjs +45 -0
- package/dist/index.cjs +7 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +3 -0
- package/dist/init.cjs +139 -0
- package/dist/init.d.cts +28 -0
- package/dist/init.d.mts +28 -0
- package/dist/init.mjs +137 -0
- package/dist/lifecycle.cjs +249 -0
- package/dist/lifecycle.d.cts +68 -0
- package/dist/lifecycle.d.mts +68 -0
- package/dist/lifecycle.mjs +245 -0
- package/dist/register.cjs +12 -0
- package/dist/register.d.cts +1 -0
- package/dist/register.d.mts +1 -0
- package/dist/register.mjs +14 -0
- package/dist/semconv.cjs +16 -0
- package/dist/semconv.mjs +13 -0
- package/dist/state.cjs +12 -0
- package/dist/state.mjs +12 -0
- package/dist/version.cjs +4 -0
- package/dist/version.mjs +4 -0
- package/package.json +114 -4
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
2
|
+
// src/exception.ts
|
|
3
|
+
const MAX_CAUSES = 5;
|
|
4
|
+
/**
|
|
5
|
+
* Builds the `exception.*` attributes of the crash record that the logs SDK does not derive from
|
|
6
|
+
* the error itself. Attributes set here win over the SDK's.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* An error's `cause` chain, up to five deep, is appended to its stack as `Caused by:` sections, the
|
|
10
|
+
* layout of Java's `printStackTrace`, which ClickStack splits on. A thrown value that is not an
|
|
11
|
+
* error is recorded as JSON text, because the logs SDK records only strings and numbers. Stacks
|
|
12
|
+
* stay raw; ClickStack parses them.
|
|
13
|
+
*
|
|
14
|
+
* @param value - The uncaught exception.
|
|
15
|
+
* @returns The attributes; empty when the SDK's own are complete.
|
|
16
|
+
*/
|
|
17
|
+
function exceptionAttributes(value) {
|
|
18
|
+
try {
|
|
19
|
+
if (typeof value === "string") return {};
|
|
20
|
+
if (!isErrorLike(value)) return { [_opentelemetry_semantic_conventions.ATTR_EXCEPTION_MESSAGE]: toText(value) };
|
|
21
|
+
const { cause } = value;
|
|
22
|
+
return cause === void 0 ? {} : { [_opentelemetry_semantic_conventions.ATTR_EXCEPTION_STACKTRACE]: stackWithCauses(value) };
|
|
23
|
+
} catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function isErrorLike(value) {
|
|
28
|
+
return typeof value === "object" && value !== null && ("message" in value || "stack" in value);
|
|
29
|
+
}
|
|
30
|
+
function stackWithCauses(error) {
|
|
31
|
+
let text = errorText(error);
|
|
32
|
+
const seen = /* @__PURE__ */ new Set([error]);
|
|
33
|
+
let { cause } = error;
|
|
34
|
+
for (let depth = 0; depth < MAX_CAUSES && cause !== void 0 && !seen.has(cause); depth++) {
|
|
35
|
+
seen.add(cause);
|
|
36
|
+
text += `\nCaused by: ${isErrorLike(cause) ? errorText(cause) : toText(cause)}`;
|
|
37
|
+
cause = isErrorLike(cause) ? cause.cause : void 0;
|
|
38
|
+
}
|
|
39
|
+
return text;
|
|
40
|
+
}
|
|
41
|
+
function errorText(error) {
|
|
42
|
+
const header = Error.prototype.toString.call(error);
|
|
43
|
+
const { stack } = error;
|
|
44
|
+
if (typeof stack !== "string") return header;
|
|
45
|
+
return stack.startsWith(header) ? stack : `${header}\n${stack}`;
|
|
46
|
+
}
|
|
47
|
+
function toText(value) {
|
|
48
|
+
try {
|
|
49
|
+
const json = JSON.stringify(value);
|
|
50
|
+
if (json !== void 0) return json;
|
|
51
|
+
} catch {}
|
|
52
|
+
return String(value);
|
|
53
|
+
}
|
|
54
|
+
exports.exceptionAttributes = exceptionAttributes;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ATTR_EXCEPTION_MESSAGE, ATTR_EXCEPTION_STACKTRACE } from "@opentelemetry/semantic-conventions";
|
|
2
|
+
// src/exception.ts
|
|
3
|
+
const MAX_CAUSES = 5;
|
|
4
|
+
/**
|
|
5
|
+
* Builds the `exception.*` attributes of the crash record that the logs SDK does not derive from
|
|
6
|
+
* the error itself. Attributes set here win over the SDK's.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* An error's `cause` chain, up to five deep, is appended to its stack as `Caused by:` sections, the
|
|
10
|
+
* layout of Java's `printStackTrace`, which ClickStack splits on. A thrown value that is not an
|
|
11
|
+
* error is recorded as JSON text, because the logs SDK records only strings and numbers. Stacks
|
|
12
|
+
* stay raw; ClickStack parses them.
|
|
13
|
+
*
|
|
14
|
+
* @param value - The uncaught exception.
|
|
15
|
+
* @returns The attributes; empty when the SDK's own are complete.
|
|
16
|
+
*/
|
|
17
|
+
function exceptionAttributes(value) {
|
|
18
|
+
try {
|
|
19
|
+
if (typeof value === "string") return {};
|
|
20
|
+
if (!isErrorLike(value)) return { [ATTR_EXCEPTION_MESSAGE]: toText(value) };
|
|
21
|
+
const { cause } = value;
|
|
22
|
+
return cause === void 0 ? {} : { [ATTR_EXCEPTION_STACKTRACE]: stackWithCauses(value) };
|
|
23
|
+
} catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function isErrorLike(value) {
|
|
28
|
+
return typeof value === "object" && value !== null && ("message" in value || "stack" in value);
|
|
29
|
+
}
|
|
30
|
+
function stackWithCauses(error) {
|
|
31
|
+
let text = errorText(error);
|
|
32
|
+
const seen = /* @__PURE__ */ new Set([error]);
|
|
33
|
+
let { cause } = error;
|
|
34
|
+
for (let depth = 0; depth < MAX_CAUSES && cause !== void 0 && !seen.has(cause); depth++) {
|
|
35
|
+
seen.add(cause);
|
|
36
|
+
text += `\nCaused by: ${isErrorLike(cause) ? errorText(cause) : toText(cause)}`;
|
|
37
|
+
cause = isErrorLike(cause) ? cause.cause : void 0;
|
|
38
|
+
}
|
|
39
|
+
return text;
|
|
40
|
+
}
|
|
41
|
+
function errorText(error) {
|
|
42
|
+
const header = Error.prototype.toString.call(error);
|
|
43
|
+
const { stack } = error;
|
|
44
|
+
if (typeof stack !== "string") return header;
|
|
45
|
+
return stack.startsWith(header) ? stack : `${header}\n${stack}`;
|
|
46
|
+
}
|
|
47
|
+
function toText(value) {
|
|
48
|
+
try {
|
|
49
|
+
const json = JSON.stringify(value);
|
|
50
|
+
if (json !== void 0) return json;
|
|
51
|
+
} catch {}
|
|
52
|
+
return String(value);
|
|
53
|
+
}
|
|
54
|
+
export { exceptionAttributes };
|
package/dist/hook.cjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
let node_module = require("node:module");
|
|
3
|
+
node_module = require_runtime.__toESM(node_module, 1);
|
|
4
|
+
let node_url = require("node:url");
|
|
5
|
+
// src/hook.ts
|
|
6
|
+
const restoreFormat = (specifier, context, nextResolve) => {
|
|
7
|
+
const isEntry = context.parentURL === void 0;
|
|
8
|
+
const result = nextResolve(specifier, context);
|
|
9
|
+
if (result.format !== "commonjs") return result;
|
|
10
|
+
if (result.url.startsWith("node:")) return {
|
|
11
|
+
...result,
|
|
12
|
+
format: "builtin"
|
|
13
|
+
};
|
|
14
|
+
return isEntry && result.url.startsWith("file:") && !/\.[cm]?[jt]s$/.test(result.url) ? {
|
|
15
|
+
...result,
|
|
16
|
+
format: void 0
|
|
17
|
+
} : result;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Installs the import-in-the-middle ESM loader hook in this thread.
|
|
21
|
+
*
|
|
22
|
+
* import-in-the-middle is loaded from `@opentelemetry/instrumentation`'s own location: only the
|
|
23
|
+
* copy next to it sees the `Hook()` calls instrumentations make. The package's own dependency on
|
|
24
|
+
* it is never imported; it raises that copy to the 3.5 line, whose behavior the repair above
|
|
25
|
+
* expects (`@opentelemetry/instrumentation` accepts any 3.x). Synchronous hooks
|
|
26
|
+
* (`module.registerHooks`) are used where import-in-the-middle supports them; `module.register`,
|
|
27
|
+
* which Node 26 deprecates (DEP0205), is the fallback for older Node versions and for runtimes
|
|
28
|
+
* that disable `require(esm)` (AWS Lambda).
|
|
29
|
+
*
|
|
30
|
+
* @returns `'sync'` for `module.registerHooks`, `'async'` for `module.register`.
|
|
31
|
+
* @throws If `@opentelemetry/instrumentation/hook.mjs` cannot be resolved or Node rejects the hook.
|
|
32
|
+
*/
|
|
33
|
+
function installEsmHook() {
|
|
34
|
+
const hookFile = node_module.createRequire(require("url").pathToFileURL(__filename).href).resolve("@opentelemetry/instrumentation/hook.mjs");
|
|
35
|
+
let syncHooks;
|
|
36
|
+
try {
|
|
37
|
+
syncHooks = node_module.createRequire(hookFile)("import-in-the-middle/register-hooks.mjs");
|
|
38
|
+
} catch {}
|
|
39
|
+
if (syncHooks?.supportsSyncHooks() === true) {
|
|
40
|
+
syncHooks.register();
|
|
41
|
+
node_module.registerHooks({ resolve: restoreFormat });
|
|
42
|
+
return "sync";
|
|
43
|
+
}
|
|
44
|
+
node_module.register((0, node_url.pathToFileURL)(hookFile));
|
|
45
|
+
return "async";
|
|
46
|
+
}
|
|
47
|
+
exports.installEsmHook = installEsmHook;
|
package/dist/hook.mjs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as nodeModule from "node:module";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
// src/hook.ts
|
|
4
|
+
const restoreFormat = (specifier, context, nextResolve) => {
|
|
5
|
+
const isEntry = context.parentURL === void 0;
|
|
6
|
+
const result = nextResolve(specifier, context);
|
|
7
|
+
if (result.format !== "commonjs") return result;
|
|
8
|
+
if (result.url.startsWith("node:")) return {
|
|
9
|
+
...result,
|
|
10
|
+
format: "builtin"
|
|
11
|
+
};
|
|
12
|
+
return isEntry && result.url.startsWith("file:") && !/\.[cm]?[jt]s$/.test(result.url) ? {
|
|
13
|
+
...result,
|
|
14
|
+
format: void 0
|
|
15
|
+
} : result;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Installs the import-in-the-middle ESM loader hook in this thread.
|
|
19
|
+
*
|
|
20
|
+
* import-in-the-middle is loaded from `@opentelemetry/instrumentation`'s own location: only the
|
|
21
|
+
* copy next to it sees the `Hook()` calls instrumentations make. The package's own dependency on
|
|
22
|
+
* it is never imported; it raises that copy to the 3.5 line, whose behavior the repair above
|
|
23
|
+
* expects (`@opentelemetry/instrumentation` accepts any 3.x). Synchronous hooks
|
|
24
|
+
* (`module.registerHooks`) are used where import-in-the-middle supports them; `module.register`,
|
|
25
|
+
* which Node 26 deprecates (DEP0205), is the fallback for older Node versions and for runtimes
|
|
26
|
+
* that disable `require(esm)` (AWS Lambda).
|
|
27
|
+
*
|
|
28
|
+
* @returns `'sync'` for `module.registerHooks`, `'async'` for `module.register`.
|
|
29
|
+
* @throws If `@opentelemetry/instrumentation/hook.mjs` cannot be resolved or Node rejects the hook.
|
|
30
|
+
*/
|
|
31
|
+
function installEsmHook() {
|
|
32
|
+
const hookFile = nodeModule.createRequire(import.meta.url).resolve("@opentelemetry/instrumentation/hook.mjs");
|
|
33
|
+
let syncHooks;
|
|
34
|
+
try {
|
|
35
|
+
syncHooks = nodeModule.createRequire(hookFile)("import-in-the-middle/register-hooks.mjs");
|
|
36
|
+
} catch {}
|
|
37
|
+
if (syncHooks?.supportsSyncHooks() === true) {
|
|
38
|
+
syncHooks.register();
|
|
39
|
+
nodeModule.registerHooks({ resolve: restoreFormat });
|
|
40
|
+
return "sync";
|
|
41
|
+
}
|
|
42
|
+
nodeModule.register(pathToFileURL(hookFile));
|
|
43
|
+
return "async";
|
|
44
|
+
}
|
|
45
|
+
export { installEsmHook };
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_lifecycle = require("./lifecycle.cjs");
|
|
3
|
+
const require_init = require("./init.cjs");
|
|
4
|
+
exports.flush = require_lifecycle.flush;
|
|
5
|
+
exports.init = require_init.init;
|
|
6
|
+
exports.shutdown = require_lifecycle.shutdown;
|
|
7
|
+
exports.withFlush = require_lifecycle.withFlush;
|
package/dist/index.d.cts
ADDED
package/dist/index.d.mts
ADDED
package/dist/index.mjs
ADDED
package/dist/init.cjs
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
const require_state = require("./state.cjs");
|
|
3
|
+
const require_env = require("./env.cjs");
|
|
4
|
+
const require_lifecycle = require("./lifecycle.cjs");
|
|
5
|
+
const require_compose = require("./compose.cjs");
|
|
6
|
+
const require_config = require("./config.cjs");
|
|
7
|
+
const require_exception = require("./exception.cjs");
|
|
8
|
+
const require_hook = require("./hook.cjs");
|
|
9
|
+
const require_version = require("./version.cjs");
|
|
10
|
+
let node_worker_threads = require("node:worker_threads");
|
|
11
|
+
node_worker_threads = require_runtime.__toESM(node_worker_threads, 1);
|
|
12
|
+
let _opentelemetry_api = require("@opentelemetry/api");
|
|
13
|
+
let _opentelemetry_auto_instrumentations_node = require("@opentelemetry/auto-instrumentations-node");
|
|
14
|
+
let _opentelemetry_core = require("@opentelemetry/core");
|
|
15
|
+
let _opentelemetry_instrumentation_console = require("@opentelemetry/instrumentation-console");
|
|
16
|
+
let _opentelemetry_resources = require("@opentelemetry/resources");
|
|
17
|
+
let _opentelemetry_sdk_node = require("@opentelemetry/sdk-node");
|
|
18
|
+
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
19
|
+
// src/init.ts
|
|
20
|
+
/**
|
|
21
|
+
* Starts the OpenTelemetry SDK with ClickStack defaults: traces, metrics and logs over OTLP,
|
|
22
|
+
* `console` capture, and a flush on shutdown, termination signals and crashes.
|
|
23
|
+
*
|
|
24
|
+
* Options with an environment variable are written to `process.env` only when that variable is
|
|
25
|
+
* unset, so a variable set by an operator always wins. A second call, or a call after another
|
|
26
|
+
* OpenTelemetry SDK registered a tracer provider, logs a warning and does nothing. With
|
|
27
|
+
* `OTEL_SDK_DISABLED=true` it does nothing at all. It never throws.
|
|
28
|
+
*
|
|
29
|
+
* Call it before the app loads the modules to instrument. In an ES module entry, static imports
|
|
30
|
+
* run first, so start the process with the register entry instead:
|
|
31
|
+
* `node --import @clickhouse/clickstack-node/register app.mjs` (it also works with `--require` and
|
|
32
|
+
* in `NODE_OPTIONS`); that entry calls `init()` without options.
|
|
33
|
+
*
|
|
34
|
+
* @param options - Code options; each loses to its environment variable.
|
|
35
|
+
* @example
|
|
36
|
+
* At the top of a CommonJS entry, before anything else is required:
|
|
37
|
+
* ```js
|
|
38
|
+
* const { init } = require('@clickhouse/clickstack-node');
|
|
39
|
+
*
|
|
40
|
+
* init({ serviceName: 'checkout', endpoint: 'https://otel.example.com:4318', apiKey: process.env.KEY });
|
|
41
|
+
*
|
|
42
|
+
* const express = require('express');
|
|
43
|
+
* ```
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
function init(options = {}) {
|
|
47
|
+
try {
|
|
48
|
+
start(options);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
require_state.getState().unsubscribeUndici?.();
|
|
51
|
+
require_env.report("error", "init() failed; the application is not instrumented", err);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function start(options) {
|
|
55
|
+
if (node_worker_threads.isInternalThread) return;
|
|
56
|
+
if ((0, _opentelemetry_core.getBooleanFromEnv)("OTEL_SDK_DISABLED")) return;
|
|
57
|
+
const state = require_state.getState();
|
|
58
|
+
const registry = require_env.apiRegistry();
|
|
59
|
+
if (state.initialized === true || registry?.trace !== void 0) {
|
|
60
|
+
require_env.report("warn", "an OpenTelemetry SDK is already started; init() does nothing");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
state.initialized = true;
|
|
64
|
+
require_config.applyOptions(options);
|
|
65
|
+
try {
|
|
66
|
+
state.esmHook = require_hook.installEsmHook();
|
|
67
|
+
} catch (err) {
|
|
68
|
+
state.esmHook = "failed";
|
|
69
|
+
require_env.warnOnce("esm-hook", "ESM loader hook not installed; ES modules are not instrumented", err);
|
|
70
|
+
}
|
|
71
|
+
const bodies = (0, _opentelemetry_core.getBooleanFromEnv)("CLICKSTACK_CAPTURE_HTTP_BODY");
|
|
72
|
+
const { map, unsubscribe } = require_compose.composeInstrumentationConfig(options.instrumentations, bodies);
|
|
73
|
+
if (unsubscribe !== void 0) state.unsubscribeUndici = unsubscribe;
|
|
74
|
+
const runtimeNode = "@opentelemetry/instrumentation-runtime-node";
|
|
75
|
+
const runtime = map[runtimeNode] ?? {};
|
|
76
|
+
map[runtimeNode] = {
|
|
77
|
+
captureUncaughtException: true,
|
|
78
|
+
...runtime,
|
|
79
|
+
applyCustomExceptionAttributes: (error, eventType) => ({
|
|
80
|
+
...require_exception.exceptionAttributes(error),
|
|
81
|
+
...runtime.applyCustomExceptionAttributes?.(error, eventType)
|
|
82
|
+
})
|
|
83
|
+
};
|
|
84
|
+
const disabled = (0, _opentelemetry_core.getStringListFromEnv)("OTEL_NODE_DISABLED_INSTRUMENTATIONS");
|
|
85
|
+
const enabled = (0, _opentelemetry_core.getStringListFromEnv)("OTEL_NODE_ENABLED_INSTRUMENTATIONS");
|
|
86
|
+
const router = "@opentelemetry/instrumentation-router";
|
|
87
|
+
if (enabled === void 0) map[router] = {
|
|
88
|
+
enabled: false,
|
|
89
|
+
...map[router]
|
|
90
|
+
};
|
|
91
|
+
const consoleListed = disabled?.includes("console") !== true && (enabled?.includes("console") ?? true);
|
|
92
|
+
const consoleExporter = [
|
|
93
|
+
"OTEL_TRACES_EXPORTER",
|
|
94
|
+
"OTEL_METRICS_EXPORTER",
|
|
95
|
+
"OTEL_LOGS_EXPORTER"
|
|
96
|
+
].find((name) => (0, _opentelemetry_core.getStringListFromEnv)(name)?.includes("console"));
|
|
97
|
+
if (consoleListed && consoleExporter !== void 0) require_env.warnOnce("console-exporter", `console capture is off because ${consoleExporter} includes console`);
|
|
98
|
+
const consoleCapture = consoleListed && consoleExporter === void 0;
|
|
99
|
+
const instrumentations = [
|
|
100
|
+
...require_env.withStartupLogger(() => (0, _opentelemetry_auto_instrumentations_node.getNodeAutoInstrumentations)(map)),
|
|
101
|
+
...consoleCapture ? [new _opentelemetry_instrumentation_console.ConsoleInstrumentation()] : [],
|
|
102
|
+
...options.additionalInstrumentations ?? []
|
|
103
|
+
];
|
|
104
|
+
const build = () => new _opentelemetry_sdk_node.NodeSDK({
|
|
105
|
+
resource: (0, _opentelemetry_resources.defaultResource)().merge((0, _opentelemetry_resources.resourceFromAttributes)({
|
|
106
|
+
...options.environment ? { [_opentelemetry_semantic_conventions.ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: options.environment } : {},
|
|
107
|
+
[_opentelemetry_semantic_conventions.ATTR_TELEMETRY_DISTRO_NAME]: "clickstack-node",
|
|
108
|
+
[_opentelemetry_semantic_conventions.ATTR_TELEMETRY_DISTRO_VERSION]: require_version.VERSION
|
|
109
|
+
})),
|
|
110
|
+
resourceDetectors: withDefault("OTEL_NODE_RESOURCE_DETECTORS", "container,host,os,process,serviceinstance,env", () => require_env.withStartupLogger(_opentelemetry_auto_instrumentations_node.getResourceDetectors)),
|
|
111
|
+
instrumentations
|
|
112
|
+
});
|
|
113
|
+
const sdk = registry?.diag === void 0 ? withDefault("OTEL_LOG_LEVEL", "warn", build) : build();
|
|
114
|
+
sdk.start();
|
|
115
|
+
state.sdk = sdk;
|
|
116
|
+
if (consoleCapture) {
|
|
117
|
+
state.consoleLog = console.log;
|
|
118
|
+
state.repatchConsole = () => {
|
|
119
|
+
new _opentelemetry_instrumentation_console.ConsoleInstrumentation();
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
require_lifecycle.installLifecycle();
|
|
123
|
+
const provider = _opentelemetry_api.trace.getTracerProvider();
|
|
124
|
+
const bounded = {
|
|
125
|
+
getTracer: (...args) => provider.getTracer(...args),
|
|
126
|
+
forceFlush: () => require_lifecycle.flush()
|
|
127
|
+
};
|
|
128
|
+
for (const instrumentation of instrumentations) if (instrumentation.instrumentationName === "@opentelemetry/instrumentation-aws-lambda") instrumentation.setTracerProvider(bounded);
|
|
129
|
+
}
|
|
130
|
+
function withDefault(name, value, call) {
|
|
131
|
+
if ((0, _opentelemetry_core.getStringFromEnv)(name) !== void 0) return call();
|
|
132
|
+
process.env[name] = value;
|
|
133
|
+
try {
|
|
134
|
+
return call();
|
|
135
|
+
} finally {
|
|
136
|
+
Reflect.deleteProperty(process.env, name);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
exports.init = init;
|
package/dist/init.d.cts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ClickStackNodeOptions } from "./config.cjs";
|
|
2
|
+
/**
|
|
3
|
+
* Starts the OpenTelemetry SDK with ClickStack defaults: traces, metrics and logs over OTLP,
|
|
4
|
+
* `console` capture, and a flush on shutdown, termination signals and crashes.
|
|
5
|
+
*
|
|
6
|
+
* Options with an environment variable are written to `process.env` only when that variable is
|
|
7
|
+
* unset, so a variable set by an operator always wins. A second call, or a call after another
|
|
8
|
+
* OpenTelemetry SDK registered a tracer provider, logs a warning and does nothing. With
|
|
9
|
+
* `OTEL_SDK_DISABLED=true` it does nothing at all. It never throws.
|
|
10
|
+
*
|
|
11
|
+
* Call it before the app loads the modules to instrument. In an ES module entry, static imports
|
|
12
|
+
* run first, so start the process with the register entry instead:
|
|
13
|
+
* `node --import @clickhouse/clickstack-node/register app.mjs` (it also works with `--require` and
|
|
14
|
+
* in `NODE_OPTIONS`); that entry calls `init()` without options.
|
|
15
|
+
*
|
|
16
|
+
* @param options - Code options; each loses to its environment variable.
|
|
17
|
+
* @example
|
|
18
|
+
* At the top of a CommonJS entry, before anything else is required:
|
|
19
|
+
* ```js
|
|
20
|
+
* const { init } = require('@clickhouse/clickstack-node');
|
|
21
|
+
*
|
|
22
|
+
* init({ serviceName: 'checkout', endpoint: 'https://otel.example.com:4318', apiKey: process.env.KEY });
|
|
23
|
+
*
|
|
24
|
+
* const express = require('express');
|
|
25
|
+
* ```
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export declare function init(options?: ClickStackNodeOptions): void;
|
package/dist/init.d.mts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ClickStackNodeOptions } from "./config.mjs";
|
|
2
|
+
/**
|
|
3
|
+
* Starts the OpenTelemetry SDK with ClickStack defaults: traces, metrics and logs over OTLP,
|
|
4
|
+
* `console` capture, and a flush on shutdown, termination signals and crashes.
|
|
5
|
+
*
|
|
6
|
+
* Options with an environment variable are written to `process.env` only when that variable is
|
|
7
|
+
* unset, so a variable set by an operator always wins. A second call, or a call after another
|
|
8
|
+
* OpenTelemetry SDK registered a tracer provider, logs a warning and does nothing. With
|
|
9
|
+
* `OTEL_SDK_DISABLED=true` it does nothing at all. It never throws.
|
|
10
|
+
*
|
|
11
|
+
* Call it before the app loads the modules to instrument. In an ES module entry, static imports
|
|
12
|
+
* run first, so start the process with the register entry instead:
|
|
13
|
+
* `node --import @clickhouse/clickstack-node/register app.mjs` (it also works with `--require` and
|
|
14
|
+
* in `NODE_OPTIONS`); that entry calls `init()` without options.
|
|
15
|
+
*
|
|
16
|
+
* @param options - Code options; each loses to its environment variable.
|
|
17
|
+
* @example
|
|
18
|
+
* At the top of a CommonJS entry, before anything else is required:
|
|
19
|
+
* ```js
|
|
20
|
+
* const { init } = require('@clickhouse/clickstack-node');
|
|
21
|
+
*
|
|
22
|
+
* init({ serviceName: 'checkout', endpoint: 'https://otel.example.com:4318', apiKey: process.env.KEY });
|
|
23
|
+
*
|
|
24
|
+
* const express = require('express');
|
|
25
|
+
* ```
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export declare function init(options?: ClickStackNodeOptions): void;
|
package/dist/init.mjs
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { getState } from "./state.mjs";
|
|
2
|
+
import { apiRegistry, report, warnOnce, withStartupLogger } from "./env.mjs";
|
|
3
|
+
import { flush, installLifecycle } from "./lifecycle.mjs";
|
|
4
|
+
import { composeInstrumentationConfig } from "./compose.mjs";
|
|
5
|
+
import { applyOptions } from "./config.mjs";
|
|
6
|
+
import { exceptionAttributes } from "./exception.mjs";
|
|
7
|
+
import { installEsmHook } from "./hook.mjs";
|
|
8
|
+
import { VERSION } from "./version.mjs";
|
|
9
|
+
import * as workerThreads from "node:worker_threads";
|
|
10
|
+
import { trace } from "@opentelemetry/api";
|
|
11
|
+
import { getNodeAutoInstrumentations, getResourceDetectors } from "@opentelemetry/auto-instrumentations-node";
|
|
12
|
+
import { getBooleanFromEnv, getStringFromEnv, getStringListFromEnv } from "@opentelemetry/core";
|
|
13
|
+
import { ConsoleInstrumentation } from "@opentelemetry/instrumentation-console";
|
|
14
|
+
import { defaultResource, resourceFromAttributes } from "@opentelemetry/resources";
|
|
15
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
16
|
+
import { ATTR_DEPLOYMENT_ENVIRONMENT_NAME, ATTR_TELEMETRY_DISTRO_NAME, ATTR_TELEMETRY_DISTRO_VERSION } from "@opentelemetry/semantic-conventions";
|
|
17
|
+
// src/init.ts
|
|
18
|
+
/**
|
|
19
|
+
* Starts the OpenTelemetry SDK with ClickStack defaults: traces, metrics and logs over OTLP,
|
|
20
|
+
* `console` capture, and a flush on shutdown, termination signals and crashes.
|
|
21
|
+
*
|
|
22
|
+
* Options with an environment variable are written to `process.env` only when that variable is
|
|
23
|
+
* unset, so a variable set by an operator always wins. A second call, or a call after another
|
|
24
|
+
* OpenTelemetry SDK registered a tracer provider, logs a warning and does nothing. With
|
|
25
|
+
* `OTEL_SDK_DISABLED=true` it does nothing at all. It never throws.
|
|
26
|
+
*
|
|
27
|
+
* Call it before the app loads the modules to instrument. In an ES module entry, static imports
|
|
28
|
+
* run first, so start the process with the register entry instead:
|
|
29
|
+
* `node --import @clickhouse/clickstack-node/register app.mjs` (it also works with `--require` and
|
|
30
|
+
* in `NODE_OPTIONS`); that entry calls `init()` without options.
|
|
31
|
+
*
|
|
32
|
+
* @param options - Code options; each loses to its environment variable.
|
|
33
|
+
* @example
|
|
34
|
+
* At the top of a CommonJS entry, before anything else is required:
|
|
35
|
+
* ```js
|
|
36
|
+
* const { init } = require('@clickhouse/clickstack-node');
|
|
37
|
+
*
|
|
38
|
+
* init({ serviceName: 'checkout', endpoint: 'https://otel.example.com:4318', apiKey: process.env.KEY });
|
|
39
|
+
*
|
|
40
|
+
* const express = require('express');
|
|
41
|
+
* ```
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
function init(options = {}) {
|
|
45
|
+
try {
|
|
46
|
+
start(options);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
getState().unsubscribeUndici?.();
|
|
49
|
+
report("error", "init() failed; the application is not instrumented", err);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function start(options) {
|
|
53
|
+
if (workerThreads.isInternalThread) return;
|
|
54
|
+
if (getBooleanFromEnv("OTEL_SDK_DISABLED")) return;
|
|
55
|
+
const state = getState();
|
|
56
|
+
const registry = apiRegistry();
|
|
57
|
+
if (state.initialized === true || registry?.trace !== void 0) {
|
|
58
|
+
report("warn", "an OpenTelemetry SDK is already started; init() does nothing");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
state.initialized = true;
|
|
62
|
+
applyOptions(options);
|
|
63
|
+
try {
|
|
64
|
+
state.esmHook = installEsmHook();
|
|
65
|
+
} catch (err) {
|
|
66
|
+
state.esmHook = "failed";
|
|
67
|
+
warnOnce("esm-hook", "ESM loader hook not installed; ES modules are not instrumented", err);
|
|
68
|
+
}
|
|
69
|
+
const bodies = getBooleanFromEnv("CLICKSTACK_CAPTURE_HTTP_BODY");
|
|
70
|
+
const { map, unsubscribe } = composeInstrumentationConfig(options.instrumentations, bodies);
|
|
71
|
+
if (unsubscribe !== void 0) state.unsubscribeUndici = unsubscribe;
|
|
72
|
+
const runtimeNode = "@opentelemetry/instrumentation-runtime-node";
|
|
73
|
+
const runtime = map[runtimeNode] ?? {};
|
|
74
|
+
map[runtimeNode] = {
|
|
75
|
+
captureUncaughtException: true,
|
|
76
|
+
...runtime,
|
|
77
|
+
applyCustomExceptionAttributes: (error, eventType) => ({
|
|
78
|
+
...exceptionAttributes(error),
|
|
79
|
+
...runtime.applyCustomExceptionAttributes?.(error, eventType)
|
|
80
|
+
})
|
|
81
|
+
};
|
|
82
|
+
const disabled = getStringListFromEnv("OTEL_NODE_DISABLED_INSTRUMENTATIONS");
|
|
83
|
+
const enabled = getStringListFromEnv("OTEL_NODE_ENABLED_INSTRUMENTATIONS");
|
|
84
|
+
const router = "@opentelemetry/instrumentation-router";
|
|
85
|
+
if (enabled === void 0) map[router] = {
|
|
86
|
+
enabled: false,
|
|
87
|
+
...map[router]
|
|
88
|
+
};
|
|
89
|
+
const consoleListed = disabled?.includes("console") !== true && (enabled?.includes("console") ?? true);
|
|
90
|
+
const consoleExporter = [
|
|
91
|
+
"OTEL_TRACES_EXPORTER",
|
|
92
|
+
"OTEL_METRICS_EXPORTER",
|
|
93
|
+
"OTEL_LOGS_EXPORTER"
|
|
94
|
+
].find((name) => getStringListFromEnv(name)?.includes("console"));
|
|
95
|
+
if (consoleListed && consoleExporter !== void 0) warnOnce("console-exporter", `console capture is off because ${consoleExporter} includes console`);
|
|
96
|
+
const consoleCapture = consoleListed && consoleExporter === void 0;
|
|
97
|
+
const instrumentations = [
|
|
98
|
+
...withStartupLogger(() => getNodeAutoInstrumentations(map)),
|
|
99
|
+
...consoleCapture ? [new ConsoleInstrumentation()] : [],
|
|
100
|
+
...options.additionalInstrumentations ?? []
|
|
101
|
+
];
|
|
102
|
+
const build = () => new NodeSDK({
|
|
103
|
+
resource: defaultResource().merge(resourceFromAttributes({
|
|
104
|
+
...options.environment ? { [ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: options.environment } : {},
|
|
105
|
+
[ATTR_TELEMETRY_DISTRO_NAME]: "clickstack-node",
|
|
106
|
+
[ATTR_TELEMETRY_DISTRO_VERSION]: VERSION
|
|
107
|
+
})),
|
|
108
|
+
resourceDetectors: withDefault("OTEL_NODE_RESOURCE_DETECTORS", "container,host,os,process,serviceinstance,env", () => withStartupLogger(getResourceDetectors)),
|
|
109
|
+
instrumentations
|
|
110
|
+
});
|
|
111
|
+
const sdk = registry?.diag === void 0 ? withDefault("OTEL_LOG_LEVEL", "warn", build) : build();
|
|
112
|
+
sdk.start();
|
|
113
|
+
state.sdk = sdk;
|
|
114
|
+
if (consoleCapture) {
|
|
115
|
+
state.consoleLog = console.log;
|
|
116
|
+
state.repatchConsole = () => {
|
|
117
|
+
new ConsoleInstrumentation();
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
installLifecycle();
|
|
121
|
+
const provider = trace.getTracerProvider();
|
|
122
|
+
const bounded = {
|
|
123
|
+
getTracer: (...args) => provider.getTracer(...args),
|
|
124
|
+
forceFlush: () => flush()
|
|
125
|
+
};
|
|
126
|
+
for (const instrumentation of instrumentations) if (instrumentation.instrumentationName === "@opentelemetry/instrumentation-aws-lambda") instrumentation.setTracerProvider(bounded);
|
|
127
|
+
}
|
|
128
|
+
function withDefault(name, value, call) {
|
|
129
|
+
if (getStringFromEnv(name) !== void 0) return call();
|
|
130
|
+
process.env[name] = value;
|
|
131
|
+
try {
|
|
132
|
+
return call();
|
|
133
|
+
} finally {
|
|
134
|
+
Reflect.deleteProperty(process.env, name);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
export { init };
|