Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable next #39

Merged
merged 5 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lmnr-ai/lmnr",
"version": "0.4.27",
"version": "0.4.28",
"description": "TypeScript SDK for Laminar AI",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
11 changes: 11 additions & 0 deletions src/sdk/interfaces/initialize-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,15 @@ export interface InitializeOptions {
* This is useful for advanced use cases where the user wants to manage the tracer provider themselves.
*/
useExternalTracerProvider?: boolean;

/**
* Whether to preserve Next.js spans. Optional.
* Defaults to false.
* Next.js instrumentation is very verbose and can result in
* a lot of noise in the traces. By default, Laminar
* will ignore the Next.js spans (looking at the attributes like `next.span_name`)
* and set the topmost non-Next span as the root span in the trace.
* This option allows to preserve the Next.js spans.
*/
preserveNextJsSpans?: boolean;
}
89 changes: 85 additions & 4 deletions src/sdk/tracing/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import { baggageUtils } from "@opentelemetry/core";
import { ProxyTracerProvider, Span, TracerProvider, context, diag, trace } from "@opentelemetry/api";
import {
Context,
ProxyTracerProvider,
TracerProvider,
context,
diag,
trace,
} from "@opentelemetry/api";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { Instrumentation } from "@opentelemetry/instrumentation";
import { Span } from "@opentelemetry/sdk-trace-base";
import { InitializeOptions } from "../interfaces";
import {
ASSOCIATION_PROPERTIES_KEY,
SPAN_PATH_KEY,
} from "./tracing";
import { _configuration } from "../configuration";
import { NodeTracerProvider, SimpleSpanProcessor, BatchSpanProcessor, BasicTracerProvider, SpanProcessor } from "@opentelemetry/sdk-trace-node";
import {
NodeTracerProvider,
SimpleSpanProcessor,
BatchSpanProcessor,
BasicTracerProvider,
SpanProcessor,
ReadableSpan,
} from "@opentelemetry/sdk-trace-node";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { AnthropicInstrumentation } from "@traceloop/instrumentation-anthropic";
import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai";
Expand All @@ -25,7 +40,13 @@ import { PineconeInstrumentation } from "@traceloop/instrumentation-pinecone";
import { LangChainInstrumentation } from "@traceloop/instrumentation-langchain";
import { ChromaDBInstrumentation } from "@traceloop/instrumentation-chromadb";
import { QdrantInstrumentation } from "@traceloop/instrumentation-qdrant";
import { ASSOCIATION_PROPERTIES, ASSOCIATION_PROPERTIES_OVERRIDES, SPAN_INSTRUMENTATION_SOURCE, SPAN_PATH } from "./attributes";
import {
ASSOCIATION_PROPERTIES,
ASSOCIATION_PROPERTIES_OVERRIDES,
OVERRIDE_PARENT_SPAN,
SPAN_INSTRUMENTATION_SOURCE,
SPAN_PATH,
} from "./attributes";

let _spanProcessor: SimpleSpanProcessor | BatchSpanProcessor | SpanProcessor;
let openAIInstrumentation: OpenAIInstrumentation | undefined;
Expand All @@ -41,6 +62,7 @@ let pineconeInstrumentation: PineconeInstrumentation | undefined;
let chromadbInstrumentation: ChromaDBInstrumentation | undefined;
let qdrantInstrumentation: QdrantInstrumentation | undefined;

const NUM_TRACKED_NEXT_SPANS = 10000;

const instrumentations: Instrumentation[] = [];

Expand Down Expand Up @@ -173,6 +195,18 @@ const manuallyInitInstrumentations = (
}
};

// Next.js instrumentation is very verbose and can result in
// a lot of noise in the traces. By default, Laminar
// will ignore the Next.js spans (looking at the attributes like `next.span_name`)
// and set the topmost non-Next span as the root span in the trace.
// Here's the set of possible attributes as of Next.js 15.1
// See also: https://github.com/vercel/next.js/blob/790efc5941e41c32bb50cd915121209040ea432c/packages/next/src/server/lib/trace/tracer.ts#L297
const isNextSpan = (span: ReadableSpan) => {
dinmukhamedm marked this conversation as resolved.
Show resolved Hide resolved
return span.attributes['next.span_name'] !== undefined
|| span.attributes['next.span_type'] !== undefined
|| span.attributes['next.clientComponentLoadCount'] !== undefined;
}

/**
* Initializes the Traceloop SDK.
* Must be called once before any other SDK methods.
Expand Down Expand Up @@ -230,7 +264,22 @@ export const startTracing = (options: InitializeOptions) => {
? new SimpleSpanProcessor(traceExporter)
: new BatchSpanProcessor(traceExporter));

_spanProcessor.onStart = (span: Span) => {
const nextSpanIds = new Set<string>();
// In the program runtime, the set may become very large, so every now and then
// we remove half of the elements from the set to keep it from growing too much.
// We use the fact that JS Set preserves insertion order to remove the oldest elements.
const addNextSpanId = (spanId: string) => {
if (nextSpanIds.size >= NUM_TRACKED_NEXT_SPANS) {
const toRemove = Array.from(nextSpanIds).slice(0, NUM_TRACKED_NEXT_SPANS / 2);
toRemove.forEach(id => nextSpanIds.delete(id));
}
nextSpanIds.add(spanId);
};

const originalOnEnd = _spanProcessor.onEnd.bind(_spanProcessor);
const originalOnStart = _spanProcessor.onStart.bind(_spanProcessor);

_spanProcessor.onStart = (span: Span, parentContext: Context) => {
const spanPath = context.active().getValue(SPAN_PATH_KEY);
if (spanPath) {
span.setAttribute(SPAN_PATH, spanPath as string);
Expand All @@ -253,8 +302,39 @@ export const startTracing = (options: InitializeOptions) => {
}
}
}
// OVERRIDE_PARENT_SPAN makes the current span the root span in the trace.
// The backend looks for this attribute and deletes the parentSpanId if the
// attribute is present. We do that to make the topmost non-Next.js span
// the root span in the trace.
if (span.parentSpanId
&& nextSpanIds.has(span.parentSpanId)
&& !options.preserveNextJsSpans
) {
span.setAttribute(OVERRIDE_PARENT_SPAN, true);
}
originalOnStart(span, parentContext);
// If we know by this time that this span is created by Next.js,
// we add it to the set of nextSpanIds.
if (isNextSpan(span) && !options.preserveNextJsSpans) {
addNextSpanId(span.spanContext().spanId);
}
};

_spanProcessor.onEnd = (span: ReadableSpan) => {
// Sometimes we only have know that this span is a Next.js only at the end.
// We add it to the set of nextSpanIds in that case. Also, we do not call
// the original onEnd, so that we don't send it to the backend.
if ((isNextSpan(span) || nextSpanIds.has(span.spanContext().spanId)) && !options.preserveNextJsSpans) {
addNextSpanId(span.spanContext().spanId);
} else {
// By default, we call the original onEnd.
originalOnEnd(span);
dinmukhamedm marked this conversation as resolved.
Show resolved Hide resolved
}
};

// This is an experimental workaround for the issue where Laminar fails
// to work when there is another tracer provider already initialized.
// See: https://github.com/lmnr-ai/lmnr/issues/231
if (options.useExternalTracerProvider) {
const globalProvider = trace.getTracerProvider();
let provider: TracerProvider;
Expand All @@ -269,6 +349,7 @@ export const startTracing = (options: InitializeOptions) => {
throw new Error("The active tracer provider does not support adding a span processor");
}
} else {
// Default behavior, if no external tracer provider is used.
const provider = new NodeTracerProvider({
spanProcessors: [_spanProcessor],
});
Expand Down
Loading