Skip to main content
Version: 4.7.48

Setup with OpenTelemetry

🛑Alpha

OpenTelemetry support is currently in alpha and may undergo significant changes.

This guide shows how to integrate Gentrace with existing OpenTelemetry pipelines.

Prerequisites​

  • An existing OpenTelemetry deployment in your application
  • A Gentrace API key (available from your Gentrace dashboard)

Core concepts and architecture​

OpenTelemetry is an observability framework for instrumenting, generating, collecting, and exporting telemetry data, including spans, metrics, and logs. OpenTelemetry is built around three core components:

  • Instrumentation: Language SDKs and API calls that create and manage spans (units of work in your application), either through automated or manual instrumentations.
  • Collection and processing: The OpenTelemetry Collector (or in-process processors like Sampler) batches, enriches, and filters spans.
  • Exporters: Modules that send spans to one or more backends via protocols like OTLP/HTTP.

Gentrace supports span ingestion in accordance with the OpenTelemetry generative AI specification. Spans can be annotated with attributes (key/value metadata) and events (time-stamped messages). Context propagation (via Baggage) links spans across services. Because the OTEL collector supports multiple exporters, you can fan out identical traces to Gentrace alongside other systems by simply adding a Gentrace OTLP exporter.

To connect your OpenTelemetry pipeline to Gentrace, you'll need to:

  1. Configure an OTLP/HTTP exporter to send data to Gentrace's endpoint
  2. Add semantic conventions for LLM interactions either through automated or manual instrumentations
  3. Use Gentrace-specific attributes to enable advanced features

OTEL architecture diagram

Configuring your OpenTelemetry SDK​

Before sending spans to Gentrace, you need to configure your OpenTelemetry SDK with the NodeSDK and an OTLP exporter. For example:

ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { BaggageSpanProcessor } from '@opentelemetry/baggage-span-processor';
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
const traceExporter = new OTLPTraceExporter({
url: 'https://gentrace.ai/api/otel/v1/traces',
headers: {
Authorization: `Bearer ${GENTRACE_API_KEY}`,
},
});
const sdk = new NodeSDK({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'your-service-name',
}),
traceExporter,
spanProcessors: [
new BaggageSpanProcessor((entry) => entry === 'gentrace.sample'),
new SimpleSpanProcessor(traceExporter),
],
});
sdk.start();

The NodeSDK setup above is key for Gentrace:

  • It configures the OTLP Exporter to send traces to Gentrace.
  • It includes the BaggageSpanProcessor, which is vital for selecting traces:
    • This processor looks for an OpenTelemetry Baggage item named gentrace.sample.
    • If gentrace.sample is "true", the processor adds a gentrace.sample="true" attribute to the span.
    • This attribute signals to Gentrace that the span should be selected for processing.

For more details on gentrace.sample and other attributes, see the Traces API Reference.

The next sections detail how gentrace.sample is set and how spans are sent.

Automatic baggage handling with Gentrace SDK helpers​

The Gentrace SDK aims to simplify common tracing scenarios. When you use the built-in helper functions like interaction() and traced(), they automatically manage the OpenTelemetry Baggage for you. Specifically, they ensure the gentrace.sample baggage item is set to "true" for the operations they trace.

Here's a conceptual illustration using interaction():

typescript
import OpenAI from 'openai';
import { interaction } from 'gentrace';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
async function getOpenAIChatCompletion(prompt: string) {
// ✅ At this point, the active span has the "gentrace.sample" attribute set
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: prompt }],
});
return response.choices[0].message.content;
}
const tracedOpenAICall = interaction(
'<pipeline uuid>',
getOpenAIChatCompletion
);
async function main() {
// The traced invocation sets the baggage attribute on the span
const result = await tracedOpenAICall("What is the capital of France?");
console.log("AI Response:", result);
}

Because these SDK helpers handle this for you, if you are primarily using them for your Gentrace-related tracing, you generally do not need to manually manipulate OpenTelemetry Baggage for the gentrace.sample item for those specific operations.

Manual span decoration​

If you're not using the Gentrace SDK helpers (interaction, test, etc.) you can still send rich LLM traces by adding these attributes and events to your spans:

ts
span.setAttribute('gentrace.pipeline_id', '<pipeline-id>'); // Associate with a Gentrace Pipeline
span.addEvent('gentrace.fn.args', { args: JSON.stringify([inputs]) }); // Capture function inputs
span.addEvent('gentrace.fn.output', { output: JSON.stringify(result) }); // Capture function outputs

For semantic LLM context, we recommend emitting OTEL Gen AI events/attributes as described in the Traces API reference.

For spans to be considered by Gentrace sampling mechanisms (either in-process or via a collector), they need to have the gentrace.sample="true" attribute. As highlighted in the "Automatic baggage handling" section, Gentrace SDK helper functions like interaction() and traced() manage this for the spans they create.

If you are manually instrumenting other parts of your code (i.e., not using Gentrace SDK helpers for those specific operations) and need those spans to be selected by Gentrace, you are responsible for ensuring the gentrace.sample="true" attribute is present on those spans. The recommended way to achieve this for consistent propagation across any child spans is to set a gentrace.sample item with the value "true" in OpenTelemetry Baggage. The BaggageSpanProcessor (configured in your NodeSDK setup, as shown earlier) will then automatically detect this baggage item and convert it into the required gentrace.sample="true" span attribute for you.

Sending spans to Gentrace​

You have two main methods to control which spans get sent to Gentrace:

  1. In-Process Sampling using a custom OTEL Sampler in your application.
  2. Collector-Based Filtering using the OpenTelemetry Collector to filter spans.

1. In-process sampling with a custom sampler​

Endpoint Configuration: Ensure your OTLP exporter is pointed at Gentrace's OTEL API (https://gentrace.ai/api/otel/v1/traces) or to your Collector's OTLP endpoint (e.g. http://collector.example.com:4318).

To implement in-process sampling, you can use a custom sampler. The Gentrace SDK exports a GentraceSampler that you can use directly:

typescript
import { GentraceSampler } from 'gentrace';
import { NodeSDK } from '@opentelemetry/sdk-node';
new NodeSDK({
sampler: new GentraceSampler(),
// ... other NodeSDK options (resource, exporters, etc.)
}).start();

This GentraceSampler is designed to check for the gentrace.sample attribute (propagated via Baggage by the BaggageSpanProcessor or Gentrace SDK helpers) and sample accordingly.

If you do not have the gentrace package installed, or if you prefer to implement custom logic, you can create your own sampler by implementing the Sampler interface from @opentelemetry/sdk-trace-base. You would typically check for the gentrace.sample attribute within your custom shouldSample method. Refer to the official OpenTelemetry documentation on Samplers for more details on custom sampler implementation. For reference, a simplified example of such a custom sampler is shown below:

2. Collector-based filtering​

Endpoint Configuration: Your application should send spans to your Collector's OTLP receiver (e.g. http://collector.example.com:4318), and the Collector will export only filtered spans to Gentrace. In the Collector config below, otlphttp/gentrace still points at Gentrace's OTEL API.

Alternatively, if you're using the OpenTelemetry Collector, you can add Gentrace as an exporter and configure filtering directly in the collector. This approach is useful if you want to manage sampling and routing logic outside your application code.

First, add Gentrace as an OTLP/HTTP exporter in your collector configuration file (e.g., otel-collector-config.yaml):

yaml
exporters:
otlphttp/gentrace:
endpoint: https://gentrace.ai/api/otel/v1/traces
headers:
Authorization: "Bearer ${env:GENTRACE_API_KEY}"

Make sure to replace GENTRACE_API_KEY with your actual API key.

Then, define a pipeline that uses this exporter. For a simple setup sending all received traces to Gentrace:

yaml
service:
pipelines:
traces: # Default traces pipeline
receivers: [otlp] # Assumes an OTLP receiver is configured
processors: [batch] # Batch processor is recommended for production
exporters: [otlphttp/gentrace]

Filtering only Gentrace spans with the Collector​

If you are already sending traces to another backend (e.g., Datadog, Jaeger) and want to send only certain traces to Gentrace, you can use a filter processor. The filter/gentrace_sample processor below keeps only spans where the attribute gentrace.sample equals "true".

yaml
processors:
filter/gentrace_sample:
error_mode: ignore
traces:
span:
# For filter processor, it's often inverted to drop:
# Drop spans that DO NOT have gentrace.sample = "true"
- 'attributes["gentrace.sample"] != "true"'
service:
pipelines:
traces/other_backend: # Pipeline for your existing backend
receivers: [otlp]
processors: [batch]
exporters: [otlphttp/other_backend] # Replace with your other exporter, e.g., datadog
traces/gentrace: # Dedicated pipeline for Gentrace
receivers: [otlp]
processors: [batch, filter/gentrace_sample]
exporters: [otlphttp/gentrace]

In this configuration:

  • Spans are received via OTLP.
  • One pipeline (traces/other_backend) sends all traces to your primary observability backend.
  • Another pipeline (traces/gentrace) uses the filter/gentrace_sample processor. This processor will drop spans unless the gentrace.sample attribute is present and set to "true".
  • The Gentrace SDK automatically sets the gentrace.sample attribute (via Baggage, then converted by BaggageSpanProcessor) on spans created by its helper functions (interaction, test, etc.).
  • If you are not using the Gentrace SDK's helpers and are manually instrumenting your OpenTelemetry client, you'll need to ensure the gentrace.sample attribute is set to "true" on the spans you intend for Gentrace.

Note: Regardless of which method you choose, you must propagate the gentrace.sample baggage entry into your spans so that sampling and filtering work correctly. The BaggageSpanProcessor from the above code example is critical.

ts
import { BaggageSpanProcessor } from '@opentelemetry/baggage-span-processor';
new NodeSDK({
// ... existing NodeSDK options (resource, exporters, sampler, etc.)
spanProcessors: [
new BaggageSpanProcessor((entry) => entry === 'gentrace.sample'),
// ... other processors
],
}).start();

Next steps​