apitally 1.0.0-beta.3 → 1.0.0-beta.5

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.
Files changed (60) hide show
  1. package/README.md +120 -120
  2. package/dist/activation.cjs +2 -2
  3. package/dist/activation.cjs.map +1 -1
  4. package/dist/activation.js +2 -2
  5. package/dist/activation.js.map +1 -1
  6. package/dist/config.cjs +6 -3
  7. package/dist/config.cjs.map +1 -1
  8. package/dist/config.js +6 -3
  9. package/dist/config.js.map +1 -1
  10. package/dist/exportWorker.cjs +6 -5
  11. package/dist/exportWorker.cjs.map +1 -1
  12. package/dist/exportWorker.js +6 -5
  13. package/dist/exportWorker.js.map +1 -1
  14. package/dist/index.cjs +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/metrics.cjs +164 -35
  17. package/dist/metrics.cjs.map +1 -1
  18. package/dist/metrics.d.cts +4 -6
  19. package/dist/metrics.d.ts +4 -6
  20. package/dist/metrics.js +165 -36
  21. package/dist/metrics.js.map +1 -1
  22. package/dist/providers.cjs +10 -11
  23. package/dist/providers.cjs.map +1 -1
  24. package/dist/providers.d.cts +0 -2
  25. package/dist/providers.d.ts +0 -2
  26. package/dist/providers.js +11 -11
  27. package/dist/providers.js.map +1 -1
  28. package/dist/redaction.cjs +9 -5
  29. package/dist/redaction.cjs.map +1 -1
  30. package/dist/redaction.js +9 -5
  31. package/dist/redaction.js.map +1 -1
  32. package/dist/requestObservation.cjs +22 -17
  33. package/dist/requestObservation.cjs.map +1 -1
  34. package/dist/requestObservation.js +22 -17
  35. package/dist/requestObservation.js.map +1 -1
  36. package/dist/serverErrors.cjs +30 -28
  37. package/dist/serverErrors.cjs.map +1 -1
  38. package/dist/serverErrors.js +30 -28
  39. package/dist/serverErrors.js.map +1 -1
  40. package/dist/spanExporter.cjs +10 -7
  41. package/dist/spanExporter.cjs.map +1 -1
  42. package/dist/spanExporter.js +10 -7
  43. package/dist/spanExporter.js.map +1 -1
  44. package/dist/spanProcessor.cjs +9 -20
  45. package/dist/spanProcessor.cjs.map +1 -1
  46. package/dist/spanProcessor.d.cts +2 -1
  47. package/dist/spanProcessor.d.ts +2 -1
  48. package/dist/spanProcessor.js +8 -21
  49. package/dist/spanProcessor.js.map +1 -1
  50. package/dist/spool.cjs +5 -0
  51. package/dist/spool.cjs.map +1 -1
  52. package/dist/spool.d.cts +2 -1
  53. package/dist/spool.d.ts +2 -1
  54. package/dist/spool.js +5 -0
  55. package/dist/spool.js.map +1 -1
  56. package/dist/validationErrors.cjs +29 -26
  57. package/dist/validationErrors.cjs.map +1 -1
  58. package/dist/validationErrors.js +29 -26
  59. package/dist/validationErrors.js.map +1 -1
  60. package/package.json +3 -3
package/README.md CHANGED
@@ -33,7 +33,7 @@ The SDK is an [OpenTelemetry](https://opentelemetry.io) distribution and works a
33
33
  Learn more about Apitally on our 🌎 [website](https://apitally.io) or check out the 📚 [documentation](https://docs.apitally.io).
34
34
 
35
35
  > [!IMPORTANT]
36
- > **Upgrading from 0.x?** Version 1.0 is a full rewrite with a new setup API. See the [migration guide](MIGRATION.md) for a full 0.x to 1.x mapping.
36
+ > **Upgrading from 0.x?** Version 1.0 is a full rewrite with a new setup API. See the [migration guide](MIGRATION.md) for how to upgrade.
37
37
 
38
38
  ## Key features
39
39
 
@@ -62,13 +62,13 @@ The SDK supports **Node.js** `>= 20.6` and **Bun** `>= 1.2.0`.
62
62
 
63
63
  \* For Hono on Cloudflare Workers use our [Serverless SDK](https://github.com/apitally/apitally-js-serverless) instead.
64
64
 
65
- Apitally also supports many other web frameworks in [Python](https://github.com/apitally/apitally-py), [Go](https://github.com/apitally/apitally-go), [.NET](https://github.com/apitally/apitally-dotnet) and [Java](https://github.com/apitally/apitally-java) via our other SDKs.
65
+ Apitally also supports many other web frameworks in [Python](https://github.com/apitally/apitally-py), [Go](https://github.com/apitally/apitally-go) and [.NET](https://github.com/apitally/apitally-dotnet) via our other SDKs.
66
66
 
67
67
  ## Getting started
68
68
 
69
69
  If you don't have an Apitally account yet, first [sign up here](https://app.apitally.io/?signup). Then create an app in the Apitally dashboard. You'll see detailed setup instructions with code snippets you can copy and paste. These also include your write token.
70
70
 
71
- To install the SDK as a dependency in your project run:
71
+ Install the SDK in your project:
72
72
 
73
73
  ```bash
74
74
  npm install apitally
@@ -78,35 +78,32 @@ See the [SDK reference](https://docs.apitally.io/sdk-reference/javascript/v1/con
78
78
 
79
79
  ### AdonisJS
80
80
 
81
- Run the Ace add command from your application directory:
81
+ Run the Ace `add` command from your application directory:
82
82
 
83
83
  ```bash
84
84
  node ace add apitally
85
85
  ```
86
86
 
87
- The command installs and configures Apitally. It creates `config/apitally.ts`, adds the required environment declarations, registers the service provider and server middleware, and updates the conventional exception handler to capture validation and server errors.
88
-
89
- If needed, you can change settings in `config/apitally.ts`.
87
+ The command installs Apitally, asks for your write token and environment, and sets up the provider, middleware, and exception handling. To change settings later, edit `config/apitally.ts`.
90
88
 
91
89
  For further instructions, see our [setup guide for AdonisJS](https://docs.apitally.io/sdk-reference/javascript/v1/setup-guides/adonisjs).
92
90
 
93
91
  ### Elysia
94
92
 
95
- Register `apitallyPlugin()` immediately after creating the app, before routes or plugins that add routes:
93
+ Register `apitallyPlugin()` immediately after creating the app, before registering plugins and routes:
96
94
 
97
95
  ```javascript
98
96
  import { Elysia } from "elysia";
99
97
  import { apitallyPlugin } from "apitally/elysia";
100
98
 
101
- const app = new Elysia()
102
- .use(
103
- apitallyPlugin({
104
- writeToken: "your-write-token", // or set APITALLY_WRITE_TOKEN
105
- env: "dev", // optional, defaults to "dev"
106
- }),
107
- )
108
- // register plugins and routes below this point
109
- .get("/items/:id", ({ params }) => ({ id: params.id }));
99
+ const app = new Elysia().use(
100
+ apitallyPlugin({
101
+ writeToken: "your-write-token",
102
+ env: "dev",
103
+ }),
104
+ );
105
+
106
+ // register plugins and routes below this point
110
107
  ```
111
108
 
112
109
  For further instructions, see our [setup guide for Elysia](https://docs.apitally.io/sdk-reference/javascript/v1/setup-guides/elysia).
@@ -124,8 +121,8 @@ import { useApitally } from "apitally";
124
121
  const app = express();
125
122
 
126
123
  useApitally(app, {
127
- writeToken: "your-write-token", // or set APITALLY_WRITE_TOKEN
128
- env: "dev", // or "prod" etc.
124
+ writeToken: "your-write-token",
125
+ env: "dev",
129
126
  });
130
127
  ```
131
128
 
@@ -142,8 +139,8 @@ import { useApitally } from "apitally";
142
139
  const app = Fastify();
143
140
 
144
141
  useApitally(app, {
145
- writeToken: "your-write-token", // or set APITALLY_WRITE_TOKEN
146
- env: "dev", // or "prod" etc.
142
+ writeToken: "your-write-token",
143
+ env: "dev",
147
144
  });
148
145
 
149
146
  // register plugins and routes below this point
@@ -151,30 +148,9 @@ useApitally(app, {
151
148
 
152
149
  For further instructions, see our [setup guide for Fastify](https://docs.apitally.io/sdk-reference/javascript/v1/setup-guides/fastify).
153
150
 
154
- ### NestJS
155
-
156
- Call `useApitally(app)` immediately after creating the Nest application, before `app.init()` or `app.listen()`:
157
-
158
- ```javascript
159
- import { NestFactory } from "@nestjs/core";
160
- import { useApitally } from "apitally/nestjs";
161
- import { AppModule } from "./app.module.js";
162
-
163
- const app = await NestFactory.create(AppModule);
164
-
165
- useApitally(app, {
166
- writeToken: "your-write-token", // or set APITALLY_WRITE_TOKEN
167
- env: "dev", // or "prod" etc.
168
- });
169
-
170
- await app.listen(3000);
171
- ```
172
-
173
- For further instructions, see our [setup guide for NestJS](https://docs.apitally.io/sdk-reference/javascript/v1/setup-guides/nestjs).
174
-
175
151
  ### H3
176
152
 
177
- Add `apitallyPlugin()` when constructing the root H3 app:
153
+ Add `apitallyPlugin()` to the `plugins` of your root H3 app:
178
154
 
179
155
  ```javascript
180
156
  import { H3 } from "h3";
@@ -183,8 +159,8 @@ import { apitallyPlugin } from "apitally/h3";
183
159
  const app = new H3({
184
160
  plugins: [
185
161
  apitallyPlugin({
186
- writeToken: "your-write-token", // or set APITALLY_WRITE_TOKEN
187
- env: "dev", // or "prod" etc.
162
+ writeToken: "your-write-token",
163
+ env: "dev",
188
164
  }),
189
165
  ],
190
166
  });
@@ -194,7 +170,7 @@ For further instructions, see our [setup guide for H3](https://docs.apitally.io/
194
170
 
195
171
  ### Hapi
196
172
 
197
- Register `apitallyPlugin()` before calling `server.initialize()` or `server.start()`:
173
+ Register `apitallyPlugin()` immediately after creating the server, before registering other plugins and routes:
198
174
 
199
175
  ```javascript
200
176
  import Hapi from "@hapi/hapi";
@@ -204,12 +180,12 @@ const server = Hapi.server({ port: 3000 });
204
180
 
205
181
  await server.register(
206
182
  apitallyPlugin({
207
- writeToken: "your-write-token", // or set APITALLY_WRITE_TOKEN
208
- env: "dev", // or "prod" etc.
183
+ writeToken: "your-write-token",
184
+ env: "dev",
209
185
  }),
210
186
  );
211
187
 
212
- // register application plugins and routes below this point
188
+ // register other plugins and routes below this point
213
189
  await server.start();
214
190
  ```
215
191
 
@@ -226,8 +202,8 @@ import { useApitally } from "apitally";
226
202
  const app = new Hono();
227
203
 
228
204
  useApitally(app, {
229
- writeToken: "your-write-token", // or set APITALLY_WRITE_TOKEN
230
- env: "dev", // or "prod" etc.
205
+ writeToken: "your-write-token",
206
+ env: "dev",
231
207
  });
232
208
 
233
209
  // register middleware and routes below this point
@@ -240,14 +216,14 @@ For further instructions, see our [setup guide for Hono](https://docs.apitally.i
240
216
  Call `useApitally(app)` immediately after creating the app, before registering middleware and routes:
241
217
 
242
218
  ```javascript
243
- const Koa = require("koa");
244
- const { useApitally } = require("apitally");
219
+ import Koa from "koa";
220
+ import { useApitally } from "apitally";
245
221
 
246
222
  const app = new Koa();
247
223
 
248
224
  useApitally(app, {
249
- writeToken: "your-write-token", // or set APITALLY_WRITE_TOKEN
250
- env: "dev", // or "prod" etc.
225
+ writeToken: "your-write-token",
226
+ env: "dev",
251
227
  });
252
228
 
253
229
  // register middleware and routes below this point
@@ -255,11 +231,42 @@ useApitally(app, {
255
231
 
256
232
  For further instructions, see our [setup guide for Koa](https://docs.apitally.io/sdk-reference/javascript/v1/setup-guides/koa).
257
233
 
234
+ ### NestJS
235
+
236
+ Call `useApitally(app)` immediately after creating the app, before calling `app.init()` or `app.listen()`:
237
+
238
+ ```javascript
239
+ import { NestFactory } from "@nestjs/core";
240
+ import { useApitally } from "apitally/nestjs";
241
+ import { AppModule } from "./app.module.js";
242
+
243
+ const app = await NestFactory.create(AppModule);
244
+
245
+ useApitally(app, {
246
+ writeToken: "your-write-token",
247
+ env: "dev",
248
+ });
249
+
250
+ await app.listen(3000);
251
+ ```
252
+
253
+ For further instructions, see our [setup guide for NestJS](https://docs.apitally.io/sdk-reference/javascript/v1/setup-guides/nestjs).
254
+
258
255
  ## Configuration
259
256
 
260
- The write token and environment can also be provided via the `APITALLY_WRITE_TOKEN` and `APITALLY_ENV` environment variables instead of the `writeToken` and `env` options. The environment defaults to `dev`.
257
+ Pass configuration options to `useApitally()`, or to `apitallyPlugin()` for Elysia, H3, and Hapi. For AdonisJS, set them in `config/apitally.ts`. See the [SDK reference](https://docs.apitally.io/sdk-reference/javascript/v1/configuration) for all available options.
261
258
 
262
- By default, Apitally captures response headers but not request headers or request and response bodies. You can opt in with options:
259
+ ### Environment variables
260
+
261
+ To keep configuration out of your code, set these environment variables instead of passing the matching options:
262
+
263
+ - `APITALLY_WRITE_TOKEN`: the write token (`writeToken`)
264
+ - `APITALLY_ENV`: the environment name (`env`), defaults to `dev`
265
+ - `APITALLY_DISABLED`: set to `true` to disable Apitally (`disabled`)
266
+
267
+ ### Request and response data
268
+
269
+ Apitally captures response headers by default. To also capture request headers and request and response bodies, pass these options:
263
270
 
264
271
  ```javascript
265
272
  useApitally(app, {
@@ -271,112 +278,105 @@ useApitally(app, {
271
278
  });
272
279
  ```
273
280
 
274
- Sensitive values in query parameters, headers, and body fields are masked automatically based on built-in patterns, and you can add your own via the `maskQueryParams`, `maskHeaders`, and `maskBodyFields` options.
281
+ Apitally automatically masks common sensitive values, such as `password` fields and the `Authorization` header. To mask additional values, pass regular expressions to `maskQueryParams`, `maskHeaders`, and `maskBodyFields`. See our [documentation on masking](https://docs.apitally.io/sdk-reference/javascript/v1/masking) for details.
282
+
283
+ ### Exclusions and sampling
284
+
285
+ Apitally automatically excludes common health checks and static assets, such as `/healthz` and `/robots.txt`, from logs and traces. To exclude additional paths, pass regular expressions to `excludePaths`.
275
286
 
276
- On high-traffic applications you can capture logs and traces for only a fraction of requests by setting `sampleRate` (e.g. `0.1` for 10%), or decide per request with the `sampleOnRequest` and `sampleOnResponse` callbacks. Metrics always count every request, regardless of sampling.
287
+ To capture logs and traces for only a fraction of the remaining requests, set `sampleRate`, for example to `0.1` for 10%. To decide for each request individually, pass a callback to `sampleOnRequest` or `sampleOnResponse`.
277
288
 
278
- Application logs written via `console` and other supported loggers are captured and correlated with requests by default. Use `maskLogRecord` to transform or drop Apitally's captured copy, or opt out with `captureLogs: false`.
289
+ Metrics always include all requests. See our [documentation on sampling](https://docs.apitally.io/sdk-reference/javascript/v1/sampling) for details.
279
290
 
280
- See the [SDK reference](https://docs.apitally.io/sdk-reference/javascript/v1/configuration) for all configuration options.
291
+ ### Application logs
281
292
 
282
- ## Identifying consumers and more
293
+ Apitally captures logs written with these loggers and correlates them with the requests they belong to:
283
294
 
284
- The top-level `apitally` package provides functions you can call from anywhere in your request handling code:
295
+ - `console`
296
+ - Pino
297
+ - Winston
298
+ - NestJS `Logger`
299
+ - Hapi `request.log()`
300
+
301
+ To mask or drop individual log records, pass a callback to `maskLogRecord`. To turn off log capture, set `captureLogs: false`.
302
+
303
+ See our [documentation on masking](https://docs.apitally.io/sdk-reference/javascript/v1/masking#log-record-masking-callback) for details.
304
+
305
+ ## Adding context to requests
306
+
307
+ The following functions add context to the current request. Call them while handling a request, for example in a route handler, middleware, or authentication code.
308
+
309
+ To associate the request with the API consumer making it, call `setConsumer`. Only `identifier` is required. You can also pass it as a string, for example `setConsumer(user.identifier)`.
285
310
 
286
311
  ```javascript
287
- import { setConsumer, setRequestAttribute, captureException } from "apitally";
312
+ import { setConsumer } from "apitally";
288
313
 
289
- // Associate the current request with an API consumer, with custom attributes
290
314
  setConsumer({
291
315
  identifier: user.identifier,
292
316
  name: user.name,
293
317
  group: user.group,
294
318
  attributes: { plan: user.plan },
295
319
  });
296
-
297
- // Attach a custom attribute to the current request
298
- setRequestAttribute("tenant", tenantId);
299
-
300
- // Capture a handled exception for the current request
301
- captureException(error);
302
320
  ```
303
321
 
304
- `setConsumer()` also accepts an identifier string, for example `setConsumer(user.identifier)`.
305
-
306
- For further details, check out our [documentation](https://docs.apitally.io).
322
+ To attach custom data to the request's trace, call `setRequestAttribute`. Sampling and masking callbacks can also read these attributes.
307
323
 
308
- ## Existing OpenTelemetry setup
324
+ ```javascript
325
+ import { setRequestAttribute } from "apitally";
309
326
 
310
- If your app doesn't already use OpenTelemetry, you don't need to know it's there. The Apitally SDK configures OpenTelemetry automatically.
327
+ setRequestAttribute("tenant.id", tenantId);
328
+ ```
311
329
 
312
- If your app already registers its own tracer provider (e.g. via `NodeSDK`), Apitally does not replace it. Instead, you need to add the `ApitallySpanProcessor` to your provider's span processors:
330
+ To capture an exception that your code handles itself, call `captureException`. Unhandled exceptions are captured automatically.
313
331
 
314
332
  ```javascript
315
- import { NodeSDK } from "@opentelemetry/sdk-node";
316
- import { ApitallySpanProcessor } from "apitally";
333
+ import { captureException } from "apitally";
317
334
 
318
- const sdk = new NodeSDK({
319
- spanProcessors: [new ApitallySpanProcessor()],
320
- // ...your existing configuration
321
- });
335
+ try {
336
+ processOrder();
337
+ } catch (error) {
338
+ captureException(error);
339
+ }
322
340
  ```
323
341
 
324
- Your tracer provider's sampling settings also affect Apitally. Requests excluded by the sampler will not have request logs or traces in Apitally. Metrics still include all requests, regardless of sampling.
342
+ See our documentation on [custom attributes](https://docs.apitally.io/sdk-reference/javascript/v1/attributes#set-custom-attributes) and [handled exceptions](https://docs.apitally.io/sdk-reference/javascript/v1/tracing#capture-handled-exceptions) for details.
325
343
 
326
- ### Sentry
344
+ ## Existing OpenTelemetry setup
327
345
 
328
- Sentry's Node.js SDK configures OpenTelemetry automatically. To use Apitally alongside Sentry, add `ApitallySpanProcessor` to Sentry's configuration and call `Sentry.init()` before `useApitally()`:
346
+ If your app already registers its own tracer provider, for example with `NodeSDK`, add `ApitallySpanProcessor` to its span processors. Apitally then uses your tracer provider instead of registering its own, and keeps your existing exporters.
329
347
 
330
348
  ```javascript
331
- import * as Sentry from "@sentry/node";
349
+ import { NodeSDK } from "@opentelemetry/sdk-node";
332
350
  import { ApitallySpanProcessor } from "apitally";
333
351
 
334
- Sentry.init({
335
- dsn: "your-sentry-dsn",
336
- // Enable tracing
337
- tracesSampleRate: 1.0,
338
- // Add Apitally's span processor
339
- openTelemetrySpanProcessors: [new ApitallySpanProcessor()],
340
- // Optional: send only errors to Sentry
341
- beforeSendTransaction: () => null,
352
+ const sdk = new NodeSDK({
353
+ spanProcessors: [new ApitallySpanProcessor()],
354
+ // ...your existing configuration
342
355
  });
343
356
  ```
344
357
 
345
- Apitally uses the spans recorded by Sentry, so tracing must be enabled in Sentry. Requests excluded by Sentry's sampler will also be missing from Apitally's request logs and traces.
358
+ If you use versions 8 to 10 of the Sentry SDK, which register their own tracer provider, add `ApitallySpanProcessor` to the `openTelemetrySpanProcessors` option in `Sentry.init()` and enable Sentry tracing.
346
359
 
347
- Returning `null` from `beforeSendTransaction` prevents traces from being sent to Sentry without affecting Apitally's request logs and traces.
360
+ Your tracer provider's sampler also applies to Apitally. Requests it drops have no request logs or traces in Apitally, but still count in metrics.
348
361
 
349
- ### Elysia's OpenTelemetry plugin
362
+ ## Graceful shutdown
350
363
 
351
- When using Elysia's `@elysia/opentelemetry` plugin, register Apitally first so it adopts the OpenTelemetry SERVER span:
364
+ If your code calls `process.exit()`, await `shutdown()` before it so Apitally can send any remaining telemetry:
352
365
 
353
366
  ```javascript
354
- import { opentelemetry } from "@elysia/opentelemetry";
355
- import { Elysia } from "elysia";
356
- import { apitallyPlugin } from "apitally/elysia";
367
+ import { shutdown } from "apitally";
357
368
 
358
- const app = new Elysia()
359
- .use(apitallyPlugin(options))
360
- .use(opentelemetry());
369
+ process.on("SIGTERM", () => {
370
+ server.close(async () => {
371
+ await shutdown();
372
+ process.exit(0);
373
+ });
374
+ });
361
375
  ```
362
376
 
363
377
  ## Trusted proxies
364
378
 
365
- If your application runs behind a reverse proxy or load balancer, configure trusted proxies in your framework so Apitally can record the real client IP for GeoIP. Apitally uses the client IP reported by your framework. It does not read forwarding headers itself to determine the client IP.
366
-
367
- ## Graceful shutdown
368
-
369
- Apitally sends telemetry in batches and automatically attempts a final export during normal process shutdown.
370
-
371
- If your code calls `process.exit()`, await `shutdown()` first to give Apitally time to send buffered telemetry. Close your server and let any remaining requests finish before doing this:
372
-
373
- ```javascript
374
- import { shutdown } from "apitally";
375
-
376
- // After the server has closed and requests have finished:
377
- await shutdown();
378
- process.exit(0);
379
- ```
379
+ If your app runs behind a reverse proxy or load balancer, configure trusted proxies in your framework, for example with Express's `trust proxy` setting or Fastify's `trustProxy` option. Apitally records the client IP your framework reports and uses it for GeoIP.
380
380
 
381
381
  ## Getting help
382
382
 
@@ -6,12 +6,12 @@ const require_exportWorker = require("./exportWorker.cjs");
6
6
  const require_logCapture = require("./logCapture.cjs");
7
7
  const require_logRecordExporter = require("./logRecordExporter.cjs");
8
8
  const require_logRecordProcessor = require("./logRecordProcessor.cjs");
9
- const require_providers = require("./providers.cjs");
10
9
  const require_metrics = require("./metrics.cjs");
10
+ const require_spanProcessor = require("./spanProcessor.cjs");
11
+ const require_providers = require("./providers.cjs");
11
12
  const require_redaction = require("./redaction.cjs");
12
13
  const require_sentry = require("./sentry.cjs");
13
14
  const require_serverErrors = require("./serverErrors.cjs");
14
- const require_spanProcessor = require("./spanProcessor.cjs");
15
15
  const require_spanExporter = require("./spanExporter.cjs");
16
16
  const require_spool = require("./spool.cjs");
17
17
  const require_startup = require("./startup.cjs");
@@ -1 +1 @@
1
- {"version":3,"file":"activation.cjs","names":["setConfig","Spool","ExportWorker","VALIDATION_ERROR_EVENT_NAME","drainValidationErrors","SERVER_ERROR_EVENT_NAME","drainServerErrors","ROOT_CONTEXT","getDistroVersion","getConfig","isApitallySpanProcessorDeclared","resolveEnvAndCreateResource","ApitallySpanExporter","Redaction","BatchSpanProcessor","SpanPipeline","setupTracerProvider","BatchLogRecordProcessor","ApitallyLogRecordExporter","ApitallyLogRecordProcessor","createLoggerProvider","MetricsPipeline","UndiciInstrumentation","isMainThread"],"sources":["../src/activation.ts"],"sourcesContent":["import { isMainThread } from \"node:worker_threads\";\nimport { ROOT_CONTEXT } from \"@opentelemetry/api\";\nimport type { AnyValueMap } from \"@opentelemetry/api-logs\";\nimport { UndiciInstrumentation } from \"@opentelemetry/instrumentation-undici\";\nimport { BatchLogRecordProcessor, type LoggerProvider } from \"@opentelemetry/sdk-logs\";\nimport { BatchSpanProcessor } from \"@opentelemetry/sdk-trace-base\";\nimport type { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport { type ApitallyConfig, type ApitallyOptions, getConfig, setConfig } from \"./config.js\";\nimport { ExportWorker, type ExportWorkerOptions } from \"./exportWorker.js\";\nimport {\n installConsoleCapture,\n installNestLoggerCapture,\n installPinoCapture,\n installWinstonCapture,\n} from \"./logCapture.js\";\nimport { logDebug, logError, logWarning } from \"./logger.js\";\nimport { ApitallyLogRecordExporter } from \"./logRecordExporter.js\";\nimport { ApitallyLogRecordProcessor } from \"./logRecordProcessor.js\";\nimport { MetricsPipeline } from \"./metrics.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\nimport {\n createLoggerProvider,\n resolveEnvAndCreateResource,\n setupTracerProvider,\n} from \"./providers.js\";\nimport { Redaction } from \"./redaction.js\";\nimport { installSentryEventIdRecording } from \"./sentry.js\";\nimport { drainServerErrors, resetServerErrors, SERVER_ERROR_EVENT_NAME } from \"./serverErrors.js\";\nimport { ApitallySpanExporter } from \"./spanExporter.js\";\nimport {\n isApitallySpanProcessorDeclared,\n SpanPipeline,\n setActiveSpanPipeline,\n setServerSpanActivationCallback,\n} from \"./spanProcessor.js\";\nimport { Spool } from \"./spool.js\";\nimport { emitStartupEvent, type StartupEventInfo } from \"./startup.js\";\nimport {\n drainValidationErrors,\n resetValidationErrors,\n VALIDATION_ERROR_EVENT_NAME,\n} from \"./validationErrors.js\";\n\n// The worker sends every 15 seconds, so batches only need to reach the spool\n// within that interval.\nconst BATCH_SCHEDULE_DELAY_MILLIS = 1_000;\nconst BATCH_EXPORT_TIMEOUT_MILLIS = 30_000;\nconst BATCH_MAX_QUEUE_SIZE = 2_048;\nconst BATCH_MAX_EXPORT_BATCH_SIZE = 512;\nconst BATCH_MAX_EXPORT_BATCH_SIZE_WITH_BODY_CAPTURE = 32;\nconst SIGNAL_FLUSH_TIMEOUT_MILLIS = 5_000;\n\ntype TerminationSignal = \"SIGTERM\" | \"SIGINT\";\ntype SignalListeners = Record<TerminationSignal, () => void>;\n\n// The ESM and CJS builds can load together, so a Symbol.for key gives both\n// copies the same activation state.\nconst ACTIVATION_STATE_KEY = Symbol.for(\"apitally.activation\");\n\ninterface ActivationState {\n sdkVersion: string;\n activationAttempted: boolean;\n startupEventInfo?: StartupEventInfo;\n handles?: ActivationHandles;\n runShutdown?: () => Promise<void>;\n shutdownPromise?: Promise<void>;\n beforeExitListener?: () => void;\n signalListeners?: SignalListeners;\n signalDrainPromise?: Promise<void>;\n}\n\ninterface ActivationHandles {\n spanPipeline: SpanPipeline;\n tracerProvider?: NodeTracerProvider;\n loggerProvider: LoggerProvider;\n // Consumer identifier to the hash of its last emitted consumer update, in LRU order.\n consumerUpdateHashes: Map<string, string>;\n metricsPipeline: MetricsPipeline;\n spool: Spool;\n worker: ExportWorker;\n undiciInstrumentation?: UndiciInstrumentation;\n}\n\n// Configuration stays synchronous; activate() defers timers and I/O.\nexport function configure(options: ApitallyOptions = {}): ApitallyConfig {\n const config = setConfig(options);\n // User instrumentations read this at initialization. `http/dup` adds stable\n // HTTP attributes alongside legacy ones; a user-set value takes precedence.\n if (!config.disabled && process.env.OTEL_SEMCONV_STABILITY_OPT_IN === undefined) {\n process.env.OTEL_SEMCONV_STABILITY_OPT_IN = \"http/dup\";\n }\n setServerSpanActivationCallback(activate);\n return config;\n}\n\n// The first integration registration wins so one process emits one startup event.\nexport function registerStartupEventInfo(info: StartupEventInfo): void {\n const activationState = getActivationState();\n activationState.startupEventInfo ??= info;\n}\n\n// Activation is synchronous and attempted once, so concurrent first requests\n// observe either no activation or completed activation.\nexport function activate(): void {\n const activationState = getActivationState();\n if (activationState.activationAttempted) {\n return;\n }\n activationState.activationAttempted = true;\n if (shouldSkipActivation()) {\n return;\n }\n try {\n const handles = startPipelines(activationState.startupEventInfo);\n activationState.handles = handles;\n activationState.runShutdown = () => drainAndStop(handles);\n installBeforeExitHook(activationState);\n installSignalHooks(activationState);\n } catch (error) {\n logError(`Apitally activation failed: ${String(error)}`);\n }\n}\n\nexport function isActivated(): boolean {\n return getActivationState().handles !== undefined;\n}\n\nexport function getActivationHandles(): ActivationHandles | undefined {\n return getActivationState().handles;\n}\n\nexport async function flushTelemetry(): Promise<void> {\n try {\n await getActivationState().handles?.worker.runCycle();\n } catch (error) {\n logDebug(`Error flushing telemetry: ${String(error)}`);\n }\n}\n\n// Concurrent calls and lifecycle hooks share one final drain; calls before\n// activation are no-ops.\nexport function shutdown(): Promise<void> {\n const activationState = getActivationState();\n const handles = activationState.handles;\n const runShutdown = activationState.runShutdown;\n if (!handles || !runShutdown) {\n return Promise.resolve();\n }\n if (!activationState.shutdownPromise) {\n removeLifecycleHooks(activationState);\n const signalDrainPromise = activationState.signalDrainPromise;\n activationState.shutdownPromise = (\n signalDrainPromise\n ? signalDrainPromise.then(() => handles.worker.waitForIdle())\n : Promise.resolve()\n ).then(runShutdown);\n }\n return activationState.shutdownPromise;\n}\n\n// Tests replace these factories to isolate spool files and worker timing.\nexport const activationFactories = {\n createSpool: (): Spool => new Spool(),\n createExportWorker: (options: ExportWorkerOptions): ExportWorker => new ExportWorker(options),\n};\nconst defaultFactories = { ...activationFactories };\n\n// Tests reset process-global activation and its side effects between cases.\nexport async function resetActivation(): Promise<void> {\n Object.assign(activationFactories, defaultFactories);\n const holder = globalThis as Record<symbol, ActivationState | undefined>;\n const activationState = holder[ACTIVATION_STATE_KEY];\n delete holder[ACTIVATION_STATE_KEY];\n hasWarnedAboutVersionSkew = false;\n resetValidationErrors();\n resetServerErrors();\n if (!activationState) {\n return;\n }\n removeLifecycleHooks(activationState);\n const handles = activationState.handles;\n if (!handles) {\n return;\n }\n handles.undiciInstrumentation?.disable();\n await handles.worker.stop();\n await handles.spanPipeline.shutdown();\n await handles.loggerProvider.shutdown();\n await handles.spool.clear();\n}\n\nlet hasWarnedAboutVersionSkew = false;\n\n// One root-context record per aggregate group, drained right before the log\n// processor flush so the records leave with the same export cycle.\nfunction emitErrorEvents(loggerProvider: LoggerProvider): void {\n const logger = loggerProvider.getLogger(\"apitally\");\n const events: [string, AnyValueMap[]][] = [\n [VALIDATION_ERROR_EVENT_NAME, drainValidationErrors()],\n [SERVER_ERROR_EVENT_NAME, drainServerErrors()],\n ];\n for (const [eventName, bodies] of events) {\n for (const body of bodies) {\n logger.emit({ timestamp: Date.now(), context: ROOT_CONTEXT, eventName, body });\n }\n }\n}\n\nfunction getActivationState(): ActivationState {\n const holder = globalThis as Record<symbol, ActivationState | undefined>;\n const existing = holder[ACTIVATION_STATE_KEY];\n if (existing) {\n if (!hasWarnedAboutVersionSkew && existing.sdkVersion !== getDistroVersion()) {\n hasWarnedAboutVersionSkew = true;\n logWarning(\n `Two copies of the apitally package with different versions (${existing.sdkVersion} and ${getDistroVersion()}) are loaded in this process; version ${existing.sdkVersion} stays active. To resolve this, deduplicate the apitally installation.`,\n );\n }\n return existing;\n }\n const activationState: ActivationState = {\n sdkVersion: getDistroVersion(),\n activationAttempted: false,\n };\n holder[ACTIVATION_STATE_KEY] = activationState;\n return activationState;\n}\n\nfunction shouldSkipActivation(): boolean {\n return (\n Boolean(process.env.JEST_WORKER_ID) ||\n Boolean(process.env.VITEST) ||\n process.env.NODE_ENV === \"test\" ||\n getConfig().disabled\n );\n}\n\nfunction startPipelines(startupEventInfo: StartupEventInfo | undefined): ActivationHandles {\n const config = getConfig();\n let hasUserProvider = isApitallySpanProcessorDeclared();\n const { env, resource } = resolveEnvAndCreateResource();\n const instanceId = resource.attributes[\"service.instance.id\"] as string;\n const spool = activationFactories.createSpool();\n const spanExporter = new ApitallySpanExporter({\n redaction: new Redaction(),\n env,\n instanceId,\n spool,\n maskRequestBody: config.maskRequestBody,\n maskResponseBody: config.maskResponseBody,\n });\n // Concrete options prevent OTel environment variables from changing batch\n // behavior. Each processor gets a fresh object because its config is mutated.\n const batchSpanProcessor = new BatchSpanProcessor(spanExporter, {\n scheduledDelayMillis: BATCH_SCHEDULE_DELAY_MILLIS,\n exportTimeoutMillis: BATCH_EXPORT_TIMEOUT_MILLIS,\n maxQueueSize: BATCH_MAX_QUEUE_SIZE,\n maxExportBatchSize:\n config.captureRequestBody || config.captureResponseBody\n ? BATCH_MAX_EXPORT_BATCH_SIZE_WITH_BODY_CAPTURE\n : BATCH_MAX_EXPORT_BATCH_SIZE,\n });\n const spanPipeline = new SpanPipeline(batchSpanProcessor, () => spanExporter.forceFlush());\n let tracerProvider: NodeTracerProvider | undefined;\n if (!hasUserProvider) {\n tracerProvider = setupTracerProvider(resource, [spanPipeline]);\n if (!tracerProvider) {\n hasUserProvider = true;\n logWarning(\n \"Apitally could not register its OpenTelemetry tracer provider because another provider is already registered. Only metrics and the startup event are sent to Apitally until you add ApitallySpanProcessor (exported by the apitally package) to your tracer provider's spanProcessors constructor option or the NodeSDK spanProcessors option.\",\n );\n }\n }\n setActiveSpanPipeline(spanPipeline);\n // BatchLogRecordProcessor requires its single-options-object form; positional\n // arguments create a broken processor.\n const batchLogProcessor = new BatchLogRecordProcessor({\n exporter: new ApitallyLogRecordExporter(spool),\n scheduledDelayMillis: BATCH_SCHEDULE_DELAY_MILLIS,\n exportTimeoutMillis: BATCH_EXPORT_TIMEOUT_MILLIS,\n maxQueueSize: BATCH_MAX_QUEUE_SIZE,\n maxExportBatchSize: BATCH_MAX_EXPORT_BATCH_SIZE,\n });\n const logRecordProcessor = new ApitallyLogRecordProcessor(\n batchLogProcessor,\n spanPipeline,\n config.maskLogRecord,\n );\n const loggerProvider = createLoggerProvider(resource, [logRecordProcessor]);\n const metricsPipeline = new MetricsPipeline(resource, spool);\n spanPipeline.metricsRecorder = (record) => metricsPipeline.recordFromRequest(record);\n const worker = activationFactories.createExportWorker({\n spool,\n otlpEndpoint: config.otlpEndpoint,\n writeToken: config.writeToken,\n env,\n });\n worker.flushCallbacks.push(\n () => metricsPipeline.collectAndExport(),\n () => spanPipeline.forceFlush(),\n () => emitErrorEvents(loggerProvider),\n () => batchLogProcessor.forceFlush(),\n );\n if (config.captureLogs) {\n installConsoleCapture(loggerProvider);\n installNestLoggerCapture(loggerProvider);\n installWinstonCapture(loggerProvider);\n installPinoCapture(loggerProvider);\n }\n installSentryEventIdRecording();\n let undiciInstrumentation: UndiciInstrumentation | undefined;\n if (!hasUserProvider) {\n undiciInstrumentation = createUndiciInstrumentation(config.otlpEndpoint);\n }\n worker.start();\n if (startupEventInfo) {\n emitStartupEvent(loggerProvider, startupEventInfo);\n }\n return {\n spanPipeline,\n tracerProvider,\n loggerProvider,\n consumerUpdateHashes: new Map(),\n metricsPipeline,\n spool,\n worker,\n undiciInstrumentation,\n };\n}\n\n// ignoreRequestHook prevents telemetry for export POSTs. The request origin can\n// be a string or URL at runtime despite its string type.\nfunction createUndiciInstrumentation(otlpEndpoint: string): UndiciInstrumentation {\n const endpointOrigin = new URL(otlpEndpoint).origin;\n return new UndiciInstrumentation({\n ignoreRequestHook: (request) => {\n try {\n return new URL(String(request.origin)).origin === endpointOrigin;\n } catch {\n return false;\n }\n },\n });\n}\n\nasync function drainAndStop(handles: ActivationHandles): Promise<void> {\n try {\n await handles.spanPipeline.shutdown();\n await handles.worker.finalDrain();\n await handles.loggerProvider.shutdown();\n await handles.worker.stop();\n } catch (error) {\n logWarning(`Error draining telemetry on shutdown: ${String(error)}`);\n }\n}\n\nfunction installSignalHooks(activationState: ActivationState): void {\n if (process.platform === \"win32\" || !isMainThread) {\n return;\n }\n let listeners: SignalListeners;\n listeners = {\n SIGTERM: () => handleTerminationSignal(activationState, listeners, \"SIGTERM\"),\n SIGINT: () => handleTerminationSignal(activationState, listeners, \"SIGINT\"),\n };\n activationState.signalListeners = listeners;\n try {\n process.prependOnceListener(\"SIGTERM\", listeners.SIGTERM);\n process.prependOnceListener(\"SIGINT\", listeners.SIGINT);\n } catch (error) {\n removeSignalHooks(activationState, listeners);\n logWarning(`Unable to install Apitally signal handlers: ${String(error)}`);\n }\n}\n\nfunction handleTerminationSignal(\n activationState: ActivationState,\n listeners: SignalListeners,\n signal: TerminationSignal,\n): void {\n const hasApplicationListener = process\n .listeners(signal)\n .some((listener) => listener !== listeners[signal]);\n removeSignalHooks(activationState, listeners);\n const drainPromise = flushAfterTerminationSignal(activationState, signal, hasApplicationListener);\n activationState.signalDrainPromise = drainPromise;\n void drainPromise.catch((error: unknown) => {\n logDebug(`Error flushing telemetry on ${signal}: ${String(error)}`);\n });\n}\n\nasync function flushAfterTerminationSignal(\n activationState: ActivationState,\n signal: TerminationSignal,\n hasApplicationListener: boolean,\n): Promise<void> {\n const handles = activationState.handles;\n if (!handles) {\n return;\n }\n try {\n if (activationState.shutdownPromise) {\n await waitForSignalDeadline(activationState.shutdownPromise);\n } else {\n await handles.worker.finalDrain(SIGNAL_FLUSH_TIMEOUT_MILLIS);\n }\n } catch (error) {\n logDebug(`Error flushing telemetry on ${signal}: ${String(error)}`);\n }\n if (hasApplicationListener || process.listeners(signal).length > 0) {\n return;\n }\n const exitCode = signal === \"SIGTERM\" ? 143 : 130;\n if (process.platform === \"linux\" && process.pid === 1) {\n process.exit(exitCode);\n }\n try {\n process.kill(process.pid, signal);\n } catch {\n process.exit(exitCode);\n }\n}\n\nasync function waitForSignalDeadline(promise: Promise<void>): Promise<void> {\n let deadlineTimer: NodeJS.Timeout | undefined;\n const deadlineReached = new Promise<void>((resolve) => {\n deadlineTimer = setTimeout(resolve, SIGNAL_FLUSH_TIMEOUT_MILLIS);\n });\n try {\n await Promise.race([promise, deadlineReached]);\n } finally {\n if (deadlineTimer) {\n clearTimeout(deadlineTimer);\n }\n }\n}\n\nfunction removeSignalHooks(\n activationState: ActivationState,\n listeners = activationState.signalListeners,\n): void {\n if (!listeners) {\n return;\n }\n process.removeListener(\"SIGTERM\", listeners.SIGTERM);\n process.removeListener(\"SIGINT\", listeners.SIGINT);\n if (activationState.signalListeners === listeners) {\n activationState.signalListeners = undefined;\n }\n}\n\nfunction removeLifecycleHooks(activationState: ActivationState): void {\n removeSignalHooks(activationState);\n if (activationState.beforeExitListener) {\n process.removeListener(\"beforeExit\", activationState.beforeExitListener);\n activationState.beforeExitListener = undefined;\n }\n}\n\nfunction installBeforeExitHook(activationState: ActivationState): void {\n const listener = () => {\n // The drain keeps the event loop alive until shutdown settles.\n void shutdown().catch((error: unknown) => {\n logDebug(`Error draining telemetry on shutdown: ${String(error)}`);\n });\n };\n activationState.beforeExitListener = listener;\n process.on(\"beforeExit\", listener);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"activation.cjs","names":["setConfig","Spool","ExportWorker","VALIDATION_ERROR_EVENT_NAME","drainValidationErrors","SERVER_ERROR_EVENT_NAME","drainServerErrors","ROOT_CONTEXT","getDistroVersion","getConfig","isApitallySpanProcessorDeclared","resolveEnvAndCreateResource","ApitallySpanExporter","Redaction","BatchSpanProcessor","SpanPipeline","setupTracerProvider","BatchLogRecordProcessor","ApitallyLogRecordExporter","ApitallyLogRecordProcessor","createLoggerProvider","MetricsPipeline","UndiciInstrumentation","isMainThread"],"sources":["../src/activation.ts"],"sourcesContent":["import { isMainThread } from \"node:worker_threads\";\nimport { ROOT_CONTEXT } from \"@opentelemetry/api\";\nimport type { AnyValueMap } from \"@opentelemetry/api-logs\";\nimport { UndiciInstrumentation } from \"@opentelemetry/instrumentation-undici\";\nimport { BatchLogRecordProcessor, type LoggerProvider } from \"@opentelemetry/sdk-logs\";\nimport { BatchSpanProcessor } from \"@opentelemetry/sdk-trace-base\";\nimport type { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport { type ApitallyConfig, type ApitallyOptions, getConfig, setConfig } from \"./config.js\";\nimport { ExportWorker, type ExportWorkerOptions } from \"./exportWorker.js\";\nimport {\n installConsoleCapture,\n installNestLoggerCapture,\n installPinoCapture,\n installWinstonCapture,\n} from \"./logCapture.js\";\nimport { logDebug, logError, logWarning } from \"./logger.js\";\nimport { ApitallyLogRecordExporter } from \"./logRecordExporter.js\";\nimport { ApitallyLogRecordProcessor } from \"./logRecordProcessor.js\";\nimport { MetricsPipeline } from \"./metrics.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\nimport {\n createLoggerProvider,\n resolveEnvAndCreateResource,\n setupTracerProvider,\n} from \"./providers.js\";\nimport { Redaction } from \"./redaction.js\";\nimport { installSentryEventIdRecording } from \"./sentry.js\";\nimport { drainServerErrors, resetServerErrors, SERVER_ERROR_EVENT_NAME } from \"./serverErrors.js\";\nimport { ApitallySpanExporter } from \"./spanExporter.js\";\nimport {\n isApitallySpanProcessorDeclared,\n SpanPipeline,\n setActiveSpanPipeline,\n setServerSpanActivationCallback,\n} from \"./spanProcessor.js\";\nimport { Spool } from \"./spool.js\";\nimport { emitStartupEvent, type StartupEventInfo } from \"./startup.js\";\nimport {\n drainValidationErrors,\n resetValidationErrors,\n VALIDATION_ERROR_EVENT_NAME,\n} from \"./validationErrors.js\";\n\n// The worker sends every 15 seconds, so batches only need to reach the spool\n// within that interval.\nconst BATCH_SCHEDULE_DELAY_MILLIS = 1_000;\nconst BATCH_EXPORT_TIMEOUT_MILLIS = 30_000;\nconst BATCH_MAX_QUEUE_SIZE = 2_048;\nconst BATCH_MAX_EXPORT_BATCH_SIZE = 512;\nconst BATCH_MAX_EXPORT_BATCH_SIZE_WITH_BODY_CAPTURE = 32;\nconst SIGNAL_FLUSH_TIMEOUT_MILLIS = 5_000;\n\ntype TerminationSignal = \"SIGTERM\" | \"SIGINT\";\ntype SignalListeners = Record<TerminationSignal, () => void>;\n\n// The ESM and CJS builds can load together, so a Symbol.for key gives both\n// copies the same activation state.\nconst ACTIVATION_STATE_KEY = Symbol.for(\"apitally.activation\");\n\ninterface ActivationState {\n sdkVersion: string;\n activationAttempted: boolean;\n startupEventInfo?: StartupEventInfo;\n handles?: ActivationHandles;\n runShutdown?: () => Promise<void>;\n shutdownPromise?: Promise<void>;\n beforeExitListener?: () => void;\n signalListeners?: SignalListeners;\n signalDrainPromise?: Promise<void>;\n}\n\ninterface ActivationHandles {\n spanPipeline: SpanPipeline;\n tracerProvider?: NodeTracerProvider;\n loggerProvider: LoggerProvider;\n // Consumer identifier to the hash of its last emitted consumer update, in LRU order.\n consumerUpdateHashes: Map<string, string>;\n metricsPipeline: MetricsPipeline;\n spool: Spool;\n worker: ExportWorker;\n undiciInstrumentation?: UndiciInstrumentation;\n}\n\n// Configuration stays synchronous; activate() defers timers and I/O.\nexport function configure(options: ApitallyOptions = {}): ApitallyConfig {\n const config = setConfig(options);\n // User instrumentations read this at initialization. `http/dup` adds stable\n // HTTP attributes alongside legacy ones; a user-set value takes precedence.\n if (!config.disabled && process.env.OTEL_SEMCONV_STABILITY_OPT_IN === undefined) {\n process.env.OTEL_SEMCONV_STABILITY_OPT_IN = \"http/dup\";\n }\n setServerSpanActivationCallback(activate);\n return config;\n}\n\n// The first integration registration wins so one process emits one startup event.\nexport function registerStartupEventInfo(info: StartupEventInfo): void {\n const activationState = getActivationState();\n activationState.startupEventInfo ??= info;\n}\n\n// Activation is synchronous and attempted once, so concurrent first requests\n// observe either no activation or completed activation.\nexport function activate(): void {\n const activationState = getActivationState();\n if (activationState.activationAttempted) {\n return;\n }\n activationState.activationAttempted = true;\n if (shouldSkipActivation()) {\n return;\n }\n try {\n const handles = startPipelines(activationState.startupEventInfo);\n activationState.handles = handles;\n activationState.runShutdown = () => drainAndStop(handles);\n installBeforeExitHook(activationState);\n installSignalHooks(activationState);\n } catch (error) {\n logError(`Apitally activation failed: ${String(error)}`);\n }\n}\n\nexport function isActivated(): boolean {\n return getActivationState().handles !== undefined;\n}\n\nexport function getActivationHandles(): ActivationHandles | undefined {\n return getActivationState().handles;\n}\n\nexport async function flushTelemetry(): Promise<void> {\n try {\n await getActivationState().handles?.worker.runCycle();\n } catch (error) {\n logDebug(`Error flushing telemetry: ${String(error)}`);\n }\n}\n\n// Concurrent calls and lifecycle hooks share one final drain; calls before\n// activation are no-ops.\nexport function shutdown(): Promise<void> {\n const activationState = getActivationState();\n const handles = activationState.handles;\n const runShutdown = activationState.runShutdown;\n if (!handles || !runShutdown) {\n return Promise.resolve();\n }\n if (!activationState.shutdownPromise) {\n removeLifecycleHooks(activationState);\n const signalDrainPromise = activationState.signalDrainPromise;\n activationState.shutdownPromise = (\n signalDrainPromise\n ? signalDrainPromise.then(() => handles.worker.waitForIdle())\n : Promise.resolve()\n ).then(runShutdown);\n }\n return activationState.shutdownPromise;\n}\n\n// Tests replace these factories to isolate spool files and worker timing.\nexport const activationFactories = {\n createSpool: (): Spool => new Spool(),\n createExportWorker: (options: ExportWorkerOptions): ExportWorker => new ExportWorker(options),\n};\nconst defaultFactories = { ...activationFactories };\n\n// Tests reset process-global activation and its side effects between cases.\nexport async function resetActivation(): Promise<void> {\n Object.assign(activationFactories, defaultFactories);\n const holder = globalThis as Record<symbol, ActivationState | undefined>;\n const activationState = holder[ACTIVATION_STATE_KEY];\n delete holder[ACTIVATION_STATE_KEY];\n hasWarnedAboutVersionSkew = false;\n resetValidationErrors();\n resetServerErrors();\n if (!activationState) {\n return;\n }\n removeLifecycleHooks(activationState);\n const handles = activationState.handles;\n if (!handles) {\n return;\n }\n handles.undiciInstrumentation?.disable();\n await handles.worker.stop();\n await handles.spanPipeline.shutdown();\n await handles.loggerProvider.shutdown();\n await handles.spool.clear();\n}\n\nlet hasWarnedAboutVersionSkew = false;\n\n// One root-context record per aggregated error, drained right before the log\n// processor flush so the records leave with the same export cycle.\nfunction emitErrorEvents(loggerProvider: LoggerProvider): void {\n const logger = loggerProvider.getLogger(\"apitally\");\n const events: [string, AnyValueMap[]][] = [\n [VALIDATION_ERROR_EVENT_NAME, drainValidationErrors()],\n [SERVER_ERROR_EVENT_NAME, drainServerErrors()],\n ];\n for (const [eventName, bodies] of events) {\n for (const body of bodies) {\n logger.emit({ timestamp: Date.now(), context: ROOT_CONTEXT, eventName, body });\n }\n }\n}\n\nfunction getActivationState(): ActivationState {\n const holder = globalThis as Record<symbol, ActivationState | undefined>;\n const existing = holder[ACTIVATION_STATE_KEY];\n if (existing) {\n if (!hasWarnedAboutVersionSkew && existing.sdkVersion !== getDistroVersion()) {\n hasWarnedAboutVersionSkew = true;\n logWarning(\n `Two copies of the apitally package with different versions (${existing.sdkVersion} and ${getDistroVersion()}) are loaded in this process; version ${existing.sdkVersion} stays active. To resolve this, deduplicate the apitally installation.`,\n );\n }\n return existing;\n }\n const activationState: ActivationState = {\n sdkVersion: getDistroVersion(),\n activationAttempted: false,\n };\n holder[ACTIVATION_STATE_KEY] = activationState;\n return activationState;\n}\n\nfunction shouldSkipActivation(): boolean {\n return (\n Boolean(process.env.JEST_WORKER_ID) ||\n Boolean(process.env.VITEST) ||\n process.env.NODE_ENV === \"test\" ||\n getConfig().disabled\n );\n}\n\nfunction startPipelines(startupEventInfo: StartupEventInfo | undefined): ActivationHandles {\n const config = getConfig();\n let hasUserProvider = isApitallySpanProcessorDeclared();\n const { env, resource } = resolveEnvAndCreateResource();\n const instanceId = resource.attributes[\"service.instance.id\"] as string;\n const spool = activationFactories.createSpool();\n const spanExporter = new ApitallySpanExporter({\n redaction: new Redaction(),\n env,\n instanceId,\n spool,\n maskRequestBody: config.maskRequestBody,\n maskResponseBody: config.maskResponseBody,\n });\n // Concrete options prevent OTel environment variables from changing batch\n // behavior. Each processor gets a fresh object because its config is mutated.\n const batchSpanProcessor = new BatchSpanProcessor(spanExporter, {\n scheduledDelayMillis: BATCH_SCHEDULE_DELAY_MILLIS,\n exportTimeoutMillis: BATCH_EXPORT_TIMEOUT_MILLIS,\n maxQueueSize: BATCH_MAX_QUEUE_SIZE,\n maxExportBatchSize:\n config.captureRequestBody || config.captureResponseBody\n ? BATCH_MAX_EXPORT_BATCH_SIZE_WITH_BODY_CAPTURE\n : BATCH_MAX_EXPORT_BATCH_SIZE,\n });\n const spanPipeline = new SpanPipeline(batchSpanProcessor, () => spanExporter.forceFlush());\n let tracerProvider: NodeTracerProvider | undefined;\n if (!hasUserProvider) {\n tracerProvider = setupTracerProvider(resource, [spanPipeline]);\n if (!tracerProvider) {\n hasUserProvider = true;\n logWarning(\n \"Apitally could not register its OpenTelemetry tracer provider because another provider is already registered. Only metrics and the startup event are sent to Apitally until you add ApitallySpanProcessor (exported by the apitally package) to your tracer provider's spanProcessors constructor option or the NodeSDK spanProcessors option.\",\n );\n }\n }\n setActiveSpanPipeline(spanPipeline);\n // BatchLogRecordProcessor requires its single-options-object form; positional\n // arguments create a broken processor.\n const batchLogProcessor = new BatchLogRecordProcessor({\n exporter: new ApitallyLogRecordExporter(spool),\n scheduledDelayMillis: BATCH_SCHEDULE_DELAY_MILLIS,\n exportTimeoutMillis: BATCH_EXPORT_TIMEOUT_MILLIS,\n maxQueueSize: BATCH_MAX_QUEUE_SIZE,\n maxExportBatchSize: BATCH_MAX_EXPORT_BATCH_SIZE,\n });\n const logRecordProcessor = new ApitallyLogRecordProcessor(\n batchLogProcessor,\n spanPipeline,\n config.maskLogRecord,\n );\n const loggerProvider = createLoggerProvider(resource, [logRecordProcessor]);\n const metricsPipeline = new MetricsPipeline(resource, spool);\n spanPipeline.metricsRecorder = (record) => metricsPipeline.recordFromRequest(record);\n const worker = activationFactories.createExportWorker({\n spool,\n otlpEndpoint: config.otlpEndpoint,\n writeToken: config.writeToken,\n env,\n });\n worker.flushCallbacks.push(\n () => metricsPipeline.collectAndExport(),\n () => spanPipeline.forceFlush(),\n () => emitErrorEvents(loggerProvider),\n () => batchLogProcessor.forceFlush(),\n );\n if (config.captureLogs) {\n installConsoleCapture(loggerProvider);\n installNestLoggerCapture(loggerProvider);\n installWinstonCapture(loggerProvider);\n installPinoCapture(loggerProvider);\n }\n installSentryEventIdRecording();\n let undiciInstrumentation: UndiciInstrumentation | undefined;\n if (!hasUserProvider) {\n undiciInstrumentation = createUndiciInstrumentation(config.otlpEndpoint);\n }\n worker.start();\n if (startupEventInfo) {\n emitStartupEvent(loggerProvider, startupEventInfo);\n }\n return {\n spanPipeline,\n tracerProvider,\n loggerProvider,\n consumerUpdateHashes: new Map(),\n metricsPipeline,\n spool,\n worker,\n undiciInstrumentation,\n };\n}\n\n// ignoreRequestHook prevents telemetry for export POSTs. The request origin can\n// be a string or URL at runtime despite its string type.\nfunction createUndiciInstrumentation(otlpEndpoint: string): UndiciInstrumentation {\n const endpointOrigin = new URL(otlpEndpoint).origin;\n return new UndiciInstrumentation({\n ignoreRequestHook: (request) => {\n try {\n return new URL(String(request.origin)).origin === endpointOrigin;\n } catch {\n return false;\n }\n },\n });\n}\n\nasync function drainAndStop(handles: ActivationHandles): Promise<void> {\n try {\n await handles.spanPipeline.shutdown();\n await handles.worker.finalDrain();\n await handles.loggerProvider.shutdown();\n await handles.worker.stop();\n } catch (error) {\n logWarning(`Error draining telemetry on shutdown: ${String(error)}`);\n }\n}\n\nfunction installSignalHooks(activationState: ActivationState): void {\n if (process.platform === \"win32\" || !isMainThread) {\n return;\n }\n let listeners: SignalListeners;\n listeners = {\n SIGTERM: () => handleTerminationSignal(activationState, listeners, \"SIGTERM\"),\n SIGINT: () => handleTerminationSignal(activationState, listeners, \"SIGINT\"),\n };\n activationState.signalListeners = listeners;\n try {\n process.prependOnceListener(\"SIGTERM\", listeners.SIGTERM);\n process.prependOnceListener(\"SIGINT\", listeners.SIGINT);\n } catch (error) {\n removeSignalHooks(activationState, listeners);\n logWarning(`Unable to install Apitally signal handlers: ${String(error)}`);\n }\n}\n\nfunction handleTerminationSignal(\n activationState: ActivationState,\n listeners: SignalListeners,\n signal: TerminationSignal,\n): void {\n const hasApplicationListener = process\n .listeners(signal)\n .some((listener) => listener !== listeners[signal]);\n removeSignalHooks(activationState, listeners);\n const drainPromise = flushAfterTerminationSignal(activationState, signal, hasApplicationListener);\n activationState.signalDrainPromise = drainPromise;\n void drainPromise.catch((error: unknown) => {\n logDebug(`Error flushing telemetry on ${signal}: ${String(error)}`);\n });\n}\n\nasync function flushAfterTerminationSignal(\n activationState: ActivationState,\n signal: TerminationSignal,\n hasApplicationListener: boolean,\n): Promise<void> {\n const handles = activationState.handles;\n if (!handles) {\n return;\n }\n try {\n if (activationState.shutdownPromise) {\n await waitForSignalDeadline(activationState.shutdownPromise);\n } else {\n await handles.worker.finalDrain(SIGNAL_FLUSH_TIMEOUT_MILLIS);\n }\n } catch (error) {\n logDebug(`Error flushing telemetry on ${signal}: ${String(error)}`);\n }\n if (hasApplicationListener || process.listeners(signal).length > 0) {\n return;\n }\n const exitCode = signal === \"SIGTERM\" ? 143 : 130;\n if (process.platform === \"linux\" && process.pid === 1) {\n process.exit(exitCode);\n }\n try {\n process.kill(process.pid, signal);\n } catch {\n process.exit(exitCode);\n }\n}\n\nasync function waitForSignalDeadline(promise: Promise<void>): Promise<void> {\n let deadlineTimer: NodeJS.Timeout | undefined;\n const deadlineReached = new Promise<void>((resolve) => {\n deadlineTimer = setTimeout(resolve, SIGNAL_FLUSH_TIMEOUT_MILLIS);\n });\n try {\n await Promise.race([promise, deadlineReached]);\n } finally {\n if (deadlineTimer) {\n clearTimeout(deadlineTimer);\n }\n }\n}\n\nfunction removeSignalHooks(\n activationState: ActivationState,\n listeners = activationState.signalListeners,\n): void {\n if (!listeners) {\n return;\n }\n process.removeListener(\"SIGTERM\", listeners.SIGTERM);\n process.removeListener(\"SIGINT\", listeners.SIGINT);\n if (activationState.signalListeners === listeners) {\n activationState.signalListeners = undefined;\n }\n}\n\nfunction removeLifecycleHooks(activationState: ActivationState): void {\n removeSignalHooks(activationState);\n if (activationState.beforeExitListener) {\n process.removeListener(\"beforeExit\", activationState.beforeExitListener);\n activationState.beforeExitListener = undefined;\n }\n}\n\nfunction installBeforeExitHook(activationState: ActivationState): void {\n const listener = () => {\n // The drain keeps the event loop alive until shutdown settles.\n void shutdown().catch((error: unknown) => {\n logDebug(`Error draining telemetry on shutdown: ${String(error)}`);\n });\n };\n activationState.beforeExitListener = listener;\n process.on(\"beforeExit\", listener);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA6CA,MAAM,8BAA8B;AACpC,MAAM,8BAA8B;AACpC,MAAM,uBAAuB;AAC7B,MAAM,8BAA8B;AACpC,MAAM,gDAAgD;AACtD,MAAM,8BAA8B;AAOpC,MAAM,uBAAuB,OAAO,IAAI,qBAAqB;AA2B7D,SAAgB,UAAU,UAA2B,CAAC,GAAmB;CACvE,MAAM,SAASA,eAAAA,UAAU,OAAO;CAGhC,IAAI,CAAC,OAAO,YAAY,QAAQ,IAAI,kCAAkC,KAAA,GACpE,QAAQ,IAAI,gCAAgC;CAE9C,sBAAA,gCAAgC,QAAQ;CACxC,OAAO;AACT;AAGA,SAAgB,yBAAyB,MAA8B;CACrE,MAAM,kBAAkB,mBAAmB;CAC3C,gBAAgB,qBAAqB;AACvC;AAIA,SAAgB,WAAiB;CAC/B,MAAM,kBAAkB,mBAAmB;CAC3C,IAAI,gBAAgB,qBAClB;CAEF,gBAAgB,sBAAsB;CACtC,IAAI,qBAAqB,GACvB;CAEF,IAAI;EACF,MAAM,UAAU,eAAe,gBAAgB,gBAAgB;EAC/D,gBAAgB,UAAU;EAC1B,gBAAgB,oBAAoB,aAAa,OAAO;EACxD,sBAAsB,eAAe;EACrC,mBAAmB,eAAe;CACpC,SAAS,OAAO;EACd,eAAA,SAAS,+BAA+B,OAAO,KAAK,GAAG;CACzD;AACF;AAEA,SAAgB,cAAuB;CACrC,OAAO,mBAAmB,CAAC,CAAC,YAAY,KAAA;AAC1C;AAEA,SAAgB,uBAAsD;CACpE,OAAO,mBAAmB,CAAC,CAAC;AAC9B;AAEA,eAAsB,iBAAgC;CACpD,IAAI;EACF,MAAM,mBAAmB,CAAC,CAAC,SAAS,OAAO,SAAS;CACtD,SAAS,OAAO;EACd,eAAA,SAAS,6BAA6B,OAAO,KAAK,GAAG;CACvD;AACF;AAIA,SAAgB,WAA0B;CACxC,MAAM,kBAAkB,mBAAmB;CAC3C,MAAM,UAAU,gBAAgB;CAChC,MAAM,cAAc,gBAAgB;CACpC,IAAI,CAAC,WAAW,CAAC,aACf,OAAO,QAAQ,QAAQ;CAEzB,IAAI,CAAC,gBAAgB,iBAAiB;EACpC,qBAAqB,eAAe;EACpC,MAAM,qBAAqB,gBAAgB;EAC3C,gBAAgB,mBACd,qBACI,mBAAmB,WAAW,QAAQ,OAAO,YAAY,CAAC,IAC1D,QAAQ,QAAQ,EAAA,CACpB,KAAK,WAAW;CACpB;CACA,OAAO,gBAAgB;AACzB;AAGA,MAAa,sBAAsB;CACjC,mBAA0B,IAAIC,cAAAA,MAAM;CACpC,qBAAqB,YAA+C,IAAIC,qBAAAA,aAAa,OAAO;AAC9F;AACA,MAAM,mBAAmB,EAAE,GAAG,oBAAoB;AAGlD,eAAsB,kBAAiC;CACrD,OAAO,OAAO,qBAAqB,gBAAgB;CACnD,MAAM,SAAS;CACf,MAAM,kBAAkB,OAAO;CAC/B,OAAO,OAAO;CACd,4BAA4B;CAC5B,yBAAA,sBAAsB;CACtB,qBAAA,kBAAkB;CAClB,IAAI,CAAC,iBACH;CAEF,qBAAqB,eAAe;CACpC,MAAM,UAAU,gBAAgB;CAChC,IAAI,CAAC,SACH;CAEF,QAAQ,uBAAuB,QAAQ;CACvC,MAAM,QAAQ,OAAO,KAAK;CAC1B,MAAM,QAAQ,aAAa,SAAS;CACpC,MAAM,QAAQ,eAAe,SAAS;CACtC,MAAM,QAAQ,MAAM,MAAM;AAC5B;AAEA,IAAI,4BAA4B;AAIhC,SAAS,gBAAgB,gBAAsC;CAC7D,MAAM,SAAS,eAAe,UAAU,UAAU;CAClD,MAAM,SAAoC,CACxC,CAACC,yBAAAA,6BAA6BC,yBAAAA,sBAAsB,CAAC,GACrD,CAACC,qBAAAA,yBAAyBC,qBAAAA,kBAAkB,CAAC,CAC/C;CACA,KAAK,MAAM,CAAC,WAAW,WAAW,QAChC,KAAK,MAAM,QAAQ,QACjB,OAAO,KAAK;EAAE,WAAW,KAAK,IAAI;EAAG,SAASC,mBAAAA;EAAc;EAAW;CAAK,CAAC;AAGnF;AAEA,SAAS,qBAAsC;CAC7C,MAAM,SAAS;CACf,MAAM,WAAW,OAAO;CACxB,IAAI,UAAU;EACZ,IAAI,CAAC,6BAA6B,SAAS,eAAeC,uBAAAA,iBAAiB,GAAG;GAC5E,4BAA4B;GAC5B,eAAA,WACE,+DAA+D,SAAS,WAAW,OAAOA,uBAAAA,iBAAiB,EAAE,wCAAwC,SAAS,WAAW,uEAC3K;EACF;EACA,OAAO;CACT;CACA,MAAM,kBAAmC;EACvC,YAAYA,uBAAAA,iBAAiB;EAC7B,qBAAqB;CACvB;CACA,OAAO,wBAAwB;CAC/B,OAAO;AACT;AAEA,SAAS,uBAAgC;CACvC,OACE,QAAQ,QAAQ,IAAI,cAAc,KAClC,QAAQ,QAAQ,IAAI,MAAM,KAC1B,QAAQ,IAAI,aAAa,UACzBC,eAAAA,UAAU,CAAC,CAAC;AAEhB;AAEA,SAAS,eAAe,kBAAmE;CACzF,MAAM,SAASA,eAAAA,UAAU;CACzB,IAAI,kBAAkBC,sBAAAA,gCAAgC;CACtD,MAAM,EAAE,KAAK,aAAaC,kBAAAA,4BAA4B;CACtD,MAAM,aAAa,SAAS,WAAW;CACvC,MAAM,QAAQ,oBAAoB,YAAY;CAC9C,MAAM,eAAe,IAAIC,qBAAAA,qBAAqB;EAC5C,WAAW,IAAIC,kBAAAA,UAAU;EACzB;EACA;EACA;EACA,iBAAiB,OAAO;EACxB,kBAAkB,OAAO;CAC3B,CAAC;CAGD,MAAM,qBAAqB,IAAIC,8BAAAA,mBAAmB,cAAc;EAC9D,sBAAsB;EACtB,qBAAqB;EACrB,cAAc;EACd,oBACE,OAAO,sBAAsB,OAAO,sBAChC,gDACA;CACR,CAAC;CACD,MAAM,eAAe,IAAIC,sBAAAA,aAAa,0BAA0B,aAAa,WAAW,CAAC;CACzF,IAAI;CACJ,IAAI,CAAC,iBAAiB;EACpB,iBAAiBC,kBAAAA,oBAAoB,UAAU,CAAC,YAAY,CAAC;EAC7D,IAAI,CAAC,gBAAgB;GACnB,kBAAkB;GAClB,eAAA,WACE,gVACF;EACF;CACF;CACA,sBAAA,sBAAsB,YAAY;CAGlC,MAAM,oBAAoB,IAAIC,wBAAAA,wBAAwB;EACpD,UAAU,IAAIC,0BAAAA,0BAA0B,KAAK;EAC7C,sBAAsB;EACtB,qBAAqB;EACrB,cAAc;EACd,oBAAoB;CACtB,CAAC;CACD,MAAM,qBAAqB,IAAIC,2BAAAA,2BAC7B,mBACA,cACA,OAAO,aACT;CACA,MAAM,iBAAiBC,kBAAAA,qBAAqB,UAAU,CAAC,kBAAkB,CAAC;CAC1E,MAAM,kBAAkB,IAAIC,gBAAAA,gBAAgB,UAAU,KAAK;CAC3D,aAAa,mBAAmB,WAAW,gBAAgB,kBAAkB,MAAM;CACnF,MAAM,SAAS,oBAAoB,mBAAmB;EACpD;EACA,cAAc,OAAO;EACrB,YAAY,OAAO;EACnB;CACF,CAAC;CACD,OAAO,eAAe,WACd,gBAAgB,iBAAiB,SACjC,aAAa,WAAW,SACxB,gBAAgB,cAAc,SAC9B,kBAAkB,WAAW,CACrC;CACA,IAAI,OAAO,aAAa;EACtB,mBAAA,sBAAsB,cAAc;EACpC,mBAAA,yBAAyB,cAAc;EACvC,mBAAA,sBAAsB,cAAc;EACpC,mBAAA,mBAAmB,cAAc;CACnC;CACA,eAAA,8BAA8B;CAC9B,IAAI;CACJ,IAAI,CAAC,iBACH,wBAAwB,4BAA4B,OAAO,YAAY;CAEzE,OAAO,MAAM;CACb,IAAI,kBACF,gBAAA,iBAAiB,gBAAgB,gBAAgB;CAEnD,OAAO;EACL;EACA;EACA;EACA,sCAAsB,IAAI,IAAI;EAC9B;EACA;EACA;EACA;CACF;AACF;AAIA,SAAS,4BAA4B,cAA6C;CAChF,MAAM,iBAAiB,IAAI,IAAI,YAAY,CAAC,CAAC;CAC7C,OAAO,IAAIC,sCAAAA,sBAAsB,EAC/B,oBAAoB,YAAY;EAC9B,IAAI;GACF,OAAO,IAAI,IAAI,OAAO,QAAQ,MAAM,CAAC,CAAC,CAAC,WAAW;EACpD,QAAQ;GACN,OAAO;EACT;CACF,EACF,CAAC;AACH;AAEA,eAAe,aAAa,SAA2C;CACrE,IAAI;EACF,MAAM,QAAQ,aAAa,SAAS;EACpC,MAAM,QAAQ,OAAO,WAAW;EAChC,MAAM,QAAQ,eAAe,SAAS;EACtC,MAAM,QAAQ,OAAO,KAAK;CAC5B,SAAS,OAAO;EACd,eAAA,WAAW,yCAAyC,OAAO,KAAK,GAAG;CACrE;AACF;AAEA,SAAS,mBAAmB,iBAAwC;CAClE,IAAI,QAAQ,aAAa,WAAW,CAACC,oBAAAA,cACnC;CAEF,IAAI,YACQ;EACV,eAAe,wBAAwB,iBAAiB,WAAW,SAAS;EAC5E,cAAc,wBAAwB,iBAAiB,WAAW,QAAQ;CAC5E;CACA,gBAAgB,kBAAkB;CAClC,IAAI;EACF,QAAQ,oBAAoB,WAAW,UAAU,OAAO;EACxD,QAAQ,oBAAoB,UAAU,UAAU,MAAM;CACxD,SAAS,OAAO;EACd,kBAAkB,iBAAiB,SAAS;EAC5C,eAAA,WAAW,+CAA+C,OAAO,KAAK,GAAG;CAC3E;AACF;AAEA,SAAS,wBACP,iBACA,WACA,QACM;CACN,MAAM,yBAAyB,QAC5B,UAAU,MAAM,CAAC,CACjB,MAAM,aAAa,aAAa,UAAU,OAAO;CACpD,kBAAkB,iBAAiB,SAAS;CAC5C,MAAM,eAAe,4BAA4B,iBAAiB,QAAQ,sBAAsB;CAChG,gBAAgB,qBAAqB;CACrC,aAAkB,OAAO,UAAmB;EAC1C,eAAA,SAAS,+BAA+B,OAAO,IAAI,OAAO,KAAK,GAAG;CACpE,CAAC;AACH;AAEA,eAAe,4BACb,iBACA,QACA,wBACe;CACf,MAAM,UAAU,gBAAgB;CAChC,IAAI,CAAC,SACH;CAEF,IAAI;EACF,IAAI,gBAAgB,iBAClB,MAAM,sBAAsB,gBAAgB,eAAe;OAE3D,MAAM,QAAQ,OAAO,WAAW,2BAA2B;CAE/D,SAAS,OAAO;EACd,eAAA,SAAS,+BAA+B,OAAO,IAAI,OAAO,KAAK,GAAG;CACpE;CACA,IAAI,0BAA0B,QAAQ,UAAU,MAAM,CAAC,CAAC,SAAS,GAC/D;CAEF,MAAM,WAAW,WAAW,YAAY,MAAM;CAC9C,IAAI,QAAQ,aAAa,WAAW,QAAQ,QAAQ,GAClD,QAAQ,KAAK,QAAQ;CAEvB,IAAI;EACF,QAAQ,KAAK,QAAQ,KAAK,MAAM;CAClC,QAAQ;EACN,QAAQ,KAAK,QAAQ;CACvB;AACF;AAEA,eAAe,sBAAsB,SAAuC;CAC1E,IAAI;CACJ,MAAM,kBAAkB,IAAI,SAAe,YAAY;EACrD,gBAAgB,WAAW,SAAS,2BAA2B;CACjE,CAAC;CACD,IAAI;EACF,MAAM,QAAQ,KAAK,CAAC,SAAS,eAAe,CAAC;CAC/C,UAAU;EACR,IAAI,eACF,aAAa,aAAa;CAE9B;AACF;AAEA,SAAS,kBACP,iBACA,YAAY,gBAAgB,iBACtB;CACN,IAAI,CAAC,WACH;CAEF,QAAQ,eAAe,WAAW,UAAU,OAAO;CACnD,QAAQ,eAAe,UAAU,UAAU,MAAM;CACjD,IAAI,gBAAgB,oBAAoB,WACtC,gBAAgB,kBAAkB,KAAA;AAEtC;AAEA,SAAS,qBAAqB,iBAAwC;CACpE,kBAAkB,eAAe;CACjC,IAAI,gBAAgB,oBAAoB;EACtC,QAAQ,eAAe,cAAc,gBAAgB,kBAAkB;EACvE,gBAAgB,qBAAqB,KAAA;CACvC;AACF;AAEA,SAAS,sBAAsB,iBAAwC;CACrE,MAAM,iBAAiB;EAErB,SAAc,CAAC,CAAC,OAAO,UAAmB;GACxC,eAAA,SAAS,yCAAyC,OAAO,KAAK,GAAG;EACnE,CAAC;CACH;CACA,gBAAgB,qBAAqB;CACrC,QAAQ,GAAG,cAAc,QAAQ;AACnC"}
@@ -5,12 +5,12 @@ import { ExportWorker } from "./exportWorker.js";
5
5
  import { installConsoleCapture, installNestLoggerCapture, installPinoCapture, installWinstonCapture } from "./logCapture.js";
6
6
  import { ApitallyLogRecordExporter } from "./logRecordExporter.js";
7
7
  import { ApitallyLogRecordProcessor } from "./logRecordProcessor.js";
8
- import { createLoggerProvider, resolveEnvAndCreateResource, setupTracerProvider } from "./providers.js";
9
8
  import { MetricsPipeline } from "./metrics.js";
9
+ import { SpanPipeline, isApitallySpanProcessorDeclared, setActiveSpanPipeline, setServerSpanActivationCallback } from "./spanProcessor.js";
10
+ import { createLoggerProvider, resolveEnvAndCreateResource, setupTracerProvider } from "./providers.js";
10
11
  import { Redaction } from "./redaction.js";
11
12
  import { installSentryEventIdRecording } from "./sentry.js";
12
13
  import { SERVER_ERROR_EVENT_NAME, drainServerErrors, resetServerErrors } from "./serverErrors.js";
13
- import { SpanPipeline, isApitallySpanProcessorDeclared, setActiveSpanPipeline, setServerSpanActivationCallback } from "./spanProcessor.js";
14
14
  import { ApitallySpanExporter } from "./spanExporter.js";
15
15
  import { Spool } from "./spool.js";
16
16
  import { emitStartupEvent } from "./startup.js";