@langfuse/otel 4.6.1 → 5.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +98 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -5
- package/dist/index.d.ts +17 -5
- package/dist/index.mjs +64 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -20,13 +20,18 @@ 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
|
+
KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES: () => KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES,
|
|
24
|
+
LangfuseSpanProcessor: () => LangfuseSpanProcessor,
|
|
25
|
+
isDefaultExportSpan: () => isDefaultExportSpan,
|
|
26
|
+
isGenAISpan: () => isGenAISpan,
|
|
27
|
+
isKnownLLMInstrumentor: () => isKnownLLMInstrumentor,
|
|
28
|
+
isLangfuseSpan: () => isLangfuseSpan
|
|
24
29
|
});
|
|
25
30
|
module.exports = __toCommonJS(index_exports);
|
|
26
31
|
|
|
27
32
|
// src/span-processor.ts
|
|
28
|
-
var
|
|
29
|
-
var
|
|
33
|
+
var import_core3 = require("@langfuse/core");
|
|
34
|
+
var import_core4 = require("@opentelemetry/core");
|
|
30
35
|
var import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
|
|
31
36
|
var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
32
37
|
|
|
@@ -270,6 +275,38 @@ var MediaService = class {
|
|
|
270
275
|
}
|
|
271
276
|
};
|
|
272
277
|
|
|
278
|
+
// src/span-filter.ts
|
|
279
|
+
var import_core2 = require("@langfuse/core");
|
|
280
|
+
var KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES = [
|
|
281
|
+
import_core2.LANGFUSE_TRACER_NAME,
|
|
282
|
+
"agent_framework",
|
|
283
|
+
"ai",
|
|
284
|
+
"haystack",
|
|
285
|
+
"langsmith",
|
|
286
|
+
"litellm",
|
|
287
|
+
"openinference",
|
|
288
|
+
"opentelemetry.instrumentation.anthropic",
|
|
289
|
+
"strands-agents",
|
|
290
|
+
"vllm"
|
|
291
|
+
];
|
|
292
|
+
function isLangfuseSpan(span) {
|
|
293
|
+
return span.instrumentationScope.name === import_core2.LANGFUSE_TRACER_NAME;
|
|
294
|
+
}
|
|
295
|
+
function isGenAISpan(span) {
|
|
296
|
+
return Object.keys(span.attributes).some(
|
|
297
|
+
(attributeKey) => attributeKey.startsWith("gen_ai.")
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
function isKnownLLMInstrumentor(span) {
|
|
301
|
+
const scope = span.instrumentationScope.name;
|
|
302
|
+
return KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES.some(
|
|
303
|
+
(prefix) => scope === prefix || scope.startsWith(`${prefix}.`)
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
function isDefaultExportSpan(span) {
|
|
307
|
+
return isLangfuseSpan(span) || isGenAISpan(span) || isKnownLLMInstrumentor(span);
|
|
308
|
+
}
|
|
309
|
+
|
|
273
310
|
// src/span-processor.ts
|
|
274
311
|
var LangfuseSpanProcessor = class {
|
|
275
312
|
/**
|
|
@@ -292,19 +329,20 @@ var LangfuseSpanProcessor = class {
|
|
|
292
329
|
* : data;
|
|
293
330
|
* },
|
|
294
331
|
* shouldExportSpan: ({ otelSpan }) => {
|
|
295
|
-
* //
|
|
296
|
-
*
|
|
332
|
+
* // Full override of default filtering:
|
|
333
|
+
* // export only spans from specific services
|
|
334
|
+
* return otelSpan.name.startsWith("my-service");
|
|
297
335
|
* }
|
|
298
336
|
* });
|
|
299
337
|
* ```
|
|
300
338
|
*/
|
|
301
339
|
constructor(params) {
|
|
302
340
|
this.pendingEndedSpans = /* @__PURE__ */ new Set();
|
|
303
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
304
|
-
const logger = (0,
|
|
305
|
-
const publicKey = (_a = params == null ? void 0 : params.publicKey) != null ? _a : (0,
|
|
306
|
-
const secretKey = (_b = params == null ? void 0 : params.secretKey) != null ? _b : (0,
|
|
307
|
-
const baseUrl = (_e = (_d = (_c = params == null ? void 0 : params.baseUrl) != null ? _c : (0,
|
|
341
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
342
|
+
const logger = (0, import_core3.getGlobalLogger)();
|
|
343
|
+
const publicKey = (_a = params == null ? void 0 : params.publicKey) != null ? _a : (0, import_core3.getEnv)("LANGFUSE_PUBLIC_KEY");
|
|
344
|
+
const secretKey = (_b = params == null ? void 0 : params.secretKey) != null ? _b : (0, import_core3.getEnv)("LANGFUSE_SECRET_KEY");
|
|
345
|
+
const baseUrl = (_e = (_d = (_c = params == null ? void 0 : params.baseUrl) != null ? _c : (0, import_core3.getEnv)("LANGFUSE_BASE_URL")) != null ? _d : (0, import_core3.getEnv)("LANGFUSE_BASEURL")) != null ? _e : (
|
|
308
346
|
// legacy v2
|
|
309
347
|
"https://cloud.langfuse.com"
|
|
310
348
|
);
|
|
@@ -318,17 +356,17 @@ var LangfuseSpanProcessor = class {
|
|
|
318
356
|
"No exporter configured and no secret key provided in constructor or as LANGFUSE_SECRET_KEY env var. Span exports will fail."
|
|
319
357
|
);
|
|
320
358
|
}
|
|
321
|
-
const flushAt = (_f = params == null ? void 0 : params.flushAt) != null ? _f : (0,
|
|
322
|
-
const flushIntervalSeconds = (_g = params == null ? void 0 : params.flushInterval) != null ? _g : (0,
|
|
323
|
-
const authHeaderValue = (0,
|
|
324
|
-
const timeoutSeconds = (_i = params == null ? void 0 : params.timeout) != null ? _i : Number((_h = (0,
|
|
359
|
+
const flushAt = (_f = params == null ? void 0 : params.flushAt) != null ? _f : (0, import_core3.getEnv)("LANGFUSE_FLUSH_AT");
|
|
360
|
+
const flushIntervalSeconds = (_g = params == null ? void 0 : params.flushInterval) != null ? _g : (0, import_core3.getEnv)("LANGFUSE_FLUSH_INTERVAL");
|
|
361
|
+
const authHeaderValue = (0, import_core3.base64Encode)(`${publicKey}:${secretKey}`);
|
|
362
|
+
const timeoutSeconds = (_i = params == null ? void 0 : params.timeout) != null ? _i : Number((_h = (0, import_core3.getEnv)("LANGFUSE_TIMEOUT")) != null ? _h : 5);
|
|
325
363
|
const exporter = (_j = params == null ? void 0 : params.exporter) != null ? _j : new import_exporter_trace_otlp_http.OTLPTraceExporter({
|
|
326
364
|
url: `${baseUrl}/api/public/otel/v1/traces`,
|
|
327
365
|
headers: {
|
|
328
366
|
Authorization: `Basic ${authHeaderValue}`,
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
367
|
+
"x-langfuse-sdk-name": "javascript",
|
|
368
|
+
"x-langfuse-sdk-version": import_core3.LANGFUSE_SDK_VERSION,
|
|
369
|
+
"x-langfuse-public-key": publicKey != null ? publicKey : "<missing>",
|
|
332
370
|
...params == null ? void 0 : params.additionalHeaders
|
|
333
371
|
},
|
|
334
372
|
timeoutMillis: timeoutSeconds * 1e3
|
|
@@ -339,16 +377,16 @@ var LangfuseSpanProcessor = class {
|
|
|
339
377
|
});
|
|
340
378
|
this.publicKey = publicKey;
|
|
341
379
|
this.baseUrl = baseUrl;
|
|
342
|
-
this.environment = (_k = params == null ? void 0 : params.environment) != null ? _k : (0,
|
|
343
|
-
this.release = (_l = params == null ? void 0 : params.release) != null ? _l : (0,
|
|
380
|
+
this.environment = (_k = params == null ? void 0 : params.environment) != null ? _k : (0, import_core3.getEnv)("LANGFUSE_TRACING_ENVIRONMENT");
|
|
381
|
+
this.release = (_l = params == null ? void 0 : params.release) != null ? _l : (0, import_core3.getEnv)("LANGFUSE_RELEASE");
|
|
344
382
|
this.mask = params == null ? void 0 : params.mask;
|
|
345
|
-
this.shouldExportSpan = params == null ? void 0 : params.shouldExportSpan;
|
|
346
|
-
this.apiClient = new
|
|
383
|
+
this.shouldExportSpan = (_m = params == null ? void 0 : params.shouldExportSpan) != null ? _m : ({ otelSpan }) => isDefaultExportSpan(otelSpan);
|
|
384
|
+
this.apiClient = new import_core3.LangfuseAPIClient({
|
|
347
385
|
baseUrl: this.baseUrl,
|
|
348
386
|
username: this.publicKey,
|
|
349
387
|
password: secretKey,
|
|
350
388
|
xLangfusePublicKey: this.publicKey,
|
|
351
|
-
xLangfuseSdkVersion:
|
|
389
|
+
xLangfuseSdkVersion: import_core3.LANGFUSE_SDK_VERSION,
|
|
352
390
|
xLangfuseSdkName: "javascript",
|
|
353
391
|
environment: "",
|
|
354
392
|
// noop as baseUrl is set
|
|
@@ -366,7 +404,7 @@ var LangfuseSpanProcessor = class {
|
|
|
366
404
|
});
|
|
367
405
|
}
|
|
368
406
|
get logger() {
|
|
369
|
-
return (0,
|
|
407
|
+
return (0, import_core3.getGlobalLogger)();
|
|
370
408
|
}
|
|
371
409
|
/**
|
|
372
410
|
* Called when a span is started. Adds environment, release, and propagated attributes to the span.
|
|
@@ -378,9 +416,9 @@ var LangfuseSpanProcessor = class {
|
|
|
378
416
|
*/
|
|
379
417
|
onStart(span, parentContext) {
|
|
380
418
|
span.setAttributes({
|
|
381
|
-
[
|
|
382
|
-
[
|
|
383
|
-
...(0,
|
|
419
|
+
[import_core3.LangfuseOtelSpanAttributes.ENVIRONMENT]: this.environment,
|
|
420
|
+
[import_core3.LangfuseOtelSpanAttributes.RELEASE]: this.release,
|
|
421
|
+
...(0, import_core3.getPropagatedAttributesFromContext)(parentContext)
|
|
384
422
|
});
|
|
385
423
|
return this.processor.onStart(span, parentContext);
|
|
386
424
|
}
|
|
@@ -388,7 +426,8 @@ var LangfuseSpanProcessor = class {
|
|
|
388
426
|
* Called when a span ends. Processes the span for export to Langfuse.
|
|
389
427
|
*
|
|
390
428
|
* This method:
|
|
391
|
-
* 1. Checks if the span should be exported using
|
|
429
|
+
* 1. Checks if the span should be exported using shouldExportSpan
|
|
430
|
+
* (custom override or default smart filter)
|
|
392
431
|
* 2. Applies data masking to sensitive attributes
|
|
393
432
|
* 3. Handles media content extraction and upload
|
|
394
433
|
* 4. Logs span details in debug mode
|
|
@@ -435,20 +474,28 @@ var LangfuseSpanProcessor = class {
|
|
|
435
474
|
}
|
|
436
475
|
async processEndedSpan(span) {
|
|
437
476
|
var _a, _b;
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
err
|
|
445
|
-
);
|
|
477
|
+
try {
|
|
478
|
+
if (this.shouldExportSpan({ otelSpan: span }) === false) {
|
|
479
|
+
this.logger.debug("Dropped span due to shouldExportSpan filter.", {
|
|
480
|
+
spanName: span.name,
|
|
481
|
+
instrumentationScope: span.instrumentationScope.name
|
|
482
|
+
});
|
|
446
483
|
return;
|
|
447
484
|
}
|
|
485
|
+
} catch (err) {
|
|
486
|
+
this.logger.error(
|
|
487
|
+
"shouldExportSpan failed with error. Dropping span.",
|
|
488
|
+
{
|
|
489
|
+
spanName: span.name,
|
|
490
|
+
instrumentationScope: span.instrumentationScope.name
|
|
491
|
+
},
|
|
492
|
+
err
|
|
493
|
+
);
|
|
494
|
+
return;
|
|
448
495
|
}
|
|
449
496
|
this.applyMaskInPlace(span);
|
|
450
497
|
await this.mediaService.process(span);
|
|
451
|
-
if (this.logger.isLevelEnabled(
|
|
498
|
+
if (this.logger.isLevelEnabled(import_core3.LogLevel.DEBUG)) {
|
|
452
499
|
this.logger.debug(
|
|
453
500
|
`Processed span:
|
|
454
501
|
${JSON.stringify(
|
|
@@ -458,9 +505,9 @@ ${JSON.stringify(
|
|
|
458
505
|
spanId: span.spanContext().spanId,
|
|
459
506
|
parentSpanId: (_b = (_a = span.parentSpanContext) == null ? void 0 : _a.spanId) != null ? _b : null,
|
|
460
507
|
attributes: span.attributes,
|
|
461
|
-
startTime: new Date((0,
|
|
462
|
-
endTime: new Date((0,
|
|
463
|
-
durationMs: (0,
|
|
508
|
+
startTime: new Date((0, import_core4.hrTimeToMilliseconds)(span.startTime)),
|
|
509
|
+
endTime: new Date((0, import_core4.hrTimeToMilliseconds)(span.endTime)),
|
|
510
|
+
durationMs: (0, import_core4.hrTimeToMilliseconds)(span.duration),
|
|
464
511
|
kind: span.kind,
|
|
465
512
|
status: span.status,
|
|
466
513
|
resource: span.resource.attributes,
|
|
@@ -475,12 +522,12 @@ ${JSON.stringify(
|
|
|
475
522
|
}
|
|
476
523
|
applyMaskInPlace(span) {
|
|
477
524
|
const maskCandidates = [
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
525
|
+
import_core3.LangfuseOtelSpanAttributes.OBSERVATION_INPUT,
|
|
526
|
+
import_core3.LangfuseOtelSpanAttributes.TRACE_INPUT,
|
|
527
|
+
import_core3.LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,
|
|
528
|
+
import_core3.LangfuseOtelSpanAttributes.TRACE_OUTPUT,
|
|
529
|
+
import_core3.LangfuseOtelSpanAttributes.OBSERVATION_METADATA,
|
|
530
|
+
import_core3.LangfuseOtelSpanAttributes.TRACE_METADATA
|
|
484
531
|
];
|
|
485
532
|
for (const maskCandidate of maskCandidates) {
|
|
486
533
|
if (maskCandidate in span.attributes) {
|
|
@@ -504,6 +551,11 @@ ${JSON.stringify(
|
|
|
504
551
|
};
|
|
505
552
|
// Annotate the CommonJS export names for ESM import in node:
|
|
506
553
|
0 && (module.exports = {
|
|
507
|
-
|
|
554
|
+
KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES,
|
|
555
|
+
LangfuseSpanProcessor,
|
|
556
|
+
isDefaultExportSpan,
|
|
557
|
+
isGenAISpan,
|
|
558
|
+
isKnownLLMInstrumentor,
|
|
559
|
+
isLangfuseSpan
|
|
508
560
|
});
|
|
509
561
|
//# 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 LogLevel,\n getGlobalLogger,\n LangfuseAPIClient,\n LANGFUSE_SDK_VERSION,\n LangfuseOtelSpanAttributes,\n getEnv,\n base64Encode,\n getPropagatedAttributesFromContext,\n} from \"@langfuse/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 Langfuse.\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 * Langfuse public API key. Can also be set via LANGFUSE_PUBLIC_KEY environment variable.\n */\n publicKey?: string;\n\n /**\n * Langfuse secret API key. Can also be set via LANGFUSE_SECRET_KEY environment variable.\n */\n secretKey?: string;\n\n /**\n * Langfuse instance base URL. Can also be set via LANGFUSE_BASE_URL environment variable.\n * @defaultValue \"https://cloud.langfuse.com\"\n */\n baseUrl?: string;\n\n /**\n * Number of spans to batch before flushing. Can also be set via LANGFUSE_FLUSH_AT environment variable.\n */\n flushAt?: number;\n\n /**\n * Flush interval in seconds. Can also be set via LANGFUSE_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 Langfuse.\n */\n shouldExportSpan?: ShouldExportSpan;\n\n /**\n * Environment identifier for the traces. Can also be set via LANGFUSE_TRACING_ENVIRONMENT environment variable.\n */\n environment?: string;\n\n /**\n * Release identifier for the traces. Can also be set via LANGFUSE_RELEASE environment variable.\n */\n release?: string;\n\n /**\n * Request timeout in seconds. Can also be set via LANGFUSE_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 Langfuse.\n *\n * This processor extends the standard BatchSpanProcessor to provide:\n * - Automatic batching and flushing of spans to Langfuse\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 '@langfuse/otel';\n *\n * const sdk = new NodeSDK({\n * spanProcessors: [\n * new LangfuseSpanProcessor({\n * publicKey: 'pk_...',\n * secretKey: 'sk_...',\n * baseUrl: 'https://cloud.langfuse.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(\"LANGFUSE_PUBLIC_KEY\");\n const secretKey = params?.secretKey ?? getEnv(\"LANGFUSE_SECRET_KEY\");\n const baseUrl =\n params?.baseUrl ??\n getEnv(\"LANGFUSE_BASE_URL\") ??\n getEnv(\"LANGFUSE_BASEURL\") ?? // legacy v2\n \"https://cloud.langfuse.com\";\n\n if (!params?.exporter && !publicKey) {\n logger.warn(\n \"No exporter configured and no public key provided in constructor or as LANGFUSE_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 LANGFUSE_SECRET_KEY env var. Span exports will fail.\",\n );\n }\n const flushAt = params?.flushAt ?? getEnv(\"LANGFUSE_FLUSH_AT\");\n const flushIntervalSeconds =\n params?.flushInterval ?? getEnv(\"LANGFUSE_FLUSH_INTERVAL\");\n\n const authHeaderValue = base64Encode(`${publicKey}:${secretKey}`);\n const timeoutSeconds =\n params?.timeout ?? Number(getEnv(\"LANGFUSE_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: LANGFUSE_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(\"LANGFUSE_TRACING_ENVIRONMENT\");\n this.release = params?.release ?? getEnv(\"LANGFUSE_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: LANGFUSE_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 Langfuse.\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 if (this.logger.isLevelEnabled(LogLevel.DEBUG)) {\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\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 \"@langfuse/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 Langfuse 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 Langfuse 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","../src/span-filter.ts"],"sourcesContent":["export * from \"./span-processor.js\";\nexport * from \"./span-filter.js\";\n","import {\n Logger,\n LogLevel,\n getGlobalLogger,\n LangfuseAPIClient,\n LANGFUSE_SDK_VERSION,\n LangfuseOtelSpanAttributes,\n getEnv,\n base64Encode,\n getPropagatedAttributesFromContext,\n} from \"@langfuse/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\";\nimport { isDefaultExportSpan } from \"./span-filter.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 Langfuse.\n * If provided, this is treated as a full override of the default filtering behavior.\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 * Langfuse public API key. Can also be set via LANGFUSE_PUBLIC_KEY environment variable.\n */\n publicKey?: string;\n\n /**\n * Langfuse secret API key. Can also be set via LANGFUSE_SECRET_KEY environment variable.\n */\n secretKey?: string;\n\n /**\n * Langfuse instance base URL. Can also be set via LANGFUSE_BASE_URL environment variable.\n * @defaultValue \"https://cloud.langfuse.com\"\n */\n baseUrl?: string;\n\n /**\n * Number of spans to batch before flushing. Can also be set via LANGFUSE_FLUSH_AT environment variable.\n */\n flushAt?: number;\n\n /**\n * Flush interval in seconds. Can also be set via LANGFUSE_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 Langfuse.\n * If not provided, a smart default filter is applied to export Langfuse spans,\n * spans with `gen_ai.` attributes, and spans from known LLM instrumentors.\n */\n shouldExportSpan?: ShouldExportSpan;\n\n /**\n * Environment identifier for the traces. Can also be set via LANGFUSE_TRACING_ENVIRONMENT environment variable.\n */\n environment?: string;\n\n /**\n * Release identifier for the traces. Can also be set via LANGFUSE_RELEASE environment variable.\n */\n release?: string;\n\n /**\n * Request timeout in seconds. Can also be set via LANGFUSE_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 Langfuse.\n *\n * This processor extends the standard BatchSpanProcessor to provide:\n * - Automatic batching and flushing of spans to Langfuse\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 * (or default smart filtering when no custom filter is provided)\n * - Environment and release tagging\n *\n * @example\n * ```typescript\n * import { NodeSDK } from '@opentelemetry/sdk-node';\n * import { LangfuseSpanProcessor } from '@langfuse/otel';\n *\n * const sdk = new NodeSDK({\n * spanProcessors: [\n * new LangfuseSpanProcessor({\n * publicKey: 'pk_...',\n * secretKey: 'sk_...',\n * baseUrl: 'https://cloud.langfuse.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 * // Full override of default filtering:\n * // export only 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(\"LANGFUSE_PUBLIC_KEY\");\n const secretKey = params?.secretKey ?? getEnv(\"LANGFUSE_SECRET_KEY\");\n const baseUrl =\n params?.baseUrl ??\n getEnv(\"LANGFUSE_BASE_URL\") ??\n getEnv(\"LANGFUSE_BASEURL\") ?? // legacy v2\n \"https://cloud.langfuse.com\";\n\n if (!params?.exporter && !publicKey) {\n logger.warn(\n \"No exporter configured and no public key provided in constructor or as LANGFUSE_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 LANGFUSE_SECRET_KEY env var. Span exports will fail.\",\n );\n }\n const flushAt = params?.flushAt ?? getEnv(\"LANGFUSE_FLUSH_AT\");\n const flushIntervalSeconds =\n params?.flushInterval ?? getEnv(\"LANGFUSE_FLUSH_INTERVAL\");\n\n const authHeaderValue = base64Encode(`${publicKey}:${secretKey}`);\n const timeoutSeconds =\n params?.timeout ?? Number(getEnv(\"LANGFUSE_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\": LANGFUSE_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(\"LANGFUSE_TRACING_ENVIRONMENT\");\n this.release = params?.release ?? getEnv(\"LANGFUSE_RELEASE\");\n this.mask = params?.mask;\n this.shouldExportSpan =\n params?.shouldExportSpan ??\n (({ otelSpan }) => isDefaultExportSpan(otelSpan));\n this.apiClient = new LangfuseAPIClient({\n baseUrl: this.baseUrl,\n username: this.publicKey,\n password: secretKey,\n xLangfusePublicKey: this.publicKey,\n xLangfuseSdkVersion: LANGFUSE_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 Langfuse.\n *\n * This method:\n * 1. Checks if the span should be exported using shouldExportSpan\n * (custom override or default smart filter)\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 try {\n if (this.shouldExportSpan({ otelSpan: span }) === false) {\n this.logger.debug(\"Dropped span due to shouldExportSpan filter.\", {\n spanName: span.name,\n instrumentationScope: span.instrumentationScope.name,\n });\n\n return;\n }\n } catch (err) {\n this.logger.error(\n \"shouldExportSpan failed with error. Dropping span.\",\n {\n spanName: span.name,\n instrumentationScope: span.instrumentationScope.name,\n },\n err,\n );\n\n return;\n }\n\n this.applyMaskInPlace(span);\n await this.mediaService.process(span);\n\n if (this.logger.isLevelEnabled(LogLevel.DEBUG)) {\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\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 \"@langfuse/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 Langfuse 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 Langfuse 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","import { LANGFUSE_TRACER_NAME } from \"@langfuse/core\";\nimport type { ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\n\nexport const KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES = [\n LANGFUSE_TRACER_NAME,\n \"agent_framework\",\n \"ai\",\n \"haystack\",\n \"langsmith\",\n \"litellm\",\n \"openinference\",\n \"opentelemetry.instrumentation.anthropic\",\n \"strands-agents\",\n \"vllm\",\n] as const;\n\nexport function isLangfuseSpan(span: ReadableSpan): boolean {\n return span.instrumentationScope.name === LANGFUSE_TRACER_NAME;\n}\n\nexport function isGenAISpan(span: ReadableSpan): boolean {\n return Object.keys(span.attributes).some((attributeKey) =>\n attributeKey.startsWith(\"gen_ai.\"),\n );\n}\n\nexport function isKnownLLMInstrumentor(span: ReadableSpan): boolean {\n const scope = span.instrumentationScope.name;\n\n return KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES.some(\n (prefix) => scope === prefix || scope.startsWith(`${prefix}.`),\n );\n}\n\nexport function isDefaultExportSpan(span: ReadableSpan): boolean {\n return (\n isLangfuseSpan(span) || isGenAISpan(span) || isKnownLLMInstrumentor(span)\n );\n}\n"],"mappings":"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","names":["import_core","import_core"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -25,6 +25,7 @@ type MaskFunction = (params: {
|
|
|
25
25
|
}) => any;
|
|
26
26
|
/**
|
|
27
27
|
* Function type for determining whether a span should be exported to Langfuse.
|
|
28
|
+
* If provided, this is treated as a full override of the default filtering behavior.
|
|
28
29
|
*
|
|
29
30
|
* @param params - Object containing the span to evaluate
|
|
30
31
|
* @param params.otelSpan - The OpenTelemetry span to evaluate
|
|
@@ -80,6 +81,8 @@ interface LangfuseSpanProcessorParams {
|
|
|
80
81
|
mask?: MaskFunction;
|
|
81
82
|
/**
|
|
82
83
|
* Function to determine whether a span should be exported to Langfuse.
|
|
84
|
+
* If not provided, a smart default filter is applied to export Langfuse spans,
|
|
85
|
+
* spans with `gen_ai.` attributes, and spans from known LLM instrumentors.
|
|
83
86
|
*/
|
|
84
87
|
shouldExportSpan?: ShouldExportSpan;
|
|
85
88
|
/**
|
|
@@ -119,6 +122,7 @@ interface LangfuseSpanProcessorParams {
|
|
|
119
122
|
* - Media content extraction and upload from base64 data URIs
|
|
120
123
|
* - Data masking capabilities for sensitive information
|
|
121
124
|
* - Conditional span export based on custom logic
|
|
125
|
+
* (or default smart filtering when no custom filter is provided)
|
|
122
126
|
* - Environment and release tagging
|
|
123
127
|
*
|
|
124
128
|
* @example
|
|
@@ -153,7 +157,7 @@ declare class LangfuseSpanProcessor implements SpanProcessor {
|
|
|
153
157
|
private environment?;
|
|
154
158
|
private release?;
|
|
155
159
|
private mask?;
|
|
156
|
-
private shouldExportSpan
|
|
160
|
+
private shouldExportSpan;
|
|
157
161
|
private apiClient;
|
|
158
162
|
private processor;
|
|
159
163
|
private mediaService;
|
|
@@ -177,8 +181,9 @@ declare class LangfuseSpanProcessor implements SpanProcessor {
|
|
|
177
181
|
* : data;
|
|
178
182
|
* },
|
|
179
183
|
* shouldExportSpan: ({ otelSpan }) => {
|
|
180
|
-
* //
|
|
181
|
-
*
|
|
184
|
+
* // Full override of default filtering:
|
|
185
|
+
* // export only spans from specific services
|
|
186
|
+
* return otelSpan.name.startsWith("my-service");
|
|
182
187
|
* }
|
|
183
188
|
* });
|
|
184
189
|
* ```
|
|
@@ -198,7 +203,8 @@ declare class LangfuseSpanProcessor implements SpanProcessor {
|
|
|
198
203
|
* Called when a span ends. Processes the span for export to Langfuse.
|
|
199
204
|
*
|
|
200
205
|
* This method:
|
|
201
|
-
* 1. Checks if the span should be exported using
|
|
206
|
+
* 1. Checks if the span should be exported using shouldExportSpan
|
|
207
|
+
* (custom override or default smart filter)
|
|
202
208
|
* 2. Applies data masking to sensitive attributes
|
|
203
209
|
* 3. Handles media content extraction and upload
|
|
204
210
|
* 4. Logs span details in debug mode
|
|
@@ -231,4 +237,10 @@ declare class LangfuseSpanProcessor implements SpanProcessor {
|
|
|
231
237
|
private applyMask;
|
|
232
238
|
}
|
|
233
239
|
|
|
234
|
-
|
|
240
|
+
declare const KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES: readonly ["langfuse-sdk", "agent_framework", "ai", "haystack", "langsmith", "litellm", "openinference", "opentelemetry.instrumentation.anthropic", "strands-agents", "vllm"];
|
|
241
|
+
declare function isLangfuseSpan(span: ReadableSpan): boolean;
|
|
242
|
+
declare function isGenAISpan(span: ReadableSpan): boolean;
|
|
243
|
+
declare function isKnownLLMInstrumentor(span: ReadableSpan): boolean;
|
|
244
|
+
declare function isDefaultExportSpan(span: ReadableSpan): boolean;
|
|
245
|
+
|
|
246
|
+
export { KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES, LangfuseSpanProcessor, type LangfuseSpanProcessorParams, type MaskFunction, type ShouldExportSpan, isDefaultExportSpan, isGenAISpan, isKnownLLMInstrumentor, isLangfuseSpan };
|
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ type MaskFunction = (params: {
|
|
|
25
25
|
}) => any;
|
|
26
26
|
/**
|
|
27
27
|
* Function type for determining whether a span should be exported to Langfuse.
|
|
28
|
+
* If provided, this is treated as a full override of the default filtering behavior.
|
|
28
29
|
*
|
|
29
30
|
* @param params - Object containing the span to evaluate
|
|
30
31
|
* @param params.otelSpan - The OpenTelemetry span to evaluate
|
|
@@ -80,6 +81,8 @@ interface LangfuseSpanProcessorParams {
|
|
|
80
81
|
mask?: MaskFunction;
|
|
81
82
|
/**
|
|
82
83
|
* Function to determine whether a span should be exported to Langfuse.
|
|
84
|
+
* If not provided, a smart default filter is applied to export Langfuse spans,
|
|
85
|
+
* spans with `gen_ai.` attributes, and spans from known LLM instrumentors.
|
|
83
86
|
*/
|
|
84
87
|
shouldExportSpan?: ShouldExportSpan;
|
|
85
88
|
/**
|
|
@@ -119,6 +122,7 @@ interface LangfuseSpanProcessorParams {
|
|
|
119
122
|
* - Media content extraction and upload from base64 data URIs
|
|
120
123
|
* - Data masking capabilities for sensitive information
|
|
121
124
|
* - Conditional span export based on custom logic
|
|
125
|
+
* (or default smart filtering when no custom filter is provided)
|
|
122
126
|
* - Environment and release tagging
|
|
123
127
|
*
|
|
124
128
|
* @example
|
|
@@ -153,7 +157,7 @@ declare class LangfuseSpanProcessor implements SpanProcessor {
|
|
|
153
157
|
private environment?;
|
|
154
158
|
private release?;
|
|
155
159
|
private mask?;
|
|
156
|
-
private shouldExportSpan
|
|
160
|
+
private shouldExportSpan;
|
|
157
161
|
private apiClient;
|
|
158
162
|
private processor;
|
|
159
163
|
private mediaService;
|
|
@@ -177,8 +181,9 @@ declare class LangfuseSpanProcessor implements SpanProcessor {
|
|
|
177
181
|
* : data;
|
|
178
182
|
* },
|
|
179
183
|
* shouldExportSpan: ({ otelSpan }) => {
|
|
180
|
-
* //
|
|
181
|
-
*
|
|
184
|
+
* // Full override of default filtering:
|
|
185
|
+
* // export only spans from specific services
|
|
186
|
+
* return otelSpan.name.startsWith("my-service");
|
|
182
187
|
* }
|
|
183
188
|
* });
|
|
184
189
|
* ```
|
|
@@ -198,7 +203,8 @@ declare class LangfuseSpanProcessor implements SpanProcessor {
|
|
|
198
203
|
* Called when a span ends. Processes the span for export to Langfuse.
|
|
199
204
|
*
|
|
200
205
|
* This method:
|
|
201
|
-
* 1. Checks if the span should be exported using
|
|
206
|
+
* 1. Checks if the span should be exported using shouldExportSpan
|
|
207
|
+
* (custom override or default smart filter)
|
|
202
208
|
* 2. Applies data masking to sensitive attributes
|
|
203
209
|
* 3. Handles media content extraction and upload
|
|
204
210
|
* 4. Logs span details in debug mode
|
|
@@ -231,4 +237,10 @@ declare class LangfuseSpanProcessor implements SpanProcessor {
|
|
|
231
237
|
private applyMask;
|
|
232
238
|
}
|
|
233
239
|
|
|
234
|
-
|
|
240
|
+
declare const KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES: readonly ["langfuse-sdk", "agent_framework", "ai", "haystack", "langsmith", "litellm", "openinference", "opentelemetry.instrumentation.anthropic", "strands-agents", "vllm"];
|
|
241
|
+
declare function isLangfuseSpan(span: ReadableSpan): boolean;
|
|
242
|
+
declare function isGenAISpan(span: ReadableSpan): boolean;
|
|
243
|
+
declare function isKnownLLMInstrumentor(span: ReadableSpan): boolean;
|
|
244
|
+
declare function isDefaultExportSpan(span: ReadableSpan): boolean;
|
|
245
|
+
|
|
246
|
+
export { KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES, LangfuseSpanProcessor, type LangfuseSpanProcessorParams, type MaskFunction, type ShouldExportSpan, isDefaultExportSpan, isGenAISpan, isKnownLLMInstrumentor, isLangfuseSpan };
|
package/dist/index.mjs
CHANGED
|
@@ -261,6 +261,38 @@ var MediaService = class {
|
|
|
261
261
|
}
|
|
262
262
|
};
|
|
263
263
|
|
|
264
|
+
// src/span-filter.ts
|
|
265
|
+
import { LANGFUSE_TRACER_NAME } from "@langfuse/core";
|
|
266
|
+
var KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES = [
|
|
267
|
+
LANGFUSE_TRACER_NAME,
|
|
268
|
+
"agent_framework",
|
|
269
|
+
"ai",
|
|
270
|
+
"haystack",
|
|
271
|
+
"langsmith",
|
|
272
|
+
"litellm",
|
|
273
|
+
"openinference",
|
|
274
|
+
"opentelemetry.instrumentation.anthropic",
|
|
275
|
+
"strands-agents",
|
|
276
|
+
"vllm"
|
|
277
|
+
];
|
|
278
|
+
function isLangfuseSpan(span) {
|
|
279
|
+
return span.instrumentationScope.name === LANGFUSE_TRACER_NAME;
|
|
280
|
+
}
|
|
281
|
+
function isGenAISpan(span) {
|
|
282
|
+
return Object.keys(span.attributes).some(
|
|
283
|
+
(attributeKey) => attributeKey.startsWith("gen_ai.")
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
function isKnownLLMInstrumentor(span) {
|
|
287
|
+
const scope = span.instrumentationScope.name;
|
|
288
|
+
return KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES.some(
|
|
289
|
+
(prefix) => scope === prefix || scope.startsWith(`${prefix}.`)
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
function isDefaultExportSpan(span) {
|
|
293
|
+
return isLangfuseSpan(span) || isGenAISpan(span) || isKnownLLMInstrumentor(span);
|
|
294
|
+
}
|
|
295
|
+
|
|
264
296
|
// src/span-processor.ts
|
|
265
297
|
var LangfuseSpanProcessor = class {
|
|
266
298
|
/**
|
|
@@ -283,15 +315,16 @@ var LangfuseSpanProcessor = class {
|
|
|
283
315
|
* : data;
|
|
284
316
|
* },
|
|
285
317
|
* shouldExportSpan: ({ otelSpan }) => {
|
|
286
|
-
* //
|
|
287
|
-
*
|
|
318
|
+
* // Full override of default filtering:
|
|
319
|
+
* // export only spans from specific services
|
|
320
|
+
* return otelSpan.name.startsWith("my-service");
|
|
288
321
|
* }
|
|
289
322
|
* });
|
|
290
323
|
* ```
|
|
291
324
|
*/
|
|
292
325
|
constructor(params) {
|
|
293
326
|
this.pendingEndedSpans = /* @__PURE__ */ new Set();
|
|
294
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
327
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
295
328
|
const logger = getGlobalLogger2();
|
|
296
329
|
const publicKey = (_a = params == null ? void 0 : params.publicKey) != null ? _a : getEnv("LANGFUSE_PUBLIC_KEY");
|
|
297
330
|
const secretKey = (_b = params == null ? void 0 : params.secretKey) != null ? _b : getEnv("LANGFUSE_SECRET_KEY");
|
|
@@ -317,9 +350,9 @@ var LangfuseSpanProcessor = class {
|
|
|
317
350
|
url: `${baseUrl}/api/public/otel/v1/traces`,
|
|
318
351
|
headers: {
|
|
319
352
|
Authorization: `Basic ${authHeaderValue}`,
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
353
|
+
"x-langfuse-sdk-name": "javascript",
|
|
354
|
+
"x-langfuse-sdk-version": LANGFUSE_SDK_VERSION,
|
|
355
|
+
"x-langfuse-public-key": publicKey != null ? publicKey : "<missing>",
|
|
323
356
|
...params == null ? void 0 : params.additionalHeaders
|
|
324
357
|
},
|
|
325
358
|
timeoutMillis: timeoutSeconds * 1e3
|
|
@@ -333,7 +366,7 @@ var LangfuseSpanProcessor = class {
|
|
|
333
366
|
this.environment = (_k = params == null ? void 0 : params.environment) != null ? _k : getEnv("LANGFUSE_TRACING_ENVIRONMENT");
|
|
334
367
|
this.release = (_l = params == null ? void 0 : params.release) != null ? _l : getEnv("LANGFUSE_RELEASE");
|
|
335
368
|
this.mask = params == null ? void 0 : params.mask;
|
|
336
|
-
this.shouldExportSpan = params == null ? void 0 : params.shouldExportSpan;
|
|
369
|
+
this.shouldExportSpan = (_m = params == null ? void 0 : params.shouldExportSpan) != null ? _m : ({ otelSpan }) => isDefaultExportSpan(otelSpan);
|
|
337
370
|
this.apiClient = new LangfuseAPIClient2({
|
|
338
371
|
baseUrl: this.baseUrl,
|
|
339
372
|
username: this.publicKey,
|
|
@@ -379,7 +412,8 @@ var LangfuseSpanProcessor = class {
|
|
|
379
412
|
* Called when a span ends. Processes the span for export to Langfuse.
|
|
380
413
|
*
|
|
381
414
|
* This method:
|
|
382
|
-
* 1. Checks if the span should be exported using
|
|
415
|
+
* 1. Checks if the span should be exported using shouldExportSpan
|
|
416
|
+
* (custom override or default smart filter)
|
|
383
417
|
* 2. Applies data masking to sensitive attributes
|
|
384
418
|
* 3. Handles media content extraction and upload
|
|
385
419
|
* 4. Logs span details in debug mode
|
|
@@ -426,16 +460,24 @@ var LangfuseSpanProcessor = class {
|
|
|
426
460
|
}
|
|
427
461
|
async processEndedSpan(span) {
|
|
428
462
|
var _a, _b;
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
err
|
|
436
|
-
);
|
|
463
|
+
try {
|
|
464
|
+
if (this.shouldExportSpan({ otelSpan: span }) === false) {
|
|
465
|
+
this.logger.debug("Dropped span due to shouldExportSpan filter.", {
|
|
466
|
+
spanName: span.name,
|
|
467
|
+
instrumentationScope: span.instrumentationScope.name
|
|
468
|
+
});
|
|
437
469
|
return;
|
|
438
470
|
}
|
|
471
|
+
} catch (err) {
|
|
472
|
+
this.logger.error(
|
|
473
|
+
"shouldExportSpan failed with error. Dropping span.",
|
|
474
|
+
{
|
|
475
|
+
spanName: span.name,
|
|
476
|
+
instrumentationScope: span.instrumentationScope.name
|
|
477
|
+
},
|
|
478
|
+
err
|
|
479
|
+
);
|
|
480
|
+
return;
|
|
439
481
|
}
|
|
440
482
|
this.applyMaskInPlace(span);
|
|
441
483
|
await this.mediaService.process(span);
|
|
@@ -494,6 +536,11 @@ ${JSON.stringify(
|
|
|
494
536
|
}
|
|
495
537
|
};
|
|
496
538
|
export {
|
|
497
|
-
|
|
539
|
+
KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES,
|
|
540
|
+
LangfuseSpanProcessor,
|
|
541
|
+
isDefaultExportSpan,
|
|
542
|
+
isGenAISpan,
|
|
543
|
+
isKnownLLMInstrumentor,
|
|
544
|
+
isLangfuseSpan
|
|
498
545
|
};
|
|
499
546
|
//# 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 LogLevel,\n getGlobalLogger,\n LangfuseAPIClient,\n LANGFUSE_SDK_VERSION,\n LangfuseOtelSpanAttributes,\n getEnv,\n base64Encode,\n getPropagatedAttributesFromContext,\n} from \"@langfuse/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 Langfuse.\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 * Langfuse public API key. Can also be set via LANGFUSE_PUBLIC_KEY environment variable.\n */\n publicKey?: string;\n\n /**\n * Langfuse secret API key. Can also be set via LANGFUSE_SECRET_KEY environment variable.\n */\n secretKey?: string;\n\n /**\n * Langfuse instance base URL. Can also be set via LANGFUSE_BASE_URL environment variable.\n * @defaultValue \"https://cloud.langfuse.com\"\n */\n baseUrl?: string;\n\n /**\n * Number of spans to batch before flushing. Can also be set via LANGFUSE_FLUSH_AT environment variable.\n */\n flushAt?: number;\n\n /**\n * Flush interval in seconds. Can also be set via LANGFUSE_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 Langfuse.\n */\n shouldExportSpan?: ShouldExportSpan;\n\n /**\n * Environment identifier for the traces. Can also be set via LANGFUSE_TRACING_ENVIRONMENT environment variable.\n */\n environment?: string;\n\n /**\n * Release identifier for the traces. Can also be set via LANGFUSE_RELEASE environment variable.\n */\n release?: string;\n\n /**\n * Request timeout in seconds. Can also be set via LANGFUSE_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 Langfuse.\n *\n * This processor extends the standard BatchSpanProcessor to provide:\n * - Automatic batching and flushing of spans to Langfuse\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 '@langfuse/otel';\n *\n * const sdk = new NodeSDK({\n * spanProcessors: [\n * new LangfuseSpanProcessor({\n * publicKey: 'pk_...',\n * secretKey: 'sk_...',\n * baseUrl: 'https://cloud.langfuse.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(\"LANGFUSE_PUBLIC_KEY\");\n const secretKey = params?.secretKey ?? getEnv(\"LANGFUSE_SECRET_KEY\");\n const baseUrl =\n params?.baseUrl ??\n getEnv(\"LANGFUSE_BASE_URL\") ??\n getEnv(\"LANGFUSE_BASEURL\") ?? // legacy v2\n \"https://cloud.langfuse.com\";\n\n if (!params?.exporter && !publicKey) {\n logger.warn(\n \"No exporter configured and no public key provided in constructor or as LANGFUSE_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 LANGFUSE_SECRET_KEY env var. Span exports will fail.\",\n );\n }\n const flushAt = params?.flushAt ?? getEnv(\"LANGFUSE_FLUSH_AT\");\n const flushIntervalSeconds =\n params?.flushInterval ?? getEnv(\"LANGFUSE_FLUSH_INTERVAL\");\n\n const authHeaderValue = base64Encode(`${publicKey}:${secretKey}`);\n const timeoutSeconds =\n params?.timeout ?? Number(getEnv(\"LANGFUSE_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: LANGFUSE_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(\"LANGFUSE_TRACING_ENVIRONMENT\");\n this.release = params?.release ?? getEnv(\"LANGFUSE_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: LANGFUSE_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 Langfuse.\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 if (this.logger.isLevelEnabled(LogLevel.DEBUG)) {\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\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 \"@langfuse/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 Langfuse 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 Langfuse 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","../src/span-filter.ts"],"sourcesContent":["import {\n Logger,\n LogLevel,\n getGlobalLogger,\n LangfuseAPIClient,\n LANGFUSE_SDK_VERSION,\n LangfuseOtelSpanAttributes,\n getEnv,\n base64Encode,\n getPropagatedAttributesFromContext,\n} from \"@langfuse/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\";\nimport { isDefaultExportSpan } from \"./span-filter.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 Langfuse.\n * If provided, this is treated as a full override of the default filtering behavior.\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 * Langfuse public API key. Can also be set via LANGFUSE_PUBLIC_KEY environment variable.\n */\n publicKey?: string;\n\n /**\n * Langfuse secret API key. Can also be set via LANGFUSE_SECRET_KEY environment variable.\n */\n secretKey?: string;\n\n /**\n * Langfuse instance base URL. Can also be set via LANGFUSE_BASE_URL environment variable.\n * @defaultValue \"https://cloud.langfuse.com\"\n */\n baseUrl?: string;\n\n /**\n * Number of spans to batch before flushing. Can also be set via LANGFUSE_FLUSH_AT environment variable.\n */\n flushAt?: number;\n\n /**\n * Flush interval in seconds. Can also be set via LANGFUSE_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 Langfuse.\n * If not provided, a smart default filter is applied to export Langfuse spans,\n * spans with `gen_ai.` attributes, and spans from known LLM instrumentors.\n */\n shouldExportSpan?: ShouldExportSpan;\n\n /**\n * Environment identifier for the traces. Can also be set via LANGFUSE_TRACING_ENVIRONMENT environment variable.\n */\n environment?: string;\n\n /**\n * Release identifier for the traces. Can also be set via LANGFUSE_RELEASE environment variable.\n */\n release?: string;\n\n /**\n * Request timeout in seconds. Can also be set via LANGFUSE_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 Langfuse.\n *\n * This processor extends the standard BatchSpanProcessor to provide:\n * - Automatic batching and flushing of spans to Langfuse\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 * (or default smart filtering when no custom filter is provided)\n * - Environment and release tagging\n *\n * @example\n * ```typescript\n * import { NodeSDK } from '@opentelemetry/sdk-node';\n * import { LangfuseSpanProcessor } from '@langfuse/otel';\n *\n * const sdk = new NodeSDK({\n * spanProcessors: [\n * new LangfuseSpanProcessor({\n * publicKey: 'pk_...',\n * secretKey: 'sk_...',\n * baseUrl: 'https://cloud.langfuse.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 * // Full override of default filtering:\n * // export only 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(\"LANGFUSE_PUBLIC_KEY\");\n const secretKey = params?.secretKey ?? getEnv(\"LANGFUSE_SECRET_KEY\");\n const baseUrl =\n params?.baseUrl ??\n getEnv(\"LANGFUSE_BASE_URL\") ??\n getEnv(\"LANGFUSE_BASEURL\") ?? // legacy v2\n \"https://cloud.langfuse.com\";\n\n if (!params?.exporter && !publicKey) {\n logger.warn(\n \"No exporter configured and no public key provided in constructor or as LANGFUSE_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 LANGFUSE_SECRET_KEY env var. Span exports will fail.\",\n );\n }\n const flushAt = params?.flushAt ?? getEnv(\"LANGFUSE_FLUSH_AT\");\n const flushIntervalSeconds =\n params?.flushInterval ?? getEnv(\"LANGFUSE_FLUSH_INTERVAL\");\n\n const authHeaderValue = base64Encode(`${publicKey}:${secretKey}`);\n const timeoutSeconds =\n params?.timeout ?? Number(getEnv(\"LANGFUSE_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\": LANGFUSE_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(\"LANGFUSE_TRACING_ENVIRONMENT\");\n this.release = params?.release ?? getEnv(\"LANGFUSE_RELEASE\");\n this.mask = params?.mask;\n this.shouldExportSpan =\n params?.shouldExportSpan ??\n (({ otelSpan }) => isDefaultExportSpan(otelSpan));\n this.apiClient = new LangfuseAPIClient({\n baseUrl: this.baseUrl,\n username: this.publicKey,\n password: secretKey,\n xLangfusePublicKey: this.publicKey,\n xLangfuseSdkVersion: LANGFUSE_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 Langfuse.\n *\n * This method:\n * 1. Checks if the span should be exported using shouldExportSpan\n * (custom override or default smart filter)\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 try {\n if (this.shouldExportSpan({ otelSpan: span }) === false) {\n this.logger.debug(\"Dropped span due to shouldExportSpan filter.\", {\n spanName: span.name,\n instrumentationScope: span.instrumentationScope.name,\n });\n\n return;\n }\n } catch (err) {\n this.logger.error(\n \"shouldExportSpan failed with error. Dropping span.\",\n {\n spanName: span.name,\n instrumentationScope: span.instrumentationScope.name,\n },\n err,\n );\n\n return;\n }\n\n this.applyMaskInPlace(span);\n await this.mediaService.process(span);\n\n if (this.logger.isLevelEnabled(LogLevel.DEBUG)) {\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\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 \"@langfuse/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 Langfuse 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 Langfuse 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","import { LANGFUSE_TRACER_NAME } from \"@langfuse/core\";\nimport type { ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\n\nexport const KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES = [\n LANGFUSE_TRACER_NAME,\n \"agent_framework\",\n \"ai\",\n \"haystack\",\n \"langsmith\",\n \"litellm\",\n \"openinference\",\n \"opentelemetry.instrumentation.anthropic\",\n \"strands-agents\",\n \"vllm\",\n] as const;\n\nexport function isLangfuseSpan(span: ReadableSpan): boolean {\n return span.instrumentationScope.name === LANGFUSE_TRACER_NAME;\n}\n\nexport function isGenAISpan(span: ReadableSpan): boolean {\n return Object.keys(span.attributes).some((attributeKey) =>\n attributeKey.startsWith(\"gen_ai.\"),\n );\n}\n\nexport function isKnownLLMInstrumentor(span: ReadableSpan): boolean {\n const scope = span.instrumentationScope.name;\n\n return KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES.some(\n (prefix) => scope === prefix || scope.startsWith(`${prefix}.`),\n );\n}\n\nexport function isDefaultExportSpan(span: ReadableSpan): boolean {\n return (\n isLangfuseSpan(span) || isGenAISpan(span) || isKnownLLMInstrumentor(span)\n );\n}\n"],"mappings":"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","names":["getGlobalLogger","LangfuseAPIClient","LangfuseOtelSpanAttributes","getGlobalLogger","LangfuseAPIClient","LangfuseOtelSpanAttributes"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@langfuse/otel",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-beta.0",
|
|
4
4
|
"author": "Langfuse",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"dist"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@langfuse/core": "^
|
|
31
|
+
"@langfuse/core": "^5.0.0-beta.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@opentelemetry/api": "^1.9.0",
|