@pingops/sdk 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,302 @@
1
+ import { context, trace } from "@opentelemetry/api";
2
+ import { NodeSDK } from "@opentelemetry/sdk-node";
3
+ import { resourceFromAttributes } from "@opentelemetry/resources";
4
+ import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
5
+ import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
6
+ import { PingopsSpanProcessor, getInstrumentations, getPingopsTracerProvider, setPingopsTracerProvider, shutdownTracerProvider } from "@pingops/otel";
7
+ import { PINGOPS_CAPTURE_REQUEST_BODY, PINGOPS_CAPTURE_RESPONSE_BODY, PINGOPS_METADATA, PINGOPS_SESSION_ID, PINGOPS_TAGS, PINGOPS_TRACE_ID, PINGOPS_USER_ID, createLogger, createTraceId, uint8ArrayToHex } from "@pingops/core";
8
+ import { readFileSync } from "node:fs";
9
+ import { resolve } from "node:path";
10
+ import { load } from "js-yaml";
11
+
12
+ //#region src/config-loader.ts
13
+ /**
14
+ * Configuration loader for reading PingOps config from JSON/YAML files
15
+ */
16
+ /**
17
+ * Loads configuration from a JSON or YAML file
18
+ *
19
+ * @param filePath - Path to the config file (JSON or YAML)
20
+ * @returns Parsed configuration object
21
+ * @throws Error if file cannot be read or parsed
22
+ */
23
+ function loadConfigFromFile(filePath) {
24
+ const resolvedPath = resolve(filePath);
25
+ const fileContent = readFileSync(resolvedPath, "utf-8");
26
+ const ext = resolvedPath.toLowerCase();
27
+ if (ext.endsWith(".yaml") || ext.endsWith(".yml")) return load(fileContent) || {};
28
+ else if (ext.endsWith(".json")) return JSON.parse(fileContent);
29
+ else try {
30
+ return JSON.parse(fileContent);
31
+ } catch {
32
+ return load(fileContent) || {};
33
+ }
34
+ }
35
+ /**
36
+ * Merges configuration from file and environment variables.
37
+ * Environment variables take precedence over file config.
38
+ *
39
+ * @param fileConfig - Configuration loaded from file
40
+ * @returns Merged configuration with env vars taking precedence
41
+ */
42
+ function mergeConfigWithEnv(fileConfig) {
43
+ const envConfig = {};
44
+ if (process.env.PINGOPS_API_KEY) envConfig.apiKey = process.env.PINGOPS_API_KEY;
45
+ if (process.env.PINGOPS_BASE_URL) envConfig.baseUrl = process.env.PINGOPS_BASE_URL;
46
+ if (process.env.PINGOPS_SERVICE_NAME) envConfig.serviceName = process.env.PINGOPS_SERVICE_NAME;
47
+ if (process.env.PINGOPS_DEBUG) envConfig.debug = process.env.PINGOPS_DEBUG === "true";
48
+ if (process.env.PINGOPS_BATCH_SIZE) envConfig.batchSize = parseInt(process.env.PINGOPS_BATCH_SIZE, 10);
49
+ if (process.env.PINGOPS_BATCH_TIMEOUT) envConfig.batchTimeout = parseInt(process.env.PINGOPS_BATCH_TIMEOUT, 10);
50
+ if (process.env.PINGOPS_EXPORT_MODE) envConfig.exportMode = process.env.PINGOPS_EXPORT_MODE;
51
+ return {
52
+ ...fileConfig,
53
+ ...envConfig
54
+ };
55
+ }
56
+
57
+ //#endregion
58
+ //#region src/init-state.ts
59
+ /**
60
+ * Sets the SDK initialization flag.
61
+ * Called by initializePingops when the SDK is initialized.
62
+ */
63
+ function setSdkInitialized(initialized) {}
64
+
65
+ //#endregion
66
+ //#region src/pingops.ts
67
+ /**
68
+ * PingOps SDK singleton for manual instrumentation
69
+ *
70
+ * Provides initializePingops, shutdownPingops, startTrace, getActiveTraceId,
71
+ * and getActiveSpanId. startTrace can auto-initialize from environment variables if needed.
72
+ */
73
+ const TRACE_FLAG_SAMPLED = 1;
74
+ const initLogger = createLogger("[PingOps Initialize]");
75
+ const logger = createLogger("[PingOps Pingops]");
76
+ let sdkInstance = null;
77
+ let isSdkInitializedFlag = false;
78
+ /**
79
+ * Global state to track initialization
80
+ */
81
+ let isInitialized = false;
82
+ let initializationPromise = null;
83
+ function initializePingops(config, explicit = true) {
84
+ const resolvedConfig = typeof config === "string" ? resolveConfigFromFile(config) : "configFile" in config ? resolveConfigFromFile(config.configFile) : config;
85
+ if (isSdkInitializedFlag) {
86
+ if (resolvedConfig.debug) initLogger.warn("[PingOps] SDK already initialized, skipping");
87
+ return;
88
+ }
89
+ const resource = resourceFromAttributes({ [ATTR_SERVICE_NAME]: resolvedConfig.serviceName });
90
+ const processor = new PingopsSpanProcessor(resolvedConfig);
91
+ const instrumentations = getInstrumentations();
92
+ const nodeSdk = new NodeSDK({
93
+ resource,
94
+ spanProcessors: [processor],
95
+ instrumentations
96
+ });
97
+ nodeSdk.start();
98
+ sdkInstance = nodeSdk;
99
+ isSdkInitializedFlag = true;
100
+ /* @__PURE__ */ setSdkInitialized(true);
101
+ try {
102
+ const isolatedProvider = new NodeTracerProvider({
103
+ resource,
104
+ spanProcessors: [processor]
105
+ });
106
+ isolatedProvider.register();
107
+ setPingopsTracerProvider(isolatedProvider);
108
+ } catch (error) {
109
+ if (resolvedConfig.debug) initLogger.error("[PingOps] Failed to create isolated TracerProvider:", error instanceof Error ? error.message : String(error));
110
+ }
111
+ if (resolvedConfig.debug) initLogger.info("[PingOps] SDK initialized");
112
+ }
113
+ function resolveConfigFromFile(configFilePath) {
114
+ const mergedConfig = mergeConfigWithEnv(loadConfigFromFile(configFilePath));
115
+ if (!mergedConfig.baseUrl || !mergedConfig.serviceName) {
116
+ const missing = [!mergedConfig.baseUrl && "baseUrl (or PINGOPS_BASE_URL)", !mergedConfig.serviceName && "serviceName (or PINGOPS_SERVICE_NAME)"].filter(Boolean);
117
+ throw new Error(`initializePingops(configFile) requires ${missing.join(" and ")}. Provide them in the config file or via environment variables.`);
118
+ }
119
+ return mergedConfig;
120
+ }
121
+ /**
122
+ * Shuts down the SDK and flushes remaining spans
123
+ */
124
+ async function shutdownPingops() {
125
+ await shutdownTracerProvider();
126
+ if (!sdkInstance) return;
127
+ await sdkInstance.shutdown();
128
+ sdkInstance = null;
129
+ isSdkInitializedFlag = false;
130
+ /* @__PURE__ */ setSdkInitialized(false);
131
+ }
132
+ /**
133
+ * Checks if the SDK is already initialized by checking if a NodeTracerProvider is available
134
+ */
135
+ function isSdkInitialized() {
136
+ try {
137
+ const provider = getPingopsTracerProvider();
138
+ const initialized = provider instanceof NodeTracerProvider;
139
+ logger.debug("Checked SDK initialization status", {
140
+ initialized,
141
+ providerType: provider.constructor.name
142
+ });
143
+ return initialized;
144
+ } catch (error) {
145
+ logger.debug("Error checking SDK initialization status", { error: error instanceof Error ? error.message : String(error) });
146
+ return false;
147
+ }
148
+ }
149
+ /**
150
+ * Auto-initializes the SDK from environment variables if not already initialized
151
+ */
152
+ async function ensureInitialized() {
153
+ if (isSdkInitialized()) {
154
+ logger.debug("SDK already initialized, skipping auto-initialization");
155
+ isInitialized = true;
156
+ return;
157
+ }
158
+ if (isInitialized) {
159
+ logger.debug("SDK initialization flag already set, skipping");
160
+ return;
161
+ }
162
+ if (initializationPromise) {
163
+ logger.debug("SDK initialization already in progress, waiting...");
164
+ return initializationPromise;
165
+ }
166
+ logger.info("Starting SDK auto-initialization from environment variables");
167
+ initializationPromise = Promise.resolve().then(() => {
168
+ const apiKey = process.env.PINGOPS_API_KEY;
169
+ const baseUrl = process.env.PINGOPS_BASE_URL;
170
+ const serviceName = process.env.PINGOPS_SERVICE_NAME;
171
+ const debug = process.env.PINGOPS_DEBUG === "true";
172
+ logger.debug("Reading environment variables", {
173
+ hasApiKey: !!apiKey,
174
+ hasBaseUrl: !!baseUrl,
175
+ hasServiceName: !!serviceName,
176
+ debug
177
+ });
178
+ if (!apiKey || !baseUrl || !serviceName) {
179
+ const missing = [
180
+ !apiKey && "PINGOPS_API_KEY",
181
+ !baseUrl && "PINGOPS_BASE_URL",
182
+ !serviceName && "PINGOPS_SERVICE_NAME"
183
+ ].filter(Boolean);
184
+ logger.error("Missing required environment variables for auto-initialization", { missing });
185
+ throw new Error(`PingOps SDK auto-initialization requires PINGOPS_API_KEY, PINGOPS_BASE_URL, and PINGOPS_SERVICE_NAME environment variables. Missing: ${missing.join(", ")}`);
186
+ }
187
+ const config = {
188
+ apiKey,
189
+ baseUrl,
190
+ serviceName,
191
+ debug
192
+ };
193
+ logger.info("Initializing SDK with config", {
194
+ baseUrl,
195
+ serviceName,
196
+ debug
197
+ });
198
+ initializePingops(config, false);
199
+ isInitialized = true;
200
+ logger.info("SDK auto-initialization completed successfully");
201
+ });
202
+ try {
203
+ await initializationPromise;
204
+ } catch (error) {
205
+ logger.error("SDK auto-initialization failed", { error: error instanceof Error ? error.message : String(error) });
206
+ throw error;
207
+ } finally {
208
+ initializationPromise = null;
209
+ }
210
+ }
211
+ /**
212
+ * Returns the trace ID of the currently active span, if any.
213
+ */
214
+ function getActiveTraceId() {
215
+ return trace.getActiveSpan()?.spanContext().traceId;
216
+ }
217
+ /**
218
+ * Returns the span ID of the currently active span, if any.
219
+ */
220
+ function getActiveSpanId() {
221
+ return trace.getActiveSpan()?.spanContext().spanId;
222
+ }
223
+ /**
224
+ * Starts a new trace using the PingOps tracer provider and runs the callback within that trace.
225
+ * Sets attributes (traceId, userId, sessionId, tags, metadata, etc.) in context so they are
226
+ * propagated to spans created within the callback.
227
+ *
228
+ * @param options - Options including optional attributes and optional seed for deterministic traceId
229
+ * @param fn - Function to execute within the trace and attribute context
230
+ * @returns Promise resolving to the result of the function
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * import { startTrace, initializePingops } from '@pingops/sdk';
235
+ *
236
+ * initializePingops({ ... });
237
+ *
238
+ * const result = await startTrace({
239
+ * attributes: {
240
+ * userId: 'user-123',
241
+ * sessionId: 'session-456',
242
+ * tags: ['production', 'api'],
243
+ * metadata: { environment: 'prod', version: '1.0.0' }
244
+ * },
245
+ * seed: 'request-123' // optional: deterministic traceId from this seed
246
+ * }, async () => {
247
+ * const response = await fetch('https://api.example.com/users/123');
248
+ * return response.json();
249
+ * });
250
+ * ```
251
+ */
252
+ async function startTrace(options, fn) {
253
+ if (!isSdkInitialized()) await ensureInitialized();
254
+ const traceId = options.attributes?.traceId ?? await createTraceId(options?.seed);
255
+ const spanContext = {
256
+ traceId,
257
+ spanId: uint8ArrayToHex(crypto.getRandomValues(new Uint8Array(8))),
258
+ traceFlags: TRACE_FLAG_SAMPLED
259
+ };
260
+ const activeContext = context.active();
261
+ const contextWithSpanContext = trace.setSpanContext(activeContext, spanContext);
262
+ const tracer = getPingopsTracerProvider().getTracer("pingops-sdk", "1.0.0");
263
+ return new Promise((resolve$1, reject) => {
264
+ tracer.startActiveSpan("pingops-trace", {}, contextWithSpanContext, (span) => {
265
+ let contextWithAttributes = context.active();
266
+ const attrs = options.attributes;
267
+ if (attrs) contextWithAttributes = setAttributesInContext(contextWithAttributes, attrs);
268
+ contextWithAttributes = contextWithAttributes.setValue(PINGOPS_TRACE_ID, traceId);
269
+ const run = () => fn();
270
+ try {
271
+ const result = context.with(contextWithAttributes, run);
272
+ if (result instanceof Promise) result.then((v) => {
273
+ span.end();
274
+ resolve$1(v);
275
+ }).catch((err) => {
276
+ span.end();
277
+ reject(err);
278
+ });
279
+ else {
280
+ span.end();
281
+ resolve$1(result);
282
+ }
283
+ } catch (err) {
284
+ span.end();
285
+ reject(err);
286
+ }
287
+ });
288
+ });
289
+ }
290
+ function setAttributesInContext(ctx, attrs) {
291
+ if (attrs.userId !== void 0) ctx = ctx.setValue(PINGOPS_USER_ID, attrs.userId);
292
+ if (attrs.sessionId !== void 0) ctx = ctx.setValue(PINGOPS_SESSION_ID, attrs.sessionId);
293
+ if (attrs.tags !== void 0) ctx = ctx.setValue(PINGOPS_TAGS, attrs.tags);
294
+ if (attrs.metadata !== void 0) ctx = ctx.setValue(PINGOPS_METADATA, attrs.metadata);
295
+ if (attrs.captureRequestBody !== void 0) ctx = ctx.setValue(PINGOPS_CAPTURE_REQUEST_BODY, attrs.captureRequestBody);
296
+ if (attrs.captureResponseBody !== void 0) ctx = ctx.setValue(PINGOPS_CAPTURE_RESPONSE_BODY, attrs.captureResponseBody);
297
+ return ctx;
298
+ }
299
+
300
+ //#endregion
301
+ export { startTrace as a, shutdownPingops as i, getActiveTraceId as n, loadConfigFromFile as o, initializePingops as r, mergeConfigWithEnv as s, getActiveSpanId as t };
302
+ //# sourceMappingURL=pingops-DJsItvUR.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pingops-DJsItvUR.mjs","names":["loadYaml"],"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 * 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);\n });\n } else {\n span.end();\n resolve(result);\n }\n } catch (err) {\n span.end();\n reject(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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"}
@@ -0,0 +1,73 @@
1
+ const require_pingops = require('./pingops-Bbm0Xd0z.cjs');
2
+
3
+ //#region src/register.ts
4
+ /**
5
+ * @pingops/sdk/register - Auto-instrumentation setup
6
+ *
7
+ * This file automatically initializes PingOps SDK from environment variables or a config file when imported.
8
+ *
9
+ * RECOMMENDED: Import this file FIRST in your application (before any HTTP clients):
10
+ * import '@pingops/sdk/register';
11
+ * import axios from 'axios';
12
+ * // ... rest of your code
13
+ *
14
+ * ALTERNATIVE: Use Node.js --require flag (runs before any imports):
15
+ * node --require @pingops/sdk/register your-app.js
16
+ *
17
+ * Configuration can be provided via:
18
+ * 1. Config file (JSON or YAML) - Set PINGOPS_CONFIG_FILE environment variable
19
+ * 2. Environment variables (takes precedence over config file)
20
+ *
21
+ * Environment variables:
22
+ * - PINGOPS_CONFIG_FILE: Path to JSON or YAML config file (optional)
23
+ * - PINGOPS_API_KEY: Your API key
24
+ * - PINGOPS_BASE_URL: Base URL for PingOps API (required)
25
+ * - PINGOPS_SERVICE_NAME: Service name (required)
26
+ * - PINGOPS_DEBUG: Set to 'true' to enable debug logging
27
+ * - PINGOPS_BATCH_SIZE: Batch size for span export (optional)
28
+ * - PINGOPS_BATCH_TIMEOUT: Batch timeout in ms (optional)
29
+ * - PINGOPS_EXPORT_MODE: Export mode - 'batched' or 'immediate' (optional)
30
+ *
31
+ * Config file format (JSON example):
32
+ * {
33
+ * "apiKey": "your-api-key",
34
+ * "baseUrl": "https://api.pingops.com",
35
+ * "serviceName": "my-service",
36
+ * "debug": false,
37
+ * "batchSize": 50,
38
+ * "batchTimeout": 5000,
39
+ * "exportMode": "batched"
40
+ * }
41
+ *
42
+ * Config file format (YAML example):
43
+ * apiKey: your-api-key
44
+ * baseUrl: https://api.pingops.com
45
+ * serviceName: my-service
46
+ * debug: false
47
+ * batchSize: 50
48
+ * batchTimeout: 5000
49
+ * exportMode: batched
50
+ */
51
+ let config = {};
52
+ const configFilePath = process.env.PINGOPS_CONFIG_FILE;
53
+ if (configFilePath) try {
54
+ config = require_pingops.mergeConfigWithEnv(require_pingops.loadConfigFromFile(configFilePath));
55
+ } catch (error) {
56
+ console.error(`[PingOps] Failed to load config from file ${configFilePath}:`, error instanceof Error ? error.message : String(error));
57
+ config = require_pingops.mergeConfigWithEnv({});
58
+ }
59
+ else config = require_pingops.mergeConfigWithEnv({});
60
+ const baseUrl = config.baseUrl;
61
+ const serviceName = config.serviceName;
62
+ if (baseUrl && serviceName) require_pingops.initializePingops({
63
+ apiKey: config.apiKey,
64
+ baseUrl,
65
+ serviceName,
66
+ debug: config.debug,
67
+ batchSize: config.batchSize,
68
+ batchTimeout: config.batchTimeout,
69
+ exportMode: config.exportMode
70
+ });
71
+
72
+ //#endregion
73
+ //# sourceMappingURL=register.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.cjs","names":["mergeConfigWithEnv","loadConfigFromFile"],"sources":["../src/register.ts"],"sourcesContent":["/**\n * @pingops/sdk/register - Auto-instrumentation setup\n *\n * This file automatically initializes PingOps SDK from environment variables or a config file when imported.\n *\n * RECOMMENDED: Import this file FIRST in your application (before any HTTP clients):\n * import '@pingops/sdk/register';\n * import axios from 'axios';\n * // ... rest of your code\n *\n * ALTERNATIVE: Use Node.js --require flag (runs before any imports):\n * node --require @pingops/sdk/register your-app.js\n *\n * Configuration can be provided via:\n * 1. Config file (JSON or YAML) - Set PINGOPS_CONFIG_FILE environment variable\n * 2. Environment variables (takes precedence over config file)\n *\n * Environment variables:\n * - PINGOPS_CONFIG_FILE: Path to JSON or YAML config file (optional)\n * - PINGOPS_API_KEY: Your API key\n * - PINGOPS_BASE_URL: Base URL for PingOps API (required)\n * - PINGOPS_SERVICE_NAME: Service name (required)\n * - PINGOPS_DEBUG: Set to 'true' to enable debug logging\n * - PINGOPS_BATCH_SIZE: Batch size for span export (optional)\n * - PINGOPS_BATCH_TIMEOUT: Batch timeout in ms (optional)\n * - PINGOPS_EXPORT_MODE: Export mode - 'batched' or 'immediate' (optional)\n *\n * Config file format (JSON example):\n * {\n * \"apiKey\": \"your-api-key\",\n * \"baseUrl\": \"https://api.pingops.com\",\n * \"serviceName\": \"my-service\",\n * \"debug\": false,\n * \"batchSize\": 50,\n * \"batchTimeout\": 5000,\n * \"exportMode\": \"batched\"\n * }\n *\n * Config file format (YAML example):\n * apiKey: your-api-key\n * baseUrl: https://api.pingops.com\n * serviceName: my-service\n * debug: false\n * batchSize: 50\n * batchTimeout: 5000\n * exportMode: batched\n */\n\nimport { initializePingops } from \"./pingops.js\";\nimport { loadConfigFromFile, mergeConfigWithEnv } from \"./config-loader.js\";\n\nlet config: {\n apiKey?: string;\n baseUrl?: string;\n serviceName?: string;\n debug?: boolean;\n batchSize?: number;\n batchTimeout?: number;\n exportMode?: \"batched\" | \"immediate\";\n} = {};\n\n// Try to load config from file if PINGOPS_CONFIG_FILE is set\nconst configFilePath = process.env.PINGOPS_CONFIG_FILE;\nif (configFilePath) {\n try {\n const fileConfig = loadConfigFromFile(configFilePath);\n config = mergeConfigWithEnv(fileConfig);\n } catch (error) {\n console.error(\n `[PingOps] Failed to load config from file ${configFilePath}:`,\n error instanceof Error ? error.message : String(error)\n );\n // Fall back to environment variables only\n config = mergeConfigWithEnv({});\n }\n} else {\n // No config file, use environment variables only\n config = mergeConfigWithEnv({});\n}\n\n// Only auto-initialize if required config values are present\nconst baseUrl = config.baseUrl;\nconst serviceName = config.serviceName;\n\nif (baseUrl && serviceName) {\n initializePingops({\n apiKey: config.apiKey,\n baseUrl,\n serviceName,\n debug: config.debug,\n batchSize: config.batchSize,\n batchTimeout: config.batchTimeout,\n exportMode: config.exportMode,\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,IAAI,SAQA,EAAE;AAGN,MAAM,iBAAiB,QAAQ,IAAI;AACnC,IAAI,eACF,KAAI;AAEF,UAASA,mCADUC,mCAAmB,eAAe,CACd;SAChC,OAAO;AACd,SAAQ,MACN,6CAA6C,eAAe,IAC5D,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CACvD;AAED,UAASD,mCAAmB,EAAE,CAAC;;IAIjC,UAASA,mCAAmB,EAAE,CAAC;AAIjC,MAAM,UAAU,OAAO;AACvB,MAAM,cAAc,OAAO;AAE3B,IAAI,WAAW,YACb,mCAAkB;CAChB,QAAQ,OAAO;CACf;CACA;CACA,OAAO,OAAO;CACd,WAAW,OAAO;CAClB,cAAc,OAAO;CACrB,YAAY,OAAO;CACpB,CAAC"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,74 @@
1
+ import { o as loadConfigFromFile, r as initializePingops, s as mergeConfigWithEnv } from "./pingops-DJsItvUR.mjs";
2
+
3
+ //#region src/register.ts
4
+ /**
5
+ * @pingops/sdk/register - Auto-instrumentation setup
6
+ *
7
+ * This file automatically initializes PingOps SDK from environment variables or a config file when imported.
8
+ *
9
+ * RECOMMENDED: Import this file FIRST in your application (before any HTTP clients):
10
+ * import '@pingops/sdk/register';
11
+ * import axios from 'axios';
12
+ * // ... rest of your code
13
+ *
14
+ * ALTERNATIVE: Use Node.js --require flag (runs before any imports):
15
+ * node --require @pingops/sdk/register your-app.js
16
+ *
17
+ * Configuration can be provided via:
18
+ * 1. Config file (JSON or YAML) - Set PINGOPS_CONFIG_FILE environment variable
19
+ * 2. Environment variables (takes precedence over config file)
20
+ *
21
+ * Environment variables:
22
+ * - PINGOPS_CONFIG_FILE: Path to JSON or YAML config file (optional)
23
+ * - PINGOPS_API_KEY: Your API key
24
+ * - PINGOPS_BASE_URL: Base URL for PingOps API (required)
25
+ * - PINGOPS_SERVICE_NAME: Service name (required)
26
+ * - PINGOPS_DEBUG: Set to 'true' to enable debug logging
27
+ * - PINGOPS_BATCH_SIZE: Batch size for span export (optional)
28
+ * - PINGOPS_BATCH_TIMEOUT: Batch timeout in ms (optional)
29
+ * - PINGOPS_EXPORT_MODE: Export mode - 'batched' or 'immediate' (optional)
30
+ *
31
+ * Config file format (JSON example):
32
+ * {
33
+ * "apiKey": "your-api-key",
34
+ * "baseUrl": "https://api.pingops.com",
35
+ * "serviceName": "my-service",
36
+ * "debug": false,
37
+ * "batchSize": 50,
38
+ * "batchTimeout": 5000,
39
+ * "exportMode": "batched"
40
+ * }
41
+ *
42
+ * Config file format (YAML example):
43
+ * apiKey: your-api-key
44
+ * baseUrl: https://api.pingops.com
45
+ * serviceName: my-service
46
+ * debug: false
47
+ * batchSize: 50
48
+ * batchTimeout: 5000
49
+ * exportMode: batched
50
+ */
51
+ let config = {};
52
+ const configFilePath = process.env.PINGOPS_CONFIG_FILE;
53
+ if (configFilePath) try {
54
+ config = mergeConfigWithEnv(loadConfigFromFile(configFilePath));
55
+ } catch (error) {
56
+ console.error(`[PingOps] Failed to load config from file ${configFilePath}:`, error instanceof Error ? error.message : String(error));
57
+ config = mergeConfigWithEnv({});
58
+ }
59
+ else config = mergeConfigWithEnv({});
60
+ const baseUrl = config.baseUrl;
61
+ const serviceName = config.serviceName;
62
+ if (baseUrl && serviceName) initializePingops({
63
+ apiKey: config.apiKey,
64
+ baseUrl,
65
+ serviceName,
66
+ debug: config.debug,
67
+ batchSize: config.batchSize,
68
+ batchTimeout: config.batchTimeout,
69
+ exportMode: config.exportMode
70
+ });
71
+
72
+ //#endregion
73
+ export { };
74
+ //# sourceMappingURL=register.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.mjs","names":[],"sources":["../src/register.ts"],"sourcesContent":["/**\n * @pingops/sdk/register - Auto-instrumentation setup\n *\n * This file automatically initializes PingOps SDK from environment variables or a config file when imported.\n *\n * RECOMMENDED: Import this file FIRST in your application (before any HTTP clients):\n * import '@pingops/sdk/register';\n * import axios from 'axios';\n * // ... rest of your code\n *\n * ALTERNATIVE: Use Node.js --require flag (runs before any imports):\n * node --require @pingops/sdk/register your-app.js\n *\n * Configuration can be provided via:\n * 1. Config file (JSON or YAML) - Set PINGOPS_CONFIG_FILE environment variable\n * 2. Environment variables (takes precedence over config file)\n *\n * Environment variables:\n * - PINGOPS_CONFIG_FILE: Path to JSON or YAML config file (optional)\n * - PINGOPS_API_KEY: Your API key\n * - PINGOPS_BASE_URL: Base URL for PingOps API (required)\n * - PINGOPS_SERVICE_NAME: Service name (required)\n * - PINGOPS_DEBUG: Set to 'true' to enable debug logging\n * - PINGOPS_BATCH_SIZE: Batch size for span export (optional)\n * - PINGOPS_BATCH_TIMEOUT: Batch timeout in ms (optional)\n * - PINGOPS_EXPORT_MODE: Export mode - 'batched' or 'immediate' (optional)\n *\n * Config file format (JSON example):\n * {\n * \"apiKey\": \"your-api-key\",\n * \"baseUrl\": \"https://api.pingops.com\",\n * \"serviceName\": \"my-service\",\n * \"debug\": false,\n * \"batchSize\": 50,\n * \"batchTimeout\": 5000,\n * \"exportMode\": \"batched\"\n * }\n *\n * Config file format (YAML example):\n * apiKey: your-api-key\n * baseUrl: https://api.pingops.com\n * serviceName: my-service\n * debug: false\n * batchSize: 50\n * batchTimeout: 5000\n * exportMode: batched\n */\n\nimport { initializePingops } from \"./pingops.js\";\nimport { loadConfigFromFile, mergeConfigWithEnv } from \"./config-loader.js\";\n\nlet config: {\n apiKey?: string;\n baseUrl?: string;\n serviceName?: string;\n debug?: boolean;\n batchSize?: number;\n batchTimeout?: number;\n exportMode?: \"batched\" | \"immediate\";\n} = {};\n\n// Try to load config from file if PINGOPS_CONFIG_FILE is set\nconst configFilePath = process.env.PINGOPS_CONFIG_FILE;\nif (configFilePath) {\n try {\n const fileConfig = loadConfigFromFile(configFilePath);\n config = mergeConfigWithEnv(fileConfig);\n } catch (error) {\n console.error(\n `[PingOps] Failed to load config from file ${configFilePath}:`,\n error instanceof Error ? error.message : String(error)\n );\n // Fall back to environment variables only\n config = mergeConfigWithEnv({});\n }\n} else {\n // No config file, use environment variables only\n config = mergeConfigWithEnv({});\n}\n\n// Only auto-initialize if required config values are present\nconst baseUrl = config.baseUrl;\nconst serviceName = config.serviceName;\n\nif (baseUrl && serviceName) {\n initializePingops({\n apiKey: config.apiKey,\n baseUrl,\n serviceName,\n debug: config.debug,\n batchSize: config.batchSize,\n batchTimeout: config.batchTimeout,\n exportMode: config.exportMode,\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,IAAI,SAQA,EAAE;AAGN,MAAM,iBAAiB,QAAQ,IAAI;AACnC,IAAI,eACF,KAAI;AAEF,UAAS,mBADU,mBAAmB,eAAe,CACd;SAChC,OAAO;AACd,SAAQ,MACN,6CAA6C,eAAe,IAC5D,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CACvD;AAED,UAAS,mBAAmB,EAAE,CAAC;;IAIjC,UAAS,mBAAmB,EAAE,CAAC;AAIjC,MAAM,UAAU,OAAO;AACvB,MAAM,cAAc,OAAO;AAE3B,IAAI,WAAW,YACb,mBAAkB;CAChB,QAAQ,OAAO;CACf;CACA;CACA,OAAO,OAAO;CACd,WAAW,OAAO;CAClB,cAAc,OAAO;CACrB,YAAY,OAAO;CACpB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pingops/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=20"
@@ -14,6 +14,11 @@
14
14
  "types": "./dist/index.d.mts",
15
15
  "import": "./dist/index.mjs",
16
16
  "require": "./dist/index.cjs"
17
+ },
18
+ "./register": {
19
+ "types": "./dist/register.d.mts",
20
+ "import": "./dist/register.mjs",
21
+ "require": "./dist/register.cjs"
17
22
  }
18
23
  },
19
24
  "files": [
@@ -38,11 +43,15 @@
38
43
  "@opentelemetry/sdk-node": "^0.208.0",
39
44
  "@opentelemetry/sdk-trace-node": "^2.2.0",
40
45
  "@opentelemetry/semantic-conventions": "^1.38.0",
41
- "@pingops/otel": "^0.1.2",
42
- "@pingops/core": "^0.1.2"
46
+ "js-yaml": "^4.1.0",
47
+ "@pingops/core": "^0.2.0",
48
+ "@pingops/otel": "^0.2.0"
43
49
  },
44
50
  "devDependencies": {
45
51
  "@types/node": "^20.11.0",
52
+ "@types/js-yaml": "^4.0.9",
53
+ "axios": "^1.6.2",
54
+ "tsx": "^4.7.0",
46
55
  "typescript": "^5.6.0"
47
56
  },
48
57
  "scripts": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","names":["isSdkInitializedFlag","ATTR_SERVICE_NAME","PingopsSpanProcessor","NodeSDK","NodeTracerProvider"],"sources":["../src/init-state.ts","../src/pingops.ts"],"sourcesContent":["/**\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 * 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/**\n * Checks if global instrumentation is enabled.\n * This is used to determine instrumentation behavior:\n * - If true: all HTTP requests are instrumented\n * - If false: only requests within wrapHttp blocks are instrumented\n */\nexport function isGlobalInstrumentationEnabled(): boolean {\n return isSdkInitializedFlag;\n}\n","/**\n * PingOps SDK singleton for manual instrumentation\n *\n * Provides initializePingops and shutdownPingops functions.\n * wrapHttp is available from @pingops/core and will auto-initialize\n * from environment variables if needed.\n */\n\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 { createLogger } from \"@pingops/core\";\nimport {\n setSdkInitialized,\n isGlobalInstrumentationEnabled,\n} from \"./init-state\";\nimport {\n wrapHttp as coreWrapHttp,\n type WrapHttpAttributes,\n} from \"@pingops/core\";\nimport { getPingopsTracerProvider } from \"@pingops/otel\";\nimport { getInstrumentations } from \"@pingops/otel\";\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 for the SDK\n * @param explicit - Whether this is an explicit call (default: true).\n * Set to false when called internally by wrapHttp auto-initialization.\n */\nexport function initializePingops(\n config: PingopsProcessorConfig,\n explicit: boolean = true\n): void {\n if (isSdkInitializedFlag) {\n if (config.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]: config.serviceName,\n });\n\n const processor = new PingopsSpanProcessor(config);\n const instrumentations = getInstrumentations(isGlobalInstrumentationEnabled);\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 // Only enable global instrumentation if this was an explicit call\n // If called via wrapHttp auto-initialization, global instrumentation stays disabled\n setSdkInitialized(explicit);\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 (config.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 (config.debug) {\n initLogger.info(\"[PingOps] SDK initialized\");\n }\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 * Wraps a function to set attributes on HTTP spans created within the wrapped block.\n *\n * This function sets attributes (userId, sessionId, tags, metadata) in the OpenTelemetry\n * context, which are automatically propagated to all spans created within the wrapped function.\n *\n * Instrumentation behavior:\n * - If `initializePingops` was called: All HTTP requests are instrumented by default.\n * `wrapHttp` only adds attributes to spans created within the wrapped block.\n * - If `initializePingops` was NOT called: Only HTTP requests within `wrapHttp` blocks\n * are instrumented. Requests outside `wrapHttp` are not instrumented.\n *\n * @param options - Options including attributes to propagate to spans\n * @param fn - Function to execute within the attribute context\n * @returns The result of the function\n *\n * @example\n * ```typescript\n * import { wrapHttp } from '@pingops/sdk';\n *\n * // Scenario 1: initializePingops was called\n * initializePingops({ ... });\n *\n * // All HTTP requests are instrumented, but this block adds attributes\n * const result = await wrapHttp({\n * attributes: {\n * userId: 'user-123',\n * sessionId: 'session-456',\n * tags: ['production', 'api'],\n * metadata: { environment: 'prod', version: '1.0.0' }\n * }\n * }, async () => {\n * // This HTTP request will be instrumented AND have the attributes set above\n * const response = await fetch('https://api.example.com/users/123');\n * return response.json();\n * });\n *\n * // HTTP requests outside wrapHttp are still instrumented, just without the attributes\n * const otherResponse = await fetch('https://api.example.com/other');\n *\n * // Scenario 2: initializePingops was NOT called\n * // Only requests within wrapHttp are instrumented\n * await wrapHttp({\n * attributes: { userId: 'user-123' }\n * }, async () => {\n * // This request IS instrumented\n * return fetch('https://api.example.com/data');\n * });\n *\n * // This request is NOT instrumented (outside wrapHttp)\n * await fetch('https://api.example.com/other');\n * ```\n */\nexport function wrapHttp<T>(\n options: { attributes?: WrapHttpAttributes },\n fn: () => T | Promise<T>\n): T | Promise<T> {\n return coreWrapHttp(\n {\n ...options,\n checkInitialized: isSdkInitialized,\n isGlobalInstrumentationEnabled: isGlobalInstrumentationEnabled,\n ensureInitialized: ensureInitialized,\n },\n fn\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":["isSdkInitializedFlag","coreWrapHttp"],"sources":["../src/init-state.ts","../src/pingops.ts"],"sourcesContent":["/**\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 * 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/**\n * Checks if global instrumentation is enabled.\n * This is used to determine instrumentation behavior:\n * - If true: all HTTP requests are instrumented\n * - If false: only requests within wrapHttp blocks are instrumented\n */\nexport function isGlobalInstrumentationEnabled(): boolean {\n return isSdkInitializedFlag;\n}\n","/**\n * PingOps SDK singleton for manual instrumentation\n *\n * Provides initializePingops and shutdownPingops functions.\n * wrapHttp is available from @pingops/core and will auto-initialize\n * from environment variables if needed.\n */\n\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 { createLogger } from \"@pingops/core\";\nimport {\n setSdkInitialized,\n isGlobalInstrumentationEnabled,\n} from \"./init-state\";\nimport {\n wrapHttp as coreWrapHttp,\n type WrapHttpAttributes,\n} from \"@pingops/core\";\nimport { getPingopsTracerProvider } from \"@pingops/otel\";\nimport { getInstrumentations } from \"@pingops/otel\";\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 for the SDK\n * @param explicit - Whether this is an explicit call (default: true).\n * Set to false when called internally by wrapHttp auto-initialization.\n */\nexport function initializePingops(\n config: PingopsProcessorConfig,\n explicit: boolean = true\n): void {\n if (isSdkInitializedFlag) {\n if (config.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]: config.serviceName,\n });\n\n const processor = new PingopsSpanProcessor(config);\n const instrumentations = getInstrumentations(isGlobalInstrumentationEnabled);\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 // Only enable global instrumentation if this was an explicit call\n // If called via wrapHttp auto-initialization, global instrumentation stays disabled\n setSdkInitialized(explicit);\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 (config.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 (config.debug) {\n initLogger.info(\"[PingOps] SDK initialized\");\n }\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 * Wraps a function to set attributes on HTTP spans created within the wrapped block.\n *\n * This function sets attributes (userId, sessionId, tags, metadata) in the OpenTelemetry\n * context, which are automatically propagated to all spans created within the wrapped function.\n *\n * Instrumentation behavior:\n * - If `initializePingops` was called: All HTTP requests are instrumented by default.\n * `wrapHttp` only adds attributes to spans created within the wrapped block.\n * - If `initializePingops` was NOT called: Only HTTP requests within `wrapHttp` blocks\n * are instrumented. Requests outside `wrapHttp` are not instrumented.\n *\n * @param options - Options including attributes to propagate to spans\n * @param fn - Function to execute within the attribute context\n * @returns The result of the function\n *\n * @example\n * ```typescript\n * import { wrapHttp } from '@pingops/sdk';\n *\n * // Scenario 1: initializePingops was called\n * initializePingops({ ... });\n *\n * // All HTTP requests are instrumented, but this block adds attributes\n * const result = await wrapHttp({\n * attributes: {\n * userId: 'user-123',\n * sessionId: 'session-456',\n * tags: ['production', 'api'],\n * metadata: { environment: 'prod', version: '1.0.0' }\n * }\n * }, async () => {\n * // This HTTP request will be instrumented AND have the attributes set above\n * const response = await fetch('https://api.example.com/users/123');\n * return response.json();\n * });\n *\n * // HTTP requests outside wrapHttp are still instrumented, just without the attributes\n * const otherResponse = await fetch('https://api.example.com/other');\n *\n * // Scenario 2: initializePingops was NOT called\n * // Only requests within wrapHttp are instrumented\n * await wrapHttp({\n * attributes: { userId: 'user-123' }\n * }, async () => {\n * // This request IS instrumented\n * return fetch('https://api.example.com/data');\n * });\n *\n * // This request is NOT instrumented (outside wrapHttp)\n * await fetch('https://api.example.com/other');\n * ```\n */\nexport function wrapHttp<T>(\n options: { attributes?: WrapHttpAttributes },\n fn: () => T | Promise<T>\n): T | Promise<T> {\n return coreWrapHttp(\n {\n ...options,\n checkInitialized: isSdkInitialized,\n isGlobalInstrumentationEnabled: isGlobalInstrumentationEnabled,\n ensureInitialized: ensureInitialized,\n },\n fn\n );\n}\n"],"mappings":"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"}