OpenTelemetry
Export Tilebox workflow logs and traces to any OTLP-compatible backend in addition to Tilebox Console.
Tilebox uses OpenTelemetry data models for workflow telemetry. Built-in Tilebox observability works without any OpenTelemetry setup, but you can add OTLP export when you need the same logs and traces in another backend.
Configure OTLP export
Call the configuration functions when the runner process starts, before creating the client or runner.
from tilebox.workflows import Client
from tilebox.workflows.observability.logging import configure_otel_logging
from tilebox.workflows.observability.tracing import configure_otel_tracing
from my_workflow import ProcessScene
configure_otel_tracing(
service="sentinel-2-runner",
endpoint="http://localhost:4318/v1/traces",
headers={"Authorization": "Bearer <token>"},
)
configure_otel_logging(
service="sentinel-2-runner",
endpoint="http://localhost:4318/v1/logs",
headers={"Authorization": "Bearer <token>"},
)
client = Client(name="sentinel-2-runner")
runner = client.runner(tasks=[ProcessScene])
runner.run_forever()package main
import (
"context"
"log/slog"
"github.com/tilebox/tilebox-go/observability"
"github.com/tilebox/tilebox-go/observability/logger"
"github.com/tilebox/tilebox-go/observability/tracer"
"github.com/tilebox/tilebox-go/workflows/v1"
"go.opentelemetry.io/otel"
)
type ProcessScene struct{}
func (t *ProcessScene) Execute(ctx context.Context) error {
slog.InfoContext(ctx, "processing scene")
return nil
}
func main() {
ctx := context.Background()
service := &observability.Service{Name: "sentinel-2-runner"}
traceProvider, shutdownTracer, err := tracer.NewOtelProvider(ctx, service,
tracer.WithEndpointURL("http://localhost:4318/v1/traces"),
tracer.WithHeaders(map[string]string{"Authorization": "Bearer <token>"}),
)
if err != nil {
slog.ErrorContext(ctx, "failed to configure tracing", slog.Any("error", err))
return
}
defer shutdownTracer(ctx)
otel.SetTracerProvider(traceProvider)
logHandler, shutdownLogger, err := logger.NewOtelHandler(ctx, service,
logger.WithEndpointURL("http://localhost:4318/v1/logs"),
logger.WithHeaders(map[string]string{"Authorization": "Bearer <token>"}),
logger.WithLevel(slog.LevelInfo),
)
if err != nil {
slog.ErrorContext(ctx, "failed to configure logging", slog.Any("error", err))
return
}
defer shutdownLogger(ctx)
slog.SetDefault(logger.New(logHandler))
client := workflows.NewClient()
runner, err := client.NewTaskRunner(ctx)
if err != nil {
slog.ErrorContext(ctx, "failed to create runner", slog.Any("error", err))
return
}
if err := runner.RegisterTasks(&ProcessScene{}); err != nil {
slog.ErrorContext(ctx, "failed to register tasks", slog.Any("error", err))
return
}
runner.Run(ctx)
}If the endpoint does not include /v1/traces or /v1/logs, the Python SDK adds the correct path automatically.
Environment variables
You can omit endpoint and interval arguments by setting environment variables:
| Variable | Used by |
|---|---|
OTEL_TRACES_ENDPOINT |
configure_otel_tracing() |
OTEL_LOGS_ENDPOINT |
configure_otel_logging() |
OTEL_EXPORT_INTERVAL |
tracing and logging exporters |
OTEL_EXPORT_INTERVAL accepts durations such as 5s, 30s, or 2m.
Local collector example
For local trace testing, run Jaeger with OTLP HTTP enabled:
docker run --rm --name jaeger \
-p 16686:16686 \
-p 4318:4318 \
jaegertracing/jaeger:2.9.0Then configure tracing with endpoint="http://localhost:4318" and open the Jaeger UI at http://localhost:16686.