@clickhouse/clickstack-node 0.0.0-stage → 1.0.0-rc.1

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.
@@ -0,0 +1,249 @@
1
+ const require_state = require("./state.cjs");
2
+ const require_env = require("./env.cjs");
3
+ let node_worker_threads = require("node:worker_threads");
4
+ let _opentelemetry_api = require("@opentelemetry/api");
5
+ let _opentelemetry_core = require("@opentelemetry/core");
6
+ let node_util = require("node:util");
7
+ let node_os = require("node:os");
8
+ let _opentelemetry_api_logs = require("@opentelemetry/api-logs");
9
+ // src/lifecycle.ts
10
+ const DEFAULT_TIMEOUT_MILLIS = 5e3;
11
+ const SENTRY_SEND_MILLIS = 2e3;
12
+ const INVOCATION_MARGIN_MILLIS = 500;
13
+ const SIGNALS = ["SIGTERM", "SIGINT"];
14
+ function bounded(work, ms, keepAlive) {
15
+ let timer;
16
+ const deadline = new Promise((resolve) => {
17
+ timer = setTimeout(resolve, ms, false);
18
+ if (!keepAlive) timer.unref();
19
+ });
20
+ const settled = Promise.resolve().then(work).then(() => true);
21
+ return Promise.race([settled, deadline]).then((inTime) => {
22
+ clearTimeout(timer);
23
+ if (!inTime) require_env.report("warn", `flush or shutdown did not finish within ${String(ms)} ms`);
24
+ return inTime;
25
+ });
26
+ }
27
+ function timeoutOf(timeoutMillis) {
28
+ if (timeoutMillis !== void 0 && timeoutMillis > 0) return Math.min(timeoutMillis, 2147483647);
29
+ const deadline = require_state.getState().invocationDeadline;
30
+ return deadline === void 0 ? DEFAULT_TIMEOUT_MILLIS : Math.max(1, Math.min(DEFAULT_TIMEOUT_MILLIS, deadline - Date.now()));
31
+ }
32
+ function sdkShutdown() {
33
+ const state = require_state.getState();
34
+ const sdk = state.sdk;
35
+ if (sdk === void 0) return Promise.resolve();
36
+ return state.shutdownPromise ??= (async () => {
37
+ await state.flushInFlight;
38
+ try {
39
+ await sdk.shutdown();
40
+ } catch (error) {
41
+ require_env.report("warn", "shutdown failed", error);
42
+ }
43
+ state.unsubscribeUndici?.();
44
+ })();
45
+ }
46
+ /**
47
+ * Records when the current AWS Lambda invocation must return, so flushes end in time.
48
+ *
49
+ * @param context - The invocation context; anything without `getRemainingTimeInMillis` is ignored.
50
+ */
51
+ function recordInvocationDeadline(context) {
52
+ const remaining = context?.getRemainingTimeInMillis?.();
53
+ if (typeof remaining === "number") require_state.getState().invocationDeadline = Date.now() + remaining - INVOCATION_MARGIN_MILLIS;
54
+ }
55
+ /**
56
+ * Exports the spans, metrics and log records the SDK has buffered, without stopping it.
57
+ *
58
+ * @remarks
59
+ * Calls `forceFlush` on the global tracer, meter and logger providers. Before `init`, or with
60
+ * `OTEL_SDK_DISABLED=true`, it resolves at once; after {@link shutdown} it waits for the shutdown.
61
+ * Failures and timeouts are reported through the OpenTelemetry `diag` logger. Inside a
62
+ * {@link withFlush} invocation the default bound also ends before the invocation's deadline.
63
+ *
64
+ * @param timeoutMillis - Upper bound in milliseconds, 5000 by default; zero, negative and NaN
65
+ * mean the default.
66
+ * @returns A promise that resolves when every provider has flushed or the bound has passed. It
67
+ * never rejects.
68
+ * @public
69
+ */
70
+ async function flush(timeoutMillis) {
71
+ const state = require_state.getState();
72
+ if (state.sdk === void 0) return;
73
+ const ms = timeoutOf(timeoutMillis);
74
+ const pending = state.shutdownPromise;
75
+ if (pending !== void 0) {
76
+ await bounded(() => pending, ms, false);
77
+ return;
78
+ }
79
+ const tracer = _opentelemetry_api.trace.getTracerProvider();
80
+ const providers = [
81
+ tracer.getDelegate?.() ?? tracer,
82
+ _opentelemetry_api.metrics.getMeterProvider(),
83
+ _opentelemetry_api_logs.logs.getLoggerProvider()
84
+ ];
85
+ const work = Promise.all(providers.map(async (provider) => {
86
+ const p = provider;
87
+ if (typeof p.forceFlush !== "function") return;
88
+ try {
89
+ await p.forceFlush({ timeoutMillis: ms });
90
+ } catch (error) {
91
+ require_env.report("warn", "flush failed", error);
92
+ }
93
+ }));
94
+ state.flushInFlight = Promise.all([state.flushInFlight, work]);
95
+ await bounded(() => work, ms, false);
96
+ }
97
+ /**
98
+ * Flushes and stops the SDK started by `init`; telemetry recorded afterwards is dropped.
99
+ *
100
+ * @remarks
101
+ * Later calls, and the automatic shutdown on `beforeExit`, SIGTERM, SIGINT and uncaught
102
+ * exceptions, share the first call's work. Scripts that end with `process.exit()`, and app signal
103
+ * handlers that exit, should await it first, because `process.exit()` skips `beforeExit`.
104
+ *
105
+ * @param timeoutMillis - Upper bound in milliseconds, 5000 by default; zero, negative and NaN
106
+ * mean the default.
107
+ * @returns A promise that resolves when the SDK has shut down or the bound has passed. It never
108
+ * rejects.
109
+ * @example
110
+ * ```ts
111
+ * process.on('SIGTERM', async () => {
112
+ * await server.close();
113
+ * await shutdown();
114
+ * process.exit(0);
115
+ * });
116
+ * ```
117
+ * @public
118
+ */
119
+ async function shutdown(timeoutMillis) {
120
+ if (require_state.getState().sdk === void 0) return;
121
+ await bounded(sdkShutdown, timeoutOf(timeoutMillis), false);
122
+ }
123
+ /**
124
+ * Wraps an async serverless handler so buffered telemetry is exported before each invocation
125
+ * returns.
126
+ *
127
+ * @remarks
128
+ * Awaits the handler, then {@link flush} in a `finally` block, so telemetry is also exported when
129
+ * the handler throws. Flushes during the invocation end 500 ms before its deadline
130
+ * (`context.getRemainingTimeInMillis()`). On its first call it installs console capture again if
131
+ * the AWS Lambda runtime replaced the console methods after the preload ran. Callback-style
132
+ * handlers are not supported.
133
+ *
134
+ * @typeParam E - The invocation event type.
135
+ * @typeParam C - The runtime context type, for example the AWS Lambda `Context`.
136
+ * @typeParam R - The value the handler's promise resolves to.
137
+ * @param handler - The async handler to wrap.
138
+ * @returns A handler with the same parameters and result.
139
+ * @example
140
+ * With `NODE_OPTIONS=--import @clickhouse/clickstack-node/register` set on the function:
141
+ * ```ts
142
+ * import { withFlush } from '@clickhouse/clickstack-node';
143
+ *
144
+ * export const handler = withFlush(async (event: { name?: string }) => {
145
+ * console.log('greeting', event.name);
146
+ * return { statusCode: 200, body: 'ok' };
147
+ * });
148
+ * ```
149
+ * @public
150
+ */
151
+ function withFlush(handler) {
152
+ let firstCall = true;
153
+ return async function flushingHandler(event, context) {
154
+ const state = require_state.getState();
155
+ try {
156
+ recordInvocationDeadline(context);
157
+ if (firstCall) {
158
+ firstCall = false;
159
+ const current = console.log;
160
+ if (state.repatchConsole !== void 0 && current !== state.consoleLog) {
161
+ state.repatchConsole();
162
+ state.consoleLog = console.log;
163
+ }
164
+ }
165
+ } catch (error) {
166
+ require_env.report("warn", "could not prepare the invocation", error);
167
+ }
168
+ try {
169
+ return await handler(event, context);
170
+ } finally {
171
+ await flush();
172
+ }
173
+ };
174
+ }
175
+ function writeFatal(error) {
176
+ try {
177
+ const body = typeof error === "string" ? error : (0, node_util.inspect)(error, {
178
+ customInspect: false,
179
+ depth: 5
180
+ });
181
+ process.getBuiltinModule("node:fs").writeSync(2, `${body}\n\nNode.js ${process.version}\n`);
182
+ } catch {}
183
+ }
184
+ function signalExitListeners() {
185
+ const v4 = globalThis[Symbol.for("signal-exit emitter")];
186
+ const v3 = process.__signal_exit_emitter__;
187
+ return [v4?.count, v3?.count].reduce((sum, count) => sum + (typeof count === "number" ? count : 0), 0);
188
+ }
189
+ /**
190
+ * Installs the `beforeExit`, SIGTERM, SIGINT and `uncaughtException` handlers, once per thread.
191
+ * Signal and crash handlers go on the main thread only: workers never receive signals, and a
192
+ * worker crash listener would hide the parent's `error` event.
193
+ */
194
+ function installLifecycle() {
195
+ let terminating = false;
196
+ let terminatingSince = 0;
197
+ if ((0, _opentelemetry_core.getStringFromEnv)("AWS_LAMBDA_FUNCTION_NAME") === void 0) process.once("beforeExit", () => {
198
+ if (terminating) return;
199
+ bounded(sdkShutdown, DEFAULT_TIMEOUT_MILLIS, false).then((inTime) => {
200
+ if (!inTime) process.exit();
201
+ });
202
+ });
203
+ if (!node_worker_threads.isMainThread) return;
204
+ const onSignal = (signal) => {
205
+ if (terminating) {
206
+ if (Date.now() - terminatingSince > 1e3) process.exit(128 + node_os.constants.signals[signal]);
207
+ return;
208
+ }
209
+ if (process.listeners(signal).filter((listener) => listener !== onSignal).length > signalExitListeners()) {
210
+ process.removeListener(signal, onSignal);
211
+ flush();
212
+ return;
213
+ }
214
+ terminating = true;
215
+ terminatingSince = Date.now();
216
+ bounded(sdkShutdown, DEFAULT_TIMEOUT_MILLIS, true).then(() => {
217
+ for (const name of SIGNALS) process.removeListener(name, onSignal);
218
+ try {
219
+ process.kill(process.pid, signal);
220
+ } catch {}
221
+ process.exit(128 + node_os.constants.signals[signal]);
222
+ });
223
+ };
224
+ for (const signal of SIGNALS) process.prependListener(signal, onSignal);
225
+ const onUncaughtException = (error) => {
226
+ try {
227
+ const listeners = process.listeners("uncaughtException");
228
+ const isSentry = (listener) => "_errorHandler" in listener && listener._errorHandler === true;
229
+ if (listeners.some((listener) => listener !== onUncaughtException && listener.name !== "domainUncaughtExceptionClear" && !isSentry(listener))) return;
230
+ process.exitCode = 1;
231
+ if (terminating) return;
232
+ writeFatal(error);
233
+ terminating = true;
234
+ terminatingSince = Date.now();
235
+ const sentry = listeners.some(isSentry) ? new Promise((resolve) => setTimeout(resolve, SENTRY_SEND_MILLIS)) : void 0;
236
+ Promise.all([bounded(sdkShutdown, DEFAULT_TIMEOUT_MILLIS, true), sentry]).then(() => {
237
+ process.exit(1);
238
+ });
239
+ } catch {
240
+ process.exit(1);
241
+ }
242
+ };
243
+ process.prependListener("uncaughtException", onUncaughtException);
244
+ }
245
+ exports.flush = flush;
246
+ exports.installLifecycle = installLifecycle;
247
+ exports.recordInvocationDeadline = recordInvocationDeadline;
248
+ exports.shutdown = shutdown;
249
+ exports.withFlush = withFlush;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Exports the spans, metrics and log records the SDK has buffered, without stopping it.
3
+ *
4
+ * @remarks
5
+ * Calls `forceFlush` on the global tracer, meter and logger providers. Before `init`, or with
6
+ * `OTEL_SDK_DISABLED=true`, it resolves at once; after {@link shutdown} it waits for the shutdown.
7
+ * Failures and timeouts are reported through the OpenTelemetry `diag` logger. Inside a
8
+ * {@link withFlush} invocation the default bound also ends before the invocation's deadline.
9
+ *
10
+ * @param timeoutMillis - Upper bound in milliseconds, 5000 by default; zero, negative and NaN
11
+ * mean the default.
12
+ * @returns A promise that resolves when every provider has flushed or the bound has passed. It
13
+ * never rejects.
14
+ * @public
15
+ */
16
+ export declare function flush(timeoutMillis?: number): Promise<void>;
17
+ /**
18
+ * Flushes and stops the SDK started by `init`; telemetry recorded afterwards is dropped.
19
+ *
20
+ * @remarks
21
+ * Later calls, and the automatic shutdown on `beforeExit`, SIGTERM, SIGINT and uncaught
22
+ * exceptions, share the first call's work. Scripts that end with `process.exit()`, and app signal
23
+ * handlers that exit, should await it first, because `process.exit()` skips `beforeExit`.
24
+ *
25
+ * @param timeoutMillis - Upper bound in milliseconds, 5000 by default; zero, negative and NaN
26
+ * mean the default.
27
+ * @returns A promise that resolves when the SDK has shut down or the bound has passed. It never
28
+ * rejects.
29
+ * @example
30
+ * ```ts
31
+ * process.on('SIGTERM', async () => {
32
+ * await server.close();
33
+ * await shutdown();
34
+ * process.exit(0);
35
+ * });
36
+ * ```
37
+ * @public
38
+ */
39
+ export declare function shutdown(timeoutMillis?: number): Promise<void>;
40
+ /**
41
+ * Wraps an async serverless handler so buffered telemetry is exported before each invocation
42
+ * returns.
43
+ *
44
+ * @remarks
45
+ * Awaits the handler, then {@link flush} in a `finally` block, so telemetry is also exported when
46
+ * the handler throws. Flushes during the invocation end 500 ms before its deadline
47
+ * (`context.getRemainingTimeInMillis()`). On its first call it installs console capture again if
48
+ * the AWS Lambda runtime replaced the console methods after the preload ran. Callback-style
49
+ * handlers are not supported.
50
+ *
51
+ * @typeParam E - The invocation event type.
52
+ * @typeParam C - The runtime context type, for example the AWS Lambda `Context`.
53
+ * @typeParam R - The value the handler's promise resolves to.
54
+ * @param handler - The async handler to wrap.
55
+ * @returns A handler with the same parameters and result.
56
+ * @example
57
+ * With `NODE_OPTIONS=--import @clickhouse/clickstack-node/register` set on the function:
58
+ * ```ts
59
+ * import { withFlush } from '@clickhouse/clickstack-node';
60
+ *
61
+ * export const handler = withFlush(async (event: { name?: string }) => {
62
+ * console.log('greeting', event.name);
63
+ * return { statusCode: 200, body: 'ok' };
64
+ * });
65
+ * ```
66
+ * @public
67
+ */
68
+ export declare function withFlush<E, C, R>(handler: (event: E, context: C) => Promise<R>): (event: E, context: C) => Promise<R>;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Exports the spans, metrics and log records the SDK has buffered, without stopping it.
3
+ *
4
+ * @remarks
5
+ * Calls `forceFlush` on the global tracer, meter and logger providers. Before `init`, or with
6
+ * `OTEL_SDK_DISABLED=true`, it resolves at once; after {@link shutdown} it waits for the shutdown.
7
+ * Failures and timeouts are reported through the OpenTelemetry `diag` logger. Inside a
8
+ * {@link withFlush} invocation the default bound also ends before the invocation's deadline.
9
+ *
10
+ * @param timeoutMillis - Upper bound in milliseconds, 5000 by default; zero, negative and NaN
11
+ * mean the default.
12
+ * @returns A promise that resolves when every provider has flushed or the bound has passed. It
13
+ * never rejects.
14
+ * @public
15
+ */
16
+ export declare function flush(timeoutMillis?: number): Promise<void>;
17
+ /**
18
+ * Flushes and stops the SDK started by `init`; telemetry recorded afterwards is dropped.
19
+ *
20
+ * @remarks
21
+ * Later calls, and the automatic shutdown on `beforeExit`, SIGTERM, SIGINT and uncaught
22
+ * exceptions, share the first call's work. Scripts that end with `process.exit()`, and app signal
23
+ * handlers that exit, should await it first, because `process.exit()` skips `beforeExit`.
24
+ *
25
+ * @param timeoutMillis - Upper bound in milliseconds, 5000 by default; zero, negative and NaN
26
+ * mean the default.
27
+ * @returns A promise that resolves when the SDK has shut down or the bound has passed. It never
28
+ * rejects.
29
+ * @example
30
+ * ```ts
31
+ * process.on('SIGTERM', async () => {
32
+ * await server.close();
33
+ * await shutdown();
34
+ * process.exit(0);
35
+ * });
36
+ * ```
37
+ * @public
38
+ */
39
+ export declare function shutdown(timeoutMillis?: number): Promise<void>;
40
+ /**
41
+ * Wraps an async serverless handler so buffered telemetry is exported before each invocation
42
+ * returns.
43
+ *
44
+ * @remarks
45
+ * Awaits the handler, then {@link flush} in a `finally` block, so telemetry is also exported when
46
+ * the handler throws. Flushes during the invocation end 500 ms before its deadline
47
+ * (`context.getRemainingTimeInMillis()`). On its first call it installs console capture again if
48
+ * the AWS Lambda runtime replaced the console methods after the preload ran. Callback-style
49
+ * handlers are not supported.
50
+ *
51
+ * @typeParam E - The invocation event type.
52
+ * @typeParam C - The runtime context type, for example the AWS Lambda `Context`.
53
+ * @typeParam R - The value the handler's promise resolves to.
54
+ * @param handler - The async handler to wrap.
55
+ * @returns A handler with the same parameters and result.
56
+ * @example
57
+ * With `NODE_OPTIONS=--import @clickhouse/clickstack-node/register` set on the function:
58
+ * ```ts
59
+ * import { withFlush } from '@clickhouse/clickstack-node';
60
+ *
61
+ * export const handler = withFlush(async (event: { name?: string }) => {
62
+ * console.log('greeting', event.name);
63
+ * return { statusCode: 200, body: 'ok' };
64
+ * });
65
+ * ```
66
+ * @public
67
+ */
68
+ export declare function withFlush<E, C, R>(handler: (event: E, context: C) => Promise<R>): (event: E, context: C) => Promise<R>;
@@ -0,0 +1,245 @@
1
+ import { getState } from "./state.mjs";
2
+ import { report } from "./env.mjs";
3
+ import { isMainThread } from "node:worker_threads";
4
+ import { metrics, trace } from "@opentelemetry/api";
5
+ import { getStringFromEnv } from "@opentelemetry/core";
6
+ import { inspect } from "node:util";
7
+ import { constants } from "node:os";
8
+ import { logs } from "@opentelemetry/api-logs";
9
+ // src/lifecycle.ts
10
+ const DEFAULT_TIMEOUT_MILLIS = 5e3;
11
+ const SENTRY_SEND_MILLIS = 2e3;
12
+ const INVOCATION_MARGIN_MILLIS = 500;
13
+ const SIGNALS = ["SIGTERM", "SIGINT"];
14
+ function bounded(work, ms, keepAlive) {
15
+ let timer;
16
+ const deadline = new Promise((resolve) => {
17
+ timer = setTimeout(resolve, ms, false);
18
+ if (!keepAlive) timer.unref();
19
+ });
20
+ const settled = Promise.resolve().then(work).then(() => true);
21
+ return Promise.race([settled, deadline]).then((inTime) => {
22
+ clearTimeout(timer);
23
+ if (!inTime) report("warn", `flush or shutdown did not finish within ${String(ms)} ms`);
24
+ return inTime;
25
+ });
26
+ }
27
+ function timeoutOf(timeoutMillis) {
28
+ if (timeoutMillis !== void 0 && timeoutMillis > 0) return Math.min(timeoutMillis, 2147483647);
29
+ const deadline = getState().invocationDeadline;
30
+ return deadline === void 0 ? DEFAULT_TIMEOUT_MILLIS : Math.max(1, Math.min(DEFAULT_TIMEOUT_MILLIS, deadline - Date.now()));
31
+ }
32
+ function sdkShutdown() {
33
+ const state = getState();
34
+ const sdk = state.sdk;
35
+ if (sdk === void 0) return Promise.resolve();
36
+ return state.shutdownPromise ??= (async () => {
37
+ await state.flushInFlight;
38
+ try {
39
+ await sdk.shutdown();
40
+ } catch (error) {
41
+ report("warn", "shutdown failed", error);
42
+ }
43
+ state.unsubscribeUndici?.();
44
+ })();
45
+ }
46
+ /**
47
+ * Records when the current AWS Lambda invocation must return, so flushes end in time.
48
+ *
49
+ * @param context - The invocation context; anything without `getRemainingTimeInMillis` is ignored.
50
+ */
51
+ function recordInvocationDeadline(context) {
52
+ const remaining = context?.getRemainingTimeInMillis?.();
53
+ if (typeof remaining === "number") getState().invocationDeadline = Date.now() + remaining - INVOCATION_MARGIN_MILLIS;
54
+ }
55
+ /**
56
+ * Exports the spans, metrics and log records the SDK has buffered, without stopping it.
57
+ *
58
+ * @remarks
59
+ * Calls `forceFlush` on the global tracer, meter and logger providers. Before `init`, or with
60
+ * `OTEL_SDK_DISABLED=true`, it resolves at once; after {@link shutdown} it waits for the shutdown.
61
+ * Failures and timeouts are reported through the OpenTelemetry `diag` logger. Inside a
62
+ * {@link withFlush} invocation the default bound also ends before the invocation's deadline.
63
+ *
64
+ * @param timeoutMillis - Upper bound in milliseconds, 5000 by default; zero, negative and NaN
65
+ * mean the default.
66
+ * @returns A promise that resolves when every provider has flushed or the bound has passed. It
67
+ * never rejects.
68
+ * @public
69
+ */
70
+ async function flush(timeoutMillis) {
71
+ const state = getState();
72
+ if (state.sdk === void 0) return;
73
+ const ms = timeoutOf(timeoutMillis);
74
+ const pending = state.shutdownPromise;
75
+ if (pending !== void 0) {
76
+ await bounded(() => pending, ms, false);
77
+ return;
78
+ }
79
+ const tracer = trace.getTracerProvider();
80
+ const providers = [
81
+ tracer.getDelegate?.() ?? tracer,
82
+ metrics.getMeterProvider(),
83
+ logs.getLoggerProvider()
84
+ ];
85
+ const work = Promise.all(providers.map(async (provider) => {
86
+ const p = provider;
87
+ if (typeof p.forceFlush !== "function") return;
88
+ try {
89
+ await p.forceFlush({ timeoutMillis: ms });
90
+ } catch (error) {
91
+ report("warn", "flush failed", error);
92
+ }
93
+ }));
94
+ state.flushInFlight = Promise.all([state.flushInFlight, work]);
95
+ await bounded(() => work, ms, false);
96
+ }
97
+ /**
98
+ * Flushes and stops the SDK started by `init`; telemetry recorded afterwards is dropped.
99
+ *
100
+ * @remarks
101
+ * Later calls, and the automatic shutdown on `beforeExit`, SIGTERM, SIGINT and uncaught
102
+ * exceptions, share the first call's work. Scripts that end with `process.exit()`, and app signal
103
+ * handlers that exit, should await it first, because `process.exit()` skips `beforeExit`.
104
+ *
105
+ * @param timeoutMillis - Upper bound in milliseconds, 5000 by default; zero, negative and NaN
106
+ * mean the default.
107
+ * @returns A promise that resolves when the SDK has shut down or the bound has passed. It never
108
+ * rejects.
109
+ * @example
110
+ * ```ts
111
+ * process.on('SIGTERM', async () => {
112
+ * await server.close();
113
+ * await shutdown();
114
+ * process.exit(0);
115
+ * });
116
+ * ```
117
+ * @public
118
+ */
119
+ async function shutdown(timeoutMillis) {
120
+ if (getState().sdk === void 0) return;
121
+ await bounded(sdkShutdown, timeoutOf(timeoutMillis), false);
122
+ }
123
+ /**
124
+ * Wraps an async serverless handler so buffered telemetry is exported before each invocation
125
+ * returns.
126
+ *
127
+ * @remarks
128
+ * Awaits the handler, then {@link flush} in a `finally` block, so telemetry is also exported when
129
+ * the handler throws. Flushes during the invocation end 500 ms before its deadline
130
+ * (`context.getRemainingTimeInMillis()`). On its first call it installs console capture again if
131
+ * the AWS Lambda runtime replaced the console methods after the preload ran. Callback-style
132
+ * handlers are not supported.
133
+ *
134
+ * @typeParam E - The invocation event type.
135
+ * @typeParam C - The runtime context type, for example the AWS Lambda `Context`.
136
+ * @typeParam R - The value the handler's promise resolves to.
137
+ * @param handler - The async handler to wrap.
138
+ * @returns A handler with the same parameters and result.
139
+ * @example
140
+ * With `NODE_OPTIONS=--import @clickhouse/clickstack-node/register` set on the function:
141
+ * ```ts
142
+ * import { withFlush } from '@clickhouse/clickstack-node';
143
+ *
144
+ * export const handler = withFlush(async (event: { name?: string }) => {
145
+ * console.log('greeting', event.name);
146
+ * return { statusCode: 200, body: 'ok' };
147
+ * });
148
+ * ```
149
+ * @public
150
+ */
151
+ function withFlush(handler) {
152
+ let firstCall = true;
153
+ return async function flushingHandler(event, context) {
154
+ const state = getState();
155
+ try {
156
+ recordInvocationDeadline(context);
157
+ if (firstCall) {
158
+ firstCall = false;
159
+ const current = console.log;
160
+ if (state.repatchConsole !== void 0 && current !== state.consoleLog) {
161
+ state.repatchConsole();
162
+ state.consoleLog = console.log;
163
+ }
164
+ }
165
+ } catch (error) {
166
+ report("warn", "could not prepare the invocation", error);
167
+ }
168
+ try {
169
+ return await handler(event, context);
170
+ } finally {
171
+ await flush();
172
+ }
173
+ };
174
+ }
175
+ function writeFatal(error) {
176
+ try {
177
+ const body = typeof error === "string" ? error : inspect(error, {
178
+ customInspect: false,
179
+ depth: 5
180
+ });
181
+ process.getBuiltinModule("node:fs").writeSync(2, `${body}\n\nNode.js ${process.version}\n`);
182
+ } catch {}
183
+ }
184
+ function signalExitListeners() {
185
+ const v4 = globalThis[Symbol.for("signal-exit emitter")];
186
+ const v3 = process.__signal_exit_emitter__;
187
+ return [v4?.count, v3?.count].reduce((sum, count) => sum + (typeof count === "number" ? count : 0), 0);
188
+ }
189
+ /**
190
+ * Installs the `beforeExit`, SIGTERM, SIGINT and `uncaughtException` handlers, once per thread.
191
+ * Signal and crash handlers go on the main thread only: workers never receive signals, and a
192
+ * worker crash listener would hide the parent's `error` event.
193
+ */
194
+ function installLifecycle() {
195
+ let terminating = false;
196
+ let terminatingSince = 0;
197
+ if (getStringFromEnv("AWS_LAMBDA_FUNCTION_NAME") === void 0) process.once("beforeExit", () => {
198
+ if (terminating) return;
199
+ bounded(sdkShutdown, DEFAULT_TIMEOUT_MILLIS, false).then((inTime) => {
200
+ if (!inTime) process.exit();
201
+ });
202
+ });
203
+ if (!isMainThread) return;
204
+ const onSignal = (signal) => {
205
+ if (terminating) {
206
+ if (Date.now() - terminatingSince > 1e3) process.exit(128 + constants.signals[signal]);
207
+ return;
208
+ }
209
+ if (process.listeners(signal).filter((listener) => listener !== onSignal).length > signalExitListeners()) {
210
+ process.removeListener(signal, onSignal);
211
+ flush();
212
+ return;
213
+ }
214
+ terminating = true;
215
+ terminatingSince = Date.now();
216
+ bounded(sdkShutdown, DEFAULT_TIMEOUT_MILLIS, true).then(() => {
217
+ for (const name of SIGNALS) process.removeListener(name, onSignal);
218
+ try {
219
+ process.kill(process.pid, signal);
220
+ } catch {}
221
+ process.exit(128 + constants.signals[signal]);
222
+ });
223
+ };
224
+ for (const signal of SIGNALS) process.prependListener(signal, onSignal);
225
+ const onUncaughtException = (error) => {
226
+ try {
227
+ const listeners = process.listeners("uncaughtException");
228
+ const isSentry = (listener) => "_errorHandler" in listener && listener._errorHandler === true;
229
+ if (listeners.some((listener) => listener !== onUncaughtException && listener.name !== "domainUncaughtExceptionClear" && !isSentry(listener))) return;
230
+ process.exitCode = 1;
231
+ if (terminating) return;
232
+ writeFatal(error);
233
+ terminating = true;
234
+ terminatingSince = Date.now();
235
+ const sentry = listeners.some(isSentry) ? new Promise((resolve) => setTimeout(resolve, SENTRY_SEND_MILLIS)) : void 0;
236
+ Promise.all([bounded(sdkShutdown, DEFAULT_TIMEOUT_MILLIS, true), sentry]).then(() => {
237
+ process.exit(1);
238
+ });
239
+ } catch {
240
+ process.exit(1);
241
+ }
242
+ };
243
+ process.prependListener("uncaughtException", onUncaughtException);
244
+ }
245
+ export { flush, installLifecycle, recordInvocationDeadline, shutdown, withFlush };
@@ -0,0 +1,12 @@
1
+ const require_init = require("./init.cjs");
2
+ // src/register.ts
3
+ /**
4
+ * Zero-code entry: starts the SDK from environment variables only, as `init()` without options.
5
+ * Worker threads are skipped; call `init()` inside a worker to instrument it.
6
+ *
7
+ * @example
8
+ * ```sh
9
+ * NODE_OPTIONS="--import @clickhouse/clickstack-node/register" node app.mjs
10
+ * ```
11
+ */
12
+ if (require("node:worker_threads").isMainThread) require_init.init();
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,14 @@
1
+ import { init } from "./init.mjs";
2
+ import { isMainThread } from "node:worker_threads";
3
+ // src/register.ts
4
+ /**
5
+ * Zero-code entry: starts the SDK from environment variables only, as `init()` without options.
6
+ * Worker threads are skipped; call `init()` inside a worker to instrument it.
7
+ *
8
+ * @example
9
+ * ```sh
10
+ * NODE_OPTIONS="--import @clickhouse/clickstack-node/register" node app.mjs
11
+ * ```
12
+ */
13
+ if (isMainThread) init();
14
+ export {};
@@ -0,0 +1,16 @@
1
+ // src/semconv.ts
2
+ /** `http.request.body.size`: semantic-conventions v1.43.0, development. */
3
+ const ATTR_HTTP_REQUEST_BODY_SIZE = "http.request.body.size";
4
+ /** `http.response.body.size`: semantic-conventions v1.43.0, development. */
5
+ const ATTR_HTTP_RESPONSE_BODY_SIZE = "http.response.body.size";
6
+ /**
7
+ * `http.request.body.content`: development. Added on semantic-conventions `main`
8
+ * by PR #3521 (commit bcd7e291081f, 2026-08-14); not in a release as of v1.44.0.
9
+ */
10
+ const ATTR_HTTP_REQUEST_BODY_CONTENT = "http.request.body.content";
11
+ /** `http.response.body.content`: same source as {@link ATTR_HTTP_REQUEST_BODY_CONTENT}. */
12
+ const ATTR_HTTP_RESPONSE_BODY_CONTENT = "http.response.body.content";
13
+ exports.ATTR_HTTP_REQUEST_BODY_CONTENT = ATTR_HTTP_REQUEST_BODY_CONTENT;
14
+ exports.ATTR_HTTP_REQUEST_BODY_SIZE = ATTR_HTTP_REQUEST_BODY_SIZE;
15
+ exports.ATTR_HTTP_RESPONSE_BODY_CONTENT = ATTR_HTTP_RESPONSE_BODY_CONTENT;
16
+ exports.ATTR_HTTP_RESPONSE_BODY_SIZE = ATTR_HTTP_RESPONSE_BODY_SIZE;