Better Stack OpenTelemetry logging

Start logging in 5 minutes

Send logs and metrics to Better Stack using OpenTelemetry collector.

1. Install

Install latest OpenTelemetry collector:

Install OpenTelemetry collector
curl -sSL https://telemetry.betterstack.com/install/otelcol-contrib | sudo -E bash

2. Setup

Download OpenTelemetry configuration for your source:

Download OpenTelemetry config
sudo curl -L https://telemetry.betterstack.com/otel/$SOURCE_TOKEN -o /etc/otelcol-contrib/config.yaml

3. Restart

Restart OpenTelemetry collector for the new configuration to take effect:

Restart
sudo systemctl restart otelcol-contrib

You should see your logs in Better Stack β†’ Live tail.

Check out your metrics in the OpenTelemetry dashboard.

No logs coming in?

Check the status and logs of OpenTelemetry collector:

Check OpenTelemetry collector
systemctl status otelcol-contrib
journalctl -u otelcol-contrib -n 20 -f

Already have OpenTelemetry collector running?

Update your existing collector configuration, and send metrics and logs from your receivers to Better Stack.

You can usually find the configuration in /etc/otelcol-contrib/config.yaml or otelcol-config.yml in your project.

Collector config Legacy collector config
exporters:
  # Add exporter for Better Stack:
  otlphttp/betterstack:
    endpoint: "https://$INGESTING_HOST"
    headers:
      Authorization: "Bearer $SOURCE_TOKEN"

processors:
  batch:

service:
  pipelines:
    # Add extra logs and metrics pipelines for Better Stack:
    metrics/betterstack:
      receivers: [hostmetrics, otlp] # your metrics receivers
      processors: [batch]
      exporters: [otlphttp/betterstack]
    logs/betterstack:
      receivers: [otlp] # your logs receivers
      processors: [batch]
      exporters: [otlphttp/betterstack]
# Use this configuration for sources created before February 2025

exporters:
  # Add exporters for Better Stack:
  otlp/betterstack:
    endpoint: "https://in-otel.logs.betterstack.com:443"
  prometheusremotewrite/betterstack:
    endpoint: "https://in-otel.logs.betterstack.com/metrics"

processors:
  # Add processors for Better Stack:
  batch:
  attributes/betterstack:
    actions:
      - key: better_stack_source_token
        value: $SOURCE_TOKEN
        action: insert

service:
  pipelines:
    # Add extra logs and metrics pipelines for Better Stack:
    metrics/betterstack:
      receivers: [hostmetrics, otlp] # your metrics receivers
      processors: [batch, attributes/betterstack]
      exporters: [prometheusremotewrite/betterstack]
    logs/betterstack:
      receivers: [otlp] # your logs receivers
      processors: [batch, attributes/betterstack]
      exporters: [otlp/betterstack]

Restart the OpenTelemetry collector for the new configuration to take effect.

Sending data directly via language SDKs

You can send traces or other telemetry directly from your application using the OpenTelemetry SDK for your language, without needing to run the OpenTelemetry Collector.

This approach is useful for simpler setups or when you want to instrument your application code directly.

See the official OpenTelemetry docs for setup guides across many supported languages.

OTLP/HTTP exporter configuration
Endpoint for traces:  https://$INGESTING_HOST/v1/traces
Endpoint for metrics: https://$INGESTING_HOST/v1/metrics
Endpoint for logs:    https://$INGESTING_HOST/v1/logs
HTTP headers:         Authorization: Bearer $SOURCE_TOKEN
Compression:          gzip

Python example

Here is an example of how to send telemetry directly from a Python application.

First, install the necessary packages:

Install Python packages
pip install opentelemetry-exporter-otlp-proto-http
Traces Metrics Logs
from opentelemetry.sdk.resources import SERVICE_NAME, Resource

from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.exporter.otlp.proto.http import Compression
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# Service name is required for most backends
resource = Resource.create(attributes={
    SERVICE_NAME: "your-service-name"
})

# Define the processing of traces
tracerProvider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(
    OTLPSpanExporter(
endpoint="https://$INGESTING_HOST/v1/traces",
headers={"Authorization": "Bearer $SOURCE_TOKEN"},
compression=Compression.Gzip,
) ) tracerProvider.add_span_processor(processor) trace.set_tracer_provider(tracerProvider) # Create a tracer from the global tracer provider tracer = trace.get_tracer("my.tracer.name") with tracer.start_as_current_span("parent") as parent: # Do some work that 'parent' tracks print("Doing some work...") # Add custom attributes to any span parent.set_attribute("operation.my_field", 1234) # Track specific custom events parent.add_event("Parent done some work") # Create a nested span to track nested work with tracer.start_as_current_span("child") as child: # Do some work that 'child' tracks print("Doing some nested work...") child.add_event("Child done some work") # the nested span is closed when it's out of scope print("Finishing the work...") parent.add_event("Parent is finished") # This span is also closed when it goes out of scope
from opentelemetry.sdk.resources import SERVICE_NAME, Resource

from opentelemetry import metrics
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
from opentelemetry.exporter.otlp.proto.http import Compression
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader

# Service name is required for most backends
resource = Resource.create(attributes={
    SERVICE_NAME: "your-service-name"
})

# Define the processing of metrics
reader = PeriodicExportingMetricReader(
    OTLPMetricExporter(
endpoint="https://$INGESTING_HOST/v1/metrics",
headers={"Authorization": "Bearer $SOURCE_TOKEN"},
compression=Compression.Gzip,
) ) meterProvider = MeterProvider(resource=resource, metric_readers=[reader]) metrics.set_meter_provider(meterProvider) # Create a meter from the global meter provider meter = metrics.get_meter("my.meter.name") # Define a counter work_counter = meter.create_counter("work.counter", unit="1", description="Counts the amount of work done") # Add to a counter with custom metric tags print("Doing some work on A...") work_counter.add(1, {"work.type": "job A"}) print("Doing some more work on A...") work_counter.add(1, {"work.type": "job A"}) print("Doing some work on B...") work_counter.add(1, {"work.type": "job B"})
import logging

from opentelemetry.sdk.resources import SERVICE_NAME, Resource

from opentelemetry._logs import set_logger_provider
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
from opentelemetry.exporter.otlp.proto.http import Compression
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor

# Service name is required for most backends
resource = Resource.create(attributes={
    SERVICE_NAME: "your-service-name"
})

# Define the processing of logs
logger_provider = LoggerProvider(resource=resource)
set_logger_provider(logger_provider)

exporter = OTLPLogExporter(
endpoint="https://$INGESTING_HOST/v1/logs",
headers={"Authorization": "Bearer $SOURCE_TOKEN"},
compression=Compression.Gzip,
) logger_provider.add_log_record_processor(BatchLogRecordProcessor(exporter)) # Attach OTLP handler to root logger handler = LoggingHandler(level=logging.INFO, logger_provider=logger_provider) logging.getLogger().addHandler(handler) # Log some messages logging.info("Hello from OpenTelemetry Logs!") logging.warning("This is a warning message.") logging.error("This is an error message with some context.", extra={"some_key": "some_value"})

Need help?

Please let us know at hello@betterstack.com.
We're happy to help! πŸ™

Step-by-step video guide