← Back to Blog Home

Routing OpenTelemetry logs to Sentry using OTLP

Routing OpenTelemetry logs to Sentry using OTLP

If you’ve already instrumented your app with OpenTelemetry, you don’t have to rip it out to use Sentry. Two environment variables and your logs start flowing into Sentry, no SDK changes, no re-instrumentation. Here’s how to set it up in a sample app, and when the native Sentry SDK might be the better call.

Why you’d use OTLP instead of the native SDK

The main advantage of OTLP is that your logging code stays decoupled from any specific observability backend. You can switch where logs go by changing a few config lines. That’s useful if you:

  • Already have OpenTelemetry logging in place
  • Want to send logs to multiple backends
  • Need vendor-neutral instrumentation
  • Work with AI or LLM frameworks that use OpenTelemetry by default
  • Want to use the broader OpenTelemetry ecosystem

If you’re starting from scratch and only need Sentry, the native Sentry SDK is probably the better call. With the native SDK, you get issue creation from logs, session replay integration, automatic breadcrumbs, and built-in error correlation. Ingesting OpenTelemetry traces and logs with Sentry via OTLP endpoints is still in beta and currently lacks these integrated features.

Guide prerequisites

Before we start, you need:

  • A Sentry account (the free tier works)
  • Node.js 18 or later installed
  • Basic familiarity with Express.js

If you don’t have a Sentry project yet, create one now. Select Express as the platform. You can skip the DSN setup instructions because you’ll use the OTLP endpoint instead.

Get your Sentry OTLP credentials

Sentry exposes separate OTLP endpoints for logs and traces. In this guide, we’re focusing on the Logs endpoint. To find your OTLP credentials:

  • Click Settings in the left sidebar.
  • Under the Organization section in the Settings sidebar, click Projects.
  • Find your project in the list and click on it to open the project settings.
  • In the project settings sidebar, click Client Keys (DSN) under the SDK Setup section.
  • Select the OpenTelemetry tab. Click the Expand button to see all OTLP endpoint values.

Keep this tab open. We’ll use the following values in the next step:

  • OTLP Logs Endpoint: The URL where Sentry receives logs (which looks like https://o{ORG_ID}.ingest.us.sentry.io/api/{PROJECT_ID}/integration/otlp/v1/logs)
  • OTLP Logs Endpoint Headers: The authentication header (which looks like x-sentry-auth=sentry sentry_key={YOUR_PUBLIC_KEY})

One thing worth knowing: most OTLP exporters expect headers as raw key/value pairs, not full header strings. You’ll need to parse the header in your app. We’ll handle this in the setup below.

Connect your OpenTelemetry app to Sentry

We’ll use a sample payment processing service that already has OpenTelemetry logging instrumentation. You don’t need to touch the logging code itself. Just point it at Sentry’s OTLP endpoint.

Clone the starter app

Run the following commands to clone the payment processing app:

git clone https://github.com/getsentry/otlp-logging-sentry.git
cd otlp-logging-sentry
npm install

This app includes the OpenTelemetry SDK already configured, structured logging throughout, multiple log severity levels (INFO, DEBUG, WARN, and ERROR), and rich log attributes for every entry.

Configure Sentry as the OTLP destination

Create a .env file in the project root:

cp .env.example .env

Now edit .env and add your Sentry OTLP credentials from the previous step:

OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://o{YOUR_ORG_ID}.ingest.us.sentry.io/api/{YOUR_PROJECT_ID}/integration/otlp/v1/logs
OTEL_EXPORTER_OTLP_LOGS_HEADERS=x-sentry-auth=sentry sentry_key={YOUR_PUBLIC_KEY}
OTEL_SERVICE_NAME=payment-processing-service
PORT=3000

Replace the placeholders with your actual Sentry credentials. The OTEL_SERVICE_NAME will let you filter logs by service in Sentry later.

That’s it. Two config lines and OpenTelemetry logs are flowing to Sentry.

Test the integration

Start the app:

npm start

You should see:

OpenTelemetry logging initialized
Service: payment-processing-service
Payment Processing Service running on http://localhost:3000

Generate some logs

In a new terminal window, send a request to process a payment:

curl -X POST http://localhost:3000/process-payment \
  -H "Content-Type: application/json" \
  -d '{"userId": "user123", "amount": 99.99, "paymentMethod": "credit_card"}'

You’ll get a JSON response confirming the payment:

{
  "success": true,
  "transactionId": "txn_1730123456789_abc123def",
  "amount": 99.99,
  "currency": "USD",
  "status": "completed"
}

View the logs in Sentry

Now let’s see what this looks like in Sentry’s Logs view:

  • Go to your Sentry project.
  • Navigate to Explore in the left sidebar, then click Logs.

You’ll see a list of log entries from your payment processing workflow. Each log shows a timestamp, severity indicator (colored dot), and message.

Explore log attributes

Click on any log entry to expand it and see all its attributes.

For example, the High-risk transaction detected log includes attributes like the following:

  • fraud_check.score: 97.98
  • fraud_check.threshold: 70
  • fraud_check.reason: unusual_amount_pattern
  • user.id: user123
  • transaction.id: txn_1762164637756_0hscczobm
  • severity: warn

All of these are searchable. To add any attribute as a filter, hover over it, click the overflow menu (three dots), and select Add to filter.

How OpenTelemetry logging works

Here’s what’s happening under the hood, in case you’re applying these patterns to your own app.

OpenTelemetry SDK initialization

The instrument.js file configures the OTLP exporter and wires up the logger provider:

import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
import { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { Resource } from '@opentelemetry/resources';
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';

// Configure the OTLP log exporter
const logExporter = new OTLPLogExporter({
  url: process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
  headers: {
    'x-sentry-auth': process.env.OTEL_EXPORTER_OTLP_LOGS_HEADERS?.replace('x-sentry-auth=', '') || '',
  },
});

// Create logger provider
const loggerProvider = new LoggerProvider({
  resource: new Resource({
    [ATTR_SERVICE_NAME]: 'payment-processing-service',
  }),
});

loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));

// Make logger provider available globally
global.loggerProvider = loggerProvider;

These are the key parts:

Emitting structured logs

The index.js file imports instrument.js first, then creates a logger and emits records:

import './instrument.js';
import { logs, SeverityNumber } from '@opentelemetry/api-logs';
const logger = logs.getLogger('payment-processing-service', '1.0.0');

Here’s how we emit a structured log:

function log(severity, severityNumber, message, attributes = {}) {
  logger.emit({
    severityNumber,
    severityText: severity,
    body: message,
    attributes,
  });
}

// Example usage
log('INFO', SeverityNumber.INFO, 'Payment request received', {
  'user.id': userId,
  'payment.amount': amount,
  'payment.method': paymentMethod,
  'transaction.id': transactionId,
});

Each call to logger.emit() takes a severity level, a message body, and a set of attributes. The attributes are what make logs searchable — the more context you add here, the easier it is to find specific events later.

Log severity levels

OpenTelemetry supports six severity levels:

import { SeverityNumber } from '@opentelemetry/api-logs';
// TRACE (most detailed)
log('TRACE', SeverityNumber.TRACE, 'Function entry', {...});
// DEBUG (debugging info)
log('DEBUG', SeverityNumber.DEBUG, 'Validating payment', {...});
// INFO (informational)
log('INFO', SeverityNumber.INFO, 'Payment received', {...});
// WARN (warnings)
log('WARN', SeverityNumber.WARN, 'High-risk transaction', {...});
// ERROR (errors)
log('ERROR', SeverityNumber.ERROR, 'Payment failed', {...});
// FATAL (critical)
log('FATAL', SeverityNumber.FATAL, 'System failure', {...});

Adding rich attributes

The more attributes you add, the easier it is to debug issues. Here’s an example from the fraud detection path:

log('WARN', SeverityNumber.WARN, 'High-risk transaction detected', {
  'user.id': userId,
  'transaction.id': transactionId,
  'fraud_check.score': 85.2,
  'fraud_check.threshold': 70,
  'fraud_check.reason': 'unusual_amount_pattern',
});

All these attributes are searchable in Sentry, so you can find specific transactions quickly without scanning log text.

OTLP vs native Sentry SDK

Both approaches send logs to Sentry. The difference is in what you get automatically.

Setup and configuration

OTLP

// instrument.js
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
import { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
const logExporter = new OTLPLogExporter({
  url: process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
  headers: {
    'x-sentry-auth': process.env.OTEL_EXPORTER_OTLP_LOGS_HEADERS
  },
});
const loggerProvider = new LoggerProvider({...});
loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));

Native Sentry SDK

// instrument.js
import * as Sentry from '@sentry/node';
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  enableLogs: true, // Required for structured logging
});

Note that Sentry.logger requires Sentry JavaScript SDK v9.41.0 or above.

Emitting logs

OTLP

import { logs, SeverityNumber } from '@opentelemetry/api-logs';
const logger = logs.getLogger('my-service', '1.0.0');
logger.emit({
  severityNumber: SeverityNumber.INFO,
  severityText: 'INFO',
  body: 'Payment request received',
  attributes: {
    'user.id': userId,
    'payment.amount': amount,
  },
});

Native Sentry SDK

import * as Sentry from '@sentry/node';
Sentry.logger.info('Payment request received', {
  'user.id': userId,
  'payment.amount': amount,
});

With OpenTelemetry, you specify both severityNumber and severityText manually. The Sentry SDK infers both from the method you call (info(), warn(), and so on). The SDK also associates logs with errors, transactions, and user sessions automatically, without any extra setup.

Log levels

OTLP

import { SeverityNumber } from '@opentelemetry/api-logs';
logger.emit({ severityNumber: SeverityNumber.DEBUG, ... });
logger.emit({ severityNumber: SeverityNumber.INFO, ... });
logger.emit({ severityNumber: SeverityNumber.WARN, ... });
logger.emit({ severityNumber: SeverityNumber.ERROR, ... });

Native Sentry SDK

Sentry.logger.debug('message', {...});
Sentry.logger.info('message', {...});
Sentry.logger.warn('message', {...});
Sentry.logger.error('message', {...});

What’s next

You now have OpenTelemetry logs flowing into Sentry. A few ways to get more value from here:

  • Add context to your logs. The more attributes you add, the easier it is to debug issues. Add user IDs, request IDs, transaction IDs, feature flags, or any relevant business context to every log entry.
  • Use consistent attribute naming. Follow OpenTelemetry Semantic Conventions for standardized attribute names. This keeps your logs consistent and easier to search across services.
  • Set up alerts. Configure Sentry alerts to notify you when certain log patterns appear — ERROR logs exceeding a threshold, or high-risk transactions crossing a fraud score cutoff.
  • Combine logs with traces. If you’re also sending traces to Sentry, you can correlate them with logs to get a complete picture of your application’s behavior.

OTLP logging support is still in open beta. If you run into a limitation not listed here, open an issue on GitHub. That’s the fastest way to get it on our radar.

FAQs

When should I use the native Sentry SDK?

If you're starting from scratch and Sentry is your only observability tool, the native SDK will get you more out of the box — with less configuration. You'll get tighter integration with the rest of Sentry's platform without any extra setup. It's the better choice if you:

  • Are starting afresh without existing instrumentation
  • Only use Sentry for observability
  • Need automatic issue creation from logs
  • Want session replay integration
  • Require automatic correlation with errors, transactions, and user sessions
  • Need searchable array attributes (currently limited in OTLP beta)
  • Want automatic integration with Sentry error tracking and breadcrumbs
When should I use OTLP?

OTLP is a good fit if you're already invested in the OpenTelemetry ecosystem and don't want to rip out your existing instrumentation. It's also the right call if you need flexibility — for example, sending logs to multiple backends or keeping your code vendor-neutral. Specifically, consider OTLP if you:

  • Already have OpenTelemetry logging in your codebase
  • Send logs to multiple observability backends
  • Need vendor-neutral instrumentation
  • Work with AI and LLM frameworks that use OpenTelemetry by default
  • Use the OpenTelemetry Collector for processing logs
Why aren't my logs appearing in Sentry?

Check your credentials first — verify the OTLP endpoint and headers in your .env file match the values from Settings → Client Keys (DSN) → OpenTelemetry (OTLP). If the credentials look right, wait a minute. Logs can take 30–60 seconds to appear after they're sent. If logs still aren't showing up, check your console output when the app starts. You should see:

OpenTelemetry logging initialized
Service: payment-processing-service

If you don't, enable OpenTelemetry debug logging to see whether logs are being exported at all:

// Add to instrument.js
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);

See the OpenTelemetry troubleshooting guide for more debugging options.

Why are some log attributes missing?

OpenTelemetry attributes must be simple types — strings, numbers, or booleans. Complex objects won't serialize correctly through the OTLP protocol. Flatten them instead:

// Won't work
attributes: {
  'user': { id: 123, name: 'Alice' }
}

// Works
attributes: {
  'user.id': 123,
  'user.name': 'Alice'
}

This is an OpenTelemetry requirement, not specific to Sentry. See the OpenTelemetry attribute specification for details.

Can I search or filter by array attributes?

Array attributes are ingested and visible in the log detail view, but you can't search, filter, or aggregate by them yet — this is a current limitation of the OTLP beta. If you need searchable data, use separate attributes or join the array into a comma-separated string:

// Can view, but can't search
attributes: {
  'user.preferences': ['fiction', 'mystery', 'sci-fi'],
}

// Searchable
attributes: {
  'user.preferences': 'fiction,mystery,sci-fi',
}
Why is memory usage high?

The BatchLogRecordProcessor may be batching too many logs. Adjust the batch size in instrument.js:

loggerProvider.addLogRecordProcessor(
  new BatchLogRecordProcessor(logExporter, {
    maxQueueSize: 1000,        // Default: 2048
    maxExportBatchSize: 256,   // Default: 512
    scheduledDelayMillis: 5000 // Default: 1000
  })
);

See the BatchLogRecordProcessor documentation for more on configuring batch processing.

Syntax.fm logo

Listen to the Syntax Podcast

Of course we sponsor a developer podcast. Check it out on your favorite listening platform.

Listen To Syntax