@pingops/sdk 0.1.1 → 0.1.3

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 CHANGED
@@ -10,16 +10,52 @@ pnpm add @pingops/sdk
10
10
 
11
11
  ## Quick Start
12
12
 
13
+ ### Option 1: Auto-initialization (Recommended)
14
+
15
+ **Most automatic approach** - Use Node.js `--require` flag (runs before any imports):
16
+
17
+ ```bash
18
+ node --require @pingops/sdk/register your-app.js
19
+ ```
20
+
21
+ Set environment variables:
22
+
23
+ ```bash
24
+ export PINGOPS_API_KEY="your-api-key"
25
+ export PINGOPS_BASE_URL="https://api.pingops.com"
26
+ export PINGOPS_SERVICE_NAME="my-service"
27
+ ```
28
+
29
+ **Or** import the register file FIRST in your code:
30
+
31
+ ```typescript
32
+ // Import this FIRST, before any HTTP clients
33
+ import "@pingops/sdk/register";
34
+
35
+ import axios from "axios";
36
+ // ... rest of your code
37
+ ```
38
+
39
+ ### Option 2: Manual initialization
40
+
13
41
  ```typescript
14
42
  import { initializePingops } from "@pingops/sdk";
15
43
 
44
+ // Option A: pass a config object directly
16
45
  initializePingops({
17
46
  apiKey: "your-api-key", // or set PINGOPS_API_KEY env var
18
47
  baseUrl: "https://api.pingops.com",
19
48
  serviceName: "my-service",
20
49
  });
50
+
51
+ // Option B: pass a JSON/YAML config file path (env vars override file values)
52
+ initializePingops("./pingops.config.yaml");
53
+ // or:
54
+ initializePingops({ configFile: "./pingops.config.json" });
21
55
  ```
22
56
 
57
+ **Important**: If using manual initialization, call `initializePingops()` before importing any HTTP clients (axios, fetch, etc.) to ensure proper instrumentation.
58
+
23
59
  ## Features
24
60
 
25
61
  - **Automatic Instrumentation**: Captures HTTP and fetch API calls automatically
package/dist/index.cjs ADDED
@@ -0,0 +1,5 @@
1
+ const require_pingops = require('./pingops-Cb6UV5D8.cjs');
2
+
3
+ exports.initializePingops = require_pingops.initializePingops;
4
+ exports.shutdownPingops = require_pingops.shutdownPingops;
5
+ exports.wrapHttp = require_pingops.wrapHttp;
@@ -1,12 +1,8 @@
1
- /**
2
- * PingOps SDK singleton for manual instrumentation
3
- *
4
- * Provides initializePingops and shutdownPingops functions.
5
- * wrapHttp is available from @pingops/core and will auto-initialize
6
- * from environment variables if needed.
7
- */
8
- import type { PingopsProcessorConfig } from "@pingops/otel";
9
- import { type WrapHttpAttributes } from "@pingops/core";
1
+ import { PingopsProcessorConfig } from "@pingops/otel";
2
+ import { WrapHttpAttributes, WrapHttpAttributes as WrapHttpAttributes$1 } from "@pingops/core";
3
+
4
+ //#region src/pingops.d.ts
5
+
10
6
  /**
11
7
  * Initializes PingOps SDK
12
8
  *
@@ -17,15 +13,19 @@ import { type WrapHttpAttributes } from "@pingops/core";
17
13
  * 4. Enables HTTP/fetch/GenAI instrumentation
18
14
  * 5. Starts the SDK
19
15
  *
20
- * @param config - Configuration for the SDK
16
+ * @param config - Configuration object, config file path, or config file wrapper
21
17
  * @param explicit - Whether this is an explicit call (default: true).
22
18
  * Set to false when called internally by wrapHttp auto-initialization.
23
19
  */
24
- export declare function initializePingops(config: PingopsProcessorConfig, explicit?: boolean): void;
20
+ declare function initializePingops(config: PingopsProcessorConfig, explicit?: boolean): void;
21
+ declare function initializePingops(configFilePath: string, explicit?: boolean): void;
22
+ declare function initializePingops(config: {
23
+ configFile: string;
24
+ }, explicit?: boolean): void;
25
25
  /**
26
26
  * Shuts down the SDK and flushes remaining spans
27
27
  */
28
- export declare function shutdownPingops(): Promise<void>;
28
+ declare function shutdownPingops(): Promise<void>;
29
29
  /**
30
30
  * Wraps a function to set attributes on HTTP spans created within the wrapped block.
31
31
  *
@@ -79,7 +79,9 @@ export declare function shutdownPingops(): Promise<void>;
79
79
  * await fetch('https://api.example.com/other');
80
80
  * ```
81
81
  */
82
- export declare function wrapHttp<T>(options: {
83
- attributes?: WrapHttpAttributes;
82
+ declare function wrapHttp<T>(options: {
83
+ attributes?: WrapHttpAttributes$1;
84
84
  }, fn: () => T | Promise<T>): T | Promise<T>;
85
- //# sourceMappingURL=pingops.d.ts.map
85
+ //#endregion
86
+ export { type WrapHttpAttributes, initializePingops, shutdownPingops, wrapHttp };
87
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/pingops.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;iBAyDgB,iBAAA,SACN;iBAGM,iBAAA;iBAIA,iBAAA;;;;;;iBAwGM,eAAA,CAAA,GAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgLzB;eACU;aACd,IAAI,QAAQ,KACrB,IAAI,QAAQ"}
@@ -0,0 +1,87 @@
1
+ import { PingopsProcessorConfig } from "@pingops/otel";
2
+ import { WrapHttpAttributes, WrapHttpAttributes as WrapHttpAttributes$1 } from "@pingops/core";
3
+
4
+ //#region src/pingops.d.ts
5
+
6
+ /**
7
+ * Initializes PingOps SDK
8
+ *
9
+ * This function:
10
+ * 1. Creates an OpenTelemetry NodeSDK instance
11
+ * 2. Configures Resource with service.name
12
+ * 3. Registers PingopsSpanProcessor
13
+ * 4. Enables HTTP/fetch/GenAI instrumentation
14
+ * 5. Starts the SDK
15
+ *
16
+ * @param config - Configuration object, config file path, or config file wrapper
17
+ * @param explicit - Whether this is an explicit call (default: true).
18
+ * Set to false when called internally by wrapHttp auto-initialization.
19
+ */
20
+ declare function initializePingops(config: PingopsProcessorConfig, explicit?: boolean): void;
21
+ declare function initializePingops(configFilePath: string, explicit?: boolean): void;
22
+ declare function initializePingops(config: {
23
+ configFile: string;
24
+ }, explicit?: boolean): void;
25
+ /**
26
+ * Shuts down the SDK and flushes remaining spans
27
+ */
28
+ declare function shutdownPingops(): Promise<void>;
29
+ /**
30
+ * Wraps a function to set attributes on HTTP spans created within the wrapped block.
31
+ *
32
+ * This function sets attributes (userId, sessionId, tags, metadata) in the OpenTelemetry
33
+ * context, which are automatically propagated to all spans created within the wrapped function.
34
+ *
35
+ * Instrumentation behavior:
36
+ * - If `initializePingops` was called: All HTTP requests are instrumented by default.
37
+ * `wrapHttp` only adds attributes to spans created within the wrapped block.
38
+ * - If `initializePingops` was NOT called: Only HTTP requests within `wrapHttp` blocks
39
+ * are instrumented. Requests outside `wrapHttp` are not instrumented.
40
+ *
41
+ * @param options - Options including attributes to propagate to spans
42
+ * @param fn - Function to execute within the attribute context
43
+ * @returns The result of the function
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * import { wrapHttp } from '@pingops/sdk';
48
+ *
49
+ * // Scenario 1: initializePingops was called
50
+ * initializePingops({ ... });
51
+ *
52
+ * // All HTTP requests are instrumented, but this block adds attributes
53
+ * const result = await wrapHttp({
54
+ * attributes: {
55
+ * userId: 'user-123',
56
+ * sessionId: 'session-456',
57
+ * tags: ['production', 'api'],
58
+ * metadata: { environment: 'prod', version: '1.0.0' }
59
+ * }
60
+ * }, async () => {
61
+ * // This HTTP request will be instrumented AND have the attributes set above
62
+ * const response = await fetch('https://api.example.com/users/123');
63
+ * return response.json();
64
+ * });
65
+ *
66
+ * // HTTP requests outside wrapHttp are still instrumented, just without the attributes
67
+ * const otherResponse = await fetch('https://api.example.com/other');
68
+ *
69
+ * // Scenario 2: initializePingops was NOT called
70
+ * // Only requests within wrapHttp are instrumented
71
+ * await wrapHttp({
72
+ * attributes: { userId: 'user-123' }
73
+ * }, async () => {
74
+ * // This request IS instrumented
75
+ * return fetch('https://api.example.com/data');
76
+ * });
77
+ *
78
+ * // This request is NOT instrumented (outside wrapHttp)
79
+ * await fetch('https://api.example.com/other');
80
+ * ```
81
+ */
82
+ declare function wrapHttp<T>(options: {
83
+ attributes?: WrapHttpAttributes$1;
84
+ }, fn: () => T | Promise<T>): T | Promise<T>;
85
+ //#endregion
86
+ export { type WrapHttpAttributes, initializePingops, shutdownPingops, wrapHttp };
87
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/pingops.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;iBAyDgB,iBAAA,SACN;iBAGM,iBAAA;iBAIA,iBAAA;;;;;;iBAwGM,eAAA,CAAA,GAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgLzB;eACU;aACd,IAAI,QAAQ,KACrB,IAAI,QAAQ"}
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import { n as shutdownPingops, r as wrapHttp, t as initializePingops } from "./pingops-DlQdUQEU.mjs";
2
+
3
+ export { initializePingops, shutdownPingops, wrapHttp };
@@ -0,0 +1,319 @@
1
+ let _opentelemetry_sdk_node = require("@opentelemetry/sdk-node");
2
+ let _opentelemetry_resources = require("@opentelemetry/resources");
3
+ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
4
+ let _opentelemetry_sdk_trace_node = require("@opentelemetry/sdk-trace-node");
5
+ let _pingops_otel = require("@pingops/otel");
6
+ let _pingops_core = require("@pingops/core");
7
+ let node_fs = require("node:fs");
8
+ let node_path = require("node:path");
9
+ let js_yaml = require("js-yaml");
10
+
11
+ //#region src/config-loader.ts
12
+ /**
13
+ * Configuration loader for reading PingOps config from JSON/YAML files
14
+ */
15
+ /**
16
+ * Loads configuration from a JSON or YAML file
17
+ *
18
+ * @param filePath - Path to the config file (JSON or YAML)
19
+ * @returns Parsed configuration object
20
+ * @throws Error if file cannot be read or parsed
21
+ */
22
+ function loadConfigFromFile(filePath) {
23
+ const resolvedPath = (0, node_path.resolve)(filePath);
24
+ const fileContent = (0, node_fs.readFileSync)(resolvedPath, "utf-8");
25
+ const ext = resolvedPath.toLowerCase();
26
+ if (ext.endsWith(".yaml") || ext.endsWith(".yml")) return (0, js_yaml.load)(fileContent) || {};
27
+ else if (ext.endsWith(".json")) return JSON.parse(fileContent);
28
+ else try {
29
+ return JSON.parse(fileContent);
30
+ } catch {
31
+ return (0, js_yaml.load)(fileContent) || {};
32
+ }
33
+ }
34
+ /**
35
+ * Merges configuration from file and environment variables.
36
+ * Environment variables take precedence over file config.
37
+ *
38
+ * @param fileConfig - Configuration loaded from file
39
+ * @returns Merged configuration with env vars taking precedence
40
+ */
41
+ function mergeConfigWithEnv(fileConfig) {
42
+ const envConfig = {};
43
+ if (process.env.PINGOPS_API_KEY) envConfig.apiKey = process.env.PINGOPS_API_KEY;
44
+ if (process.env.PINGOPS_BASE_URL) envConfig.baseUrl = process.env.PINGOPS_BASE_URL;
45
+ if (process.env.PINGOPS_SERVICE_NAME) envConfig.serviceName = process.env.PINGOPS_SERVICE_NAME;
46
+ if (process.env.PINGOPS_DEBUG) envConfig.debug = process.env.PINGOPS_DEBUG === "true";
47
+ if (process.env.PINGOPS_BATCH_SIZE) envConfig.batchSize = parseInt(process.env.PINGOPS_BATCH_SIZE, 10);
48
+ if (process.env.PINGOPS_BATCH_TIMEOUT) envConfig.batchTimeout = parseInt(process.env.PINGOPS_BATCH_TIMEOUT, 10);
49
+ if (process.env.PINGOPS_EXPORT_MODE) envConfig.exportMode = process.env.PINGOPS_EXPORT_MODE;
50
+ return {
51
+ ...fileConfig,
52
+ ...envConfig
53
+ };
54
+ }
55
+
56
+ //#endregion
57
+ //#region src/init-state.ts
58
+ /**
59
+ * Shared state for tracking SDK initialization
60
+ * This module exists to avoid circular dependencies between pingops.ts and instrumentation.ts
61
+ */
62
+ let isSdkInitializedFlag$1 = false;
63
+ /**
64
+ * Sets the SDK initialization flag.
65
+ * Called by initializePingops when the SDK is initialized.
66
+ */
67
+ function setSdkInitialized(initialized) {
68
+ isSdkInitializedFlag$1 = initialized;
69
+ }
70
+ /**
71
+ * Checks if global instrumentation is enabled.
72
+ * This is used to determine instrumentation behavior:
73
+ * - If true: all HTTP requests are instrumented
74
+ * - If false: only requests within wrapHttp blocks are instrumented
75
+ */
76
+ function isGlobalInstrumentationEnabled() {
77
+ return isSdkInitializedFlag$1;
78
+ }
79
+
80
+ //#endregion
81
+ //#region src/pingops.ts
82
+ /**
83
+ * PingOps SDK singleton for manual instrumentation
84
+ *
85
+ * Provides initializePingops and shutdownPingops functions.
86
+ * wrapHttp is available from @pingops/core and will auto-initialize
87
+ * from environment variables if needed.
88
+ */
89
+ const initLogger = (0, _pingops_core.createLogger)("[PingOps Initialize]");
90
+ const logger = (0, _pingops_core.createLogger)("[PingOps Pingops]");
91
+ let sdkInstance = null;
92
+ let isSdkInitializedFlag = false;
93
+ /**
94
+ * Global state to track initialization
95
+ */
96
+ let isInitialized = false;
97
+ let initializationPromise = null;
98
+ function initializePingops(config, explicit = true) {
99
+ const resolvedConfig = typeof config === "string" ? resolveConfigFromFile(config) : "configFile" in config ? resolveConfigFromFile(config.configFile) : config;
100
+ if (isSdkInitializedFlag) {
101
+ if (resolvedConfig.debug) initLogger.warn("[PingOps] SDK already initialized, skipping");
102
+ return;
103
+ }
104
+ const resource = (0, _opentelemetry_resources.resourceFromAttributes)({ [_opentelemetry_semantic_conventions.ATTR_SERVICE_NAME]: resolvedConfig.serviceName });
105
+ const processor = new _pingops_otel.PingopsSpanProcessor(resolvedConfig);
106
+ const instrumentations = (0, _pingops_otel.getInstrumentations)(isGlobalInstrumentationEnabled);
107
+ const nodeSdk = new _opentelemetry_sdk_node.NodeSDK({
108
+ resource,
109
+ spanProcessors: [processor],
110
+ instrumentations
111
+ });
112
+ nodeSdk.start();
113
+ sdkInstance = nodeSdk;
114
+ isSdkInitializedFlag = true;
115
+ setSdkInitialized(explicit);
116
+ try {
117
+ const isolatedProvider = new _opentelemetry_sdk_trace_node.NodeTracerProvider({
118
+ resource,
119
+ spanProcessors: [processor]
120
+ });
121
+ isolatedProvider.register();
122
+ (0, _pingops_otel.setPingopsTracerProvider)(isolatedProvider);
123
+ } catch (error) {
124
+ if (resolvedConfig.debug) initLogger.error("[PingOps] Failed to create isolated TracerProvider:", error instanceof Error ? error.message : String(error));
125
+ }
126
+ if (resolvedConfig.debug) initLogger.info("[PingOps] SDK initialized");
127
+ }
128
+ function resolveConfigFromFile(configFilePath) {
129
+ const mergedConfig = mergeConfigWithEnv(loadConfigFromFile(configFilePath));
130
+ if (!mergedConfig.baseUrl || !mergedConfig.serviceName) {
131
+ const missing = [!mergedConfig.baseUrl && "baseUrl (or PINGOPS_BASE_URL)", !mergedConfig.serviceName && "serviceName (or PINGOPS_SERVICE_NAME)"].filter(Boolean);
132
+ throw new Error(`initializePingops(configFile) requires ${missing.join(" and ")}. Provide them in the config file or via environment variables.`);
133
+ }
134
+ return mergedConfig;
135
+ }
136
+ /**
137
+ * Shuts down the SDK and flushes remaining spans
138
+ */
139
+ async function shutdownPingops() {
140
+ await (0, _pingops_otel.shutdownTracerProvider)();
141
+ if (!sdkInstance) return;
142
+ await sdkInstance.shutdown();
143
+ sdkInstance = null;
144
+ isSdkInitializedFlag = false;
145
+ setSdkInitialized(false);
146
+ }
147
+ /**
148
+ * Checks if the SDK is already initialized by checking if a NodeTracerProvider is available
149
+ */
150
+ function isSdkInitialized() {
151
+ try {
152
+ const provider = (0, _pingops_otel.getPingopsTracerProvider)();
153
+ const initialized = provider instanceof _opentelemetry_sdk_trace_node.NodeTracerProvider;
154
+ logger.debug("Checked SDK initialization status", {
155
+ initialized,
156
+ providerType: provider.constructor.name
157
+ });
158
+ return initialized;
159
+ } catch (error) {
160
+ logger.debug("Error checking SDK initialization status", { error: error instanceof Error ? error.message : String(error) });
161
+ return false;
162
+ }
163
+ }
164
+ /**
165
+ * Auto-initializes the SDK from environment variables if not already initialized
166
+ */
167
+ async function ensureInitialized() {
168
+ if (isSdkInitialized()) {
169
+ logger.debug("SDK already initialized, skipping auto-initialization");
170
+ isInitialized = true;
171
+ return;
172
+ }
173
+ if (isInitialized) {
174
+ logger.debug("SDK initialization flag already set, skipping");
175
+ return;
176
+ }
177
+ if (initializationPromise) {
178
+ logger.debug("SDK initialization already in progress, waiting...");
179
+ return initializationPromise;
180
+ }
181
+ logger.info("Starting SDK auto-initialization from environment variables");
182
+ initializationPromise = Promise.resolve().then(() => {
183
+ const apiKey = process.env.PINGOPS_API_KEY;
184
+ const baseUrl = process.env.PINGOPS_BASE_URL;
185
+ const serviceName = process.env.PINGOPS_SERVICE_NAME;
186
+ const debug = process.env.PINGOPS_DEBUG === "true";
187
+ logger.debug("Reading environment variables", {
188
+ hasApiKey: !!apiKey,
189
+ hasBaseUrl: !!baseUrl,
190
+ hasServiceName: !!serviceName,
191
+ debug
192
+ });
193
+ if (!apiKey || !baseUrl || !serviceName) {
194
+ const missing = [
195
+ !apiKey && "PINGOPS_API_KEY",
196
+ !baseUrl && "PINGOPS_BASE_URL",
197
+ !serviceName && "PINGOPS_SERVICE_NAME"
198
+ ].filter(Boolean);
199
+ logger.error("Missing required environment variables for auto-initialization", { missing });
200
+ throw new Error(`PingOps SDK auto-initialization requires PINGOPS_API_KEY, PINGOPS_BASE_URL, and PINGOPS_SERVICE_NAME environment variables. Missing: ${missing.join(", ")}`);
201
+ }
202
+ const config = {
203
+ apiKey,
204
+ baseUrl,
205
+ serviceName,
206
+ debug
207
+ };
208
+ logger.info("Initializing SDK with config", {
209
+ baseUrl,
210
+ serviceName,
211
+ debug
212
+ });
213
+ initializePingops(config, false);
214
+ isInitialized = true;
215
+ logger.info("SDK auto-initialization completed successfully");
216
+ });
217
+ try {
218
+ await initializationPromise;
219
+ } catch (error) {
220
+ logger.error("SDK auto-initialization failed", { error: error instanceof Error ? error.message : String(error) });
221
+ throw error;
222
+ } finally {
223
+ initializationPromise = null;
224
+ }
225
+ }
226
+ /**
227
+ * Wraps a function to set attributes on HTTP spans created within the wrapped block.
228
+ *
229
+ * This function sets attributes (userId, sessionId, tags, metadata) in the OpenTelemetry
230
+ * context, which are automatically propagated to all spans created within the wrapped function.
231
+ *
232
+ * Instrumentation behavior:
233
+ * - If `initializePingops` was called: All HTTP requests are instrumented by default.
234
+ * `wrapHttp` only adds attributes to spans created within the wrapped block.
235
+ * - If `initializePingops` was NOT called: Only HTTP requests within `wrapHttp` blocks
236
+ * are instrumented. Requests outside `wrapHttp` are not instrumented.
237
+ *
238
+ * @param options - Options including attributes to propagate to spans
239
+ * @param fn - Function to execute within the attribute context
240
+ * @returns The result of the function
241
+ *
242
+ * @example
243
+ * ```typescript
244
+ * import { wrapHttp } from '@pingops/sdk';
245
+ *
246
+ * // Scenario 1: initializePingops was called
247
+ * initializePingops({ ... });
248
+ *
249
+ * // All HTTP requests are instrumented, but this block adds attributes
250
+ * const result = await wrapHttp({
251
+ * attributes: {
252
+ * userId: 'user-123',
253
+ * sessionId: 'session-456',
254
+ * tags: ['production', 'api'],
255
+ * metadata: { environment: 'prod', version: '1.0.0' }
256
+ * }
257
+ * }, async () => {
258
+ * // This HTTP request will be instrumented AND have the attributes set above
259
+ * const response = await fetch('https://api.example.com/users/123');
260
+ * return response.json();
261
+ * });
262
+ *
263
+ * // HTTP requests outside wrapHttp are still instrumented, just without the attributes
264
+ * const otherResponse = await fetch('https://api.example.com/other');
265
+ *
266
+ * // Scenario 2: initializePingops was NOT called
267
+ * // Only requests within wrapHttp are instrumented
268
+ * await wrapHttp({
269
+ * attributes: { userId: 'user-123' }
270
+ * }, async () => {
271
+ * // This request IS instrumented
272
+ * return fetch('https://api.example.com/data');
273
+ * });
274
+ *
275
+ * // This request is NOT instrumented (outside wrapHttp)
276
+ * await fetch('https://api.example.com/other');
277
+ * ```
278
+ */
279
+ function wrapHttp(options, fn) {
280
+ return (0, _pingops_core.wrapHttp)({
281
+ ...options,
282
+ checkInitialized: isSdkInitialized,
283
+ isGlobalInstrumentationEnabled,
284
+ ensureInitialized
285
+ }, fn);
286
+ }
287
+
288
+ //#endregion
289
+ Object.defineProperty(exports, 'initializePingops', {
290
+ enumerable: true,
291
+ get: function () {
292
+ return initializePingops;
293
+ }
294
+ });
295
+ Object.defineProperty(exports, 'loadConfigFromFile', {
296
+ enumerable: true,
297
+ get: function () {
298
+ return loadConfigFromFile;
299
+ }
300
+ });
301
+ Object.defineProperty(exports, 'mergeConfigWithEnv', {
302
+ enumerable: true,
303
+ get: function () {
304
+ return mergeConfigWithEnv;
305
+ }
306
+ });
307
+ Object.defineProperty(exports, 'shutdownPingops', {
308
+ enumerable: true,
309
+ get: function () {
310
+ return shutdownPingops;
311
+ }
312
+ });
313
+ Object.defineProperty(exports, 'wrapHttp', {
314
+ enumerable: true,
315
+ get: function () {
316
+ return wrapHttp;
317
+ }
318
+ });
319
+ //# sourceMappingURL=pingops-Cb6UV5D8.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pingops-Cb6UV5D8.cjs","names":["isSdkInitializedFlag","ATTR_SERVICE_NAME","PingopsSpanProcessor","NodeSDK","NodeTracerProvider"],"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/**\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 { loadConfigFromFile, mergeConfigWithEnv } from \"./config-loader\";\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 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 wrapHttp 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 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(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 (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 * 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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"}