Sentry SDK

You can collect errors by integrating your Sentry SDK with Better Stack. This allows you to leverage your existing Sentry setup to send errors to our platform for centralized management and analysis.

Using Claude Code or similar agentic dev-tool?

The easiest way to integate is to use the Integrate with AI prompt.

To configure Sentry SDK integration:

  1. Navigate to Errors -> Applications.
  2. Select your application.
  3. Go to the Data ingestion tab.
  4. Copy Application token and Ingesting host to be used in Sentry SDK.

Using the Sentry SDK

To send errors to Better Stack, configure your Sentry SDK according to the Data ingestion tab for your application:

Sentry DSN configuration
https://$APPLICATION_TOKEN@$INGESTING_HOST/$APPLICATION_ID


Here are a few examples for popular Sentry SDKs:

Python Node.js Browser (JS) Ruby PHP Go
import sentry_sdk

sentry_sdk.init(
    dsn="https://$APPLICATION_TOKEN@$INGESTING_HOST/$APPLICATION_ID",
    # Set traces_sample_rate to 1.0 to capture 100%
    # of transactions for performance monitoring.
    traces_sample_rate=1.0,
)

# Example of capturing an exception
try:
    1 / 0
except ZeroDivisionError as e:
    sentry_sdk.capture_exception(e)
const Sentry = require("@sentry/node");
// Or, for ES6+
// import * as Sentry from "@sentry/node";

Sentry.init({
  dsn: "https://$APPLICATION_TOKEN@$INGESTING_HOST/$APPLICATION_ID",
  // Set tracesSampleRate to 1.0 to capture 100%
  // of transactions for performance monitoring.
  tracesSampleRate: 1.0,
});

// Example of capturing an exception
try {
  throw new Error("This is a test error from Node.js!");
} catch (e) {
  Sentry.captureException(e);
}
import * as Sentry from "@sentry/browser";

Sentry.init({
  dsn: "https://$APPLICATION_TOKEN@$INGESTING_HOST/$APPLICATION_ID",
  // To capture performance monitoring data,
  // you must set tracesSampleRate
  tracesSampleRate: 1.0,
});

// Example of capturing an exception
try {
  throw new Error("This is a test error from the browser!");
} catch (e) {
  Sentry.captureException(e);
}
require 'sentry-ruby'

Sentry.init do |config|
  config.dsn = 'https://$APPLICATION_TOKEN@$INGESTING_HOST/$APPLICATION_ID'
  config.breadcrumbs_logger = [:active_support_logger, :http_logger]

  # To activate performance monitoring, set one of these options.
  # We recommend adjusting the value in production:
  config.traces_sample_rate = 1.0
end

# Example of capturing an exception
begin
  1 / 0
rescue ZeroDivisionError => exception
  Sentry.capture_exception(exception)
end
use function Sentry\init;
use function Sentry\captureException;

// Assumes you have the Sentry SDK installed via Composer
require 'vendor/autoload.php';

init(['dsn' => 'https://$APPLICATION_TOKEN@$INGESTING_HOST/$APPLICATION_ID' ]);

// Example of capturing an exception
try {
    throw new \Exception("This is a test error from PHP!");
} catch (\Exception $e) {
    captureException($e);
}
package main

import (
	"errors"
	"log"
	"time"

	"github.com/getsentry/sentry-go"
)

func main() {
	err := sentry.Init(sentry.ClientOptions{
		Dsn: "https://$APPLICATION_TOKEN@$INGESTING_HOST/$APPLICATION_ID",
		// Set TracesSampleRate to 1.0 to capture 100%
		// of transactions for performance monitoring.
		TracesSampleRate: 1.0,
	})
	if err != nil {
		log.Fatalf("sentry.Init: %s", err)
	}

	// Flush buffered events before the program terminates.
	defer sentry.Flush(2 * time.Second)

	// Example of capturing an exception
	sentry.CaptureException(errors.New("This is a test error from Go!"))
}


Flutter and Dart release builds

Event ingestion works out of the box for Flutter and Dart. How readable your stack traces are depends on how you build:

  • Debug and non-obfuscated builds send fully readable stack traces, so there's nothing to upload.
  • Obfuscated release builds (built with --obfuscate --split-debug-info) and native iOS and Android crashes need debug symbols (iOS dSYMs, Android ProGuard/R8 mappings, and Dart symbol files) to be symbolicated. Better Stack does not support debug symbol upload yet, so frames in these builds won't be symbolicated.

By default, errors from the Sentry SDK are not correlated with OpenTelemetry (OTel) traces because they use different IDs.

To link them in Better Stack, inject the OTel trace_id and span_id into the Sentry event context using the before_send callback. This provides a unified view of your telemetry data.

Here’s how to implement this for various languages:

Python JS Ruby PHP Go Java
import sentry_sdk
from opentelemetry import trace

def before_send(event, hint):
    span = trace.get_current_span()
    if span and span.get_span_context().is_valid:
        span_context = span.get_span_context()
        event.setdefault('contexts', {}).setdefault('trace', {
            'trace_id': trace.format_trace_id(span_context.trace_id),
            'span_id': trace.format_span_id(span_context.span_id),
        })
    return event

sentry_sdk.init(
    dsn="https://$APPLICATION_TOKEN@$INGESTING_HOST/$APPLICATION_ID",
    before_send=before_send,
    # ... other config
)
import * as Sentry from "@sentry/node"; // or @sentry/browser
import { trace } from "@opentelemetry/api";

Sentry.init({
  dsn: "https://$APPLICATION_TOKEN@$INGESTING_HOST/$APPLICATION_ID",
  beforeSend(event, hint) {
    const span = trace.getActiveSpan();
    if (span) {
      const spanContext = span.spanContext();
      event.contexts = {
        ...event.contexts,
        trace: {
          trace_id: spanContext.traceId,
          span_id: spanContext.spanId,
        },
      };
    }
    return event;
  },
  // ... other config
});
# In your sentry initializer (e.g., config/initializers/sentry.rb)
require 'sentry-ruby'
require 'opentelemetry/api'

Sentry.init do |config|
  config.dsn = 'https://$APPLICATION_TOKEN@$INGESTING_HOST/$APPLICATION_ID'
  # ... other config

  config.before_send = lambda do |event, _hint|
    span = OpenTelemetry::Trace.current_span
    if span.context.valid?
      trace_id = span.context.hex_trace_id
      span_id = span.context.hex_span_id
      event.contexts[:trace] = { trace_id: trace_id, span_id: span_id }
    end
    event
  end
end
use Sentry\Event;
use OpenTelemetry\API\Trace\Span;

\Sentry\init([
    'dsn' => 'https://$APPLICATION_TOKEN@$INGESTING_HOST/$APPLICATION_ID',
    // ... other config
    'before_send' => function (Event $event): ?Event {
        $span = Span::getCurrent();
        $context = $span->getContext();

        if ($context->isValid()) {
            $event->setContext('trace', [
                'trace_id' => $context->getTraceId(),
                'span_id' => $context->getSpanId(),
            ]);
        }

        return $event;
    },
]);
import (
	"context"
	"github.com/getsentry/sentry-go"
	"go.opentelemetry.io/otel/trace"
)

// In your Sentry initialization
sentry.Init(sentry.ClientOptions{
	Dsn: "https://$APPLICATION_TOKEN@$INGESTING_HOST/$APPLICATION_ID",
	BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
		// hint.Context must be provided when capturing the event
		if hint != nil && hint.Context != nil {
			span := trace.SpanFromContext(hint.Context)
			if span.SpanContext().IsValid() {
				sCtx := span.SpanContext()
				if event.Contexts == nil {
					event.Contexts = make(map[string]interface{})
				}
				event.Contexts["trace"] = map[string]string{
					"trace_id": sCtx.TraceID().String(),
					"span_id":  sCtx.SpanID().String(),
				}
			}
		}
		return event
	},
	// ... other config
})

// When capturing an error, you must pass the context.
// For example: sentry.CaptureException(err, &sentry.Hint{Context: ctx})
import io.sentry.Sentry;
import io.sentry.Hint;
import io.sentry.SentryEvent;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import java.util.Map;

Sentry.init(options -> {
    options.setDsn("https://$APPLICATION_TOKEN@$INGESTING_HOST/$APPLICATION_ID");
    // ... other config

    options.setBeforeSend((event, hint) -> {
        Span span = Span.current();
        if (span.getSpanContext().isValid()) {
            SpanContext spanContext = span.getSpanContext();
            event.getContexts().put("trace", Map.of(
                "trace_id", spanContext.getTraceId(),
                "span_id", spanContext.getSpanId()
            ));
        }
        return event;
    });
});


Using a different platform?

Supported Sentry SDK versions

The Sentry SDK versions listed below are the minimum supported. If you're using an older version, please upgrade before sending data to Better Stack.

Platform Minimum Version
JavaScript 7.0.0
Python 2.0.0
Ruby 4.0.0
Java/Android 3.0.0
Cocoa (iOS/macOS) 6.0.0
.NET 3.0.0
PHP 4.0.0
Go 0.1.0
React Native 3.0.0
Dart/Flutter 8.0.0

See the error in Better Stack

After running the script, you should see your first report in Errors 🚀

First error in Better stack