@elasticdash/otel 0.0.7 → 0.0.9
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/dist/index.cjs +35 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -9
- package/dist/index.d.ts +9 -9
- package/dist/index.mjs +38 -38
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -20,7 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
|
|
23
|
+
ElasticDashSpanProcessor: () => ElasticDashSpanProcessor
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
26
|
|
|
@@ -46,12 +46,12 @@ var MediaService = class {
|
|
|
46
46
|
async process(span) {
|
|
47
47
|
var _a;
|
|
48
48
|
const mediaAttributes = [
|
|
49
|
-
import_core.
|
|
50
|
-
import_core.
|
|
51
|
-
import_core.
|
|
52
|
-
import_core.
|
|
53
|
-
import_core.
|
|
54
|
-
import_core.
|
|
49
|
+
import_core.ElasticDashOtelSpanAttributes.OBSERVATION_INPUT,
|
|
50
|
+
import_core.ElasticDashOtelSpanAttributes.TRACE_INPUT,
|
|
51
|
+
import_core.ElasticDashOtelSpanAttributes.OBSERVATION_OUTPUT,
|
|
52
|
+
import_core.ElasticDashOtelSpanAttributes.TRACE_OUTPUT,
|
|
53
|
+
import_core.ElasticDashOtelSpanAttributes.OBSERVATION_METADATA,
|
|
54
|
+
import_core.ElasticDashOtelSpanAttributes.TRACE_METADATA
|
|
55
55
|
];
|
|
56
56
|
for (const mediaAttribute of mediaAttributes) {
|
|
57
57
|
const mediaRelevantAttributeKeys = Object.keys(span.attributes).filter(
|
|
@@ -70,12 +70,12 @@ var MediaService = class {
|
|
|
70
70
|
const foundMedia = [...new Set((_a = value.match(regex)) != null ? _a : [])];
|
|
71
71
|
if (foundMedia.length === 0) continue;
|
|
72
72
|
for (const mediaDataUri of foundMedia) {
|
|
73
|
-
const media = new import_core.
|
|
73
|
+
const media = new import_core.ElasticDashMedia({
|
|
74
74
|
base64DataUri: mediaDataUri,
|
|
75
75
|
source: "base64_data_uri"
|
|
76
76
|
});
|
|
77
|
-
const
|
|
78
|
-
if (!
|
|
77
|
+
const elasticDashMediaTag = await media.getTag();
|
|
78
|
+
if (!elasticDashMediaTag) {
|
|
79
79
|
this.logger.warn(
|
|
80
80
|
"Failed to create ElasticDash media tag. Skipping media item."
|
|
81
81
|
);
|
|
@@ -89,7 +89,7 @@ var MediaService = class {
|
|
|
89
89
|
});
|
|
90
90
|
mediaReplacedValue = mediaReplacedValue.replaceAll(
|
|
91
91
|
mediaDataUri,
|
|
92
|
-
|
|
92
|
+
elasticDashMediaTag
|
|
93
93
|
);
|
|
94
94
|
}
|
|
95
95
|
span.attributes[key] = mediaReplacedValue;
|
|
@@ -119,13 +119,13 @@ var MediaService = class {
|
|
|
119
119
|
base64Content = part["image"];
|
|
120
120
|
}
|
|
121
121
|
if (!base64Content) continue;
|
|
122
|
-
const media = new import_core.
|
|
122
|
+
const media = new import_core.ElasticDashMedia({
|
|
123
123
|
contentType: part["mediaType"],
|
|
124
124
|
contentBytes: (0, import_core.base64ToBytes)(base64Content),
|
|
125
125
|
source: "bytes"
|
|
126
126
|
});
|
|
127
|
-
const
|
|
128
|
-
if (!
|
|
127
|
+
const elasticDashMediaTag = await media.getTag();
|
|
128
|
+
if (!elasticDashMediaTag) {
|
|
129
129
|
this.logger.warn(
|
|
130
130
|
"Failed to create ElasticDash media tag. Skipping media item."
|
|
131
131
|
);
|
|
@@ -138,7 +138,7 @@ var MediaService = class {
|
|
|
138
138
|
});
|
|
139
139
|
mediaReplacedValue = mediaReplacedValue.replaceAll(
|
|
140
140
|
base64Content,
|
|
141
|
-
|
|
141
|
+
elasticDashMediaTag
|
|
142
142
|
);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
@@ -271,15 +271,15 @@ var MediaService = class {
|
|
|
271
271
|
};
|
|
272
272
|
|
|
273
273
|
// src/span-processor.ts
|
|
274
|
-
var
|
|
274
|
+
var ElasticDashSpanProcessor = class {
|
|
275
275
|
/**
|
|
276
|
-
* Creates a new
|
|
276
|
+
* Creates a new ElasticDashSpanProcessor instance.
|
|
277
277
|
*
|
|
278
278
|
* @param params - Configuration parameters for the processor
|
|
279
279
|
*
|
|
280
280
|
* @example
|
|
281
281
|
* ```typescript
|
|
282
|
-
* const processor = new
|
|
282
|
+
* const processor = new ElasticDashSpanProcessor({
|
|
283
283
|
* publicKey: 'pk_...',
|
|
284
284
|
* secretKey: 'sk_...',
|
|
285
285
|
* environment: 'staging',
|
|
@@ -326,9 +326,9 @@ var LangfuseSpanProcessor = class {
|
|
|
326
326
|
url: `${baseUrl}/api/public/otel/v1/traces`,
|
|
327
327
|
headers: {
|
|
328
328
|
Authorization: `Basic ${authHeaderValue}`,
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
329
|
+
x_elasticdash_sdk_name: "javascript",
|
|
330
|
+
x_elasticdash_sdk_version: import_core2.ELASTICDASH_SDK_VERSION,
|
|
331
|
+
x_elasticdash_public_key: publicKey != null ? publicKey : "<missing>",
|
|
332
332
|
...params == null ? void 0 : params.additionalHeaders
|
|
333
333
|
},
|
|
334
334
|
timeoutMillis: timeoutSeconds * 1e3
|
|
@@ -343,19 +343,19 @@ var LangfuseSpanProcessor = class {
|
|
|
343
343
|
this.release = (_l = params == null ? void 0 : params.release) != null ? _l : (0, import_core2.getEnv)("ELASTICDASH_RELEASE");
|
|
344
344
|
this.mask = params == null ? void 0 : params.mask;
|
|
345
345
|
this.shouldExportSpan = params == null ? void 0 : params.shouldExportSpan;
|
|
346
|
-
this.apiClient = new import_core2.
|
|
346
|
+
this.apiClient = new import_core2.ElasticDashAPIClient({
|
|
347
347
|
baseUrl: this.baseUrl,
|
|
348
348
|
username: this.publicKey,
|
|
349
349
|
password: secretKey,
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
350
|
+
xElasticDashPublicKey: this.publicKey,
|
|
351
|
+
xElasticDashSdkVersion: import_core2.ELASTICDASH_SDK_VERSION,
|
|
352
|
+
xElasticDashSdkName: "javascript",
|
|
353
353
|
environment: "",
|
|
354
354
|
// noop as baseUrl is set
|
|
355
355
|
headers: params == null ? void 0 : params.additionalHeaders
|
|
356
356
|
});
|
|
357
357
|
this.mediaService = new MediaService({ apiClient: this.apiClient });
|
|
358
|
-
logger.debug("Initialized
|
|
358
|
+
logger.debug("Initialized ElasticDashSpanProcessor with params:", {
|
|
359
359
|
publicKey,
|
|
360
360
|
baseUrl,
|
|
361
361
|
environment: this.environment,
|
|
@@ -378,8 +378,8 @@ var LangfuseSpanProcessor = class {
|
|
|
378
378
|
*/
|
|
379
379
|
onStart(span, parentContext) {
|
|
380
380
|
span.setAttributes({
|
|
381
|
-
[import_core2.
|
|
382
|
-
[import_core2.
|
|
381
|
+
[import_core2.ElasticDashOtelSpanAttributes.ENVIRONMENT]: this.environment,
|
|
382
|
+
[import_core2.ElasticDashOtelSpanAttributes.RELEASE]: this.release,
|
|
383
383
|
...(0, import_core2.getPropagatedAttributesFromContext)(parentContext)
|
|
384
384
|
});
|
|
385
385
|
return this.processor.onStart(span, parentContext);
|
|
@@ -473,12 +473,12 @@ ${JSON.stringify(
|
|
|
473
473
|
}
|
|
474
474
|
applyMaskInPlace(span) {
|
|
475
475
|
const maskCandidates = [
|
|
476
|
-
import_core2.
|
|
477
|
-
import_core2.
|
|
478
|
-
import_core2.
|
|
479
|
-
import_core2.
|
|
480
|
-
import_core2.
|
|
481
|
-
import_core2.
|
|
476
|
+
import_core2.ElasticDashOtelSpanAttributes.OBSERVATION_INPUT,
|
|
477
|
+
import_core2.ElasticDashOtelSpanAttributes.TRACE_INPUT,
|
|
478
|
+
import_core2.ElasticDashOtelSpanAttributes.OBSERVATION_OUTPUT,
|
|
479
|
+
import_core2.ElasticDashOtelSpanAttributes.TRACE_OUTPUT,
|
|
480
|
+
import_core2.ElasticDashOtelSpanAttributes.OBSERVATION_METADATA,
|
|
481
|
+
import_core2.ElasticDashOtelSpanAttributes.TRACE_METADATA
|
|
482
482
|
];
|
|
483
483
|
for (const maskCandidate of maskCandidates) {
|
|
484
484
|
if (maskCandidate in span.attributes) {
|
|
@@ -502,6 +502,6 @@ ${JSON.stringify(
|
|
|
502
502
|
};
|
|
503
503
|
// Annotate the CommonJS export names for ESM import in node:
|
|
504
504
|
0 && (module.exports = {
|
|
505
|
-
|
|
505
|
+
ElasticDashSpanProcessor
|
|
506
506
|
});
|
|
507
507
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/span-processor.ts","../src/MediaService.ts"],"sourcesContent":["export * from \"./span-processor.js\";\n","import {\n Logger,\n getGlobalLogger,\n LangfuseAPIClient,\n ELASTICDASH_SDK_VERSION,\n LangfuseOtelSpanAttributes,\n getEnv,\n base64Encode,\n getPropagatedAttributesFromContext,\n} from \"@elasticdash/core\";\nimport { Context } from \"@opentelemetry/api\";\nimport { hrTimeToMilliseconds } from \"@opentelemetry/core\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport {\n Span,\n BatchSpanProcessor,\n SimpleSpanProcessor,\n SpanExporter,\n ReadableSpan,\n SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\n\nimport { MediaService } from \"./MediaService.js\";\n\n/**\n * Function type for masking sensitive data in spans before export.\n *\n * @param params - Object containing the data to be masked\n * @param params.data - The data that should be masked\n * @returns The masked data (can be of any type)\n *\n * @example\n * ```typescript\n * const maskFunction: MaskFunction = ({ data }) => {\n * if (typeof data === 'string') {\n * return data.replace(/password=\\w+/g, 'password=***');\n * }\n * return data;\n * };\n * ```\n *\n * @public\n */\nexport type MaskFunction = (params: { data: any }) => any;\n\n/**\n * Function type for determining whether a span should be exported to ElasticDash.\n *\n * @param params - Object containing the span to evaluate\n * @param params.otelSpan - The OpenTelemetry span to evaluate\n * @returns `true` if the span should be exported, `false` otherwise\n *\n * @example\n * ```typescript\n * const shouldExportSpan: ShouldExportSpan = ({ otelSpan }) => {\n * // Only export spans that took longer than 100ms\n * return otelSpan.duration[0] * 1000 + otelSpan.duration[1] / 1000000 > 100;\n * };\n * ```\n *\n * @public\n */\nexport type ShouldExportSpan = (params: { otelSpan: ReadableSpan }) => boolean;\n\n/**\n * Configuration parameters for the LangfuseSpanProcessor.\n *\n * @public\n */\nexport interface LangfuseSpanProcessorParams {\n /**\n * Custom OpenTelemetry span exporter. If not provided, a default OTLP exporter will be used.\n */\n exporter?: SpanExporter;\n\n /**\n * ElasticDash public API key. Can also be set via ELASTICDASH_PUBLIC_KEY environment variable.\n */\n publicKey?: string;\n\n /**\n * ElasticDash secret API key. Can also be set via ELASTICDASH_SECRET_KEY environment variable.\n */\n secretKey?: string;\n\n /**\n * ElasticDash instance base URL. Can also be set via ELASTICDASH_BASE_URL environment variable.\n * @defaultValue \"https://cloud.elasticdash.com\"\n */\n baseUrl?: string;\n\n /**\n * Number of spans to batch before flushing. Can also be set via ELASTICDASH_FLUSH_AT environment variable.\n */\n flushAt?: number;\n\n /**\n * Flush interval in seconds. Can also be set via ELASTICDASH_FLUSH_INTERVAL environment variable.\n */\n flushInterval?: number;\n\n /**\n * Function to mask sensitive data in spans before export.\n */\n mask?: MaskFunction;\n\n /**\n * Function to determine whether a span should be exported to ElasticDash.\n */\n shouldExportSpan?: ShouldExportSpan;\n\n /**\n * Environment identifier for the traces. Can also be set via ELASTICDASH_TRACING_ENVIRONMENT environment variable.\n */\n environment?: string;\n\n /**\n * Release identifier for the traces. Can also be set via ELASTICDASH_RELEASE environment variable.\n */\n release?: string;\n\n /**\n * Request timeout in seconds. Can also be set via ELASTICDASH_TIMEOUT environment variable.\n * @defaultValue 5\n */\n timeout?: number;\n\n /**\n * Additional HTTP headers to include with requests.\n */\n additionalHeaders?: Record<string, string>;\n /**\n * Span export mode to use.\n *\n * - **batched**: Recommended for production environments with long-running processes.\n * Spans are batched and exported in groups for optimal performance.\n * - **immediate**: Recommended for short-lived environments such as serverless functions.\n * Spans are exported immediately to prevent data loss when the process terminates / is frozen.\n *\n * @defaultValue \"batched\"\n */\n exportMode?: \"immediate\" | \"batched\";\n}\n\n/**\n * OpenTelemetry span processor for sending spans to ElasticDash.\n *\n * This processor extends the standard BatchSpanProcessor to provide:\n * - Automatic batching and flushing of spans to ElasticDash\n * - Media content extraction and upload from base64 data URIs\n * - Data masking capabilities for sensitive information\n * - Conditional span export based on custom logic\n * - Environment and release tagging\n *\n * @example\n * ```typescript\n * import { NodeSDK } from '@opentelemetry/sdk-node';\n * import { LangfuseSpanProcessor } from '@elasticdash/otel';\n *\n * const sdk = new NodeSDK({\n * spanProcessors: [\n * new LangfuseSpanProcessor({\n * publicKey: 'pk_...',\n * secretKey: 'sk_...',\n * baseUrl: 'https://cloud.elasticdash.com',\n * environment: 'production',\n * mask: ({ data }) => {\n * // Mask sensitive data\n * return data.replace(/api_key=\\w+/g, 'api_key=***');\n * }\n * })\n * ]\n * });\n *\n * sdk.start();\n * ```\n *\n * @public\n */\nexport class LangfuseSpanProcessor implements SpanProcessor {\n private pendingEndedSpans: Set<Promise<void>> = new Set();\n\n private publicKey?: string;\n private baseUrl?: string;\n private environment?: string;\n private release?: string;\n private mask?: MaskFunction;\n private shouldExportSpan?: ShouldExportSpan;\n private apiClient: LangfuseAPIClient;\n private processor: SpanProcessor;\n private mediaService: MediaService;\n\n /**\n * Creates a new LangfuseSpanProcessor instance.\n *\n * @param params - Configuration parameters for the processor\n *\n * @example\n * ```typescript\n * const processor = new LangfuseSpanProcessor({\n * publicKey: 'pk_...',\n * secretKey: 'sk_...',\n * environment: 'staging',\n * flushAt: 10,\n * flushInterval: 2,\n * mask: ({ data }) => {\n * // Custom masking logic\n * return typeof data === 'string'\n * ? data.replace(/secret_\\w+/g, 'secret_***')\n * : data;\n * },\n * shouldExportSpan: ({ otelSpan }) => {\n * // Only export spans from specific services\n * return otelSpan.name.startsWith('my-service');\n * }\n * });\n * ```\n */\n constructor(params?: LangfuseSpanProcessorParams) {\n const logger = getGlobalLogger();\n\n const publicKey = params?.publicKey ?? getEnv(\"ELASTICDASH_PUBLIC_KEY\");\n const secretKey = params?.secretKey ?? getEnv(\"ELASTICDASH_SECRET_KEY\");\n const baseUrl =\n params?.baseUrl ??\n getEnv(\"ELASTICDASH_BASE_URL\") ??\n getEnv(\"ELASTICDASH_BASEURL\") ?? // legacy v2\n \"https://cloud.elasticdash.com\";\n\n if (!params?.exporter && !publicKey) {\n logger.warn(\n \"No exporter configured and no public key provided in constructor or as ELASTICDASH_PUBLIC_KEY env var. Span exports will fail.\",\n );\n }\n if (!params?.exporter && !secretKey) {\n logger.warn(\n \"No exporter configured and no secret key provided in constructor or as ELASTICDASH_SECRET_KEY env var. Span exports will fail.\",\n );\n }\n const flushAt = params?.flushAt ?? getEnv(\"ELASTICDASH_FLUSH_AT\");\n const flushIntervalSeconds =\n params?.flushInterval ?? getEnv(\"ELASTICDASH_FLUSH_INTERVAL\");\n\n const authHeaderValue = base64Encode(`${publicKey}:${secretKey}`);\n const timeoutSeconds =\n params?.timeout ?? Number(getEnv(\"ELASTICDASH_TIMEOUT\") ?? 5);\n\n const exporter =\n params?.exporter ??\n new OTLPTraceExporter({\n url: `${baseUrl}/api/public/otel/v1/traces`,\n headers: {\n Authorization: `Basic ${authHeaderValue}`,\n x_langfuse_sdk_name: \"javascript\",\n x_langfuse_sdk_version: ELASTICDASH_SDK_VERSION,\n x_langfuse_public_key: publicKey ?? \"<missing>\",\n ...params?.additionalHeaders,\n },\n timeoutMillis: timeoutSeconds * 1_000,\n });\n\n this.processor =\n params?.exportMode === \"immediate\"\n ? new SimpleSpanProcessor(exporter)\n : new BatchSpanProcessor(exporter, {\n maxExportBatchSize: flushAt ? Number(flushAt) : undefined,\n scheduledDelayMillis: flushIntervalSeconds\n ? Number(flushIntervalSeconds) * 1_000\n : undefined,\n });\n\n this.publicKey = publicKey;\n this.baseUrl = baseUrl;\n this.environment =\n params?.environment ?? getEnv(\"ELASTICDASH_TRACING_ENVIRONMENT\");\n this.release = params?.release ?? getEnv(\"ELASTICDASH_RELEASE\");\n this.mask = params?.mask;\n this.shouldExportSpan = params?.shouldExportSpan;\n this.apiClient = new LangfuseAPIClient({\n baseUrl: this.baseUrl,\n username: this.publicKey,\n password: secretKey,\n xLangfusePublicKey: this.publicKey,\n xLangfuseSdkVersion: ELASTICDASH_SDK_VERSION,\n xLangfuseSdkName: \"javascript\",\n environment: \"\", // noop as baseUrl is set\n headers: params?.additionalHeaders,\n });\n\n this.mediaService = new MediaService({ apiClient: this.apiClient });\n\n logger.debug(\"Initialized LangfuseSpanProcessor with params:\", {\n publicKey,\n baseUrl,\n environment: this.environment,\n release: this.release,\n timeoutSeconds,\n flushAt,\n flushIntervalSeconds,\n });\n }\n\n private get logger(): Logger {\n return getGlobalLogger();\n }\n\n /**\n * Called when a span is started. Adds environment, release, and propagated attributes to the span.\n *\n * @param span - The span that was started\n * @param parentContext - The parent context\n *\n * @override\n */\n public onStart(span: Span, parentContext: Context): void {\n // Set propagated attributes, environment and release attributes\n span.setAttributes({\n [LangfuseOtelSpanAttributes.ENVIRONMENT]: this.environment,\n [LangfuseOtelSpanAttributes.RELEASE]: this.release,\n ...getPropagatedAttributesFromContext(parentContext),\n });\n\n return this.processor.onStart(span, parentContext);\n }\n\n /**\n * Called when a span ends. Processes the span for export to ElasticDash.\n *\n * This method:\n * 1. Checks if the span should be exported using the shouldExportSpan function\n * 2. Applies data masking to sensitive attributes\n * 3. Handles media content extraction and upload\n * 4. Logs span details in debug mode\n * 5. Passes the span to the parent processor for export\n *\n * @param span - The span that ended\n *\n * @override\n */\n public onEnd(span: ReadableSpan): void {\n const processEndedSpanPromise = this.processEndedSpan(span).catch((err) => {\n this.logger.error(err);\n });\n\n // Enqueue this export to the pending list so it can be flushed by the user.\n this.pendingEndedSpans.add(processEndedSpanPromise);\n\n void processEndedSpanPromise.finally(() =>\n this.pendingEndedSpans.delete(processEndedSpanPromise),\n );\n }\n\n private async flush(): Promise<void> {\n await Promise.all(Array.from(this.pendingEndedSpans));\n await this.mediaService.flush();\n }\n\n /**\n * Forces an immediate flush of all pending spans and media uploads.\n *\n * @returns Promise that resolves when all pending operations are complete\n *\n * @override\n */\n public async forceFlush(): Promise<void> {\n await this.flush();\n\n return this.processor.forceFlush();\n }\n\n /**\n * Gracefully shuts down the processor, ensuring all pending operations are completed.\n *\n * @returns Promise that resolves when shutdown is complete\n *\n * @override\n */\n public async shutdown(): Promise<void> {\n await this.flush();\n\n return this.processor.shutdown();\n }\n\n private async processEndedSpan(span: ReadableSpan) {\n if (this.shouldExportSpan) {\n try {\n if (this.shouldExportSpan({ otelSpan: span }) === false) return;\n } catch (err) {\n this.logger.error(\n \"ShouldExportSpan failed with error. Excluding span. Error: \",\n err,\n );\n\n return;\n }\n }\n\n this.applyMaskInPlace(span);\n await this.mediaService.process(span);\n\n this.logger.debug(\n `Processed span:\\n${JSON.stringify(\n {\n name: span.name,\n traceId: span.spanContext().traceId,\n spanId: span.spanContext().spanId,\n parentSpanId: span.parentSpanContext?.spanId ?? null,\n attributes: span.attributes,\n startTime: new Date(hrTimeToMilliseconds(span.startTime)),\n endTime: new Date(hrTimeToMilliseconds(span.endTime)),\n durationMs: hrTimeToMilliseconds(span.duration),\n kind: span.kind,\n status: span.status,\n resource: span.resource.attributes,\n instrumentationScope: span.instrumentationScope,\n },\n null,\n 2,\n )}`,\n );\n\n this.processor.onEnd(span);\n }\n private applyMaskInPlace(span: ReadableSpan): void {\n const maskCandidates = [\n LangfuseOtelSpanAttributes.OBSERVATION_INPUT,\n LangfuseOtelSpanAttributes.TRACE_INPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,\n LangfuseOtelSpanAttributes.TRACE_OUTPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_METADATA,\n LangfuseOtelSpanAttributes.TRACE_METADATA,\n ];\n\n for (const maskCandidate of maskCandidates) {\n if (maskCandidate in span.attributes) {\n span.attributes[maskCandidate] = this.applyMask(\n span.attributes[maskCandidate],\n );\n }\n }\n }\n\n private applyMask<T>(data: T): T | string {\n if (!this.mask) return data;\n\n try {\n return this.mask({ data });\n } catch (err) {\n this.logger.warn(\n `Applying mask function failed due to error, fully masking property. Error: ${err}`,\n );\n\n return \"<fully masked due to failed mask function>\";\n }\n }\n}\n","import {\n LangfuseAPIClient,\n LangfuseMedia,\n LangfuseOtelSpanAttributes,\n Logger,\n base64ToBytes,\n getGlobalLogger,\n} from \"@elasticdash/core\";\nimport { ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\n\nexport class MediaService {\n private pendingMediaUploads: Set<Promise<void>> = new Set();\n private apiClient: LangfuseAPIClient;\n\n constructor(params: { apiClient: LangfuseAPIClient }) {\n this.apiClient = params.apiClient;\n }\n\n get logger(): Logger {\n return getGlobalLogger();\n }\n\n public async flush(): Promise<void> {\n await Promise.all(Array.from(this.pendingMediaUploads));\n }\n\n public async process(span: ReadableSpan) {\n const mediaAttributes = [\n LangfuseOtelSpanAttributes.OBSERVATION_INPUT,\n LangfuseOtelSpanAttributes.TRACE_INPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,\n LangfuseOtelSpanAttributes.TRACE_OUTPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_METADATA,\n LangfuseOtelSpanAttributes.TRACE_METADATA,\n ];\n\n for (const mediaAttribute of mediaAttributes) {\n const mediaRelevantAttributeKeys = Object.keys(span.attributes).filter(\n (attributeName) => attributeName.startsWith(mediaAttribute),\n );\n\n for (const key of mediaRelevantAttributeKeys) {\n const value = span.attributes[key];\n\n if (typeof value !== \"string\") {\n this.logger.warn(\n `Span attribute ${mediaAttribute} is not a stringified object. Skipping media handling.`,\n );\n\n continue;\n }\n\n // Find media base64 data URI\n let mediaReplacedValue = value;\n const regex = /data:[^;]+;base64,[A-Za-z0-9+/]+=*/g;\n const foundMedia = [...new Set(value.match(regex) ?? [])];\n\n if (foundMedia.length === 0) continue;\n\n for (const mediaDataUri of foundMedia) {\n // For each media, create media tag and initiate upload\n const media = new LangfuseMedia({\n base64DataUri: mediaDataUri,\n source: \"base64_data_uri\",\n });\n\n const langfuseMediaTag = await media.getTag();\n\n if (!langfuseMediaTag) {\n this.logger.warn(\n \"Failed to create ElasticDash media tag. Skipping media item.\",\n );\n\n continue;\n }\n\n this.scheduleUpload({\n span,\n media,\n field: mediaAttribute.includes(\"input\")\n ? \"input\"\n : mediaAttribute.includes(\"output\")\n ? \"output\"\n : \"metadata\", // todo: make more robust\n });\n\n // Replace original attribute with media escaped attribute\n mediaReplacedValue = mediaReplacedValue.replaceAll(\n mediaDataUri,\n langfuseMediaTag,\n );\n }\n\n span.attributes[key] = mediaReplacedValue;\n }\n }\n\n // Handle media from Vercel AI SDK\n if (span.instrumentationScope.name === \"ai\") {\n const aiSDKMediaAttributes = [\"ai.prompt.messages\", \"ai.prompt\"];\n\n for (const mediaAttribute of aiSDKMediaAttributes) {\n const value = span.attributes[mediaAttribute];\n\n if (!value || typeof value !== \"string\") {\n continue;\n }\n\n // Find media base64 data URI\n let mediaReplacedValue = value;\n\n try {\n const parsed = JSON.parse(value);\n\n if (Array.isArray(parsed)) {\n for (const message of parsed) {\n if (Array.isArray(message[\"content\"])) {\n const contentParts = message[\"content\"];\n\n for (const part of contentParts) {\n if (part[\"type\"] === \"file\") {\n let base64Content: string | null = null;\n // FilePart\n if (part[\"data\"] != null && part[\"mediaType\"] != null) {\n base64Content = part[\"data\"];\n }\n\n //ImagePart\n if (\n part[\"image\"] != null &&\n part[\"mediaType\"] != null &&\n !part[\"image\"].startsWith(\"http\") // skip URLs\n ) {\n base64Content = part[\"image\"];\n }\n\n if (!base64Content) continue;\n\n const media = new LangfuseMedia({\n contentType: part[\"mediaType\"],\n contentBytes: base64ToBytes(base64Content),\n source: \"bytes\",\n });\n\n const langfuseMediaTag = await media.getTag();\n\n if (!langfuseMediaTag) {\n this.logger.warn(\n \"Failed to create ElasticDash media tag. Skipping media item.\",\n );\n\n continue;\n }\n\n this.scheduleUpload({\n span,\n media,\n field: \"input\",\n });\n\n // Replace original attribute with media escaped attribute\n mediaReplacedValue = mediaReplacedValue.replaceAll(\n base64Content,\n langfuseMediaTag,\n );\n }\n }\n }\n }\n }\n\n span.attributes[mediaAttribute] = mediaReplacedValue;\n } catch (err) {\n this.logger.warn(\n `Failed to handle media for AI SDK attribute ${mediaAttribute} for span ${span.spanContext().spanId}`,\n err,\n );\n }\n }\n }\n }\n\n private scheduleUpload(params: {\n span: ReadableSpan;\n field: string;\n media: LangfuseMedia;\n }) {\n const { span, field, media } = params;\n\n const uploadPromise: Promise<void> = this.handleUpload({\n media,\n traceId: span.spanContext().traceId,\n observationId: span.spanContext().spanId,\n field,\n }).catch((err) => {\n this.logger.error(\"Media upload failed with error: \", err);\n });\n\n this.pendingMediaUploads.add(uploadPromise);\n\n uploadPromise.finally(() => {\n this.pendingMediaUploads.delete(uploadPromise);\n });\n }\n\n private async handleUpload({\n media,\n traceId,\n observationId,\n field,\n }: {\n media: LangfuseMedia;\n traceId: string;\n observationId?: string;\n field: string;\n }): Promise<void> {\n try {\n const contentSha256Hash = await media.getSha256Hash();\n\n if (\n !media.contentLength ||\n !media._contentType ||\n !contentSha256Hash ||\n !media._contentBytes\n ) {\n return;\n }\n\n const { uploadUrl, mediaId } = await this.apiClient.media.getUploadUrl({\n contentLength: media.contentLength,\n traceId,\n observationId,\n field,\n contentType: media._contentType,\n sha256Hash: contentSha256Hash,\n });\n\n if (!uploadUrl) {\n this.logger.debug(\n `Media status: Media with ID ${mediaId} already uploaded. Skipping duplicate upload.`,\n );\n\n return;\n }\n\n const clientSideMediaId = await media.getId();\n if (clientSideMediaId !== mediaId) {\n this.logger.error(\n `Media integrity error: Media ID mismatch between SDK (${clientSideMediaId}) and Server (${mediaId}). Upload cancelled. Please check media ID generation logic.`,\n );\n\n return;\n }\n\n this.logger.debug(`Uploading media ${mediaId}...`);\n\n const startTime = Date.now();\n\n const uploadResponse = await this.uploadWithBackoff({\n uploadUrl,\n contentBytes: media._contentBytes,\n contentType: media._contentType,\n contentSha256Hash: contentSha256Hash,\n maxRetries: 3,\n baseDelay: 1000,\n });\n\n if (!uploadResponse) {\n throw Error(\"Media upload process failed\");\n }\n\n await this.apiClient.media.patch(mediaId, {\n uploadedAt: new Date().toISOString(),\n uploadHttpStatus: uploadResponse.status,\n uploadHttpError: await uploadResponse.text(),\n uploadTimeMs: Date.now() - startTime,\n });\n\n this.logger.debug(`Media upload status reported for ${mediaId}`);\n } catch (err) {\n this.logger.error(`Error processing media item: ${err}`);\n }\n }\n\n private async uploadWithBackoff(params: {\n uploadUrl: string;\n contentType: string;\n contentSha256Hash: string;\n contentBytes: Uint8Array;\n maxRetries: number;\n baseDelay: number;\n }) {\n const {\n uploadUrl,\n contentType,\n contentSha256Hash,\n contentBytes,\n maxRetries,\n baseDelay,\n } = params;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n let parsedHostname: string;\n\n try {\n parsedHostname = new URL(uploadUrl).hostname;\n } catch {\n parsedHostname = \"\";\n }\n\n const isSelfHostedGcsBucket =\n parsedHostname === \"storage.googleapis.com\" ||\n parsedHostname.endsWith(\".storage.googleapis.com\");\n\n const headers = isSelfHostedGcsBucket\n ? { \"Content-Type\": contentType }\n : {\n \"Content-Type\": contentType,\n \"x-amz-checksum-sha256\": contentSha256Hash,\n \"x-ms-blob-type\": \"BlockBlob\",\n };\n\n const uploadResponse = await fetch(uploadUrl, {\n method: \"PUT\",\n body: contentBytes,\n headers,\n });\n\n if (\n attempt < maxRetries &&\n uploadResponse.status !== 200 &&\n uploadResponse.status !== 201\n ) {\n throw new Error(`Upload failed with status ${uploadResponse.status}`);\n }\n\n return uploadResponse;\n } catch (e) {\n if (attempt === maxRetries) {\n throw e;\n }\n\n const delay = baseDelay * Math.pow(2, attempt);\n const jitter = Math.random() * 1000;\n\n await new Promise((resolve) => setTimeout(resolve, delay + jitter));\n }\n }\n }\n}\n"],"mappings":"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","names":["import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/span-processor.ts","../src/MediaService.ts"],"sourcesContent":["export * from \"./span-processor.js\";\n","import {\n Logger,\n getGlobalLogger,\n ElasticDashAPIClient,\n ELASTICDASH_SDK_VERSION,\n ElasticDashOtelSpanAttributes,\n getEnv,\n base64Encode,\n getPropagatedAttributesFromContext,\n} from \"@elasticdash/core\";\nimport { Context } from \"@opentelemetry/api\";\nimport { hrTimeToMilliseconds } from \"@opentelemetry/core\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport {\n Span,\n BatchSpanProcessor,\n SimpleSpanProcessor,\n SpanExporter,\n ReadableSpan,\n SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\n\nimport { MediaService } from \"./MediaService.js\";\n\n/**\n * Function type for masking sensitive data in spans before export.\n *\n * @param params - Object containing the data to be masked\n * @param params.data - The data that should be masked\n * @returns The masked data (can be of any type)\n *\n * @example\n * ```typescript\n * const maskFunction: MaskFunction = ({ data }) => {\n * if (typeof data === 'string') {\n * return data.replace(/password=\\w+/g, 'password=***');\n * }\n * return data;\n * };\n * ```\n *\n * @public\n */\nexport type MaskFunction = (params: { data: any }) => any;\n\n/**\n * Function type for determining whether a span should be exported to ElasticDash.\n *\n * @param params - Object containing the span to evaluate\n * @param params.otelSpan - The OpenTelemetry span to evaluate\n * @returns `true` if the span should be exported, `false` otherwise\n *\n * @example\n * ```typescript\n * const shouldExportSpan: ShouldExportSpan = ({ otelSpan }) => {\n * // Only export spans that took longer than 100ms\n * return otelSpan.duration[0] * 1000 + otelSpan.duration[1] / 1000000 > 100;\n * };\n * ```\n *\n * @public\n */\nexport type ShouldExportSpan = (params: { otelSpan: ReadableSpan }) => boolean;\n\n/**\n * Configuration parameters for the ElasticDashSpanProcessor.\n *\n * @public\n */\nexport interface ElasticDashSpanProcessorParams {\n /**\n * Custom OpenTelemetry span exporter. If not provided, a default OTLP exporter will be used.\n */\n exporter?: SpanExporter;\n\n /**\n * ElasticDash public API key. Can also be set via ELASTICDASH_PUBLIC_KEY environment variable.\n */\n publicKey?: string;\n\n /**\n * ElasticDash secret API key. Can also be set via ELASTICDASH_SECRET_KEY environment variable.\n */\n secretKey?: string;\n\n /**\n * ElasticDash instance base URL. Can also be set via ELASTICDASH_BASE_URL environment variable.\n * @defaultValue \"https://cloud.elasticdash.com\"\n */\n baseUrl?: string;\n\n /**\n * Number of spans to batch before flushing. Can also be set via ELASTICDASH_FLUSH_AT environment variable.\n */\n flushAt?: number;\n\n /**\n * Flush interval in seconds. Can also be set via ELASTICDASH_FLUSH_INTERVAL environment variable.\n */\n flushInterval?: number;\n\n /**\n * Function to mask sensitive data in spans before export.\n */\n mask?: MaskFunction;\n\n /**\n * Function to determine whether a span should be exported to ElasticDash.\n */\n shouldExportSpan?: ShouldExportSpan;\n\n /**\n * Environment identifier for the traces. Can also be set via ELASTICDASH_TRACING_ENVIRONMENT environment variable.\n */\n environment?: string;\n\n /**\n * Release identifier for the traces. Can also be set via ELASTICDASH_RELEASE environment variable.\n */\n release?: string;\n\n /**\n * Request timeout in seconds. Can also be set via ELASTICDASH_TIMEOUT environment variable.\n * @defaultValue 5\n */\n timeout?: number;\n\n /**\n * Additional HTTP headers to include with requests.\n */\n additionalHeaders?: Record<string, string>;\n /**\n * Span export mode to use.\n *\n * - **batched**: Recommended for production environments with long-running processes.\n * Spans are batched and exported in groups for optimal performance.\n * - **immediate**: Recommended for short-lived environments such as serverless functions.\n * Spans are exported immediately to prevent data loss when the process terminates / is frozen.\n *\n * @defaultValue \"batched\"\n */\n exportMode?: \"immediate\" | \"batched\";\n}\n\n/**\n * OpenTelemetry span processor for sending spans to ElasticDash.\n *\n * This processor extends the standard BatchSpanProcessor to provide:\n * - Automatic batching and flushing of spans to ElasticDash\n * - Media content extraction and upload from base64 data URIs\n * - Data masking capabilities for sensitive information\n * - Conditional span export based on custom logic\n * - Environment and release tagging\n *\n * @example\n * ```typescript\n * import { NodeSDK } from '@opentelemetry/sdk-node';\n * import { ElasticDashSpanProcessor } from '@elasticdash/otel';\n *\n * const sdk = new NodeSDK({\n * spanProcessors: [\n * new ElasticDashSpanProcessor({\n * publicKey: 'pk_...',\n * secretKey: 'sk_...',\n * baseUrl: 'https://cloud.elasticdash.com',\n * environment: 'production',\n * mask: ({ data }) => {\n * // Mask sensitive data\n * return data.replace(/api_key=\\w+/g, 'api_key=***');\n * }\n * })\n * ]\n * });\n *\n * sdk.start();\n * ```\n *\n * @public\n */\nexport class ElasticDashSpanProcessor implements SpanProcessor {\n private pendingEndedSpans: Set<Promise<void>> = new Set();\n\n private publicKey?: string;\n private baseUrl?: string;\n private environment?: string;\n private release?: string;\n private mask?: MaskFunction;\n private shouldExportSpan?: ShouldExportSpan;\n private apiClient: ElasticDashAPIClient;\n private processor: SpanProcessor;\n private mediaService: MediaService;\n\n /**\n * Creates a new ElasticDashSpanProcessor instance.\n *\n * @param params - Configuration parameters for the processor\n *\n * @example\n * ```typescript\n * const processor = new ElasticDashSpanProcessor({\n * publicKey: 'pk_...',\n * secretKey: 'sk_...',\n * environment: 'staging',\n * flushAt: 10,\n * flushInterval: 2,\n * mask: ({ data }) => {\n * // Custom masking logic\n * return typeof data === 'string'\n * ? data.replace(/secret_\\w+/g, 'secret_***')\n * : data;\n * },\n * shouldExportSpan: ({ otelSpan }) => {\n * // Only export spans from specific services\n * return otelSpan.name.startsWith('my-service');\n * }\n * });\n * ```\n */\n constructor(params?: ElasticDashSpanProcessorParams) {\n const logger = getGlobalLogger();\n\n const publicKey = params?.publicKey ?? getEnv(\"ELASTICDASH_PUBLIC_KEY\");\n const secretKey = params?.secretKey ?? getEnv(\"ELASTICDASH_SECRET_KEY\");\n const baseUrl =\n params?.baseUrl ??\n getEnv(\"ELASTICDASH_BASE_URL\") ??\n getEnv(\"ELASTICDASH_BASEURL\") ?? // legacy v2\n \"https://cloud.elasticdash.com\";\n\n if (!params?.exporter && !publicKey) {\n logger.warn(\n \"No exporter configured and no public key provided in constructor or as ELASTICDASH_PUBLIC_KEY env var. Span exports will fail.\",\n );\n }\n if (!params?.exporter && !secretKey) {\n logger.warn(\n \"No exporter configured and no secret key provided in constructor or as ELASTICDASH_SECRET_KEY env var. Span exports will fail.\",\n );\n }\n const flushAt = params?.flushAt ?? getEnv(\"ELASTICDASH_FLUSH_AT\");\n const flushIntervalSeconds =\n params?.flushInterval ?? getEnv(\"ELASTICDASH_FLUSH_INTERVAL\");\n\n const authHeaderValue = base64Encode(`${publicKey}:${secretKey}`);\n const timeoutSeconds =\n params?.timeout ?? Number(getEnv(\"ELASTICDASH_TIMEOUT\") ?? 5);\n\n const exporter =\n params?.exporter ??\n new OTLPTraceExporter({\n url: `${baseUrl}/api/public/otel/v1/traces`,\n headers: {\n Authorization: `Basic ${authHeaderValue}`,\n x_elasticdash_sdk_name: \"javascript\",\n x_elasticdash_sdk_version: ELASTICDASH_SDK_VERSION,\n x_elasticdash_public_key: publicKey ?? \"<missing>\",\n ...params?.additionalHeaders,\n },\n timeoutMillis: timeoutSeconds * 1_000,\n });\n\n this.processor =\n params?.exportMode === \"immediate\"\n ? new SimpleSpanProcessor(exporter)\n : new BatchSpanProcessor(exporter, {\n maxExportBatchSize: flushAt ? Number(flushAt) : undefined,\n scheduledDelayMillis: flushIntervalSeconds\n ? Number(flushIntervalSeconds) * 1_000\n : undefined,\n });\n\n this.publicKey = publicKey;\n this.baseUrl = baseUrl;\n this.environment =\n params?.environment ?? getEnv(\"ELASTICDASH_TRACING_ENVIRONMENT\");\n this.release = params?.release ?? getEnv(\"ELASTICDASH_RELEASE\");\n this.mask = params?.mask;\n this.shouldExportSpan = params?.shouldExportSpan;\n this.apiClient = new ElasticDashAPIClient({\n baseUrl: this.baseUrl,\n username: this.publicKey,\n password: secretKey,\n xElasticDashPublicKey: this.publicKey,\n xElasticDashSdkVersion: ELASTICDASH_SDK_VERSION,\n xElasticDashSdkName: \"javascript\",\n environment: \"\", // noop as baseUrl is set\n headers: params?.additionalHeaders,\n });\n\n this.mediaService = new MediaService({ apiClient: this.apiClient });\n\n logger.debug(\"Initialized ElasticDashSpanProcessor with params:\", {\n publicKey,\n baseUrl,\n environment: this.environment,\n release: this.release,\n timeoutSeconds,\n flushAt,\n flushIntervalSeconds,\n });\n }\n\n private get logger(): Logger {\n return getGlobalLogger();\n }\n\n /**\n * Called when a span is started. Adds environment, release, and propagated attributes to the span.\n *\n * @param span - The span that was started\n * @param parentContext - The parent context\n *\n * @override\n */\n public onStart(span: Span, parentContext: Context): void {\n // Set propagated attributes, environment and release attributes\n span.setAttributes({\n [ElasticDashOtelSpanAttributes.ENVIRONMENT]: this.environment,\n [ElasticDashOtelSpanAttributes.RELEASE]: this.release,\n ...getPropagatedAttributesFromContext(parentContext),\n });\n\n return this.processor.onStart(span, parentContext);\n }\n\n /**\n * Called when a span ends. Processes the span for export to ElasticDash.\n *\n * This method:\n * 1. Checks if the span should be exported using the shouldExportSpan function\n * 2. Applies data masking to sensitive attributes\n * 3. Handles media content extraction and upload\n * 4. Logs span details in debug mode\n * 5. Passes the span to the parent processor for export\n *\n * @param span - The span that ended\n *\n * @override\n */\n public onEnd(span: ReadableSpan): void {\n const processEndedSpanPromise = this.processEndedSpan(span).catch((err) => {\n this.logger.error(err);\n });\n\n // Enqueue this export to the pending list so it can be flushed by the user.\n this.pendingEndedSpans.add(processEndedSpanPromise);\n\n void processEndedSpanPromise.finally(() =>\n this.pendingEndedSpans.delete(processEndedSpanPromise),\n );\n }\n\n private async flush(): Promise<void> {\n await Promise.all(Array.from(this.pendingEndedSpans));\n await this.mediaService.flush();\n }\n\n /**\n * Forces an immediate flush of all pending spans and media uploads.\n *\n * @returns Promise that resolves when all pending operations are complete\n *\n * @override\n */\n public async forceFlush(): Promise<void> {\n await this.flush();\n\n return this.processor.forceFlush();\n }\n\n /**\n * Gracefully shuts down the processor, ensuring all pending operations are completed.\n *\n * @returns Promise that resolves when shutdown is complete\n *\n * @override\n */\n public async shutdown(): Promise<void> {\n await this.flush();\n\n return this.processor.shutdown();\n }\n\n private async processEndedSpan(span: ReadableSpan) {\n if (this.shouldExportSpan) {\n try {\n if (this.shouldExportSpan({ otelSpan: span }) === false) return;\n } catch (err) {\n this.logger.error(\n \"ShouldExportSpan failed with error. Excluding span. Error: \",\n err,\n );\n\n return;\n }\n }\n\n this.applyMaskInPlace(span);\n await this.mediaService.process(span);\n\n this.logger.debug(\n `Processed span:\\n${JSON.stringify(\n {\n name: span.name,\n traceId: span.spanContext().traceId,\n spanId: span.spanContext().spanId,\n parentSpanId: span.parentSpanContext?.spanId ?? null,\n attributes: span.attributes,\n startTime: new Date(hrTimeToMilliseconds(span.startTime)),\n endTime: new Date(hrTimeToMilliseconds(span.endTime)),\n durationMs: hrTimeToMilliseconds(span.duration),\n kind: span.kind,\n status: span.status,\n resource: span.resource.attributes,\n instrumentationScope: span.instrumentationScope,\n },\n null,\n 2,\n )}`,\n );\n\n this.processor.onEnd(span);\n }\n private applyMaskInPlace(span: ReadableSpan): void {\n const maskCandidates = [\n ElasticDashOtelSpanAttributes.OBSERVATION_INPUT,\n ElasticDashOtelSpanAttributes.TRACE_INPUT,\n ElasticDashOtelSpanAttributes.OBSERVATION_OUTPUT,\n ElasticDashOtelSpanAttributes.TRACE_OUTPUT,\n ElasticDashOtelSpanAttributes.OBSERVATION_METADATA,\n ElasticDashOtelSpanAttributes.TRACE_METADATA,\n ];\n\n for (const maskCandidate of maskCandidates) {\n if (maskCandidate in span.attributes) {\n span.attributes[maskCandidate] = this.applyMask(\n span.attributes[maskCandidate],\n );\n }\n }\n }\n\n private applyMask<T>(data: T): T | string {\n if (!this.mask) return data;\n\n try {\n return this.mask({ data });\n } catch (err) {\n this.logger.warn(\n `Applying mask function failed due to error, fully masking property. Error: ${err}`,\n );\n\n return \"<fully masked due to failed mask function>\";\n }\n }\n}\n","import {\n ElasticDashAPIClient,\n ElasticDashMedia,\n ElasticDashOtelSpanAttributes,\n Logger,\n base64ToBytes,\n getGlobalLogger,\n} from \"@elasticdash/core\";\nimport { ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\n\nexport class MediaService {\n private pendingMediaUploads: Set<Promise<void>> = new Set();\n private apiClient: ElasticDashAPIClient;\n\n constructor(params: { apiClient: ElasticDashAPIClient }) {\n this.apiClient = params.apiClient;\n }\n\n get logger(): Logger {\n return getGlobalLogger();\n }\n\n public async flush(): Promise<void> {\n await Promise.all(Array.from(this.pendingMediaUploads));\n }\n\n public async process(span: ReadableSpan) {\n const mediaAttributes = [\n ElasticDashOtelSpanAttributes.OBSERVATION_INPUT,\n ElasticDashOtelSpanAttributes.TRACE_INPUT,\n ElasticDashOtelSpanAttributes.OBSERVATION_OUTPUT,\n ElasticDashOtelSpanAttributes.TRACE_OUTPUT,\n ElasticDashOtelSpanAttributes.OBSERVATION_METADATA,\n ElasticDashOtelSpanAttributes.TRACE_METADATA,\n ];\n\n for (const mediaAttribute of mediaAttributes) {\n const mediaRelevantAttributeKeys = Object.keys(span.attributes).filter(\n (attributeName) => attributeName.startsWith(mediaAttribute),\n );\n\n for (const key of mediaRelevantAttributeKeys) {\n const value = span.attributes[key];\n\n if (typeof value !== \"string\") {\n this.logger.warn(\n `Span attribute ${mediaAttribute} is not a stringified object. Skipping media handling.`,\n );\n\n continue;\n }\n\n // Find media base64 data URI\n let mediaReplacedValue = value;\n const regex = /data:[^;]+;base64,[A-Za-z0-9+/]+=*/g;\n const foundMedia = [...new Set(value.match(regex) ?? [])];\n\n if (foundMedia.length === 0) continue;\n\n for (const mediaDataUri of foundMedia) {\n // For each media, create media tag and initiate upload\n const media = new ElasticDashMedia({\n base64DataUri: mediaDataUri,\n source: \"base64_data_uri\",\n });\n\n const elasticDashMediaTag = await media.getTag();\n\n if (!elasticDashMediaTag) {\n this.logger.warn(\n \"Failed to create ElasticDash media tag. Skipping media item.\",\n );\n\n continue;\n }\n\n this.scheduleUpload({\n span,\n media,\n field: mediaAttribute.includes(\"input\")\n ? \"input\"\n : mediaAttribute.includes(\"output\")\n ? \"output\"\n : \"metadata\", // todo: make more robust\n });\n\n // Replace original attribute with media escaped attribute\n mediaReplacedValue = mediaReplacedValue.replaceAll(\n mediaDataUri,\n elasticDashMediaTag,\n );\n }\n\n span.attributes[key] = mediaReplacedValue;\n }\n }\n\n // Handle media from Vercel AI SDK\n if (span.instrumentationScope.name === \"ai\") {\n const aiSDKMediaAttributes = [\"ai.prompt.messages\", \"ai.prompt\"];\n\n for (const mediaAttribute of aiSDKMediaAttributes) {\n const value = span.attributes[mediaAttribute];\n\n if (!value || typeof value !== \"string\") {\n continue;\n }\n\n // Find media base64 data URI\n let mediaReplacedValue = value;\n\n try {\n const parsed = JSON.parse(value);\n\n if (Array.isArray(parsed)) {\n for (const message of parsed) {\n if (Array.isArray(message[\"content\"])) {\n const contentParts = message[\"content\"];\n\n for (const part of contentParts) {\n if (part[\"type\"] === \"file\") {\n let base64Content: string | null = null;\n // FilePart\n if (part[\"data\"] != null && part[\"mediaType\"] != null) {\n base64Content = part[\"data\"];\n }\n\n //ImagePart\n if (\n part[\"image\"] != null &&\n part[\"mediaType\"] != null &&\n !part[\"image\"].startsWith(\"http\") // skip URLs\n ) {\n base64Content = part[\"image\"];\n }\n\n if (!base64Content) continue;\n\n const media = new ElasticDashMedia({\n contentType: part[\"mediaType\"],\n contentBytes: base64ToBytes(base64Content),\n source: \"bytes\",\n });\n\n const elasticDashMediaTag = await media.getTag();\n\n if (!elasticDashMediaTag) {\n this.logger.warn(\n \"Failed to create ElasticDash media tag. Skipping media item.\",\n );\n\n continue;\n }\n\n this.scheduleUpload({\n span,\n media,\n field: \"input\",\n });\n\n // Replace original attribute with media escaped attribute\n mediaReplacedValue = mediaReplacedValue.replaceAll(\n base64Content,\n elasticDashMediaTag,\n );\n }\n }\n }\n }\n }\n\n span.attributes[mediaAttribute] = mediaReplacedValue;\n } catch (err) {\n this.logger.warn(\n `Failed to handle media for AI SDK attribute ${mediaAttribute} for span ${span.spanContext().spanId}`,\n err,\n );\n }\n }\n }\n }\n\n private scheduleUpload(params: {\n span: ReadableSpan;\n field: string;\n media: ElasticDashMedia;\n }) {\n const { span, field, media } = params;\n\n const uploadPromise: Promise<void> = this.handleUpload({\n media,\n traceId: span.spanContext().traceId,\n observationId: span.spanContext().spanId,\n field,\n }).catch((err) => {\n this.logger.error(\"Media upload failed with error: \", err);\n });\n\n this.pendingMediaUploads.add(uploadPromise);\n\n uploadPromise.finally(() => {\n this.pendingMediaUploads.delete(uploadPromise);\n });\n }\n\n private async handleUpload({\n media,\n traceId,\n observationId,\n field,\n }: {\n media: ElasticDashMedia;\n traceId: string;\n observationId?: string;\n field: string;\n }): Promise<void> {\n try {\n const contentSha256Hash = await media.getSha256Hash();\n\n if (\n !media.contentLength ||\n !media._contentType ||\n !contentSha256Hash ||\n !media._contentBytes\n ) {\n return;\n }\n\n const { uploadUrl, mediaId } = await this.apiClient.media.getUploadUrl({\n contentLength: media.contentLength,\n traceId,\n observationId,\n field,\n contentType: media._contentType,\n sha256Hash: contentSha256Hash,\n });\n\n if (!uploadUrl) {\n this.logger.debug(\n `Media status: Media with ID ${mediaId} already uploaded. Skipping duplicate upload.`,\n );\n\n return;\n }\n\n const clientSideMediaId = await media.getId();\n if (clientSideMediaId !== mediaId) {\n this.logger.error(\n `Media integrity error: Media ID mismatch between SDK (${clientSideMediaId}) and Server (${mediaId}). Upload cancelled. Please check media ID generation logic.`,\n );\n\n return;\n }\n\n this.logger.debug(`Uploading media ${mediaId}...`);\n\n const startTime = Date.now();\n\n const uploadResponse = await this.uploadWithBackoff({\n uploadUrl,\n contentBytes: media._contentBytes,\n contentType: media._contentType,\n contentSha256Hash: contentSha256Hash,\n maxRetries: 3,\n baseDelay: 1000,\n });\n\n if (!uploadResponse) {\n throw Error(\"Media upload process failed\");\n }\n\n await this.apiClient.media.patch(mediaId, {\n uploadedAt: new Date().toISOString(),\n uploadHttpStatus: uploadResponse.status,\n uploadHttpError: await uploadResponse.text(),\n uploadTimeMs: Date.now() - startTime,\n });\n\n this.logger.debug(`Media upload status reported for ${mediaId}`);\n } catch (err) {\n this.logger.error(`Error processing media item: ${err}`);\n }\n }\n\n private async uploadWithBackoff(params: {\n uploadUrl: string;\n contentType: string;\n contentSha256Hash: string;\n contentBytes: Uint8Array;\n maxRetries: number;\n baseDelay: number;\n }) {\n const {\n uploadUrl,\n contentType,\n contentSha256Hash,\n contentBytes,\n maxRetries,\n baseDelay,\n } = params;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n let parsedHostname: string;\n\n try {\n parsedHostname = new URL(uploadUrl).hostname;\n } catch {\n parsedHostname = \"\";\n }\n\n const isSelfHostedGcsBucket =\n parsedHostname === \"storage.googleapis.com\" ||\n parsedHostname.endsWith(\".storage.googleapis.com\");\n\n const headers = isSelfHostedGcsBucket\n ? { \"Content-Type\": contentType }\n : {\n \"Content-Type\": contentType,\n \"x-amz-checksum-sha256\": contentSha256Hash,\n \"x-ms-blob-type\": \"BlockBlob\",\n };\n\n const uploadResponse = await fetch(uploadUrl, {\n method: \"PUT\",\n body: contentBytes,\n headers,\n });\n\n if (\n attempt < maxRetries &&\n uploadResponse.status !== 200 &&\n uploadResponse.status !== 201\n ) {\n throw new Error(`Upload failed with status ${uploadResponse.status}`);\n }\n\n return uploadResponse;\n } catch (e) {\n if (attempt === maxRetries) {\n throw e;\n }\n\n const delay = baseDelay * Math.pow(2, attempt);\n const jitter = Math.random() * 1000;\n\n await new Promise((resolve) => setTimeout(resolve, delay + jitter));\n }\n }\n }\n}\n"],"mappings":"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","names":["import_core"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -44,11 +44,11 @@ type ShouldExportSpan = (params: {
|
|
|
44
44
|
otelSpan: ReadableSpan;
|
|
45
45
|
}) => boolean;
|
|
46
46
|
/**
|
|
47
|
-
* Configuration parameters for the
|
|
47
|
+
* Configuration parameters for the ElasticDashSpanProcessor.
|
|
48
48
|
*
|
|
49
49
|
* @public
|
|
50
50
|
*/
|
|
51
|
-
interface
|
|
51
|
+
interface ElasticDashSpanProcessorParams {
|
|
52
52
|
/**
|
|
53
53
|
* Custom OpenTelemetry span exporter. If not provided, a default OTLP exporter will be used.
|
|
54
54
|
*/
|
|
@@ -124,11 +124,11 @@ interface LangfuseSpanProcessorParams {
|
|
|
124
124
|
* @example
|
|
125
125
|
* ```typescript
|
|
126
126
|
* import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
127
|
-
* import {
|
|
127
|
+
* import { ElasticDashSpanProcessor } from '@elasticdash/otel';
|
|
128
128
|
*
|
|
129
129
|
* const sdk = new NodeSDK({
|
|
130
130
|
* spanProcessors: [
|
|
131
|
-
* new
|
|
131
|
+
* new ElasticDashSpanProcessor({
|
|
132
132
|
* publicKey: 'pk_...',
|
|
133
133
|
* secretKey: 'sk_...',
|
|
134
134
|
* baseUrl: 'https://cloud.elasticdash.com',
|
|
@@ -146,7 +146,7 @@ interface LangfuseSpanProcessorParams {
|
|
|
146
146
|
*
|
|
147
147
|
* @public
|
|
148
148
|
*/
|
|
149
|
-
declare class
|
|
149
|
+
declare class ElasticDashSpanProcessor implements SpanProcessor {
|
|
150
150
|
private pendingEndedSpans;
|
|
151
151
|
private publicKey?;
|
|
152
152
|
private baseUrl?;
|
|
@@ -158,13 +158,13 @@ declare class LangfuseSpanProcessor implements SpanProcessor {
|
|
|
158
158
|
private processor;
|
|
159
159
|
private mediaService;
|
|
160
160
|
/**
|
|
161
|
-
* Creates a new
|
|
161
|
+
* Creates a new ElasticDashSpanProcessor instance.
|
|
162
162
|
*
|
|
163
163
|
* @param params - Configuration parameters for the processor
|
|
164
164
|
*
|
|
165
165
|
* @example
|
|
166
166
|
* ```typescript
|
|
167
|
-
* const processor = new
|
|
167
|
+
* const processor = new ElasticDashSpanProcessor({
|
|
168
168
|
* publicKey: 'pk_...',
|
|
169
169
|
* secretKey: 'sk_...',
|
|
170
170
|
* environment: 'staging',
|
|
@@ -183,7 +183,7 @@ declare class LangfuseSpanProcessor implements SpanProcessor {
|
|
|
183
183
|
* });
|
|
184
184
|
* ```
|
|
185
185
|
*/
|
|
186
|
-
constructor(params?:
|
|
186
|
+
constructor(params?: ElasticDashSpanProcessorParams);
|
|
187
187
|
private get logger();
|
|
188
188
|
/**
|
|
189
189
|
* Called when a span is started. Adds environment, release, and propagated attributes to the span.
|
|
@@ -231,4 +231,4 @@ declare class LangfuseSpanProcessor implements SpanProcessor {
|
|
|
231
231
|
private applyMask;
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
export {
|
|
234
|
+
export { ElasticDashSpanProcessor, type ElasticDashSpanProcessorParams, type MaskFunction, type ShouldExportSpan };
|
package/dist/index.d.ts
CHANGED
|
@@ -44,11 +44,11 @@ type ShouldExportSpan = (params: {
|
|
|
44
44
|
otelSpan: ReadableSpan;
|
|
45
45
|
}) => boolean;
|
|
46
46
|
/**
|
|
47
|
-
* Configuration parameters for the
|
|
47
|
+
* Configuration parameters for the ElasticDashSpanProcessor.
|
|
48
48
|
*
|
|
49
49
|
* @public
|
|
50
50
|
*/
|
|
51
|
-
interface
|
|
51
|
+
interface ElasticDashSpanProcessorParams {
|
|
52
52
|
/**
|
|
53
53
|
* Custom OpenTelemetry span exporter. If not provided, a default OTLP exporter will be used.
|
|
54
54
|
*/
|
|
@@ -124,11 +124,11 @@ interface LangfuseSpanProcessorParams {
|
|
|
124
124
|
* @example
|
|
125
125
|
* ```typescript
|
|
126
126
|
* import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
127
|
-
* import {
|
|
127
|
+
* import { ElasticDashSpanProcessor } from '@elasticdash/otel';
|
|
128
128
|
*
|
|
129
129
|
* const sdk = new NodeSDK({
|
|
130
130
|
* spanProcessors: [
|
|
131
|
-
* new
|
|
131
|
+
* new ElasticDashSpanProcessor({
|
|
132
132
|
* publicKey: 'pk_...',
|
|
133
133
|
* secretKey: 'sk_...',
|
|
134
134
|
* baseUrl: 'https://cloud.elasticdash.com',
|
|
@@ -146,7 +146,7 @@ interface LangfuseSpanProcessorParams {
|
|
|
146
146
|
*
|
|
147
147
|
* @public
|
|
148
148
|
*/
|
|
149
|
-
declare class
|
|
149
|
+
declare class ElasticDashSpanProcessor implements SpanProcessor {
|
|
150
150
|
private pendingEndedSpans;
|
|
151
151
|
private publicKey?;
|
|
152
152
|
private baseUrl?;
|
|
@@ -158,13 +158,13 @@ declare class LangfuseSpanProcessor implements SpanProcessor {
|
|
|
158
158
|
private processor;
|
|
159
159
|
private mediaService;
|
|
160
160
|
/**
|
|
161
|
-
* Creates a new
|
|
161
|
+
* Creates a new ElasticDashSpanProcessor instance.
|
|
162
162
|
*
|
|
163
163
|
* @param params - Configuration parameters for the processor
|
|
164
164
|
*
|
|
165
165
|
* @example
|
|
166
166
|
* ```typescript
|
|
167
|
-
* const processor = new
|
|
167
|
+
* const processor = new ElasticDashSpanProcessor({
|
|
168
168
|
* publicKey: 'pk_...',
|
|
169
169
|
* secretKey: 'sk_...',
|
|
170
170
|
* environment: 'staging',
|
|
@@ -183,7 +183,7 @@ declare class LangfuseSpanProcessor implements SpanProcessor {
|
|
|
183
183
|
* });
|
|
184
184
|
* ```
|
|
185
185
|
*/
|
|
186
|
-
constructor(params?:
|
|
186
|
+
constructor(params?: ElasticDashSpanProcessorParams);
|
|
187
187
|
private get logger();
|
|
188
188
|
/**
|
|
189
189
|
* Called when a span is started. Adds environment, release, and propagated attributes to the span.
|
|
@@ -231,4 +231,4 @@ declare class LangfuseSpanProcessor implements SpanProcessor {
|
|
|
231
231
|
private applyMask;
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
export {
|
|
234
|
+
export { ElasticDashSpanProcessor, type ElasticDashSpanProcessorParams, type MaskFunction, type ShouldExportSpan };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// src/span-processor.ts
|
|
2
2
|
import {
|
|
3
3
|
getGlobalLogger as getGlobalLogger2,
|
|
4
|
-
|
|
4
|
+
ElasticDashAPIClient as ElasticDashAPIClient2,
|
|
5
5
|
ELASTICDASH_SDK_VERSION,
|
|
6
|
-
|
|
6
|
+
ElasticDashOtelSpanAttributes as ElasticDashOtelSpanAttributes2,
|
|
7
7
|
getEnv,
|
|
8
8
|
base64Encode,
|
|
9
9
|
getPropagatedAttributesFromContext
|
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
|
|
18
18
|
// src/MediaService.ts
|
|
19
19
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
ElasticDashMedia,
|
|
21
|
+
ElasticDashOtelSpanAttributes,
|
|
22
22
|
base64ToBytes,
|
|
23
23
|
getGlobalLogger
|
|
24
24
|
} from "@elasticdash/core";
|
|
@@ -36,12 +36,12 @@ var MediaService = class {
|
|
|
36
36
|
async process(span) {
|
|
37
37
|
var _a;
|
|
38
38
|
const mediaAttributes = [
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
ElasticDashOtelSpanAttributes.OBSERVATION_INPUT,
|
|
40
|
+
ElasticDashOtelSpanAttributes.TRACE_INPUT,
|
|
41
|
+
ElasticDashOtelSpanAttributes.OBSERVATION_OUTPUT,
|
|
42
|
+
ElasticDashOtelSpanAttributes.TRACE_OUTPUT,
|
|
43
|
+
ElasticDashOtelSpanAttributes.OBSERVATION_METADATA,
|
|
44
|
+
ElasticDashOtelSpanAttributes.TRACE_METADATA
|
|
45
45
|
];
|
|
46
46
|
for (const mediaAttribute of mediaAttributes) {
|
|
47
47
|
const mediaRelevantAttributeKeys = Object.keys(span.attributes).filter(
|
|
@@ -60,12 +60,12 @@ var MediaService = class {
|
|
|
60
60
|
const foundMedia = [...new Set((_a = value.match(regex)) != null ? _a : [])];
|
|
61
61
|
if (foundMedia.length === 0) continue;
|
|
62
62
|
for (const mediaDataUri of foundMedia) {
|
|
63
|
-
const media = new
|
|
63
|
+
const media = new ElasticDashMedia({
|
|
64
64
|
base64DataUri: mediaDataUri,
|
|
65
65
|
source: "base64_data_uri"
|
|
66
66
|
});
|
|
67
|
-
const
|
|
68
|
-
if (!
|
|
67
|
+
const elasticDashMediaTag = await media.getTag();
|
|
68
|
+
if (!elasticDashMediaTag) {
|
|
69
69
|
this.logger.warn(
|
|
70
70
|
"Failed to create ElasticDash media tag. Skipping media item."
|
|
71
71
|
);
|
|
@@ -79,7 +79,7 @@ var MediaService = class {
|
|
|
79
79
|
});
|
|
80
80
|
mediaReplacedValue = mediaReplacedValue.replaceAll(
|
|
81
81
|
mediaDataUri,
|
|
82
|
-
|
|
82
|
+
elasticDashMediaTag
|
|
83
83
|
);
|
|
84
84
|
}
|
|
85
85
|
span.attributes[key] = mediaReplacedValue;
|
|
@@ -109,13 +109,13 @@ var MediaService = class {
|
|
|
109
109
|
base64Content = part["image"];
|
|
110
110
|
}
|
|
111
111
|
if (!base64Content) continue;
|
|
112
|
-
const media = new
|
|
112
|
+
const media = new ElasticDashMedia({
|
|
113
113
|
contentType: part["mediaType"],
|
|
114
114
|
contentBytes: base64ToBytes(base64Content),
|
|
115
115
|
source: "bytes"
|
|
116
116
|
});
|
|
117
|
-
const
|
|
118
|
-
if (!
|
|
117
|
+
const elasticDashMediaTag = await media.getTag();
|
|
118
|
+
if (!elasticDashMediaTag) {
|
|
119
119
|
this.logger.warn(
|
|
120
120
|
"Failed to create ElasticDash media tag. Skipping media item."
|
|
121
121
|
);
|
|
@@ -128,7 +128,7 @@ var MediaService = class {
|
|
|
128
128
|
});
|
|
129
129
|
mediaReplacedValue = mediaReplacedValue.replaceAll(
|
|
130
130
|
base64Content,
|
|
131
|
-
|
|
131
|
+
elasticDashMediaTag
|
|
132
132
|
);
|
|
133
133
|
}
|
|
134
134
|
}
|
|
@@ -261,15 +261,15 @@ var MediaService = class {
|
|
|
261
261
|
};
|
|
262
262
|
|
|
263
263
|
// src/span-processor.ts
|
|
264
|
-
var
|
|
264
|
+
var ElasticDashSpanProcessor = class {
|
|
265
265
|
/**
|
|
266
|
-
* Creates a new
|
|
266
|
+
* Creates a new ElasticDashSpanProcessor instance.
|
|
267
267
|
*
|
|
268
268
|
* @param params - Configuration parameters for the processor
|
|
269
269
|
*
|
|
270
270
|
* @example
|
|
271
271
|
* ```typescript
|
|
272
|
-
* const processor = new
|
|
272
|
+
* const processor = new ElasticDashSpanProcessor({
|
|
273
273
|
* publicKey: 'pk_...',
|
|
274
274
|
* secretKey: 'sk_...',
|
|
275
275
|
* environment: 'staging',
|
|
@@ -316,9 +316,9 @@ var LangfuseSpanProcessor = class {
|
|
|
316
316
|
url: `${baseUrl}/api/public/otel/v1/traces`,
|
|
317
317
|
headers: {
|
|
318
318
|
Authorization: `Basic ${authHeaderValue}`,
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
319
|
+
x_elasticdash_sdk_name: "javascript",
|
|
320
|
+
x_elasticdash_sdk_version: ELASTICDASH_SDK_VERSION,
|
|
321
|
+
x_elasticdash_public_key: publicKey != null ? publicKey : "<missing>",
|
|
322
322
|
...params == null ? void 0 : params.additionalHeaders
|
|
323
323
|
},
|
|
324
324
|
timeoutMillis: timeoutSeconds * 1e3
|
|
@@ -333,19 +333,19 @@ var LangfuseSpanProcessor = class {
|
|
|
333
333
|
this.release = (_l = params == null ? void 0 : params.release) != null ? _l : getEnv("ELASTICDASH_RELEASE");
|
|
334
334
|
this.mask = params == null ? void 0 : params.mask;
|
|
335
335
|
this.shouldExportSpan = params == null ? void 0 : params.shouldExportSpan;
|
|
336
|
-
this.apiClient = new
|
|
336
|
+
this.apiClient = new ElasticDashAPIClient2({
|
|
337
337
|
baseUrl: this.baseUrl,
|
|
338
338
|
username: this.publicKey,
|
|
339
339
|
password: secretKey,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
xElasticDashPublicKey: this.publicKey,
|
|
341
|
+
xElasticDashSdkVersion: ELASTICDASH_SDK_VERSION,
|
|
342
|
+
xElasticDashSdkName: "javascript",
|
|
343
343
|
environment: "",
|
|
344
344
|
// noop as baseUrl is set
|
|
345
345
|
headers: params == null ? void 0 : params.additionalHeaders
|
|
346
346
|
});
|
|
347
347
|
this.mediaService = new MediaService({ apiClient: this.apiClient });
|
|
348
|
-
logger.debug("Initialized
|
|
348
|
+
logger.debug("Initialized ElasticDashSpanProcessor with params:", {
|
|
349
349
|
publicKey,
|
|
350
350
|
baseUrl,
|
|
351
351
|
environment: this.environment,
|
|
@@ -368,8 +368,8 @@ var LangfuseSpanProcessor = class {
|
|
|
368
368
|
*/
|
|
369
369
|
onStart(span, parentContext) {
|
|
370
370
|
span.setAttributes({
|
|
371
|
-
[
|
|
372
|
-
[
|
|
371
|
+
[ElasticDashOtelSpanAttributes2.ENVIRONMENT]: this.environment,
|
|
372
|
+
[ElasticDashOtelSpanAttributes2.RELEASE]: this.release,
|
|
373
373
|
...getPropagatedAttributesFromContext(parentContext)
|
|
374
374
|
});
|
|
375
375
|
return this.processor.onStart(span, parentContext);
|
|
@@ -463,12 +463,12 @@ ${JSON.stringify(
|
|
|
463
463
|
}
|
|
464
464
|
applyMaskInPlace(span) {
|
|
465
465
|
const maskCandidates = [
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
466
|
+
ElasticDashOtelSpanAttributes2.OBSERVATION_INPUT,
|
|
467
|
+
ElasticDashOtelSpanAttributes2.TRACE_INPUT,
|
|
468
|
+
ElasticDashOtelSpanAttributes2.OBSERVATION_OUTPUT,
|
|
469
|
+
ElasticDashOtelSpanAttributes2.TRACE_OUTPUT,
|
|
470
|
+
ElasticDashOtelSpanAttributes2.OBSERVATION_METADATA,
|
|
471
|
+
ElasticDashOtelSpanAttributes2.TRACE_METADATA
|
|
472
472
|
];
|
|
473
473
|
for (const maskCandidate of maskCandidates) {
|
|
474
474
|
if (maskCandidate in span.attributes) {
|
|
@@ -491,6 +491,6 @@ ${JSON.stringify(
|
|
|
491
491
|
}
|
|
492
492
|
};
|
|
493
493
|
export {
|
|
494
|
-
|
|
494
|
+
ElasticDashSpanProcessor
|
|
495
495
|
};
|
|
496
496
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/span-processor.ts","../src/MediaService.ts"],"sourcesContent":["import {\n Logger,\n getGlobalLogger,\n LangfuseAPIClient,\n ELASTICDASH_SDK_VERSION,\n LangfuseOtelSpanAttributes,\n getEnv,\n base64Encode,\n getPropagatedAttributesFromContext,\n} from \"@elasticdash/core\";\nimport { Context } from \"@opentelemetry/api\";\nimport { hrTimeToMilliseconds } from \"@opentelemetry/core\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport {\n Span,\n BatchSpanProcessor,\n SimpleSpanProcessor,\n SpanExporter,\n ReadableSpan,\n SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\n\nimport { MediaService } from \"./MediaService.js\";\n\n/**\n * Function type for masking sensitive data in spans before export.\n *\n * @param params - Object containing the data to be masked\n * @param params.data - The data that should be masked\n * @returns The masked data (can be of any type)\n *\n * @example\n * ```typescript\n * const maskFunction: MaskFunction = ({ data }) => {\n * if (typeof data === 'string') {\n * return data.replace(/password=\\w+/g, 'password=***');\n * }\n * return data;\n * };\n * ```\n *\n * @public\n */\nexport type MaskFunction = (params: { data: any }) => any;\n\n/**\n * Function type for determining whether a span should be exported to ElasticDash.\n *\n * @param params - Object containing the span to evaluate\n * @param params.otelSpan - The OpenTelemetry span to evaluate\n * @returns `true` if the span should be exported, `false` otherwise\n *\n * @example\n * ```typescript\n * const shouldExportSpan: ShouldExportSpan = ({ otelSpan }) => {\n * // Only export spans that took longer than 100ms\n * return otelSpan.duration[0] * 1000 + otelSpan.duration[1] / 1000000 > 100;\n * };\n * ```\n *\n * @public\n */\nexport type ShouldExportSpan = (params: { otelSpan: ReadableSpan }) => boolean;\n\n/**\n * Configuration parameters for the LangfuseSpanProcessor.\n *\n * @public\n */\nexport interface LangfuseSpanProcessorParams {\n /**\n * Custom OpenTelemetry span exporter. If not provided, a default OTLP exporter will be used.\n */\n exporter?: SpanExporter;\n\n /**\n * ElasticDash public API key. Can also be set via ELASTICDASH_PUBLIC_KEY environment variable.\n */\n publicKey?: string;\n\n /**\n * ElasticDash secret API key. Can also be set via ELASTICDASH_SECRET_KEY environment variable.\n */\n secretKey?: string;\n\n /**\n * ElasticDash instance base URL. Can also be set via ELASTICDASH_BASE_URL environment variable.\n * @defaultValue \"https://cloud.elasticdash.com\"\n */\n baseUrl?: string;\n\n /**\n * Number of spans to batch before flushing. Can also be set via ELASTICDASH_FLUSH_AT environment variable.\n */\n flushAt?: number;\n\n /**\n * Flush interval in seconds. Can also be set via ELASTICDASH_FLUSH_INTERVAL environment variable.\n */\n flushInterval?: number;\n\n /**\n * Function to mask sensitive data in spans before export.\n */\n mask?: MaskFunction;\n\n /**\n * Function to determine whether a span should be exported to ElasticDash.\n */\n shouldExportSpan?: ShouldExportSpan;\n\n /**\n * Environment identifier for the traces. Can also be set via ELASTICDASH_TRACING_ENVIRONMENT environment variable.\n */\n environment?: string;\n\n /**\n * Release identifier for the traces. Can also be set via ELASTICDASH_RELEASE environment variable.\n */\n release?: string;\n\n /**\n * Request timeout in seconds. Can also be set via ELASTICDASH_TIMEOUT environment variable.\n * @defaultValue 5\n */\n timeout?: number;\n\n /**\n * Additional HTTP headers to include with requests.\n */\n additionalHeaders?: Record<string, string>;\n /**\n * Span export mode to use.\n *\n * - **batched**: Recommended for production environments with long-running processes.\n * Spans are batched and exported in groups for optimal performance.\n * - **immediate**: Recommended for short-lived environments such as serverless functions.\n * Spans are exported immediately to prevent data loss when the process terminates / is frozen.\n *\n * @defaultValue \"batched\"\n */\n exportMode?: \"immediate\" | \"batched\";\n}\n\n/**\n * OpenTelemetry span processor for sending spans to ElasticDash.\n *\n * This processor extends the standard BatchSpanProcessor to provide:\n * - Automatic batching and flushing of spans to ElasticDash\n * - Media content extraction and upload from base64 data URIs\n * - Data masking capabilities for sensitive information\n * - Conditional span export based on custom logic\n * - Environment and release tagging\n *\n * @example\n * ```typescript\n * import { NodeSDK } from '@opentelemetry/sdk-node';\n * import { LangfuseSpanProcessor } from '@elasticdash/otel';\n *\n * const sdk = new NodeSDK({\n * spanProcessors: [\n * new LangfuseSpanProcessor({\n * publicKey: 'pk_...',\n * secretKey: 'sk_...',\n * baseUrl: 'https://cloud.elasticdash.com',\n * environment: 'production',\n * mask: ({ data }) => {\n * // Mask sensitive data\n * return data.replace(/api_key=\\w+/g, 'api_key=***');\n * }\n * })\n * ]\n * });\n *\n * sdk.start();\n * ```\n *\n * @public\n */\nexport class LangfuseSpanProcessor implements SpanProcessor {\n private pendingEndedSpans: Set<Promise<void>> = new Set();\n\n private publicKey?: string;\n private baseUrl?: string;\n private environment?: string;\n private release?: string;\n private mask?: MaskFunction;\n private shouldExportSpan?: ShouldExportSpan;\n private apiClient: LangfuseAPIClient;\n private processor: SpanProcessor;\n private mediaService: MediaService;\n\n /**\n * Creates a new LangfuseSpanProcessor instance.\n *\n * @param params - Configuration parameters for the processor\n *\n * @example\n * ```typescript\n * const processor = new LangfuseSpanProcessor({\n * publicKey: 'pk_...',\n * secretKey: 'sk_...',\n * environment: 'staging',\n * flushAt: 10,\n * flushInterval: 2,\n * mask: ({ data }) => {\n * // Custom masking logic\n * return typeof data === 'string'\n * ? data.replace(/secret_\\w+/g, 'secret_***')\n * : data;\n * },\n * shouldExportSpan: ({ otelSpan }) => {\n * // Only export spans from specific services\n * return otelSpan.name.startsWith('my-service');\n * }\n * });\n * ```\n */\n constructor(params?: LangfuseSpanProcessorParams) {\n const logger = getGlobalLogger();\n\n const publicKey = params?.publicKey ?? getEnv(\"ELASTICDASH_PUBLIC_KEY\");\n const secretKey = params?.secretKey ?? getEnv(\"ELASTICDASH_SECRET_KEY\");\n const baseUrl =\n params?.baseUrl ??\n getEnv(\"ELASTICDASH_BASE_URL\") ??\n getEnv(\"ELASTICDASH_BASEURL\") ?? // legacy v2\n \"https://cloud.elasticdash.com\";\n\n if (!params?.exporter && !publicKey) {\n logger.warn(\n \"No exporter configured and no public key provided in constructor or as ELASTICDASH_PUBLIC_KEY env var. Span exports will fail.\",\n );\n }\n if (!params?.exporter && !secretKey) {\n logger.warn(\n \"No exporter configured and no secret key provided in constructor or as ELASTICDASH_SECRET_KEY env var. Span exports will fail.\",\n );\n }\n const flushAt = params?.flushAt ?? getEnv(\"ELASTICDASH_FLUSH_AT\");\n const flushIntervalSeconds =\n params?.flushInterval ?? getEnv(\"ELASTICDASH_FLUSH_INTERVAL\");\n\n const authHeaderValue = base64Encode(`${publicKey}:${secretKey}`);\n const timeoutSeconds =\n params?.timeout ?? Number(getEnv(\"ELASTICDASH_TIMEOUT\") ?? 5);\n\n const exporter =\n params?.exporter ??\n new OTLPTraceExporter({\n url: `${baseUrl}/api/public/otel/v1/traces`,\n headers: {\n Authorization: `Basic ${authHeaderValue}`,\n x_langfuse_sdk_name: \"javascript\",\n x_langfuse_sdk_version: ELASTICDASH_SDK_VERSION,\n x_langfuse_public_key: publicKey ?? \"<missing>\",\n ...params?.additionalHeaders,\n },\n timeoutMillis: timeoutSeconds * 1_000,\n });\n\n this.processor =\n params?.exportMode === \"immediate\"\n ? new SimpleSpanProcessor(exporter)\n : new BatchSpanProcessor(exporter, {\n maxExportBatchSize: flushAt ? Number(flushAt) : undefined,\n scheduledDelayMillis: flushIntervalSeconds\n ? Number(flushIntervalSeconds) * 1_000\n : undefined,\n });\n\n this.publicKey = publicKey;\n this.baseUrl = baseUrl;\n this.environment =\n params?.environment ?? getEnv(\"ELASTICDASH_TRACING_ENVIRONMENT\");\n this.release = params?.release ?? getEnv(\"ELASTICDASH_RELEASE\");\n this.mask = params?.mask;\n this.shouldExportSpan = params?.shouldExportSpan;\n this.apiClient = new LangfuseAPIClient({\n baseUrl: this.baseUrl,\n username: this.publicKey,\n password: secretKey,\n xLangfusePublicKey: this.publicKey,\n xLangfuseSdkVersion: ELASTICDASH_SDK_VERSION,\n xLangfuseSdkName: \"javascript\",\n environment: \"\", // noop as baseUrl is set\n headers: params?.additionalHeaders,\n });\n\n this.mediaService = new MediaService({ apiClient: this.apiClient });\n\n logger.debug(\"Initialized LangfuseSpanProcessor with params:\", {\n publicKey,\n baseUrl,\n environment: this.environment,\n release: this.release,\n timeoutSeconds,\n flushAt,\n flushIntervalSeconds,\n });\n }\n\n private get logger(): Logger {\n return getGlobalLogger();\n }\n\n /**\n * Called when a span is started. Adds environment, release, and propagated attributes to the span.\n *\n * @param span - The span that was started\n * @param parentContext - The parent context\n *\n * @override\n */\n public onStart(span: Span, parentContext: Context): void {\n // Set propagated attributes, environment and release attributes\n span.setAttributes({\n [LangfuseOtelSpanAttributes.ENVIRONMENT]: this.environment,\n [LangfuseOtelSpanAttributes.RELEASE]: this.release,\n ...getPropagatedAttributesFromContext(parentContext),\n });\n\n return this.processor.onStart(span, parentContext);\n }\n\n /**\n * Called when a span ends. Processes the span for export to ElasticDash.\n *\n * This method:\n * 1. Checks if the span should be exported using the shouldExportSpan function\n * 2. Applies data masking to sensitive attributes\n * 3. Handles media content extraction and upload\n * 4. Logs span details in debug mode\n * 5. Passes the span to the parent processor for export\n *\n * @param span - The span that ended\n *\n * @override\n */\n public onEnd(span: ReadableSpan): void {\n const processEndedSpanPromise = this.processEndedSpan(span).catch((err) => {\n this.logger.error(err);\n });\n\n // Enqueue this export to the pending list so it can be flushed by the user.\n this.pendingEndedSpans.add(processEndedSpanPromise);\n\n void processEndedSpanPromise.finally(() =>\n this.pendingEndedSpans.delete(processEndedSpanPromise),\n );\n }\n\n private async flush(): Promise<void> {\n await Promise.all(Array.from(this.pendingEndedSpans));\n await this.mediaService.flush();\n }\n\n /**\n * Forces an immediate flush of all pending spans and media uploads.\n *\n * @returns Promise that resolves when all pending operations are complete\n *\n * @override\n */\n public async forceFlush(): Promise<void> {\n await this.flush();\n\n return this.processor.forceFlush();\n }\n\n /**\n * Gracefully shuts down the processor, ensuring all pending operations are completed.\n *\n * @returns Promise that resolves when shutdown is complete\n *\n * @override\n */\n public async shutdown(): Promise<void> {\n await this.flush();\n\n return this.processor.shutdown();\n }\n\n private async processEndedSpan(span: ReadableSpan) {\n if (this.shouldExportSpan) {\n try {\n if (this.shouldExportSpan({ otelSpan: span }) === false) return;\n } catch (err) {\n this.logger.error(\n \"ShouldExportSpan failed with error. Excluding span. Error: \",\n err,\n );\n\n return;\n }\n }\n\n this.applyMaskInPlace(span);\n await this.mediaService.process(span);\n\n this.logger.debug(\n `Processed span:\\n${JSON.stringify(\n {\n name: span.name,\n traceId: span.spanContext().traceId,\n spanId: span.spanContext().spanId,\n parentSpanId: span.parentSpanContext?.spanId ?? null,\n attributes: span.attributes,\n startTime: new Date(hrTimeToMilliseconds(span.startTime)),\n endTime: new Date(hrTimeToMilliseconds(span.endTime)),\n durationMs: hrTimeToMilliseconds(span.duration),\n kind: span.kind,\n status: span.status,\n resource: span.resource.attributes,\n instrumentationScope: span.instrumentationScope,\n },\n null,\n 2,\n )}`,\n );\n\n this.processor.onEnd(span);\n }\n private applyMaskInPlace(span: ReadableSpan): void {\n const maskCandidates = [\n LangfuseOtelSpanAttributes.OBSERVATION_INPUT,\n LangfuseOtelSpanAttributes.TRACE_INPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,\n LangfuseOtelSpanAttributes.TRACE_OUTPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_METADATA,\n LangfuseOtelSpanAttributes.TRACE_METADATA,\n ];\n\n for (const maskCandidate of maskCandidates) {\n if (maskCandidate in span.attributes) {\n span.attributes[maskCandidate] = this.applyMask(\n span.attributes[maskCandidate],\n );\n }\n }\n }\n\n private applyMask<T>(data: T): T | string {\n if (!this.mask) return data;\n\n try {\n return this.mask({ data });\n } catch (err) {\n this.logger.warn(\n `Applying mask function failed due to error, fully masking property. Error: ${err}`,\n );\n\n return \"<fully masked due to failed mask function>\";\n }\n }\n}\n","import {\n LangfuseAPIClient,\n LangfuseMedia,\n LangfuseOtelSpanAttributes,\n Logger,\n base64ToBytes,\n getGlobalLogger,\n} from \"@elasticdash/core\";\nimport { ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\n\nexport class MediaService {\n private pendingMediaUploads: Set<Promise<void>> = new Set();\n private apiClient: LangfuseAPIClient;\n\n constructor(params: { apiClient: LangfuseAPIClient }) {\n this.apiClient = params.apiClient;\n }\n\n get logger(): Logger {\n return getGlobalLogger();\n }\n\n public async flush(): Promise<void> {\n await Promise.all(Array.from(this.pendingMediaUploads));\n }\n\n public async process(span: ReadableSpan) {\n const mediaAttributes = [\n LangfuseOtelSpanAttributes.OBSERVATION_INPUT,\n LangfuseOtelSpanAttributes.TRACE_INPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,\n LangfuseOtelSpanAttributes.TRACE_OUTPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_METADATA,\n LangfuseOtelSpanAttributes.TRACE_METADATA,\n ];\n\n for (const mediaAttribute of mediaAttributes) {\n const mediaRelevantAttributeKeys = Object.keys(span.attributes).filter(\n (attributeName) => attributeName.startsWith(mediaAttribute),\n );\n\n for (const key of mediaRelevantAttributeKeys) {\n const value = span.attributes[key];\n\n if (typeof value !== \"string\") {\n this.logger.warn(\n `Span attribute ${mediaAttribute} is not a stringified object. Skipping media handling.`,\n );\n\n continue;\n }\n\n // Find media base64 data URI\n let mediaReplacedValue = value;\n const regex = /data:[^;]+;base64,[A-Za-z0-9+/]+=*/g;\n const foundMedia = [...new Set(value.match(regex) ?? [])];\n\n if (foundMedia.length === 0) continue;\n\n for (const mediaDataUri of foundMedia) {\n // For each media, create media tag and initiate upload\n const media = new LangfuseMedia({\n base64DataUri: mediaDataUri,\n source: \"base64_data_uri\",\n });\n\n const langfuseMediaTag = await media.getTag();\n\n if (!langfuseMediaTag) {\n this.logger.warn(\n \"Failed to create ElasticDash media tag. Skipping media item.\",\n );\n\n continue;\n }\n\n this.scheduleUpload({\n span,\n media,\n field: mediaAttribute.includes(\"input\")\n ? \"input\"\n : mediaAttribute.includes(\"output\")\n ? \"output\"\n : \"metadata\", // todo: make more robust\n });\n\n // Replace original attribute with media escaped attribute\n mediaReplacedValue = mediaReplacedValue.replaceAll(\n mediaDataUri,\n langfuseMediaTag,\n );\n }\n\n span.attributes[key] = mediaReplacedValue;\n }\n }\n\n // Handle media from Vercel AI SDK\n if (span.instrumentationScope.name === \"ai\") {\n const aiSDKMediaAttributes = [\"ai.prompt.messages\", \"ai.prompt\"];\n\n for (const mediaAttribute of aiSDKMediaAttributes) {\n const value = span.attributes[mediaAttribute];\n\n if (!value || typeof value !== \"string\") {\n continue;\n }\n\n // Find media base64 data URI\n let mediaReplacedValue = value;\n\n try {\n const parsed = JSON.parse(value);\n\n if (Array.isArray(parsed)) {\n for (const message of parsed) {\n if (Array.isArray(message[\"content\"])) {\n const contentParts = message[\"content\"];\n\n for (const part of contentParts) {\n if (part[\"type\"] === \"file\") {\n let base64Content: string | null = null;\n // FilePart\n if (part[\"data\"] != null && part[\"mediaType\"] != null) {\n base64Content = part[\"data\"];\n }\n\n //ImagePart\n if (\n part[\"image\"] != null &&\n part[\"mediaType\"] != null &&\n !part[\"image\"].startsWith(\"http\") // skip URLs\n ) {\n base64Content = part[\"image\"];\n }\n\n if (!base64Content) continue;\n\n const media = new LangfuseMedia({\n contentType: part[\"mediaType\"],\n contentBytes: base64ToBytes(base64Content),\n source: \"bytes\",\n });\n\n const langfuseMediaTag = await media.getTag();\n\n if (!langfuseMediaTag) {\n this.logger.warn(\n \"Failed to create ElasticDash media tag. Skipping media item.\",\n );\n\n continue;\n }\n\n this.scheduleUpload({\n span,\n media,\n field: \"input\",\n });\n\n // Replace original attribute with media escaped attribute\n mediaReplacedValue = mediaReplacedValue.replaceAll(\n base64Content,\n langfuseMediaTag,\n );\n }\n }\n }\n }\n }\n\n span.attributes[mediaAttribute] = mediaReplacedValue;\n } catch (err) {\n this.logger.warn(\n `Failed to handle media for AI SDK attribute ${mediaAttribute} for span ${span.spanContext().spanId}`,\n err,\n );\n }\n }\n }\n }\n\n private scheduleUpload(params: {\n span: ReadableSpan;\n field: string;\n media: LangfuseMedia;\n }) {\n const { span, field, media } = params;\n\n const uploadPromise: Promise<void> = this.handleUpload({\n media,\n traceId: span.spanContext().traceId,\n observationId: span.spanContext().spanId,\n field,\n }).catch((err) => {\n this.logger.error(\"Media upload failed with error: \", err);\n });\n\n this.pendingMediaUploads.add(uploadPromise);\n\n uploadPromise.finally(() => {\n this.pendingMediaUploads.delete(uploadPromise);\n });\n }\n\n private async handleUpload({\n media,\n traceId,\n observationId,\n field,\n }: {\n media: LangfuseMedia;\n traceId: string;\n observationId?: string;\n field: string;\n }): Promise<void> {\n try {\n const contentSha256Hash = await media.getSha256Hash();\n\n if (\n !media.contentLength ||\n !media._contentType ||\n !contentSha256Hash ||\n !media._contentBytes\n ) {\n return;\n }\n\n const { uploadUrl, mediaId } = await this.apiClient.media.getUploadUrl({\n contentLength: media.contentLength,\n traceId,\n observationId,\n field,\n contentType: media._contentType,\n sha256Hash: contentSha256Hash,\n });\n\n if (!uploadUrl) {\n this.logger.debug(\n `Media status: Media with ID ${mediaId} already uploaded. Skipping duplicate upload.`,\n );\n\n return;\n }\n\n const clientSideMediaId = await media.getId();\n if (clientSideMediaId !== mediaId) {\n this.logger.error(\n `Media integrity error: Media ID mismatch between SDK (${clientSideMediaId}) and Server (${mediaId}). Upload cancelled. Please check media ID generation logic.`,\n );\n\n return;\n }\n\n this.logger.debug(`Uploading media ${mediaId}...`);\n\n const startTime = Date.now();\n\n const uploadResponse = await this.uploadWithBackoff({\n uploadUrl,\n contentBytes: media._contentBytes,\n contentType: media._contentType,\n contentSha256Hash: contentSha256Hash,\n maxRetries: 3,\n baseDelay: 1000,\n });\n\n if (!uploadResponse) {\n throw Error(\"Media upload process failed\");\n }\n\n await this.apiClient.media.patch(mediaId, {\n uploadedAt: new Date().toISOString(),\n uploadHttpStatus: uploadResponse.status,\n uploadHttpError: await uploadResponse.text(),\n uploadTimeMs: Date.now() - startTime,\n });\n\n this.logger.debug(`Media upload status reported for ${mediaId}`);\n } catch (err) {\n this.logger.error(`Error processing media item: ${err}`);\n }\n }\n\n private async uploadWithBackoff(params: {\n uploadUrl: string;\n contentType: string;\n contentSha256Hash: string;\n contentBytes: Uint8Array;\n maxRetries: number;\n baseDelay: number;\n }) {\n const {\n uploadUrl,\n contentType,\n contentSha256Hash,\n contentBytes,\n maxRetries,\n baseDelay,\n } = params;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n let parsedHostname: string;\n\n try {\n parsedHostname = new URL(uploadUrl).hostname;\n } catch {\n parsedHostname = \"\";\n }\n\n const isSelfHostedGcsBucket =\n parsedHostname === \"storage.googleapis.com\" ||\n parsedHostname.endsWith(\".storage.googleapis.com\");\n\n const headers = isSelfHostedGcsBucket\n ? { \"Content-Type\": contentType }\n : {\n \"Content-Type\": contentType,\n \"x-amz-checksum-sha256\": contentSha256Hash,\n \"x-ms-blob-type\": \"BlockBlob\",\n };\n\n const uploadResponse = await fetch(uploadUrl, {\n method: \"PUT\",\n body: contentBytes,\n headers,\n });\n\n if (\n attempt < maxRetries &&\n uploadResponse.status !== 200 &&\n uploadResponse.status !== 201\n ) {\n throw new Error(`Upload failed with status ${uploadResponse.status}`);\n }\n\n return uploadResponse;\n } catch (e) {\n if (attempt === maxRetries) {\n throw e;\n }\n\n const delay = baseDelay * Math.pow(2, attempt);\n const jitter = Math.random() * 1000;\n\n await new Promise((resolve) => setTimeout(resolve, delay + jitter));\n }\n }\n }\n}\n"],"mappings":"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","names":["getGlobalLogger","LangfuseAPIClient","LangfuseOtelSpanAttributes","getGlobalLogger","LangfuseAPIClient","LangfuseOtelSpanAttributes"]}
|
|
1
|
+
{"version":3,"sources":["../src/span-processor.ts","../src/MediaService.ts"],"sourcesContent":["import {\n Logger,\n getGlobalLogger,\n ElasticDashAPIClient,\n ELASTICDASH_SDK_VERSION,\n ElasticDashOtelSpanAttributes,\n getEnv,\n base64Encode,\n getPropagatedAttributesFromContext,\n} from \"@elasticdash/core\";\nimport { Context } from \"@opentelemetry/api\";\nimport { hrTimeToMilliseconds } from \"@opentelemetry/core\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport {\n Span,\n BatchSpanProcessor,\n SimpleSpanProcessor,\n SpanExporter,\n ReadableSpan,\n SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\n\nimport { MediaService } from \"./MediaService.js\";\n\n/**\n * Function type for masking sensitive data in spans before export.\n *\n * @param params - Object containing the data to be masked\n * @param params.data - The data that should be masked\n * @returns The masked data (can be of any type)\n *\n * @example\n * ```typescript\n * const maskFunction: MaskFunction = ({ data }) => {\n * if (typeof data === 'string') {\n * return data.replace(/password=\\w+/g, 'password=***');\n * }\n * return data;\n * };\n * ```\n *\n * @public\n */\nexport type MaskFunction = (params: { data: any }) => any;\n\n/**\n * Function type for determining whether a span should be exported to ElasticDash.\n *\n * @param params - Object containing the span to evaluate\n * @param params.otelSpan - The OpenTelemetry span to evaluate\n * @returns `true` if the span should be exported, `false` otherwise\n *\n * @example\n * ```typescript\n * const shouldExportSpan: ShouldExportSpan = ({ otelSpan }) => {\n * // Only export spans that took longer than 100ms\n * return otelSpan.duration[0] * 1000 + otelSpan.duration[1] / 1000000 > 100;\n * };\n * ```\n *\n * @public\n */\nexport type ShouldExportSpan = (params: { otelSpan: ReadableSpan }) => boolean;\n\n/**\n * Configuration parameters for the ElasticDashSpanProcessor.\n *\n * @public\n */\nexport interface ElasticDashSpanProcessorParams {\n /**\n * Custom OpenTelemetry span exporter. If not provided, a default OTLP exporter will be used.\n */\n exporter?: SpanExporter;\n\n /**\n * ElasticDash public API key. Can also be set via ELASTICDASH_PUBLIC_KEY environment variable.\n */\n publicKey?: string;\n\n /**\n * ElasticDash secret API key. Can also be set via ELASTICDASH_SECRET_KEY environment variable.\n */\n secretKey?: string;\n\n /**\n * ElasticDash instance base URL. Can also be set via ELASTICDASH_BASE_URL environment variable.\n * @defaultValue \"https://cloud.elasticdash.com\"\n */\n baseUrl?: string;\n\n /**\n * Number of spans to batch before flushing. Can also be set via ELASTICDASH_FLUSH_AT environment variable.\n */\n flushAt?: number;\n\n /**\n * Flush interval in seconds. Can also be set via ELASTICDASH_FLUSH_INTERVAL environment variable.\n */\n flushInterval?: number;\n\n /**\n * Function to mask sensitive data in spans before export.\n */\n mask?: MaskFunction;\n\n /**\n * Function to determine whether a span should be exported to ElasticDash.\n */\n shouldExportSpan?: ShouldExportSpan;\n\n /**\n * Environment identifier for the traces. Can also be set via ELASTICDASH_TRACING_ENVIRONMENT environment variable.\n */\n environment?: string;\n\n /**\n * Release identifier for the traces. Can also be set via ELASTICDASH_RELEASE environment variable.\n */\n release?: string;\n\n /**\n * Request timeout in seconds. Can also be set via ELASTICDASH_TIMEOUT environment variable.\n * @defaultValue 5\n */\n timeout?: number;\n\n /**\n * Additional HTTP headers to include with requests.\n */\n additionalHeaders?: Record<string, string>;\n /**\n * Span export mode to use.\n *\n * - **batched**: Recommended for production environments with long-running processes.\n * Spans are batched and exported in groups for optimal performance.\n * - **immediate**: Recommended for short-lived environments such as serverless functions.\n * Spans are exported immediately to prevent data loss when the process terminates / is frozen.\n *\n * @defaultValue \"batched\"\n */\n exportMode?: \"immediate\" | \"batched\";\n}\n\n/**\n * OpenTelemetry span processor for sending spans to ElasticDash.\n *\n * This processor extends the standard BatchSpanProcessor to provide:\n * - Automatic batching and flushing of spans to ElasticDash\n * - Media content extraction and upload from base64 data URIs\n * - Data masking capabilities for sensitive information\n * - Conditional span export based on custom logic\n * - Environment and release tagging\n *\n * @example\n * ```typescript\n * import { NodeSDK } from '@opentelemetry/sdk-node';\n * import { ElasticDashSpanProcessor } from '@elasticdash/otel';\n *\n * const sdk = new NodeSDK({\n * spanProcessors: [\n * new ElasticDashSpanProcessor({\n * publicKey: 'pk_...',\n * secretKey: 'sk_...',\n * baseUrl: 'https://cloud.elasticdash.com',\n * environment: 'production',\n * mask: ({ data }) => {\n * // Mask sensitive data\n * return data.replace(/api_key=\\w+/g, 'api_key=***');\n * }\n * })\n * ]\n * });\n *\n * sdk.start();\n * ```\n *\n * @public\n */\nexport class ElasticDashSpanProcessor implements SpanProcessor {\n private pendingEndedSpans: Set<Promise<void>> = new Set();\n\n private publicKey?: string;\n private baseUrl?: string;\n private environment?: string;\n private release?: string;\n private mask?: MaskFunction;\n private shouldExportSpan?: ShouldExportSpan;\n private apiClient: ElasticDashAPIClient;\n private processor: SpanProcessor;\n private mediaService: MediaService;\n\n /**\n * Creates a new ElasticDashSpanProcessor instance.\n *\n * @param params - Configuration parameters for the processor\n *\n * @example\n * ```typescript\n * const processor = new ElasticDashSpanProcessor({\n * publicKey: 'pk_...',\n * secretKey: 'sk_...',\n * environment: 'staging',\n * flushAt: 10,\n * flushInterval: 2,\n * mask: ({ data }) => {\n * // Custom masking logic\n * return typeof data === 'string'\n * ? data.replace(/secret_\\w+/g, 'secret_***')\n * : data;\n * },\n * shouldExportSpan: ({ otelSpan }) => {\n * // Only export spans from specific services\n * return otelSpan.name.startsWith('my-service');\n * }\n * });\n * ```\n */\n constructor(params?: ElasticDashSpanProcessorParams) {\n const logger = getGlobalLogger();\n\n const publicKey = params?.publicKey ?? getEnv(\"ELASTICDASH_PUBLIC_KEY\");\n const secretKey = params?.secretKey ?? getEnv(\"ELASTICDASH_SECRET_KEY\");\n const baseUrl =\n params?.baseUrl ??\n getEnv(\"ELASTICDASH_BASE_URL\") ??\n getEnv(\"ELASTICDASH_BASEURL\") ?? // legacy v2\n \"https://cloud.elasticdash.com\";\n\n if (!params?.exporter && !publicKey) {\n logger.warn(\n \"No exporter configured and no public key provided in constructor or as ELASTICDASH_PUBLIC_KEY env var. Span exports will fail.\",\n );\n }\n if (!params?.exporter && !secretKey) {\n logger.warn(\n \"No exporter configured and no secret key provided in constructor or as ELASTICDASH_SECRET_KEY env var. Span exports will fail.\",\n );\n }\n const flushAt = params?.flushAt ?? getEnv(\"ELASTICDASH_FLUSH_AT\");\n const flushIntervalSeconds =\n params?.flushInterval ?? getEnv(\"ELASTICDASH_FLUSH_INTERVAL\");\n\n const authHeaderValue = base64Encode(`${publicKey}:${secretKey}`);\n const timeoutSeconds =\n params?.timeout ?? Number(getEnv(\"ELASTICDASH_TIMEOUT\") ?? 5);\n\n const exporter =\n params?.exporter ??\n new OTLPTraceExporter({\n url: `${baseUrl}/api/public/otel/v1/traces`,\n headers: {\n Authorization: `Basic ${authHeaderValue}`,\n x_elasticdash_sdk_name: \"javascript\",\n x_elasticdash_sdk_version: ELASTICDASH_SDK_VERSION,\n x_elasticdash_public_key: publicKey ?? \"<missing>\",\n ...params?.additionalHeaders,\n },\n timeoutMillis: timeoutSeconds * 1_000,\n });\n\n this.processor =\n params?.exportMode === \"immediate\"\n ? new SimpleSpanProcessor(exporter)\n : new BatchSpanProcessor(exporter, {\n maxExportBatchSize: flushAt ? Number(flushAt) : undefined,\n scheduledDelayMillis: flushIntervalSeconds\n ? Number(flushIntervalSeconds) * 1_000\n : undefined,\n });\n\n this.publicKey = publicKey;\n this.baseUrl = baseUrl;\n this.environment =\n params?.environment ?? getEnv(\"ELASTICDASH_TRACING_ENVIRONMENT\");\n this.release = params?.release ?? getEnv(\"ELASTICDASH_RELEASE\");\n this.mask = params?.mask;\n this.shouldExportSpan = params?.shouldExportSpan;\n this.apiClient = new ElasticDashAPIClient({\n baseUrl: this.baseUrl,\n username: this.publicKey,\n password: secretKey,\n xElasticDashPublicKey: this.publicKey,\n xElasticDashSdkVersion: ELASTICDASH_SDK_VERSION,\n xElasticDashSdkName: \"javascript\",\n environment: \"\", // noop as baseUrl is set\n headers: params?.additionalHeaders,\n });\n\n this.mediaService = new MediaService({ apiClient: this.apiClient });\n\n logger.debug(\"Initialized ElasticDashSpanProcessor with params:\", {\n publicKey,\n baseUrl,\n environment: this.environment,\n release: this.release,\n timeoutSeconds,\n flushAt,\n flushIntervalSeconds,\n });\n }\n\n private get logger(): Logger {\n return getGlobalLogger();\n }\n\n /**\n * Called when a span is started. Adds environment, release, and propagated attributes to the span.\n *\n * @param span - The span that was started\n * @param parentContext - The parent context\n *\n * @override\n */\n public onStart(span: Span, parentContext: Context): void {\n // Set propagated attributes, environment and release attributes\n span.setAttributes({\n [ElasticDashOtelSpanAttributes.ENVIRONMENT]: this.environment,\n [ElasticDashOtelSpanAttributes.RELEASE]: this.release,\n ...getPropagatedAttributesFromContext(parentContext),\n });\n\n return this.processor.onStart(span, parentContext);\n }\n\n /**\n * Called when a span ends. Processes the span for export to ElasticDash.\n *\n * This method:\n * 1. Checks if the span should be exported using the shouldExportSpan function\n * 2. Applies data masking to sensitive attributes\n * 3. Handles media content extraction and upload\n * 4. Logs span details in debug mode\n * 5. Passes the span to the parent processor for export\n *\n * @param span - The span that ended\n *\n * @override\n */\n public onEnd(span: ReadableSpan): void {\n const processEndedSpanPromise = this.processEndedSpan(span).catch((err) => {\n this.logger.error(err);\n });\n\n // Enqueue this export to the pending list so it can be flushed by the user.\n this.pendingEndedSpans.add(processEndedSpanPromise);\n\n void processEndedSpanPromise.finally(() =>\n this.pendingEndedSpans.delete(processEndedSpanPromise),\n );\n }\n\n private async flush(): Promise<void> {\n await Promise.all(Array.from(this.pendingEndedSpans));\n await this.mediaService.flush();\n }\n\n /**\n * Forces an immediate flush of all pending spans and media uploads.\n *\n * @returns Promise that resolves when all pending operations are complete\n *\n * @override\n */\n public async forceFlush(): Promise<void> {\n await this.flush();\n\n return this.processor.forceFlush();\n }\n\n /**\n * Gracefully shuts down the processor, ensuring all pending operations are completed.\n *\n * @returns Promise that resolves when shutdown is complete\n *\n * @override\n */\n public async shutdown(): Promise<void> {\n await this.flush();\n\n return this.processor.shutdown();\n }\n\n private async processEndedSpan(span: ReadableSpan) {\n if (this.shouldExportSpan) {\n try {\n if (this.shouldExportSpan({ otelSpan: span }) === false) return;\n } catch (err) {\n this.logger.error(\n \"ShouldExportSpan failed with error. Excluding span. Error: \",\n err,\n );\n\n return;\n }\n }\n\n this.applyMaskInPlace(span);\n await this.mediaService.process(span);\n\n this.logger.debug(\n `Processed span:\\n${JSON.stringify(\n {\n name: span.name,\n traceId: span.spanContext().traceId,\n spanId: span.spanContext().spanId,\n parentSpanId: span.parentSpanContext?.spanId ?? null,\n attributes: span.attributes,\n startTime: new Date(hrTimeToMilliseconds(span.startTime)),\n endTime: new Date(hrTimeToMilliseconds(span.endTime)),\n durationMs: hrTimeToMilliseconds(span.duration),\n kind: span.kind,\n status: span.status,\n resource: span.resource.attributes,\n instrumentationScope: span.instrumentationScope,\n },\n null,\n 2,\n )}`,\n );\n\n this.processor.onEnd(span);\n }\n private applyMaskInPlace(span: ReadableSpan): void {\n const maskCandidates = [\n ElasticDashOtelSpanAttributes.OBSERVATION_INPUT,\n ElasticDashOtelSpanAttributes.TRACE_INPUT,\n ElasticDashOtelSpanAttributes.OBSERVATION_OUTPUT,\n ElasticDashOtelSpanAttributes.TRACE_OUTPUT,\n ElasticDashOtelSpanAttributes.OBSERVATION_METADATA,\n ElasticDashOtelSpanAttributes.TRACE_METADATA,\n ];\n\n for (const maskCandidate of maskCandidates) {\n if (maskCandidate in span.attributes) {\n span.attributes[maskCandidate] = this.applyMask(\n span.attributes[maskCandidate],\n );\n }\n }\n }\n\n private applyMask<T>(data: T): T | string {\n if (!this.mask) return data;\n\n try {\n return this.mask({ data });\n } catch (err) {\n this.logger.warn(\n `Applying mask function failed due to error, fully masking property. Error: ${err}`,\n );\n\n return \"<fully masked due to failed mask function>\";\n }\n }\n}\n","import {\n ElasticDashAPIClient,\n ElasticDashMedia,\n ElasticDashOtelSpanAttributes,\n Logger,\n base64ToBytes,\n getGlobalLogger,\n} from \"@elasticdash/core\";\nimport { ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\n\nexport class MediaService {\n private pendingMediaUploads: Set<Promise<void>> = new Set();\n private apiClient: ElasticDashAPIClient;\n\n constructor(params: { apiClient: ElasticDashAPIClient }) {\n this.apiClient = params.apiClient;\n }\n\n get logger(): Logger {\n return getGlobalLogger();\n }\n\n public async flush(): Promise<void> {\n await Promise.all(Array.from(this.pendingMediaUploads));\n }\n\n public async process(span: ReadableSpan) {\n const mediaAttributes = [\n ElasticDashOtelSpanAttributes.OBSERVATION_INPUT,\n ElasticDashOtelSpanAttributes.TRACE_INPUT,\n ElasticDashOtelSpanAttributes.OBSERVATION_OUTPUT,\n ElasticDashOtelSpanAttributes.TRACE_OUTPUT,\n ElasticDashOtelSpanAttributes.OBSERVATION_METADATA,\n ElasticDashOtelSpanAttributes.TRACE_METADATA,\n ];\n\n for (const mediaAttribute of mediaAttributes) {\n const mediaRelevantAttributeKeys = Object.keys(span.attributes).filter(\n (attributeName) => attributeName.startsWith(mediaAttribute),\n );\n\n for (const key of mediaRelevantAttributeKeys) {\n const value = span.attributes[key];\n\n if (typeof value !== \"string\") {\n this.logger.warn(\n `Span attribute ${mediaAttribute} is not a stringified object. Skipping media handling.`,\n );\n\n continue;\n }\n\n // Find media base64 data URI\n let mediaReplacedValue = value;\n const regex = /data:[^;]+;base64,[A-Za-z0-9+/]+=*/g;\n const foundMedia = [...new Set(value.match(regex) ?? [])];\n\n if (foundMedia.length === 0) continue;\n\n for (const mediaDataUri of foundMedia) {\n // For each media, create media tag and initiate upload\n const media = new ElasticDashMedia({\n base64DataUri: mediaDataUri,\n source: \"base64_data_uri\",\n });\n\n const elasticDashMediaTag = await media.getTag();\n\n if (!elasticDashMediaTag) {\n this.logger.warn(\n \"Failed to create ElasticDash media tag. Skipping media item.\",\n );\n\n continue;\n }\n\n this.scheduleUpload({\n span,\n media,\n field: mediaAttribute.includes(\"input\")\n ? \"input\"\n : mediaAttribute.includes(\"output\")\n ? \"output\"\n : \"metadata\", // todo: make more robust\n });\n\n // Replace original attribute with media escaped attribute\n mediaReplacedValue = mediaReplacedValue.replaceAll(\n mediaDataUri,\n elasticDashMediaTag,\n );\n }\n\n span.attributes[key] = mediaReplacedValue;\n }\n }\n\n // Handle media from Vercel AI SDK\n if (span.instrumentationScope.name === \"ai\") {\n const aiSDKMediaAttributes = [\"ai.prompt.messages\", \"ai.prompt\"];\n\n for (const mediaAttribute of aiSDKMediaAttributes) {\n const value = span.attributes[mediaAttribute];\n\n if (!value || typeof value !== \"string\") {\n continue;\n }\n\n // Find media base64 data URI\n let mediaReplacedValue = value;\n\n try {\n const parsed = JSON.parse(value);\n\n if (Array.isArray(parsed)) {\n for (const message of parsed) {\n if (Array.isArray(message[\"content\"])) {\n const contentParts = message[\"content\"];\n\n for (const part of contentParts) {\n if (part[\"type\"] === \"file\") {\n let base64Content: string | null = null;\n // FilePart\n if (part[\"data\"] != null && part[\"mediaType\"] != null) {\n base64Content = part[\"data\"];\n }\n\n //ImagePart\n if (\n part[\"image\"] != null &&\n part[\"mediaType\"] != null &&\n !part[\"image\"].startsWith(\"http\") // skip URLs\n ) {\n base64Content = part[\"image\"];\n }\n\n if (!base64Content) continue;\n\n const media = new ElasticDashMedia({\n contentType: part[\"mediaType\"],\n contentBytes: base64ToBytes(base64Content),\n source: \"bytes\",\n });\n\n const elasticDashMediaTag = await media.getTag();\n\n if (!elasticDashMediaTag) {\n this.logger.warn(\n \"Failed to create ElasticDash media tag. Skipping media item.\",\n );\n\n continue;\n }\n\n this.scheduleUpload({\n span,\n media,\n field: \"input\",\n });\n\n // Replace original attribute with media escaped attribute\n mediaReplacedValue = mediaReplacedValue.replaceAll(\n base64Content,\n elasticDashMediaTag,\n );\n }\n }\n }\n }\n }\n\n span.attributes[mediaAttribute] = mediaReplacedValue;\n } catch (err) {\n this.logger.warn(\n `Failed to handle media for AI SDK attribute ${mediaAttribute} for span ${span.spanContext().spanId}`,\n err,\n );\n }\n }\n }\n }\n\n private scheduleUpload(params: {\n span: ReadableSpan;\n field: string;\n media: ElasticDashMedia;\n }) {\n const { span, field, media } = params;\n\n const uploadPromise: Promise<void> = this.handleUpload({\n media,\n traceId: span.spanContext().traceId,\n observationId: span.spanContext().spanId,\n field,\n }).catch((err) => {\n this.logger.error(\"Media upload failed with error: \", err);\n });\n\n this.pendingMediaUploads.add(uploadPromise);\n\n uploadPromise.finally(() => {\n this.pendingMediaUploads.delete(uploadPromise);\n });\n }\n\n private async handleUpload({\n media,\n traceId,\n observationId,\n field,\n }: {\n media: ElasticDashMedia;\n traceId: string;\n observationId?: string;\n field: string;\n }): Promise<void> {\n try {\n const contentSha256Hash = await media.getSha256Hash();\n\n if (\n !media.contentLength ||\n !media._contentType ||\n !contentSha256Hash ||\n !media._contentBytes\n ) {\n return;\n }\n\n const { uploadUrl, mediaId } = await this.apiClient.media.getUploadUrl({\n contentLength: media.contentLength,\n traceId,\n observationId,\n field,\n contentType: media._contentType,\n sha256Hash: contentSha256Hash,\n });\n\n if (!uploadUrl) {\n this.logger.debug(\n `Media status: Media with ID ${mediaId} already uploaded. Skipping duplicate upload.`,\n );\n\n return;\n }\n\n const clientSideMediaId = await media.getId();\n if (clientSideMediaId !== mediaId) {\n this.logger.error(\n `Media integrity error: Media ID mismatch between SDK (${clientSideMediaId}) and Server (${mediaId}). Upload cancelled. Please check media ID generation logic.`,\n );\n\n return;\n }\n\n this.logger.debug(`Uploading media ${mediaId}...`);\n\n const startTime = Date.now();\n\n const uploadResponse = await this.uploadWithBackoff({\n uploadUrl,\n contentBytes: media._contentBytes,\n contentType: media._contentType,\n contentSha256Hash: contentSha256Hash,\n maxRetries: 3,\n baseDelay: 1000,\n });\n\n if (!uploadResponse) {\n throw Error(\"Media upload process failed\");\n }\n\n await this.apiClient.media.patch(mediaId, {\n uploadedAt: new Date().toISOString(),\n uploadHttpStatus: uploadResponse.status,\n uploadHttpError: await uploadResponse.text(),\n uploadTimeMs: Date.now() - startTime,\n });\n\n this.logger.debug(`Media upload status reported for ${mediaId}`);\n } catch (err) {\n this.logger.error(`Error processing media item: ${err}`);\n }\n }\n\n private async uploadWithBackoff(params: {\n uploadUrl: string;\n contentType: string;\n contentSha256Hash: string;\n contentBytes: Uint8Array;\n maxRetries: number;\n baseDelay: number;\n }) {\n const {\n uploadUrl,\n contentType,\n contentSha256Hash,\n contentBytes,\n maxRetries,\n baseDelay,\n } = params;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n let parsedHostname: string;\n\n try {\n parsedHostname = new URL(uploadUrl).hostname;\n } catch {\n parsedHostname = \"\";\n }\n\n const isSelfHostedGcsBucket =\n parsedHostname === \"storage.googleapis.com\" ||\n parsedHostname.endsWith(\".storage.googleapis.com\");\n\n const headers = isSelfHostedGcsBucket\n ? { \"Content-Type\": contentType }\n : {\n \"Content-Type\": contentType,\n \"x-amz-checksum-sha256\": contentSha256Hash,\n \"x-ms-blob-type\": \"BlockBlob\",\n };\n\n const uploadResponse = await fetch(uploadUrl, {\n method: \"PUT\",\n body: contentBytes,\n headers,\n });\n\n if (\n attempt < maxRetries &&\n uploadResponse.status !== 200 &&\n uploadResponse.status !== 201\n ) {\n throw new Error(`Upload failed with status ${uploadResponse.status}`);\n }\n\n return uploadResponse;\n } catch (e) {\n if (attempt === maxRetries) {\n throw e;\n }\n\n const delay = baseDelay * Math.pow(2, attempt);\n const jitter = Math.random() * 1000;\n\n await new Promise((resolve) => setTimeout(resolve, delay + jitter));\n }\n }\n }\n}\n"],"mappings":"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","names":["getGlobalLogger","ElasticDashAPIClient","ElasticDashOtelSpanAttributes","getGlobalLogger","ElasticDashAPIClient","ElasticDashOtelSpanAttributes"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elasticdash/otel",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"author": "ElasticDash",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"dist"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@elasticdash/core": "^0.0.
|
|
31
|
+
"@elasticdash/core": "^0.0.9"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@opentelemetry/api": "^1.9.0",
|