@pingops/sdk 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/index.cjs +2 -1
- package/dist/index.d.cts +6 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +6 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{pingops-BBXegVtL.cjs → pingops-BM2jW6Bi.cjs} +24 -2
- package/dist/pingops-BM2jW6Bi.cjs.map +1 -0
- package/dist/{pingops-D4xrGLxy.mjs → pingops-SV21E4bp.mjs} +20 -4
- package/dist/pingops-SV21E4bp.mjs.map +1 -0
- package/dist/register.cjs +1 -1
- package/dist/register.mjs +1 -1
- package/package.json +7 -4
- package/dist/pingops-BBXegVtL.cjs.map +0 -1
- package/dist/pingops-D4xrGLxy.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -237,6 +237,10 @@ process.on("SIGTERM", async () => {
|
|
|
237
237
|
|
|
238
238
|
Starts a new trace, sets PingOps attributes (e.g. `userId`, `sessionId`, tags, metadata) in context, runs the given function inside that context, and returns the function’s result. Any spans created inside the function (including automatic HTTP/fetch spans) are part of this trace and carry the same context.
|
|
239
239
|
|
|
240
|
+
If `startTrace` is called while the current OpenTelemetry context is tracing-suppressed, the SDK now starts the trace from a clean unsuppressed base context. This prevents leaked suppression flags from silently disabling outbound instrumentation for user traffic.
|
|
241
|
+
|
|
242
|
+
When using `initializePingops` without `startTrace`, SDK instrumentations also guard outbound user requests against leaked suppression context. Exporter traffic remains suppressed to avoid self-instrumentation recursion.
|
|
243
|
+
|
|
240
244
|
**Parameters:**
|
|
241
245
|
|
|
242
246
|
- `options.attributes` — Optional [PingopsTraceAttributes](#pingopstraceattributes) to attach to the trace and propagate to spans.
|
|
@@ -273,6 +277,24 @@ const data = await startTrace(
|
|
|
273
277
|
|
|
274
278
|
---
|
|
275
279
|
|
|
280
|
+
### `runUnsuppressed(fn)`
|
|
281
|
+
|
|
282
|
+
Runs `fn` in a clean unsuppressed OpenTelemetry context. Use this at async task/job boundaries when you are not using `startTrace`, but still need outbound HTTP/fetch instrumentation to capture spans.
|
|
283
|
+
|
|
284
|
+
**Example:**
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import { runUnsuppressed } from "@pingops/sdk";
|
|
288
|
+
|
|
289
|
+
await runUnsuppressed(async () => {
|
|
290
|
+
await fetch("https://api.example.com/work");
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
This helper is scoped to the callback only and does not globally disable OpenTelemetry suppression.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
276
298
|
### `getActiveTraceId()`
|
|
277
299
|
|
|
278
300
|
Returns the trace ID of the currently active span, or `undefined` if there is none.
|
|
@@ -470,6 +492,7 @@ Only **CLIENT** spans with HTTP (or supported semantic) attributes are exported
|
|
|
470
492
|
| Manual init | `initializePingops({ baseUrl, serviceName, ... })` before any HTTP usage |
|
|
471
493
|
| Config from file | `PINGOPS_CONFIG_FILE=./pingops.config.yaml` or `initializePingops("./pingops.config.json")` |
|
|
472
494
|
| Trace with context | `startTrace({ attributes: { userId, sessionId, tags, metadata }, seed? }, async () => { ... })` |
|
|
495
|
+
| Unsuppress a scope | `runUnsuppressed(async () => { ... })` |
|
|
473
496
|
| Get current IDs | `getActiveTraceId()`, `getActiveSpanId()` |
|
|
474
497
|
| Graceful shutdown | `await shutdownPingops()` |
|
|
475
498
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
const require_pingops = require('./pingops-
|
|
1
|
+
const require_pingops = require('./pingops-BM2jW6Bi.cjs');
|
|
2
2
|
|
|
3
3
|
exports.getActiveSpanId = require_pingops.getActiveSpanId;
|
|
4
4
|
exports.getActiveTraceId = require_pingops.getActiveTraceId;
|
|
5
5
|
exports.initializePingops = require_pingops.initializePingops;
|
|
6
|
+
exports.runUnsuppressed = require_pingops.runUnsuppressed;
|
|
6
7
|
exports.shutdownPingops = require_pingops.shutdownPingops;
|
|
7
8
|
exports.startTrace = require_pingops.startTrace;
|
package/dist/index.d.cts
CHANGED
|
@@ -67,6 +67,11 @@ declare function startTrace<T>(options: {
|
|
|
67
67
|
attributes?: PingopsTraceAttributes$1;
|
|
68
68
|
seed?: string;
|
|
69
69
|
}, fn: () => T | Promise<T>): Promise<T>;
|
|
70
|
+
/**
|
|
71
|
+
* Runs a callback in a context that is guaranteed to be unsuppressed.
|
|
72
|
+
* Useful for task/job boundaries where suppression may have leaked.
|
|
73
|
+
*/
|
|
74
|
+
declare function runUnsuppressed<T>(fn: () => T): T;
|
|
70
75
|
//#endregion
|
|
71
|
-
export { type PingopsTraceAttributes, getActiveSpanId, getActiveTraceId, initializePingops, shutdownPingops, startTrace };
|
|
76
|
+
export { type PingopsTraceAttributes, getActiveSpanId, getActiveTraceId, initializePingops, runUnsuppressed, shutdownPingops, startTrace };
|
|
72
77
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/pingops.ts"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/pingops.ts"],"sourcesContent":[],"mappings":";;;;;AAiTA;AAOA;AAiCA;;;;;;;;AA6FA;;;;iBAlXgB,iBAAA,SACN;iBAGM,iBAAA;iBAIA,iBAAA;;;;;;iBAuGM,eAAA,CAAA,GAAmB;;;;iBA8HzB,gBAAA,CAAA;;;;iBAOA,eAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCM;eACI;;aACd,IAAI,QAAQ,KACrB,QAAQ;;;;;iBA0FK,6BAA6B,IAAI"}
|
package/dist/index.d.mts
CHANGED
|
@@ -67,6 +67,11 @@ declare function startTrace<T>(options: {
|
|
|
67
67
|
attributes?: PingopsTraceAttributes$1;
|
|
68
68
|
seed?: string;
|
|
69
69
|
}, fn: () => T | Promise<T>): Promise<T>;
|
|
70
|
+
/**
|
|
71
|
+
* Runs a callback in a context that is guaranteed to be unsuppressed.
|
|
72
|
+
* Useful for task/job boundaries where suppression may have leaked.
|
|
73
|
+
*/
|
|
74
|
+
declare function runUnsuppressed<T>(fn: () => T): T;
|
|
70
75
|
//#endregion
|
|
71
|
-
export { type PingopsTraceAttributes, getActiveSpanId, getActiveTraceId, initializePingops, shutdownPingops, startTrace };
|
|
76
|
+
export { type PingopsTraceAttributes, getActiveSpanId, getActiveTraceId, initializePingops, runUnsuppressed, shutdownPingops, startTrace };
|
|
72
77
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/pingops.ts"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/pingops.ts"],"sourcesContent":[],"mappings":";;;;;AAiTA;AAOA;AAiCA;;;;;;;;AA6FA;;;;iBAlXgB,iBAAA,SACN;iBAGM,iBAAA;iBAIA,iBAAA;;;;;;iBAuGM,eAAA,CAAA,GAAmB;;;;iBA8HzB,gBAAA,CAAA;;;;iBAOA,eAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCM;eACI;;aACd,IAAI,QAAQ,KACrB,QAAQ;;;;;iBA0FK,6BAA6B,IAAI"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as shutdownPingops, i as runUnsuppressed, n as getActiveTraceId, o as startTrace, r as initializePingops, t as getActiveSpanId } from "./pingops-SV21E4bp.mjs";
|
|
2
2
|
|
|
3
|
-
export { getActiveSpanId, getActiveTraceId, initializePingops, shutdownPingops, startTrace };
|
|
3
|
+
export { getActiveSpanId, getActiveTraceId, initializePingops, runUnsuppressed, shutdownPingops, startTrace };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
2
|
+
let _opentelemetry_core = require("@opentelemetry/core");
|
|
2
3
|
let _opentelemetry_sdk_node = require("@opentelemetry/sdk-node");
|
|
3
4
|
let _opentelemetry_resources = require("@opentelemetry/resources");
|
|
4
5
|
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
@@ -87,6 +88,7 @@ let isSdkInitializedFlag = false;
|
|
|
87
88
|
*/
|
|
88
89
|
let isInitialized = false;
|
|
89
90
|
let initializationPromise = null;
|
|
91
|
+
let hasLoggedSuppressedStartTraceWarning = false;
|
|
90
92
|
function initializePingops(config, explicit = true) {
|
|
91
93
|
const resolvedConfig = typeof config === "string" ? resolveConfigFromFile(config) : "configFile" in config ? resolveConfigFromFile(config.configFile) : config;
|
|
92
94
|
if (isSdkInitializedFlag) {
|
|
@@ -265,7 +267,12 @@ async function startTrace(options, fn) {
|
|
|
265
267
|
traceFlags: TRACE_FLAG_SAMPLED
|
|
266
268
|
};
|
|
267
269
|
const activeContext = _opentelemetry_api.context.active();
|
|
268
|
-
const
|
|
270
|
+
const traceExecutionBaseContext = (0, _opentelemetry_core.isTracingSuppressed)(activeContext) ? _opentelemetry_api.ROOT_CONTEXT : activeContext;
|
|
271
|
+
if (traceExecutionBaseContext === _opentelemetry_api.ROOT_CONTEXT) if (!hasLoggedSuppressedStartTraceWarning) {
|
|
272
|
+
logger.warn("startTrace detected a suppressed active context and is running on ROOT_CONTEXT to prevent suppression leakage into user outbound instrumentation");
|
|
273
|
+
hasLoggedSuppressedStartTraceWarning = true;
|
|
274
|
+
} else logger.debug("startTrace received a suppressed active context; running trace on ROOT_CONTEXT");
|
|
275
|
+
const contextWithSpanContext = _opentelemetry_api.trace.setSpanContext(traceExecutionBaseContext, spanContext);
|
|
269
276
|
const tracer = (0, _pingops_otel.getPingopsTracerProvider)().getTracer("pingops-sdk", "1.0.0");
|
|
270
277
|
return new Promise((resolve$1, reject) => {
|
|
271
278
|
tracer.startActiveSpan("pingops-trace", {}, contextWithSpanContext, (span) => {
|
|
@@ -294,6 +301,15 @@ async function startTrace(options, fn) {
|
|
|
294
301
|
});
|
|
295
302
|
});
|
|
296
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Runs a callback in a context that is guaranteed to be unsuppressed.
|
|
306
|
+
* Useful for task/job boundaries where suppression may have leaked.
|
|
307
|
+
*/
|
|
308
|
+
function runUnsuppressed(fn) {
|
|
309
|
+
const activeContext = _opentelemetry_api.context.active();
|
|
310
|
+
const unsuppressedContext = (0, _opentelemetry_core.isTracingSuppressed)(activeContext) ? _opentelemetry_api.ROOT_CONTEXT : activeContext;
|
|
311
|
+
return _opentelemetry_api.context.with(unsuppressedContext, fn);
|
|
312
|
+
}
|
|
297
313
|
function setAttributesInContext(ctx, attrs) {
|
|
298
314
|
if (attrs.userId !== void 0) ctx = ctx.setValue(_pingops_core.PINGOPS_USER_ID, attrs.userId);
|
|
299
315
|
if (attrs.sessionId !== void 0) ctx = ctx.setValue(_pingops_core.PINGOPS_SESSION_ID, attrs.sessionId);
|
|
@@ -335,6 +351,12 @@ Object.defineProperty(exports, 'mergeConfigWithEnv', {
|
|
|
335
351
|
return mergeConfigWithEnv;
|
|
336
352
|
}
|
|
337
353
|
});
|
|
354
|
+
Object.defineProperty(exports, 'runUnsuppressed', {
|
|
355
|
+
enumerable: true,
|
|
356
|
+
get: function () {
|
|
357
|
+
return runUnsuppressed;
|
|
358
|
+
}
|
|
359
|
+
});
|
|
338
360
|
Object.defineProperty(exports, 'shutdownPingops', {
|
|
339
361
|
enumerable: true,
|
|
340
362
|
get: function () {
|
|
@@ -347,4 +369,4 @@ Object.defineProperty(exports, 'startTrace', {
|
|
|
347
369
|
return startTrace;
|
|
348
370
|
}
|
|
349
371
|
});
|
|
350
|
-
//# sourceMappingURL=pingops-
|
|
372
|
+
//# sourceMappingURL=pingops-BM2jW6Bi.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pingops-BM2jW6Bi.cjs","names":["isSdkInitializedFlag","ATTR_SERVICE_NAME","PingopsSpanProcessor","NodeSDK","NodeTracerProvider","trace","context","ROOT_CONTEXT","PINGOPS_TRACE_ID","PINGOPS_USER_ID","PINGOPS_SESSION_ID","PINGOPS_TAGS","PINGOPS_METADATA","PINGOPS_CAPTURE_REQUEST_BODY","PINGOPS_CAPTURE_RESPONSE_BODY"],"sources":["../src/config-loader.ts","../src/init-state.ts","../src/pingops.ts"],"sourcesContent":["/**\n * Configuration loader for reading PingOps config from JSON/YAML files\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { load as loadYaml } from \"js-yaml\";\nimport type { PingopsProcessorConfig } from \"@pingops/otel\";\n\n/**\n * Loads configuration from a JSON or YAML file\n *\n * @param filePath - Path to the config file (JSON or YAML)\n * @returns Parsed configuration object\n * @throws Error if file cannot be read or parsed\n */\nexport function loadConfigFromFile(\n filePath: string\n): Partial<PingopsProcessorConfig> {\n const resolvedPath = resolve(filePath);\n const fileContent = readFileSync(resolvedPath, \"utf-8\");\n\n const ext = resolvedPath.toLowerCase();\n if (ext.endsWith(\".yaml\") || ext.endsWith(\".yml\")) {\n return (loadYaml(fileContent) as Partial<PingopsProcessorConfig>) || {};\n } else if (ext.endsWith(\".json\")) {\n return JSON.parse(fileContent) as Partial<PingopsProcessorConfig>;\n } else {\n // Try to parse as JSON first, then YAML\n try {\n return JSON.parse(fileContent) as Partial<PingopsProcessorConfig>;\n } catch {\n return (loadYaml(fileContent) as Partial<PingopsProcessorConfig>) || {};\n }\n }\n}\n\n/**\n * Merges configuration from file and environment variables.\n * Environment variables take precedence over file config.\n *\n * @param fileConfig - Configuration loaded from file\n * @returns Merged configuration with env vars taking precedence\n */\nexport function mergeConfigWithEnv(\n fileConfig: Partial<PingopsProcessorConfig>\n): Partial<PingopsProcessorConfig> {\n const envConfig: Partial<PingopsProcessorConfig> = {};\n\n // Read from environment variables\n if (process.env.PINGOPS_API_KEY) {\n envConfig.apiKey = process.env.PINGOPS_API_KEY;\n }\n if (process.env.PINGOPS_BASE_URL) {\n envConfig.baseUrl = process.env.PINGOPS_BASE_URL;\n }\n if (process.env.PINGOPS_SERVICE_NAME) {\n envConfig.serviceName = process.env.PINGOPS_SERVICE_NAME;\n }\n if (process.env.PINGOPS_DEBUG) {\n envConfig.debug = process.env.PINGOPS_DEBUG === \"true\";\n }\n if (process.env.PINGOPS_BATCH_SIZE) {\n envConfig.batchSize = parseInt(process.env.PINGOPS_BATCH_SIZE, 10);\n }\n if (process.env.PINGOPS_BATCH_TIMEOUT) {\n envConfig.batchTimeout = parseInt(process.env.PINGOPS_BATCH_TIMEOUT, 10);\n }\n if (process.env.PINGOPS_EXPORT_MODE) {\n envConfig.exportMode = process.env.PINGOPS_EXPORT_MODE as\n | \"batched\"\n | \"immediate\";\n }\n\n // Merge: env vars override file config\n return {\n ...fileConfig,\n ...envConfig,\n };\n}\n","/**\n * Shared state for tracking SDK initialization\n * This module exists to avoid circular dependencies between pingops.ts and instrumentation.ts\n */\n\nlet isSdkInitializedFlag = false;\n\n/**\n * Returns whether the SDK has been initialized.\n */\nexport function isSdkInitialized(): boolean {\n return isSdkInitializedFlag;\n}\n\n/**\n * Sets the SDK initialization flag.\n * Called by initializePingops when the SDK is initialized.\n */\nexport function setSdkInitialized(initialized: boolean): void {\n isSdkInitializedFlag = initialized;\n}\n","/**\n * PingOps SDK singleton for manual instrumentation\n *\n * Provides initializePingops, shutdownPingops, startTrace, getActiveTraceId,\n * and getActiveSpanId. startTrace can auto-initialize from environment variables if needed.\n */\n\nimport { ROOT_CONTEXT, context, trace } from \"@opentelemetry/api\";\nimport { isTracingSuppressed } from \"@opentelemetry/core\";\nimport { NodeSDK } from \"@opentelemetry/sdk-node\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport { ATTR_SERVICE_NAME } from \"@opentelemetry/semantic-conventions\";\nimport { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport type { PingopsProcessorConfig } from \"@pingops/otel\";\nimport {\n setPingopsTracerProvider,\n shutdownTracerProvider,\n PingopsSpanProcessor,\n} from \"@pingops/otel\";\nimport {\n createLogger,\n createTraceId,\n uint8ArrayToHex,\n type PingopsTraceAttributes,\n} from \"@pingops/core\";\nimport {\n PINGOPS_TRACE_ID,\n PINGOPS_USER_ID,\n PINGOPS_SESSION_ID,\n PINGOPS_TAGS,\n PINGOPS_METADATA,\n PINGOPS_CAPTURE_REQUEST_BODY,\n PINGOPS_CAPTURE_RESPONSE_BODY,\n} from \"@pingops/core\";\nimport { loadConfigFromFile, mergeConfigWithEnv } from \"./config-loader\";\nimport { setSdkInitialized } from \"./init-state\";\nimport { getPingopsTracerProvider } from \"@pingops/otel\";\nimport { getInstrumentations } from \"@pingops/otel\";\n\nconst TRACE_FLAG_SAMPLED = 1;\n\nconst initLogger = createLogger(\"[PingOps Initialize]\");\nconst logger = createLogger(\"[PingOps Pingops]\");\n\nlet sdkInstance: NodeSDK | null = null;\nlet isSdkInitializedFlag = false;\n\n/**\n * Global state to track initialization\n */\nlet isInitialized = false;\nlet initializationPromise: Promise<void> | null = null;\nlet hasLoggedSuppressedStartTraceWarning = false;\n\n/**\n * Initializes PingOps SDK\n *\n * This function:\n * 1. Creates an OpenTelemetry NodeSDK instance\n * 2. Configures Resource with service.name\n * 3. Registers PingopsSpanProcessor\n * 4. Enables HTTP/fetch/GenAI instrumentation\n * 5. Starts the SDK\n *\n * @param config - Configuration object, config file path, or config file wrapper\n * @param explicit - Whether this is an explicit call (default: true).\n * Set to false when called internally by startTrace auto-initialization.\n */\nexport function initializePingops(\n config: PingopsProcessorConfig,\n explicit?: boolean\n): void;\nexport function initializePingops(\n configFilePath: string,\n explicit?: boolean\n): void;\nexport function initializePingops(\n config: { configFile: string },\n explicit?: boolean\n): void;\nexport function initializePingops(\n config:\n | PingopsProcessorConfig\n | string\n | {\n configFile: string;\n },\n explicit: boolean = true\n): void {\n void explicit; // Ignored: SDK always uses global instrumentation\n const resolvedConfig: PingopsProcessorConfig =\n typeof config === \"string\"\n ? resolveConfigFromFile(config)\n : \"configFile\" in config\n ? resolveConfigFromFile(config.configFile)\n : config;\n\n if (isSdkInitializedFlag) {\n if (resolvedConfig.debug) {\n initLogger.warn(\"[PingOps] SDK already initialized, skipping\");\n }\n return;\n }\n\n // Create resource with service name\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: resolvedConfig.serviceName,\n });\n\n const processor = new PingopsSpanProcessor(resolvedConfig);\n const instrumentations = getInstrumentations();\n\n // Node.js SDK\n const nodeSdk = new NodeSDK({\n resource,\n spanProcessors: [processor],\n instrumentations,\n });\n\n nodeSdk.start();\n sdkInstance = nodeSdk;\n\n // Mark SDK as initialized\n isSdkInitializedFlag = true;\n\n setSdkInitialized(true);\n\n // Initialize isolated TracerProvider for manual spans AFTER NodeSDK starts\n // This ensures manual spans created via startSpan are processed by the same processor\n // We register it after NodeSDK so it takes precedence as the global provider\n try {\n // In version 2.2.0, span processors are passed in the constructor\n const isolatedProvider = new NodeTracerProvider({\n resource,\n spanProcessors: [processor],\n });\n\n // Register the provider globally\n isolatedProvider.register();\n\n // Set it in global state\n setPingopsTracerProvider(isolatedProvider);\n } catch (error) {\n if (resolvedConfig.debug) {\n initLogger.error(\n \"[PingOps] Failed to create isolated TracerProvider:\",\n error instanceof Error ? error.message : String(error)\n );\n }\n // Continue without isolated provider - manual spans will use global provider\n }\n\n if (resolvedConfig.debug) {\n initLogger.info(\"[PingOps] SDK initialized\");\n }\n}\n\nfunction resolveConfigFromFile(configFilePath: string): PingopsProcessorConfig {\n const fileConfig = loadConfigFromFile(configFilePath);\n const mergedConfig = mergeConfigWithEnv(fileConfig);\n\n if (!mergedConfig.baseUrl || !mergedConfig.serviceName) {\n const missing = [\n !mergedConfig.baseUrl && \"baseUrl (or PINGOPS_BASE_URL)\",\n !mergedConfig.serviceName && \"serviceName (or PINGOPS_SERVICE_NAME)\",\n ].filter(Boolean);\n\n throw new Error(\n `initializePingops(configFile) requires ${missing.join(\" and \")}. ` +\n `Provide them in the config file or via environment variables.`\n );\n }\n\n return mergedConfig as PingopsProcessorConfig;\n}\n\n/**\n * Shuts down the SDK and flushes remaining spans\n */\nexport async function shutdownPingops(): Promise<void> {\n // Shutdown isolated TracerProvider first\n await shutdownTracerProvider();\n\n if (!sdkInstance) {\n return;\n }\n\n await sdkInstance.shutdown();\n sdkInstance = null;\n isSdkInitializedFlag = false;\n setSdkInitialized(false);\n}\n\n/**\n * Checks if the SDK is already initialized by checking if a NodeTracerProvider is available\n */\nfunction isSdkInitialized(): boolean {\n try {\n const provider = getPingopsTracerProvider();\n // If we have a NodeTracerProvider (not the default NoOpTracerProvider), SDK is initialized\n const initialized = provider instanceof NodeTracerProvider;\n logger.debug(\"Checked SDK initialization status\", {\n initialized,\n providerType: provider.constructor.name,\n });\n return initialized;\n } catch (error) {\n logger.debug(\"Error checking SDK initialization status\", {\n error: error instanceof Error ? error.message : String(error),\n });\n return false;\n }\n}\n\n/**\n * Auto-initializes the SDK from environment variables if not already initialized\n */\nasync function ensureInitialized(): Promise<void> {\n // Check if SDK is already initialized (e.g., by calling initializePingops directly)\n if (isSdkInitialized()) {\n logger.debug(\"SDK already initialized, skipping auto-initialization\");\n isInitialized = true;\n return;\n }\n\n if (isInitialized) {\n logger.debug(\"SDK initialization flag already set, skipping\");\n return;\n }\n\n // If initialization is in progress, wait for it\n if (initializationPromise) {\n logger.debug(\"SDK initialization already in progress, waiting...\");\n return initializationPromise;\n }\n\n // Start initialization\n logger.info(\"Starting SDK auto-initialization from environment variables\");\n initializationPromise = Promise.resolve().then(() => {\n const apiKey = process.env.PINGOPS_API_KEY;\n const baseUrl = process.env.PINGOPS_BASE_URL;\n const serviceName = process.env.PINGOPS_SERVICE_NAME;\n const debug = process.env.PINGOPS_DEBUG === \"true\";\n\n logger.debug(\"Reading environment variables\", {\n hasApiKey: !!apiKey,\n hasBaseUrl: !!baseUrl,\n hasServiceName: !!serviceName,\n debug,\n });\n\n if (!apiKey || !baseUrl || !serviceName) {\n const missing = [\n !apiKey && \"PINGOPS_API_KEY\",\n !baseUrl && \"PINGOPS_BASE_URL\",\n !serviceName && \"PINGOPS_SERVICE_NAME\",\n ].filter(Boolean);\n\n logger.error(\n \"Missing required environment variables for auto-initialization\",\n {\n missing,\n }\n );\n\n throw new Error(\n `PingOps SDK auto-initialization requires PINGOPS_API_KEY, PINGOPS_BASE_URL, and PINGOPS_SERVICE_NAME environment variables. Missing: ${missing.join(\", \")}`\n );\n }\n\n const config: PingopsProcessorConfig = {\n apiKey,\n baseUrl,\n serviceName,\n debug,\n };\n\n logger.info(\"Initializing SDK with config\", {\n baseUrl,\n serviceName,\n debug,\n });\n\n // Call initializePingops with explicit=false since this is auto-initialization\n initializePingops(config, false);\n isInitialized = true;\n\n logger.info(\"SDK auto-initialization completed successfully\");\n });\n\n try {\n await initializationPromise;\n } catch (error) {\n logger.error(\"SDK auto-initialization failed\", {\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n initializationPromise = null;\n }\n}\n\n/**\n * Returns the trace ID of the currently active span, if any.\n */\nexport function getActiveTraceId(): string | undefined {\n return trace.getActiveSpan()?.spanContext().traceId;\n}\n\n/**\n * Returns the span ID of the currently active span, if any.\n */\nexport function getActiveSpanId(): string | undefined {\n return trace.getActiveSpan()?.spanContext().spanId;\n}\n\n/**\n * Starts a new trace using the PingOps tracer provider and runs the callback within that trace.\n * Sets attributes (traceId, userId, sessionId, tags, metadata, etc.) in context so they are\n * propagated to spans created within the callback.\n *\n * @param options - Options including optional attributes and optional seed for deterministic traceId\n * @param fn - Function to execute within the trace and attribute context\n * @returns Promise resolving to the result of the function\n *\n * @example\n * ```typescript\n * import { startTrace, initializePingops } from '@pingops/sdk';\n *\n * initializePingops({ ... });\n *\n * const result = await startTrace({\n * attributes: {\n * userId: 'user-123',\n * sessionId: 'session-456',\n * tags: ['production', 'api'],\n * metadata: { environment: 'prod', version: '1.0.0' }\n * },\n * seed: 'request-123' // optional: deterministic traceId from this seed\n * }, async () => {\n * const response = await fetch('https://api.example.com/users/123');\n * return response.json();\n * });\n * ```\n */\nexport async function startTrace<T>(\n options: { attributes?: PingopsTraceAttributes; seed?: string },\n fn: () => T | Promise<T>\n): Promise<T> {\n if (!isSdkInitialized()) {\n await ensureInitialized();\n }\n\n const traceId =\n options.attributes?.traceId ?? (await createTraceId(options?.seed));\n const parentSpanId = uint8ArrayToHex(\n crypto.getRandomValues(new Uint8Array(8))\n );\n\n const spanContext = {\n traceId,\n spanId: parentSpanId,\n traceFlags: TRACE_FLAG_SAMPLED,\n };\n\n const activeContext = context.active();\n const traceExecutionBaseContext = isTracingSuppressed(activeContext)\n ? ROOT_CONTEXT\n : activeContext;\n if (traceExecutionBaseContext === ROOT_CONTEXT) {\n if (!hasLoggedSuppressedStartTraceWarning) {\n logger.warn(\n \"startTrace detected a suppressed active context and is running on ROOT_CONTEXT to prevent suppression leakage into user outbound instrumentation\"\n );\n hasLoggedSuppressedStartTraceWarning = true;\n } else {\n logger.debug(\n \"startTrace received a suppressed active context; running trace on ROOT_CONTEXT\"\n );\n }\n }\n const contextWithSpanContext = trace.setSpanContext(\n traceExecutionBaseContext,\n spanContext\n );\n\n const tracer = getPingopsTracerProvider().getTracer(\"pingops-sdk\", \"1.0.0\");\n\n return new Promise((resolve, reject) => {\n tracer.startActiveSpan(\n \"pingops-trace\",\n {},\n contextWithSpanContext,\n (span) => {\n let contextWithAttributes = context.active();\n const attrs = options.attributes;\n if (attrs) {\n contextWithAttributes = setAttributesInContext(\n contextWithAttributes,\n attrs\n );\n }\n contextWithAttributes = contextWithAttributes.setValue(\n PINGOPS_TRACE_ID,\n traceId\n );\n\n const run = () => fn();\n\n try {\n const result = context.with(contextWithAttributes, run);\n if (result instanceof Promise) {\n result\n .then((v) => {\n span.end();\n resolve(v);\n })\n .catch((err) => {\n span.end();\n reject(err instanceof Error ? err : new Error(String(err)));\n });\n } else {\n span.end();\n resolve(result);\n }\n } catch (err) {\n span.end();\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n }\n );\n });\n}\n\n/**\n * Runs a callback in a context that is guaranteed to be unsuppressed.\n * Useful for task/job boundaries where suppression may have leaked.\n */\nexport function runUnsuppressed<T>(fn: () => T): T {\n const activeContext = context.active();\n const unsuppressedContext = isTracingSuppressed(activeContext)\n ? ROOT_CONTEXT\n : activeContext;\n return context.with(unsuppressedContext, fn);\n}\n\nfunction setAttributesInContext(\n ctx: ReturnType<typeof context.active>,\n attrs: PingopsTraceAttributes\n): ReturnType<typeof context.active> {\n if (attrs.userId !== undefined) {\n ctx = ctx.setValue(PINGOPS_USER_ID, attrs.userId);\n }\n if (attrs.sessionId !== undefined) {\n ctx = ctx.setValue(PINGOPS_SESSION_ID, attrs.sessionId);\n }\n if (attrs.tags !== undefined) {\n ctx = ctx.setValue(PINGOPS_TAGS, attrs.tags);\n }\n if (attrs.metadata !== undefined) {\n ctx = ctx.setValue(PINGOPS_METADATA, attrs.metadata);\n }\n if (attrs.captureRequestBody !== undefined) {\n ctx = ctx.setValue(PINGOPS_CAPTURE_REQUEST_BODY, attrs.captureRequestBody);\n }\n if (attrs.captureResponseBody !== undefined) {\n ctx = ctx.setValue(\n PINGOPS_CAPTURE_RESPONSE_BODY,\n attrs.captureResponseBody\n );\n }\n return ctx;\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { context, trace } from "@opentelemetry/api";
|
|
1
|
+
import { ROOT_CONTEXT, context, trace } from "@opentelemetry/api";
|
|
2
|
+
import { isTracingSuppressed } from "@opentelemetry/core";
|
|
2
3
|
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
3
4
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
4
5
|
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
@@ -87,6 +88,7 @@ let isSdkInitializedFlag = false;
|
|
|
87
88
|
*/
|
|
88
89
|
let isInitialized = false;
|
|
89
90
|
let initializationPromise = null;
|
|
91
|
+
let hasLoggedSuppressedStartTraceWarning = false;
|
|
90
92
|
function initializePingops(config, explicit = true) {
|
|
91
93
|
const resolvedConfig = typeof config === "string" ? resolveConfigFromFile(config) : "configFile" in config ? resolveConfigFromFile(config.configFile) : config;
|
|
92
94
|
if (isSdkInitializedFlag) {
|
|
@@ -265,7 +267,12 @@ async function startTrace(options, fn) {
|
|
|
265
267
|
traceFlags: TRACE_FLAG_SAMPLED
|
|
266
268
|
};
|
|
267
269
|
const activeContext = context.active();
|
|
268
|
-
const
|
|
270
|
+
const traceExecutionBaseContext = isTracingSuppressed(activeContext) ? ROOT_CONTEXT : activeContext;
|
|
271
|
+
if (traceExecutionBaseContext === ROOT_CONTEXT) if (!hasLoggedSuppressedStartTraceWarning) {
|
|
272
|
+
logger.warn("startTrace detected a suppressed active context and is running on ROOT_CONTEXT to prevent suppression leakage into user outbound instrumentation");
|
|
273
|
+
hasLoggedSuppressedStartTraceWarning = true;
|
|
274
|
+
} else logger.debug("startTrace received a suppressed active context; running trace on ROOT_CONTEXT");
|
|
275
|
+
const contextWithSpanContext = trace.setSpanContext(traceExecutionBaseContext, spanContext);
|
|
269
276
|
const tracer = getPingopsTracerProvider().getTracer("pingops-sdk", "1.0.0");
|
|
270
277
|
return new Promise((resolve$1, reject) => {
|
|
271
278
|
tracer.startActiveSpan("pingops-trace", {}, contextWithSpanContext, (span) => {
|
|
@@ -294,6 +301,15 @@ async function startTrace(options, fn) {
|
|
|
294
301
|
});
|
|
295
302
|
});
|
|
296
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Runs a callback in a context that is guaranteed to be unsuppressed.
|
|
306
|
+
* Useful for task/job boundaries where suppression may have leaked.
|
|
307
|
+
*/
|
|
308
|
+
function runUnsuppressed(fn) {
|
|
309
|
+
const activeContext = context.active();
|
|
310
|
+
const unsuppressedContext = isTracingSuppressed(activeContext) ? ROOT_CONTEXT : activeContext;
|
|
311
|
+
return context.with(unsuppressedContext, fn);
|
|
312
|
+
}
|
|
297
313
|
function setAttributesInContext(ctx, attrs) {
|
|
298
314
|
if (attrs.userId !== void 0) ctx = ctx.setValue(PINGOPS_USER_ID, attrs.userId);
|
|
299
315
|
if (attrs.sessionId !== void 0) ctx = ctx.setValue(PINGOPS_SESSION_ID, attrs.sessionId);
|
|
@@ -305,5 +321,5 @@ function setAttributesInContext(ctx, attrs) {
|
|
|
305
321
|
}
|
|
306
322
|
|
|
307
323
|
//#endregion
|
|
308
|
-
export {
|
|
309
|
-
//# sourceMappingURL=pingops-
|
|
324
|
+
export { shutdownPingops as a, mergeConfigWithEnv as c, runUnsuppressed as i, getActiveTraceId as n, startTrace as o, initializePingops as r, loadConfigFromFile as s, getActiveSpanId as t };
|
|
325
|
+
//# sourceMappingURL=pingops-SV21E4bp.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pingops-SV21E4bp.mjs","names":["loadYaml","isSdkInitializedFlag"],"sources":["../src/config-loader.ts","../src/init-state.ts","../src/pingops.ts"],"sourcesContent":["/**\n * Configuration loader for reading PingOps config from JSON/YAML files\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { load as loadYaml } from \"js-yaml\";\nimport type { PingopsProcessorConfig } from \"@pingops/otel\";\n\n/**\n * Loads configuration from a JSON or YAML file\n *\n * @param filePath - Path to the config file (JSON or YAML)\n * @returns Parsed configuration object\n * @throws Error if file cannot be read or parsed\n */\nexport function loadConfigFromFile(\n filePath: string\n): Partial<PingopsProcessorConfig> {\n const resolvedPath = resolve(filePath);\n const fileContent = readFileSync(resolvedPath, \"utf-8\");\n\n const ext = resolvedPath.toLowerCase();\n if (ext.endsWith(\".yaml\") || ext.endsWith(\".yml\")) {\n return (loadYaml(fileContent) as Partial<PingopsProcessorConfig>) || {};\n } else if (ext.endsWith(\".json\")) {\n return JSON.parse(fileContent) as Partial<PingopsProcessorConfig>;\n } else {\n // Try to parse as JSON first, then YAML\n try {\n return JSON.parse(fileContent) as Partial<PingopsProcessorConfig>;\n } catch {\n return (loadYaml(fileContent) as Partial<PingopsProcessorConfig>) || {};\n }\n }\n}\n\n/**\n * Merges configuration from file and environment variables.\n * Environment variables take precedence over file config.\n *\n * @param fileConfig - Configuration loaded from file\n * @returns Merged configuration with env vars taking precedence\n */\nexport function mergeConfigWithEnv(\n fileConfig: Partial<PingopsProcessorConfig>\n): Partial<PingopsProcessorConfig> {\n const envConfig: Partial<PingopsProcessorConfig> = {};\n\n // Read from environment variables\n if (process.env.PINGOPS_API_KEY) {\n envConfig.apiKey = process.env.PINGOPS_API_KEY;\n }\n if (process.env.PINGOPS_BASE_URL) {\n envConfig.baseUrl = process.env.PINGOPS_BASE_URL;\n }\n if (process.env.PINGOPS_SERVICE_NAME) {\n envConfig.serviceName = process.env.PINGOPS_SERVICE_NAME;\n }\n if (process.env.PINGOPS_DEBUG) {\n envConfig.debug = process.env.PINGOPS_DEBUG === \"true\";\n }\n if (process.env.PINGOPS_BATCH_SIZE) {\n envConfig.batchSize = parseInt(process.env.PINGOPS_BATCH_SIZE, 10);\n }\n if (process.env.PINGOPS_BATCH_TIMEOUT) {\n envConfig.batchTimeout = parseInt(process.env.PINGOPS_BATCH_TIMEOUT, 10);\n }\n if (process.env.PINGOPS_EXPORT_MODE) {\n envConfig.exportMode = process.env.PINGOPS_EXPORT_MODE as\n | \"batched\"\n | \"immediate\";\n }\n\n // Merge: env vars override file config\n return {\n ...fileConfig,\n ...envConfig,\n };\n}\n","/**\n * Shared state for tracking SDK initialization\n * This module exists to avoid circular dependencies between pingops.ts and instrumentation.ts\n */\n\nlet isSdkInitializedFlag = false;\n\n/**\n * Returns whether the SDK has been initialized.\n */\nexport function isSdkInitialized(): boolean {\n return isSdkInitializedFlag;\n}\n\n/**\n * Sets the SDK initialization flag.\n * Called by initializePingops when the SDK is initialized.\n */\nexport function setSdkInitialized(initialized: boolean): void {\n isSdkInitializedFlag = initialized;\n}\n","/**\n * PingOps SDK singleton for manual instrumentation\n *\n * Provides initializePingops, shutdownPingops, startTrace, getActiveTraceId,\n * and getActiveSpanId. startTrace can auto-initialize from environment variables if needed.\n */\n\nimport { ROOT_CONTEXT, context, trace } from \"@opentelemetry/api\";\nimport { isTracingSuppressed } from \"@opentelemetry/core\";\nimport { NodeSDK } from \"@opentelemetry/sdk-node\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport { ATTR_SERVICE_NAME } from \"@opentelemetry/semantic-conventions\";\nimport { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport type { PingopsProcessorConfig } from \"@pingops/otel\";\nimport {\n setPingopsTracerProvider,\n shutdownTracerProvider,\n PingopsSpanProcessor,\n} from \"@pingops/otel\";\nimport {\n createLogger,\n createTraceId,\n uint8ArrayToHex,\n type PingopsTraceAttributes,\n} from \"@pingops/core\";\nimport {\n PINGOPS_TRACE_ID,\n PINGOPS_USER_ID,\n PINGOPS_SESSION_ID,\n PINGOPS_TAGS,\n PINGOPS_METADATA,\n PINGOPS_CAPTURE_REQUEST_BODY,\n PINGOPS_CAPTURE_RESPONSE_BODY,\n} from \"@pingops/core\";\nimport { loadConfigFromFile, mergeConfigWithEnv } from \"./config-loader\";\nimport { setSdkInitialized } from \"./init-state\";\nimport { getPingopsTracerProvider } from \"@pingops/otel\";\nimport { getInstrumentations } from \"@pingops/otel\";\n\nconst TRACE_FLAG_SAMPLED = 1;\n\nconst initLogger = createLogger(\"[PingOps Initialize]\");\nconst logger = createLogger(\"[PingOps Pingops]\");\n\nlet sdkInstance: NodeSDK | null = null;\nlet isSdkInitializedFlag = false;\n\n/**\n * Global state to track initialization\n */\nlet isInitialized = false;\nlet initializationPromise: Promise<void> | null = null;\nlet hasLoggedSuppressedStartTraceWarning = false;\n\n/**\n * Initializes PingOps SDK\n *\n * This function:\n * 1. Creates an OpenTelemetry NodeSDK instance\n * 2. Configures Resource with service.name\n * 3. Registers PingopsSpanProcessor\n * 4. Enables HTTP/fetch/GenAI instrumentation\n * 5. Starts the SDK\n *\n * @param config - Configuration object, config file path, or config file wrapper\n * @param explicit - Whether this is an explicit call (default: true).\n * Set to false when called internally by startTrace auto-initialization.\n */\nexport function initializePingops(\n config: PingopsProcessorConfig,\n explicit?: boolean\n): void;\nexport function initializePingops(\n configFilePath: string,\n explicit?: boolean\n): void;\nexport function initializePingops(\n config: { configFile: string },\n explicit?: boolean\n): void;\nexport function initializePingops(\n config:\n | PingopsProcessorConfig\n | string\n | {\n configFile: string;\n },\n explicit: boolean = true\n): void {\n void explicit; // Ignored: SDK always uses global instrumentation\n const resolvedConfig: PingopsProcessorConfig =\n typeof config === \"string\"\n ? resolveConfigFromFile(config)\n : \"configFile\" in config\n ? resolveConfigFromFile(config.configFile)\n : config;\n\n if (isSdkInitializedFlag) {\n if (resolvedConfig.debug) {\n initLogger.warn(\"[PingOps] SDK already initialized, skipping\");\n }\n return;\n }\n\n // Create resource with service name\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: resolvedConfig.serviceName,\n });\n\n const processor = new PingopsSpanProcessor(resolvedConfig);\n const instrumentations = getInstrumentations();\n\n // Node.js SDK\n const nodeSdk = new NodeSDK({\n resource,\n spanProcessors: [processor],\n instrumentations,\n });\n\n nodeSdk.start();\n sdkInstance = nodeSdk;\n\n // Mark SDK as initialized\n isSdkInitializedFlag = true;\n\n setSdkInitialized(true);\n\n // Initialize isolated TracerProvider for manual spans AFTER NodeSDK starts\n // This ensures manual spans created via startSpan are processed by the same processor\n // We register it after NodeSDK so it takes precedence as the global provider\n try {\n // In version 2.2.0, span processors are passed in the constructor\n const isolatedProvider = new NodeTracerProvider({\n resource,\n spanProcessors: [processor],\n });\n\n // Register the provider globally\n isolatedProvider.register();\n\n // Set it in global state\n setPingopsTracerProvider(isolatedProvider);\n } catch (error) {\n if (resolvedConfig.debug) {\n initLogger.error(\n \"[PingOps] Failed to create isolated TracerProvider:\",\n error instanceof Error ? error.message : String(error)\n );\n }\n // Continue without isolated provider - manual spans will use global provider\n }\n\n if (resolvedConfig.debug) {\n initLogger.info(\"[PingOps] SDK initialized\");\n }\n}\n\nfunction resolveConfigFromFile(configFilePath: string): PingopsProcessorConfig {\n const fileConfig = loadConfigFromFile(configFilePath);\n const mergedConfig = mergeConfigWithEnv(fileConfig);\n\n if (!mergedConfig.baseUrl || !mergedConfig.serviceName) {\n const missing = [\n !mergedConfig.baseUrl && \"baseUrl (or PINGOPS_BASE_URL)\",\n !mergedConfig.serviceName && \"serviceName (or PINGOPS_SERVICE_NAME)\",\n ].filter(Boolean);\n\n throw new Error(\n `initializePingops(configFile) requires ${missing.join(\" and \")}. ` +\n `Provide them in the config file or via environment variables.`\n );\n }\n\n return mergedConfig as PingopsProcessorConfig;\n}\n\n/**\n * Shuts down the SDK and flushes remaining spans\n */\nexport async function shutdownPingops(): Promise<void> {\n // Shutdown isolated TracerProvider first\n await shutdownTracerProvider();\n\n if (!sdkInstance) {\n return;\n }\n\n await sdkInstance.shutdown();\n sdkInstance = null;\n isSdkInitializedFlag = false;\n setSdkInitialized(false);\n}\n\n/**\n * Checks if the SDK is already initialized by checking if a NodeTracerProvider is available\n */\nfunction isSdkInitialized(): boolean {\n try {\n const provider = getPingopsTracerProvider();\n // If we have a NodeTracerProvider (not the default NoOpTracerProvider), SDK is initialized\n const initialized = provider instanceof NodeTracerProvider;\n logger.debug(\"Checked SDK initialization status\", {\n initialized,\n providerType: provider.constructor.name,\n });\n return initialized;\n } catch (error) {\n logger.debug(\"Error checking SDK initialization status\", {\n error: error instanceof Error ? error.message : String(error),\n });\n return false;\n }\n}\n\n/**\n * Auto-initializes the SDK from environment variables if not already initialized\n */\nasync function ensureInitialized(): Promise<void> {\n // Check if SDK is already initialized (e.g., by calling initializePingops directly)\n if (isSdkInitialized()) {\n logger.debug(\"SDK already initialized, skipping auto-initialization\");\n isInitialized = true;\n return;\n }\n\n if (isInitialized) {\n logger.debug(\"SDK initialization flag already set, skipping\");\n return;\n }\n\n // If initialization is in progress, wait for it\n if (initializationPromise) {\n logger.debug(\"SDK initialization already in progress, waiting...\");\n return initializationPromise;\n }\n\n // Start initialization\n logger.info(\"Starting SDK auto-initialization from environment variables\");\n initializationPromise = Promise.resolve().then(() => {\n const apiKey = process.env.PINGOPS_API_KEY;\n const baseUrl = process.env.PINGOPS_BASE_URL;\n const serviceName = process.env.PINGOPS_SERVICE_NAME;\n const debug = process.env.PINGOPS_DEBUG === \"true\";\n\n logger.debug(\"Reading environment variables\", {\n hasApiKey: !!apiKey,\n hasBaseUrl: !!baseUrl,\n hasServiceName: !!serviceName,\n debug,\n });\n\n if (!apiKey || !baseUrl || !serviceName) {\n const missing = [\n !apiKey && \"PINGOPS_API_KEY\",\n !baseUrl && \"PINGOPS_BASE_URL\",\n !serviceName && \"PINGOPS_SERVICE_NAME\",\n ].filter(Boolean);\n\n logger.error(\n \"Missing required environment variables for auto-initialization\",\n {\n missing,\n }\n );\n\n throw new Error(\n `PingOps SDK auto-initialization requires PINGOPS_API_KEY, PINGOPS_BASE_URL, and PINGOPS_SERVICE_NAME environment variables. Missing: ${missing.join(\", \")}`\n );\n }\n\n const config: PingopsProcessorConfig = {\n apiKey,\n baseUrl,\n serviceName,\n debug,\n };\n\n logger.info(\"Initializing SDK with config\", {\n baseUrl,\n serviceName,\n debug,\n });\n\n // Call initializePingops with explicit=false since this is auto-initialization\n initializePingops(config, false);\n isInitialized = true;\n\n logger.info(\"SDK auto-initialization completed successfully\");\n });\n\n try {\n await initializationPromise;\n } catch (error) {\n logger.error(\"SDK auto-initialization failed\", {\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n initializationPromise = null;\n }\n}\n\n/**\n * Returns the trace ID of the currently active span, if any.\n */\nexport function getActiveTraceId(): string | undefined {\n return trace.getActiveSpan()?.spanContext().traceId;\n}\n\n/**\n * Returns the span ID of the currently active span, if any.\n */\nexport function getActiveSpanId(): string | undefined {\n return trace.getActiveSpan()?.spanContext().spanId;\n}\n\n/**\n * Starts a new trace using the PingOps tracer provider and runs the callback within that trace.\n * Sets attributes (traceId, userId, sessionId, tags, metadata, etc.) in context so they are\n * propagated to spans created within the callback.\n *\n * @param options - Options including optional attributes and optional seed for deterministic traceId\n * @param fn - Function to execute within the trace and attribute context\n * @returns Promise resolving to the result of the function\n *\n * @example\n * ```typescript\n * import { startTrace, initializePingops } from '@pingops/sdk';\n *\n * initializePingops({ ... });\n *\n * const result = await startTrace({\n * attributes: {\n * userId: 'user-123',\n * sessionId: 'session-456',\n * tags: ['production', 'api'],\n * metadata: { environment: 'prod', version: '1.0.0' }\n * },\n * seed: 'request-123' // optional: deterministic traceId from this seed\n * }, async () => {\n * const response = await fetch('https://api.example.com/users/123');\n * return response.json();\n * });\n * ```\n */\nexport async function startTrace<T>(\n options: { attributes?: PingopsTraceAttributes; seed?: string },\n fn: () => T | Promise<T>\n): Promise<T> {\n if (!isSdkInitialized()) {\n await ensureInitialized();\n }\n\n const traceId =\n options.attributes?.traceId ?? (await createTraceId(options?.seed));\n const parentSpanId = uint8ArrayToHex(\n crypto.getRandomValues(new Uint8Array(8))\n );\n\n const spanContext = {\n traceId,\n spanId: parentSpanId,\n traceFlags: TRACE_FLAG_SAMPLED,\n };\n\n const activeContext = context.active();\n const traceExecutionBaseContext = isTracingSuppressed(activeContext)\n ? ROOT_CONTEXT\n : activeContext;\n if (traceExecutionBaseContext === ROOT_CONTEXT) {\n if (!hasLoggedSuppressedStartTraceWarning) {\n logger.warn(\n \"startTrace detected a suppressed active context and is running on ROOT_CONTEXT to prevent suppression leakage into user outbound instrumentation\"\n );\n hasLoggedSuppressedStartTraceWarning = true;\n } else {\n logger.debug(\n \"startTrace received a suppressed active context; running trace on ROOT_CONTEXT\"\n );\n }\n }\n const contextWithSpanContext = trace.setSpanContext(\n traceExecutionBaseContext,\n spanContext\n );\n\n const tracer = getPingopsTracerProvider().getTracer(\"pingops-sdk\", \"1.0.0\");\n\n return new Promise((resolve, reject) => {\n tracer.startActiveSpan(\n \"pingops-trace\",\n {},\n contextWithSpanContext,\n (span) => {\n let contextWithAttributes = context.active();\n const attrs = options.attributes;\n if (attrs) {\n contextWithAttributes = setAttributesInContext(\n contextWithAttributes,\n attrs\n );\n }\n contextWithAttributes = contextWithAttributes.setValue(\n PINGOPS_TRACE_ID,\n traceId\n );\n\n const run = () => fn();\n\n try {\n const result = context.with(contextWithAttributes, run);\n if (result instanceof Promise) {\n result\n .then((v) => {\n span.end();\n resolve(v);\n })\n .catch((err) => {\n span.end();\n reject(err instanceof Error ? err : new Error(String(err)));\n });\n } else {\n span.end();\n resolve(result);\n }\n } catch (err) {\n span.end();\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n }\n );\n });\n}\n\n/**\n * Runs a callback in a context that is guaranteed to be unsuppressed.\n * Useful for task/job boundaries where suppression may have leaked.\n */\nexport function runUnsuppressed<T>(fn: () => T): T {\n const activeContext = context.active();\n const unsuppressedContext = isTracingSuppressed(activeContext)\n ? ROOT_CONTEXT\n : activeContext;\n return context.with(unsuppressedContext, fn);\n}\n\nfunction setAttributesInContext(\n ctx: ReturnType<typeof context.active>,\n attrs: PingopsTraceAttributes\n): ReturnType<typeof context.active> {\n if (attrs.userId !== undefined) {\n ctx = ctx.setValue(PINGOPS_USER_ID, attrs.userId);\n }\n if (attrs.sessionId !== undefined) {\n ctx = ctx.setValue(PINGOPS_SESSION_ID, attrs.sessionId);\n }\n if (attrs.tags !== undefined) {\n ctx = ctx.setValue(PINGOPS_TAGS, attrs.tags);\n }\n if (attrs.metadata !== undefined) {\n ctx = ctx.setValue(PINGOPS_METADATA, attrs.metadata);\n }\n if (attrs.captureRequestBody !== undefined) {\n ctx = ctx.setValue(PINGOPS_CAPTURE_REQUEST_BODY, attrs.captureRequestBody);\n }\n if (attrs.captureResponseBody !== undefined) {\n ctx = ctx.setValue(\n PINGOPS_CAPTURE_RESPONSE_BODY,\n attrs.captureResponseBody\n );\n }\n return ctx;\n}\n"],"mappings":"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"}
|
package/dist/register.cjs
CHANGED
package/dist/register.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pingops/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20"
|
|
@@ -39,24 +39,27 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@opentelemetry/api": "^1.9.0",
|
|
42
|
+
"@opentelemetry/core": "^2.2.0",
|
|
42
43
|
"@opentelemetry/resources": "^2.2.0",
|
|
43
44
|
"@opentelemetry/sdk-node": "^0.208.0",
|
|
44
45
|
"@opentelemetry/sdk-trace-node": "^2.2.0",
|
|
45
46
|
"@opentelemetry/semantic-conventions": "^1.38.0",
|
|
46
47
|
"js-yaml": "^4.1.0",
|
|
47
|
-
"@pingops/otel": "^0.2.
|
|
48
|
-
"@pingops/core": "^0.2.
|
|
48
|
+
"@pingops/otel": "^0.2.2",
|
|
49
|
+
"@pingops/core": "^0.2.2"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@types/node": "^20.11.0",
|
|
52
53
|
"@types/js-yaml": "^4.0.9",
|
|
53
54
|
"axios": "^1.6.2",
|
|
55
|
+
"vitest": "^3.2.4",
|
|
54
56
|
"tsx": "^4.7.0",
|
|
55
57
|
"typescript": "^5.6.0"
|
|
56
58
|
},
|
|
57
59
|
"scripts": {
|
|
58
60
|
"build": "tsdown",
|
|
59
61
|
"dev": "tsdown --watch",
|
|
60
|
-
"clean": "rm -rf dist"
|
|
62
|
+
"clean": "rm -rf dist",
|
|
63
|
+
"test": "vitest run"
|
|
61
64
|
}
|
|
62
65
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pingops-BBXegVtL.cjs","names":["isSdkInitializedFlag","ATTR_SERVICE_NAME","PingopsSpanProcessor","NodeSDK","NodeTracerProvider","trace","context","PINGOPS_TRACE_ID","PINGOPS_USER_ID","PINGOPS_SESSION_ID","PINGOPS_TAGS","PINGOPS_METADATA","PINGOPS_CAPTURE_REQUEST_BODY","PINGOPS_CAPTURE_RESPONSE_BODY"],"sources":["../src/config-loader.ts","../src/init-state.ts","../src/pingops.ts"],"sourcesContent":["/**\n * Configuration loader for reading PingOps config from JSON/YAML files\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { load as loadYaml } from \"js-yaml\";\nimport type { PingopsProcessorConfig } from \"@pingops/otel\";\n\n/**\n * Loads configuration from a JSON or YAML file\n *\n * @param filePath - Path to the config file (JSON or YAML)\n * @returns Parsed configuration object\n * @throws Error if file cannot be read or parsed\n */\nexport function loadConfigFromFile(\n filePath: string\n): Partial<PingopsProcessorConfig> {\n const resolvedPath = resolve(filePath);\n const fileContent = readFileSync(resolvedPath, \"utf-8\");\n\n const ext = resolvedPath.toLowerCase();\n if (ext.endsWith(\".yaml\") || ext.endsWith(\".yml\")) {\n return (loadYaml(fileContent) as Partial<PingopsProcessorConfig>) || {};\n } else if (ext.endsWith(\".json\")) {\n return JSON.parse(fileContent) as Partial<PingopsProcessorConfig>;\n } else {\n // Try to parse as JSON first, then YAML\n try {\n return JSON.parse(fileContent) as Partial<PingopsProcessorConfig>;\n } catch {\n return (loadYaml(fileContent) as Partial<PingopsProcessorConfig>) || {};\n }\n }\n}\n\n/**\n * Merges configuration from file and environment variables.\n * Environment variables take precedence over file config.\n *\n * @param fileConfig - Configuration loaded from file\n * @returns Merged configuration with env vars taking precedence\n */\nexport function mergeConfigWithEnv(\n fileConfig: Partial<PingopsProcessorConfig>\n): Partial<PingopsProcessorConfig> {\n const envConfig: Partial<PingopsProcessorConfig> = {};\n\n // Read from environment variables\n if (process.env.PINGOPS_API_KEY) {\n envConfig.apiKey = process.env.PINGOPS_API_KEY;\n }\n if (process.env.PINGOPS_BASE_URL) {\n envConfig.baseUrl = process.env.PINGOPS_BASE_URL;\n }\n if (process.env.PINGOPS_SERVICE_NAME) {\n envConfig.serviceName = process.env.PINGOPS_SERVICE_NAME;\n }\n if (process.env.PINGOPS_DEBUG) {\n envConfig.debug = process.env.PINGOPS_DEBUG === \"true\";\n }\n if (process.env.PINGOPS_BATCH_SIZE) {\n envConfig.batchSize = parseInt(process.env.PINGOPS_BATCH_SIZE, 10);\n }\n if (process.env.PINGOPS_BATCH_TIMEOUT) {\n envConfig.batchTimeout = parseInt(process.env.PINGOPS_BATCH_TIMEOUT, 10);\n }\n if (process.env.PINGOPS_EXPORT_MODE) {\n envConfig.exportMode = process.env.PINGOPS_EXPORT_MODE as\n | \"batched\"\n | \"immediate\";\n }\n\n // Merge: env vars override file config\n return {\n ...fileConfig,\n ...envConfig,\n };\n}\n","/**\n * Shared state for tracking SDK initialization\n * This module exists to avoid circular dependencies between pingops.ts and instrumentation.ts\n */\n\nlet isSdkInitializedFlag = false;\n\n/**\n * Returns whether the SDK has been initialized.\n */\nexport function isSdkInitialized(): boolean {\n return isSdkInitializedFlag;\n}\n\n/**\n * Sets the SDK initialization flag.\n * Called by initializePingops when the SDK is initialized.\n */\nexport function setSdkInitialized(initialized: boolean): void {\n isSdkInitializedFlag = initialized;\n}\n","/**\n * PingOps SDK singleton for manual instrumentation\n *\n * Provides initializePingops, shutdownPingops, startTrace, getActiveTraceId,\n * and getActiveSpanId. startTrace can auto-initialize from environment variables if needed.\n */\n\nimport { context, trace } from \"@opentelemetry/api\";\nimport { NodeSDK } from \"@opentelemetry/sdk-node\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport { ATTR_SERVICE_NAME } from \"@opentelemetry/semantic-conventions\";\nimport { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport type { PingopsProcessorConfig } from \"@pingops/otel\";\nimport {\n setPingopsTracerProvider,\n shutdownTracerProvider,\n PingopsSpanProcessor,\n} from \"@pingops/otel\";\nimport {\n createLogger,\n createTraceId,\n uint8ArrayToHex,\n type PingopsTraceAttributes,\n} from \"@pingops/core\";\nimport {\n PINGOPS_TRACE_ID,\n PINGOPS_USER_ID,\n PINGOPS_SESSION_ID,\n PINGOPS_TAGS,\n PINGOPS_METADATA,\n PINGOPS_CAPTURE_REQUEST_BODY,\n PINGOPS_CAPTURE_RESPONSE_BODY,\n} from \"@pingops/core\";\nimport { loadConfigFromFile, mergeConfigWithEnv } from \"./config-loader\";\nimport { setSdkInitialized } from \"./init-state\";\nimport { getPingopsTracerProvider } from \"@pingops/otel\";\nimport { getInstrumentations } from \"@pingops/otel\";\n\nconst TRACE_FLAG_SAMPLED = 1;\n\nconst initLogger = createLogger(\"[PingOps Initialize]\");\nconst logger = createLogger(\"[PingOps Pingops]\");\n\nlet sdkInstance: NodeSDK | null = null;\nlet isSdkInitializedFlag = false;\n\n/**\n * Global state to track initialization\n */\nlet isInitialized = false;\nlet initializationPromise: Promise<void> | null = null;\n\n/**\n * Initializes PingOps SDK\n *\n * This function:\n * 1. Creates an OpenTelemetry NodeSDK instance\n * 2. Configures Resource with service.name\n * 3. Registers PingopsSpanProcessor\n * 4. Enables HTTP/fetch/GenAI instrumentation\n * 5. Starts the SDK\n *\n * @param config - Configuration object, config file path, or config file wrapper\n * @param explicit - Whether this is an explicit call (default: true).\n * Set to false when called internally by startTrace auto-initialization.\n */\nexport function initializePingops(\n config: PingopsProcessorConfig,\n explicit?: boolean\n): void;\nexport function initializePingops(\n configFilePath: string,\n explicit?: boolean\n): void;\nexport function initializePingops(\n config: { configFile: string },\n explicit?: boolean\n): void;\nexport function initializePingops(\n config:\n | PingopsProcessorConfig\n | string\n | {\n configFile: string;\n },\n explicit: boolean = true\n): void {\n void explicit; // Ignored: SDK always uses global instrumentation\n const resolvedConfig: PingopsProcessorConfig =\n typeof config === \"string\"\n ? resolveConfigFromFile(config)\n : \"configFile\" in config\n ? resolveConfigFromFile(config.configFile)\n : config;\n\n if (isSdkInitializedFlag) {\n if (resolvedConfig.debug) {\n initLogger.warn(\"[PingOps] SDK already initialized, skipping\");\n }\n return;\n }\n\n // Create resource with service name\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: resolvedConfig.serviceName,\n });\n\n const processor = new PingopsSpanProcessor(resolvedConfig);\n const instrumentations = getInstrumentations();\n\n // Node.js SDK\n const nodeSdk = new NodeSDK({\n resource,\n spanProcessors: [processor],\n instrumentations,\n });\n\n nodeSdk.start();\n sdkInstance = nodeSdk;\n\n // Mark SDK as initialized\n isSdkInitializedFlag = true;\n\n setSdkInitialized(true);\n\n // Initialize isolated TracerProvider for manual spans AFTER NodeSDK starts\n // This ensures manual spans created via startSpan are processed by the same processor\n // We register it after NodeSDK so it takes precedence as the global provider\n try {\n // In version 2.2.0, span processors are passed in the constructor\n const isolatedProvider = new NodeTracerProvider({\n resource,\n spanProcessors: [processor],\n });\n\n // Register the provider globally\n isolatedProvider.register();\n\n // Set it in global state\n setPingopsTracerProvider(isolatedProvider);\n } catch (error) {\n if (resolvedConfig.debug) {\n initLogger.error(\n \"[PingOps] Failed to create isolated TracerProvider:\",\n error instanceof Error ? error.message : String(error)\n );\n }\n // Continue without isolated provider - manual spans will use global provider\n }\n\n if (resolvedConfig.debug) {\n initLogger.info(\"[PingOps] SDK initialized\");\n }\n}\n\nfunction resolveConfigFromFile(configFilePath: string): PingopsProcessorConfig {\n const fileConfig = loadConfigFromFile(configFilePath);\n const mergedConfig = mergeConfigWithEnv(fileConfig);\n\n if (!mergedConfig.baseUrl || !mergedConfig.serviceName) {\n const missing = [\n !mergedConfig.baseUrl && \"baseUrl (or PINGOPS_BASE_URL)\",\n !mergedConfig.serviceName && \"serviceName (or PINGOPS_SERVICE_NAME)\",\n ].filter(Boolean);\n\n throw new Error(\n `initializePingops(configFile) requires ${missing.join(\" and \")}. ` +\n `Provide them in the config file or via environment variables.`\n );\n }\n\n return mergedConfig as PingopsProcessorConfig;\n}\n\n/**\n * Shuts down the SDK and flushes remaining spans\n */\nexport async function shutdownPingops(): Promise<void> {\n // Shutdown isolated TracerProvider first\n await shutdownTracerProvider();\n\n if (!sdkInstance) {\n return;\n }\n\n await sdkInstance.shutdown();\n sdkInstance = null;\n isSdkInitializedFlag = false;\n setSdkInitialized(false);\n}\n\n/**\n * Checks if the SDK is already initialized by checking if a NodeTracerProvider is available\n */\nfunction isSdkInitialized(): boolean {\n try {\n const provider = getPingopsTracerProvider();\n // If we have a NodeTracerProvider (not the default NoOpTracerProvider), SDK is initialized\n const initialized = provider instanceof NodeTracerProvider;\n logger.debug(\"Checked SDK initialization status\", {\n initialized,\n providerType: provider.constructor.name,\n });\n return initialized;\n } catch (error) {\n logger.debug(\"Error checking SDK initialization status\", {\n error: error instanceof Error ? error.message : String(error),\n });\n return false;\n }\n}\n\n/**\n * Auto-initializes the SDK from environment variables if not already initialized\n */\nasync function ensureInitialized(): Promise<void> {\n // Check if SDK is already initialized (e.g., by calling initializePingops directly)\n if (isSdkInitialized()) {\n logger.debug(\"SDK already initialized, skipping auto-initialization\");\n isInitialized = true;\n return;\n }\n\n if (isInitialized) {\n logger.debug(\"SDK initialization flag already set, skipping\");\n return;\n }\n\n // If initialization is in progress, wait for it\n if (initializationPromise) {\n logger.debug(\"SDK initialization already in progress, waiting...\");\n return initializationPromise;\n }\n\n // Start initialization\n logger.info(\"Starting SDK auto-initialization from environment variables\");\n initializationPromise = Promise.resolve().then(() => {\n const apiKey = process.env.PINGOPS_API_KEY;\n const baseUrl = process.env.PINGOPS_BASE_URL;\n const serviceName = process.env.PINGOPS_SERVICE_NAME;\n const debug = process.env.PINGOPS_DEBUG === \"true\";\n\n logger.debug(\"Reading environment variables\", {\n hasApiKey: !!apiKey,\n hasBaseUrl: !!baseUrl,\n hasServiceName: !!serviceName,\n debug,\n });\n\n if (!apiKey || !baseUrl || !serviceName) {\n const missing = [\n !apiKey && \"PINGOPS_API_KEY\",\n !baseUrl && \"PINGOPS_BASE_URL\",\n !serviceName && \"PINGOPS_SERVICE_NAME\",\n ].filter(Boolean);\n\n logger.error(\n \"Missing required environment variables for auto-initialization\",\n {\n missing,\n }\n );\n\n throw new Error(\n `PingOps SDK auto-initialization requires PINGOPS_API_KEY, PINGOPS_BASE_URL, and PINGOPS_SERVICE_NAME environment variables. Missing: ${missing.join(\", \")}`\n );\n }\n\n const config: PingopsProcessorConfig = {\n apiKey,\n baseUrl,\n serviceName,\n debug,\n };\n\n logger.info(\"Initializing SDK with config\", {\n baseUrl,\n serviceName,\n debug,\n });\n\n // Call initializePingops with explicit=false since this is auto-initialization\n initializePingops(config, false);\n isInitialized = true;\n\n logger.info(\"SDK auto-initialization completed successfully\");\n });\n\n try {\n await initializationPromise;\n } catch (error) {\n logger.error(\"SDK auto-initialization failed\", {\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n initializationPromise = null;\n }\n}\n\n/**\n * Returns the trace ID of the currently active span, if any.\n */\nexport function getActiveTraceId(): string | undefined {\n return trace.getActiveSpan()?.spanContext().traceId;\n}\n\n/**\n * Returns the span ID of the currently active span, if any.\n */\nexport function getActiveSpanId(): string | undefined {\n return trace.getActiveSpan()?.spanContext().spanId;\n}\n\n/**\n * Starts a new trace using the PingOps tracer provider and runs the callback within that trace.\n * Sets attributes (traceId, userId, sessionId, tags, metadata, etc.) in context so they are\n * propagated to spans created within the callback.\n *\n * @param options - Options including optional attributes and optional seed for deterministic traceId\n * @param fn - Function to execute within the trace and attribute context\n * @returns Promise resolving to the result of the function\n *\n * @example\n * ```typescript\n * import { startTrace, initializePingops } from '@pingops/sdk';\n *\n * initializePingops({ ... });\n *\n * const result = await startTrace({\n * attributes: {\n * userId: 'user-123',\n * sessionId: 'session-456',\n * tags: ['production', 'api'],\n * metadata: { environment: 'prod', version: '1.0.0' }\n * },\n * seed: 'request-123' // optional: deterministic traceId from this seed\n * }, async () => {\n * const response = await fetch('https://api.example.com/users/123');\n * return response.json();\n * });\n * ```\n */\nexport async function startTrace<T>(\n options: { attributes?: PingopsTraceAttributes; seed?: string },\n fn: () => T | Promise<T>\n): Promise<T> {\n if (!isSdkInitialized()) {\n await ensureInitialized();\n }\n\n const traceId =\n options.attributes?.traceId ?? (await createTraceId(options?.seed));\n const parentSpanId = uint8ArrayToHex(\n crypto.getRandomValues(new Uint8Array(8))\n );\n\n const spanContext = {\n traceId,\n spanId: parentSpanId,\n traceFlags: TRACE_FLAG_SAMPLED,\n };\n\n const activeContext = context.active();\n const contextWithSpanContext = trace.setSpanContext(\n activeContext,\n spanContext\n );\n\n const tracer = getPingopsTracerProvider().getTracer(\"pingops-sdk\", \"1.0.0\");\n\n return new Promise((resolve, reject) => {\n tracer.startActiveSpan(\n \"pingops-trace\",\n {},\n contextWithSpanContext,\n (span) => {\n let contextWithAttributes = context.active();\n const attrs = options.attributes;\n if (attrs) {\n contextWithAttributes = setAttributesInContext(\n contextWithAttributes,\n attrs\n );\n }\n contextWithAttributes = contextWithAttributes.setValue(\n PINGOPS_TRACE_ID,\n traceId\n );\n\n const run = () => fn();\n\n try {\n const result = context.with(contextWithAttributes, run);\n if (result instanceof Promise) {\n result\n .then((v) => {\n span.end();\n resolve(v);\n })\n .catch((err) => {\n span.end();\n reject(err instanceof Error ? err : new Error(String(err)));\n });\n } else {\n span.end();\n resolve(result);\n }\n } catch (err) {\n span.end();\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n }\n );\n });\n}\n\nfunction setAttributesInContext(\n ctx: ReturnType<typeof context.active>,\n attrs: PingopsTraceAttributes\n): ReturnType<typeof context.active> {\n if (attrs.userId !== undefined) {\n ctx = ctx.setValue(PINGOPS_USER_ID, attrs.userId);\n }\n if (attrs.sessionId !== undefined) {\n ctx = ctx.setValue(PINGOPS_SESSION_ID, attrs.sessionId);\n }\n if (attrs.tags !== undefined) {\n ctx = ctx.setValue(PINGOPS_TAGS, attrs.tags);\n }\n if (attrs.metadata !== undefined) {\n ctx = ctx.setValue(PINGOPS_METADATA, attrs.metadata);\n }\n if (attrs.captureRequestBody !== undefined) {\n ctx = ctx.setValue(PINGOPS_CAPTURE_REQUEST_BODY, attrs.captureRequestBody);\n }\n if (attrs.captureResponseBody !== undefined) {\n ctx = ctx.setValue(\n PINGOPS_CAPTURE_RESPONSE_BODY,\n attrs.captureResponseBody\n );\n }\n return ctx;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pingops-D4xrGLxy.mjs","names":["loadYaml","isSdkInitializedFlag"],"sources":["../src/config-loader.ts","../src/init-state.ts","../src/pingops.ts"],"sourcesContent":["/**\n * Configuration loader for reading PingOps config from JSON/YAML files\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { load as loadYaml } from \"js-yaml\";\nimport type { PingopsProcessorConfig } from \"@pingops/otel\";\n\n/**\n * Loads configuration from a JSON or YAML file\n *\n * @param filePath - Path to the config file (JSON or YAML)\n * @returns Parsed configuration object\n * @throws Error if file cannot be read or parsed\n */\nexport function loadConfigFromFile(\n filePath: string\n): Partial<PingopsProcessorConfig> {\n const resolvedPath = resolve(filePath);\n const fileContent = readFileSync(resolvedPath, \"utf-8\");\n\n const ext = resolvedPath.toLowerCase();\n if (ext.endsWith(\".yaml\") || ext.endsWith(\".yml\")) {\n return (loadYaml(fileContent) as Partial<PingopsProcessorConfig>) || {};\n } else if (ext.endsWith(\".json\")) {\n return JSON.parse(fileContent) as Partial<PingopsProcessorConfig>;\n } else {\n // Try to parse as JSON first, then YAML\n try {\n return JSON.parse(fileContent) as Partial<PingopsProcessorConfig>;\n } catch {\n return (loadYaml(fileContent) as Partial<PingopsProcessorConfig>) || {};\n }\n }\n}\n\n/**\n * Merges configuration from file and environment variables.\n * Environment variables take precedence over file config.\n *\n * @param fileConfig - Configuration loaded from file\n * @returns Merged configuration with env vars taking precedence\n */\nexport function mergeConfigWithEnv(\n fileConfig: Partial<PingopsProcessorConfig>\n): Partial<PingopsProcessorConfig> {\n const envConfig: Partial<PingopsProcessorConfig> = {};\n\n // Read from environment variables\n if (process.env.PINGOPS_API_KEY) {\n envConfig.apiKey = process.env.PINGOPS_API_KEY;\n }\n if (process.env.PINGOPS_BASE_URL) {\n envConfig.baseUrl = process.env.PINGOPS_BASE_URL;\n }\n if (process.env.PINGOPS_SERVICE_NAME) {\n envConfig.serviceName = process.env.PINGOPS_SERVICE_NAME;\n }\n if (process.env.PINGOPS_DEBUG) {\n envConfig.debug = process.env.PINGOPS_DEBUG === \"true\";\n }\n if (process.env.PINGOPS_BATCH_SIZE) {\n envConfig.batchSize = parseInt(process.env.PINGOPS_BATCH_SIZE, 10);\n }\n if (process.env.PINGOPS_BATCH_TIMEOUT) {\n envConfig.batchTimeout = parseInt(process.env.PINGOPS_BATCH_TIMEOUT, 10);\n }\n if (process.env.PINGOPS_EXPORT_MODE) {\n envConfig.exportMode = process.env.PINGOPS_EXPORT_MODE as\n | \"batched\"\n | \"immediate\";\n }\n\n // Merge: env vars override file config\n return {\n ...fileConfig,\n ...envConfig,\n };\n}\n","/**\n * Shared state for tracking SDK initialization\n * This module exists to avoid circular dependencies between pingops.ts and instrumentation.ts\n */\n\nlet isSdkInitializedFlag = false;\n\n/**\n * Returns whether the SDK has been initialized.\n */\nexport function isSdkInitialized(): boolean {\n return isSdkInitializedFlag;\n}\n\n/**\n * Sets the SDK initialization flag.\n * Called by initializePingops when the SDK is initialized.\n */\nexport function setSdkInitialized(initialized: boolean): void {\n isSdkInitializedFlag = initialized;\n}\n","/**\n * PingOps SDK singleton for manual instrumentation\n *\n * Provides initializePingops, shutdownPingops, startTrace, getActiveTraceId,\n * and getActiveSpanId. startTrace can auto-initialize from environment variables if needed.\n */\n\nimport { context, trace } from \"@opentelemetry/api\";\nimport { NodeSDK } from \"@opentelemetry/sdk-node\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport { ATTR_SERVICE_NAME } from \"@opentelemetry/semantic-conventions\";\nimport { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport type { PingopsProcessorConfig } from \"@pingops/otel\";\nimport {\n setPingopsTracerProvider,\n shutdownTracerProvider,\n PingopsSpanProcessor,\n} from \"@pingops/otel\";\nimport {\n createLogger,\n createTraceId,\n uint8ArrayToHex,\n type PingopsTraceAttributes,\n} from \"@pingops/core\";\nimport {\n PINGOPS_TRACE_ID,\n PINGOPS_USER_ID,\n PINGOPS_SESSION_ID,\n PINGOPS_TAGS,\n PINGOPS_METADATA,\n PINGOPS_CAPTURE_REQUEST_BODY,\n PINGOPS_CAPTURE_RESPONSE_BODY,\n} from \"@pingops/core\";\nimport { loadConfigFromFile, mergeConfigWithEnv } from \"./config-loader\";\nimport { setSdkInitialized } from \"./init-state\";\nimport { getPingopsTracerProvider } from \"@pingops/otel\";\nimport { getInstrumentations } from \"@pingops/otel\";\n\nconst TRACE_FLAG_SAMPLED = 1;\n\nconst initLogger = createLogger(\"[PingOps Initialize]\");\nconst logger = createLogger(\"[PingOps Pingops]\");\n\nlet sdkInstance: NodeSDK | null = null;\nlet isSdkInitializedFlag = false;\n\n/**\n * Global state to track initialization\n */\nlet isInitialized = false;\nlet initializationPromise: Promise<void> | null = null;\n\n/**\n * Initializes PingOps SDK\n *\n * This function:\n * 1. Creates an OpenTelemetry NodeSDK instance\n * 2. Configures Resource with service.name\n * 3. Registers PingopsSpanProcessor\n * 4. Enables HTTP/fetch/GenAI instrumentation\n * 5. Starts the SDK\n *\n * @param config - Configuration object, config file path, or config file wrapper\n * @param explicit - Whether this is an explicit call (default: true).\n * Set to false when called internally by startTrace auto-initialization.\n */\nexport function initializePingops(\n config: PingopsProcessorConfig,\n explicit?: boolean\n): void;\nexport function initializePingops(\n configFilePath: string,\n explicit?: boolean\n): void;\nexport function initializePingops(\n config: { configFile: string },\n explicit?: boolean\n): void;\nexport function initializePingops(\n config:\n | PingopsProcessorConfig\n | string\n | {\n configFile: string;\n },\n explicit: boolean = true\n): void {\n void explicit; // Ignored: SDK always uses global instrumentation\n const resolvedConfig: PingopsProcessorConfig =\n typeof config === \"string\"\n ? resolveConfigFromFile(config)\n : \"configFile\" in config\n ? resolveConfigFromFile(config.configFile)\n : config;\n\n if (isSdkInitializedFlag) {\n if (resolvedConfig.debug) {\n initLogger.warn(\"[PingOps] SDK already initialized, skipping\");\n }\n return;\n }\n\n // Create resource with service name\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: resolvedConfig.serviceName,\n });\n\n const processor = new PingopsSpanProcessor(resolvedConfig);\n const instrumentations = getInstrumentations();\n\n // Node.js SDK\n const nodeSdk = new NodeSDK({\n resource,\n spanProcessors: [processor],\n instrumentations,\n });\n\n nodeSdk.start();\n sdkInstance = nodeSdk;\n\n // Mark SDK as initialized\n isSdkInitializedFlag = true;\n\n setSdkInitialized(true);\n\n // Initialize isolated TracerProvider for manual spans AFTER NodeSDK starts\n // This ensures manual spans created via startSpan are processed by the same processor\n // We register it after NodeSDK so it takes precedence as the global provider\n try {\n // In version 2.2.0, span processors are passed in the constructor\n const isolatedProvider = new NodeTracerProvider({\n resource,\n spanProcessors: [processor],\n });\n\n // Register the provider globally\n isolatedProvider.register();\n\n // Set it in global state\n setPingopsTracerProvider(isolatedProvider);\n } catch (error) {\n if (resolvedConfig.debug) {\n initLogger.error(\n \"[PingOps] Failed to create isolated TracerProvider:\",\n error instanceof Error ? error.message : String(error)\n );\n }\n // Continue without isolated provider - manual spans will use global provider\n }\n\n if (resolvedConfig.debug) {\n initLogger.info(\"[PingOps] SDK initialized\");\n }\n}\n\nfunction resolveConfigFromFile(configFilePath: string): PingopsProcessorConfig {\n const fileConfig = loadConfigFromFile(configFilePath);\n const mergedConfig = mergeConfigWithEnv(fileConfig);\n\n if (!mergedConfig.baseUrl || !mergedConfig.serviceName) {\n const missing = [\n !mergedConfig.baseUrl && \"baseUrl (or PINGOPS_BASE_URL)\",\n !mergedConfig.serviceName && \"serviceName (or PINGOPS_SERVICE_NAME)\",\n ].filter(Boolean);\n\n throw new Error(\n `initializePingops(configFile) requires ${missing.join(\" and \")}. ` +\n `Provide them in the config file or via environment variables.`\n );\n }\n\n return mergedConfig as PingopsProcessorConfig;\n}\n\n/**\n * Shuts down the SDK and flushes remaining spans\n */\nexport async function shutdownPingops(): Promise<void> {\n // Shutdown isolated TracerProvider first\n await shutdownTracerProvider();\n\n if (!sdkInstance) {\n return;\n }\n\n await sdkInstance.shutdown();\n sdkInstance = null;\n isSdkInitializedFlag = false;\n setSdkInitialized(false);\n}\n\n/**\n * Checks if the SDK is already initialized by checking if a NodeTracerProvider is available\n */\nfunction isSdkInitialized(): boolean {\n try {\n const provider = getPingopsTracerProvider();\n // If we have a NodeTracerProvider (not the default NoOpTracerProvider), SDK is initialized\n const initialized = provider instanceof NodeTracerProvider;\n logger.debug(\"Checked SDK initialization status\", {\n initialized,\n providerType: provider.constructor.name,\n });\n return initialized;\n } catch (error) {\n logger.debug(\"Error checking SDK initialization status\", {\n error: error instanceof Error ? error.message : String(error),\n });\n return false;\n }\n}\n\n/**\n * Auto-initializes the SDK from environment variables if not already initialized\n */\nasync function ensureInitialized(): Promise<void> {\n // Check if SDK is already initialized (e.g., by calling initializePingops directly)\n if (isSdkInitialized()) {\n logger.debug(\"SDK already initialized, skipping auto-initialization\");\n isInitialized = true;\n return;\n }\n\n if (isInitialized) {\n logger.debug(\"SDK initialization flag already set, skipping\");\n return;\n }\n\n // If initialization is in progress, wait for it\n if (initializationPromise) {\n logger.debug(\"SDK initialization already in progress, waiting...\");\n return initializationPromise;\n }\n\n // Start initialization\n logger.info(\"Starting SDK auto-initialization from environment variables\");\n initializationPromise = Promise.resolve().then(() => {\n const apiKey = process.env.PINGOPS_API_KEY;\n const baseUrl = process.env.PINGOPS_BASE_URL;\n const serviceName = process.env.PINGOPS_SERVICE_NAME;\n const debug = process.env.PINGOPS_DEBUG === \"true\";\n\n logger.debug(\"Reading environment variables\", {\n hasApiKey: !!apiKey,\n hasBaseUrl: !!baseUrl,\n hasServiceName: !!serviceName,\n debug,\n });\n\n if (!apiKey || !baseUrl || !serviceName) {\n const missing = [\n !apiKey && \"PINGOPS_API_KEY\",\n !baseUrl && \"PINGOPS_BASE_URL\",\n !serviceName && \"PINGOPS_SERVICE_NAME\",\n ].filter(Boolean);\n\n logger.error(\n \"Missing required environment variables for auto-initialization\",\n {\n missing,\n }\n );\n\n throw new Error(\n `PingOps SDK auto-initialization requires PINGOPS_API_KEY, PINGOPS_BASE_URL, and PINGOPS_SERVICE_NAME environment variables. Missing: ${missing.join(\", \")}`\n );\n }\n\n const config: PingopsProcessorConfig = {\n apiKey,\n baseUrl,\n serviceName,\n debug,\n };\n\n logger.info(\"Initializing SDK with config\", {\n baseUrl,\n serviceName,\n debug,\n });\n\n // Call initializePingops with explicit=false since this is auto-initialization\n initializePingops(config, false);\n isInitialized = true;\n\n logger.info(\"SDK auto-initialization completed successfully\");\n });\n\n try {\n await initializationPromise;\n } catch (error) {\n logger.error(\"SDK auto-initialization failed\", {\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n initializationPromise = null;\n }\n}\n\n/**\n * Returns the trace ID of the currently active span, if any.\n */\nexport function getActiveTraceId(): string | undefined {\n return trace.getActiveSpan()?.spanContext().traceId;\n}\n\n/**\n * Returns the span ID of the currently active span, if any.\n */\nexport function getActiveSpanId(): string | undefined {\n return trace.getActiveSpan()?.spanContext().spanId;\n}\n\n/**\n * Starts a new trace using the PingOps tracer provider and runs the callback within that trace.\n * Sets attributes (traceId, userId, sessionId, tags, metadata, etc.) in context so they are\n * propagated to spans created within the callback.\n *\n * @param options - Options including optional attributes and optional seed for deterministic traceId\n * @param fn - Function to execute within the trace and attribute context\n * @returns Promise resolving to the result of the function\n *\n * @example\n * ```typescript\n * import { startTrace, initializePingops } from '@pingops/sdk';\n *\n * initializePingops({ ... });\n *\n * const result = await startTrace({\n * attributes: {\n * userId: 'user-123',\n * sessionId: 'session-456',\n * tags: ['production', 'api'],\n * metadata: { environment: 'prod', version: '1.0.0' }\n * },\n * seed: 'request-123' // optional: deterministic traceId from this seed\n * }, async () => {\n * const response = await fetch('https://api.example.com/users/123');\n * return response.json();\n * });\n * ```\n */\nexport async function startTrace<T>(\n options: { attributes?: PingopsTraceAttributes; seed?: string },\n fn: () => T | Promise<T>\n): Promise<T> {\n if (!isSdkInitialized()) {\n await ensureInitialized();\n }\n\n const traceId =\n options.attributes?.traceId ?? (await createTraceId(options?.seed));\n const parentSpanId = uint8ArrayToHex(\n crypto.getRandomValues(new Uint8Array(8))\n );\n\n const spanContext = {\n traceId,\n spanId: parentSpanId,\n traceFlags: TRACE_FLAG_SAMPLED,\n };\n\n const activeContext = context.active();\n const contextWithSpanContext = trace.setSpanContext(\n activeContext,\n spanContext\n );\n\n const tracer = getPingopsTracerProvider().getTracer(\"pingops-sdk\", \"1.0.0\");\n\n return new Promise((resolve, reject) => {\n tracer.startActiveSpan(\n \"pingops-trace\",\n {},\n contextWithSpanContext,\n (span) => {\n let contextWithAttributes = context.active();\n const attrs = options.attributes;\n if (attrs) {\n contextWithAttributes = setAttributesInContext(\n contextWithAttributes,\n attrs\n );\n }\n contextWithAttributes = contextWithAttributes.setValue(\n PINGOPS_TRACE_ID,\n traceId\n );\n\n const run = () => fn();\n\n try {\n const result = context.with(contextWithAttributes, run);\n if (result instanceof Promise) {\n result\n .then((v) => {\n span.end();\n resolve(v);\n })\n .catch((err) => {\n span.end();\n reject(err instanceof Error ? err : new Error(String(err)));\n });\n } else {\n span.end();\n resolve(result);\n }\n } catch (err) {\n span.end();\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n }\n );\n });\n}\n\nfunction setAttributesInContext(\n ctx: ReturnType<typeof context.active>,\n attrs: PingopsTraceAttributes\n): ReturnType<typeof context.active> {\n if (attrs.userId !== undefined) {\n ctx = ctx.setValue(PINGOPS_USER_ID, attrs.userId);\n }\n if (attrs.sessionId !== undefined) {\n ctx = ctx.setValue(PINGOPS_SESSION_ID, attrs.sessionId);\n }\n if (attrs.tags !== undefined) {\n ctx = ctx.setValue(PINGOPS_TAGS, attrs.tags);\n }\n if (attrs.metadata !== undefined) {\n ctx = ctx.setValue(PINGOPS_METADATA, attrs.metadata);\n }\n if (attrs.captureRequestBody !== undefined) {\n ctx = ctx.setValue(PINGOPS_CAPTURE_REQUEST_BODY, attrs.captureRequestBody);\n }\n if (attrs.captureResponseBody !== undefined) {\n ctx = ctx.setValue(\n PINGOPS_CAPTURE_RESPONSE_BODY,\n attrs.captureResponseBody\n );\n }\n return ctx;\n}\n"],"mappings":"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"}
|