Orbtrace

Instrument your apps

Send logs, traces, and metrics from your real services into Orbtrace. Copy-paste blocks for Java, .NET, Go, Node, Python, PHP, Ruby, Rust, the raw-JSON-over-HTTP path, plus databases, caches, and message queues.

This page is the bridge between "Orbtrace is installed" and "I can see my own service in the UI". Read it after Installation and First login.

The mental model

Your application uses an OpenTelemetry SDK (or its auto-instrumentation agent) to emit telemetry. The SDK sends it over the network to the OpenTelemetry Collector you run alongside Orbtrace (not part of Orbtrace itself). The Collector writes it into Orbtrace's storage. Orbtrace shows it to you in the UI.

Your service

checkout-apiOTel SDK / agent
orders-workerOTel SDK / agent
web-frontendOTel SDK / agent

logs · traces · metrics

OTLP4317 · 4318

OpenTelemetry Collector

Runs alongside Orbtrace — not part of it. You point your app at it.

ReceiversAccept OTLP — gRPC on 4317, HTTP on 4318.
ProcessorsEnrich, batch, and tail-sample; queue to disk on failure.
ExportersWrite the batches into Doris via Stream Load.
Stream Load

Orbtrace

Apache DorisTelemetry store · FE on :8030.
reads
Orbtrace server + UIReads Doris and serves the UI + API on :8080.

Every hop retries: the SDK batches and resends, the Collector queues to disk, and Doris acknowledges each write — a restart anywhere along the way loses nothing. Orbtrace never sits in the ingest path; it only reads Doris and renders.

The data flow in one sentence: the SDK batches spans, logs and metrics and ships them over OTLP to the Collector; the Collector enriches, batches and (for traces) tail-samples, then writes to Doris via Stream Load; Orbtrace never sits in the ingest path — it reads Doris and renders.

You change one config block in your app: the place to send data to — your Collector's host and OTLP port. Then your service appears in Orbtrace within seconds.

Where should that Collector live — on the Orbtrace host, as a Kubernetes DaemonSet, next to a pipeline you already run? That architecture decision, with recommended layouts per platform (including Kubernetes, OpenShift and service meshes), is the Integration patterns page. This page assumes a Collector endpoint exists and covers the per-language SDK setup.

On screen
  • Endpoint (gRPC)http://<collector-host>:4317 — fastest, recommended for backend services. This is your Collector's address, not the Orbtrace server's.
  • Endpoint (HTTP)http://<collector-host>:4318 — universally accessible, recommended when gRPC is blocked.
  • HeadersIf your Collector requires authentication (e.g. a bearer-token auth extension you configured on it), pass it as Authorization: Bearer <token> on every request. This is a Collector setting, not an Orbtrace one.
  • Resource attributesSet service.name and service.version on every signal. Orbtrace groups everything by these two.

Java / Spring Boot

The easiest path is the OpenTelemetry Java agent. It hooks into your JVM at startup and auto-instruments Spring, Hibernate, Kafka, JDBC, gRPC, and 80+ other libraries without code changes.

  1. 1

    Download the agent jar

    curl -L -o opentelemetry-javaagent.jar \
      https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar
  2. 2

    Set environment variables

    export OTEL_SERVICE_NAME=checkout-api
    export OTEL_RESOURCE_ATTRIBUTES=service.version=1.4.2,deployment.environment=production
    export OTEL_EXPORTER_OTLP_ENDPOINT=http://orbtrace.internal:4317
    export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
    # Optional, only if you set OTLP_AUTH_TOKEN in .env:
    export OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer\ <token>
  3. 3

    Add the agent to your start command

    java -javaagent:./opentelemetry-javaagent.jar -jar app.jar

    No code change. Restart the app. Done.

.NET

dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
builder.Services.AddOpenTelemetry()
  .ConfigureResource(r => r.AddService("billing-api"))
  .WithTracing(t => t
    .AddAspNetCoreInstrumentation()
    .AddHttpClientInstrumentation()
    .AddOtlpExporter(o => o.Endpoint = new Uri("http://orbtrace.internal:4317")));

Go

Go does not have a runtime agent — you import the SDK and instrument your code.

go get go.opentelemetry.io/otel \
       go.opentelemetry.io/otel/sdk \
       go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
import (
    "context"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)
 
func initTracer(ctx context.Context) (*sdktrace.TracerProvider, error) {
    exp, err := otlptracegrpc.New(ctx,
        otlptracegrpc.WithEndpoint("orbtrace.internal:4317"),
        otlptracegrpc.WithInsecure(),
    )
    if err != nil { return nil, err }
    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exp),
        sdktrace.WithResource(resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceName("inventory-service"),
        )),
    )
    otel.SetTracerProvider(tp)
    return tp, nil
}

Use github.com/go-chi/chi/middleware/otelchi or your HTTP framework's OTel middleware to get spans on every request automatically.

Node.js

npm install @opentelemetry/api @opentelemetry/sdk-node \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/exporter-trace-otlp-grpc
// otel.js — require this file before anything else
const { NodeSDK } = require("@opentelemetry/sdk-node");
const { getNodeAutoInstrumentations } = require("@opentelemetry/auto-instrumentations-node");
const { OTLPTraceExporter } = require("@opentelemetry/exporter-trace-otlp-grpc");
 
const sdk = new NodeSDK({
  serviceName: "orders-api",
  traceExporter: new OTLPTraceExporter({
    url: "http://orbtrace.internal:4317",
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();

Run with node --require ./otel.js index.js.

Python

pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install   # installs instrumentation packages for libs you use
export OTEL_SERVICE_NAME=payments-worker
export OTEL_EXPORTER_OTLP_ENDPOINT=http://orbtrace.internal:4317
opentelemetry-instrument python worker.py

PHP

PHP auto-instruments through a C extension plus per-framework packages (Laravel, Symfony, Slim, WordPress and more). It exports over OTLP/HTTP on :4318 — PHP has no built-in gRPC, so HTTP is the recommended path and needs no extra extension.

pecl install opentelemetry        # the auto-instrumentation extension
composer require \
  open-telemetry/sdk \
  open-telemetry/exporter-otlp \
  open-telemetry/opentelemetry-auto-laravel   # or -symfony, -slim, -psr15, …
export OTEL_PHP_AUTOLOAD_ENABLED=true
export OTEL_SERVICE_NAME=storefront
export OTEL_EXPORTER_OTLP_ENDPOINT=http://orbtrace.internal:4318
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf

No code change — the extension hooks the framework at request start. (FPM/CLI both work; for short CLI scripts add OTEL_PHP_TRACES_PROCESSOR=simple so spans flush before the process exits.)

Ruby

bundle add opentelemetry-sdk opentelemetry-exporter-otlp opentelemetry-instrumentation-all
# config/initializers/opentelemetry.rb — required before your app loads
require "opentelemetry/sdk"
require "opentelemetry/instrumentation/all"
require "opentelemetry/exporter/otlp"
 
OpenTelemetry::SDK.configure do |c|
  c.service_name = "notifications"
  c.use_all   # enable every available instrumentation (Rails, Sidekiq, pg, redis, …)
end

The Ruby OTLP exporter defaults to HTTP/protobuf on :4318; set OTEL_EXPORTER_OTLP_ENDPOINT=http://orbtrace.internal:4318.

Rust

Rust has no runtime agent — you wire the SDK into your tracing stack:

cargo add opentelemetry opentelemetry_sdk opentelemetry-otlp \
          tracing-opentelemetry tracing-subscriber
use opentelemetry_otlp::WithExportConfig;
 
let exporter = opentelemetry_otlp::SpanExporter::builder()
    .with_tonic()                                  // gRPC :4317
    .with_endpoint("http://orbtrace.internal:4317")
    .build()?;
let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
    .with_batch_exporter(exporter)
    .with_resource(opentelemetry_sdk::Resource::builder()
        .with_service_name("edge-router").build())
    .build();

Bridge it into tracing with tracing_opentelemetry::layer() so every #[tracing::instrument] span is exported.

Any other language

OpenTelemetry has SDKs for 11+ languages (C++, Erlang/Elixir, Swift, and more) — all speak the same OTLP, so all land in Orbtrace unchanged. The rule is identical everywhere: set service.name and point the exporter at your Collector on :4317 (gRPC) or :4318 (HTTP). For anything without an SDK, send OTLP/HTTP JSON directly:

"I just want to send raw JSON over HTTP"

For one-off scripts and exotic languages, OTLP/HTTP accepts JSON on port 4318:

curl -X POST http://orbtrace.internal:4318/v1/logs \
  -H 'Content-Type: application/json' \
  -d '{
    "resourceLogs": [{
      "resource": { "attributes": [{"key":"service.name","value":{"stringValue":"crontab"}}] },
      "scopeLogs": [{
        "logRecords": [{
          "timeUnixNano": "1715600000000000000",
          "severityText": "INFO",
          "body": { "stringValue": "Nightly cleanup complete" }
        }]
      }]
    }]
  }'

Databases, caches, and message queues

You do not instrument your PostgreSQL, Redis, or Kafka — you get their telemetry from two directions, and both are largely automatic.

1. Client-side spans — already free. The auto-instrumentation on your apps (every language above) captures each outbound call as a span on the calling service: the SQL query (db.system=postgresql, db.statement), the Redis GET/SET, the Kafka publish/consume — with the standard OTel semantic-convention attributes. So "how do I see my database" is answered by instrumenting the services that talk to it; the query timings, the slow statements and the N+1 patterns all appear on those services' traces. Nothing extra to install for the common clients.

2. Trace context across a queue — end-to-end traces through Kafka/RabbitMQ. A producer in service A and a consumer in service B join into one trace only if the trace context rides in the message. OTel's messaging instrumentation injects W3C traceparent into message headers on publish and extracts it on consume — so with both sides instrumented, Orbtrace shows the unbroken trace across the broker automatically. And where a legacy producer doesn't propagate headers, Orbtrace's async stitching engine reconstructs the producer→consumer link heuristically (matching message IDs within a time window), so cross-queue traces stay connected even against un-updatable services.

3. Infrastructure-level metrics — add a receiver to your Collector. To watch the datastore as infrastructure — connection-pool saturation, replication lag, Kafka consumer-group lag, cache hit ratio, buffer usage — the standard OTel way is a scraper receiver on your Collector, pointed at the server. The contrib Collector ships receivers for the common systems: postgresqlreceiver, mysqlreceiver, redisreceiver, kafkametricsreceiver, mongodbreceiver, rabbitmqreceiver, elasticsearchreceiver, and more. Add them to your Collector config and their metrics flow into Orbtrace like everything else:

otelcol-config.yaml (add to your Collector)
receivers:
  postgresql:
    endpoint: db.internal:5432
    username: monitor
    password: ${env:PG_MONITOR_PASSWORD}
    tls: { insecure: true }
  redis:
    endpoint: cache.internal:6379
    collection_interval: 30s
  kafkametrics:
    brokers: [kafka.internal:9092]
    scrapers: [brokers, topics, consumers]   # consumer-group lag lives here
 
service:
  pipelines:
    metrics:
      receivers: [otlp, postgresql, redis, kafkametrics]   # otlp = your apps; the rest = infra
      exporters: [doris]

Give each receiver a read-only monitoring user on the target system — none of them need write access. On Kubernetes these receivers usually live on the gateway tier (see Integration patterns → Recommended architectures), one scrape per cluster rather than per node.

How to verify it worked

  1. In Orbtrace go to Services (sidebar, services icon). Your service.name should appear within ~10 seconds of the app starting to send.
  2. Click the service. The detail screen shows its request rate (requests/sec) and p99 latency over the last hour.
  3. Go to Explore → Traces and filter service:<your-service-name>. You should see traces.

If the service doesn't appear after 60 seconds:

  • Run nc -zv orbtrace.internal 4317 from the app's host — verify the network reaches the Collector.
  • Check your Collector's logs: docker logs orbtrace-otelcol | tail -100 (the Collector you started in Integration patterns). Refused connections or auth errors show here.
  • Read the Troubleshooting → No data arriving section.

What gets sent automatically vs. what you add

Auto-instrumentation gives you all the "scaffolding" spans for free: HTTP request handlers, database calls, queue consumers, outbound HTTP calls. That's usually enough to see where time is spent.

To make Orbtrace truly powerful, add a few custom spans around the parts of your business logic that matter:

// Java
Span span = tracer.spanBuilder("pricing.calculateDiscount").startSpan();
try (Scope s = span.makeCurrent()) {
    span.setAttribute("customer.tier", tier);
    span.setAttribute("cart.size", cart.size());
    return calculate(tier, cart);
} finally {
    span.end();
}

Those attributes will appear as filterable columns in the Traces screen and as queryable dimensions in Search syntax.

What about logs and metrics?

Auto-instrumentation handles traces out of the box. For logs, most SDKs auto-bridge from the standard logger (SLF4J in Java, logging in Python, etc.) — set OTEL_LOGS_EXPORTER=otlp and they flow in. For metrics, the SDK emits JVM / runtime / HTTP metrics automatically; add Meter.counterBuilder(...) calls for business metrics.

Examples for each language live in the OpenTelemetry official docs at opentelemetry.io/docs/languages. Anything that's valid OTel will be accepted by Orbtrace's Collector — we do not modify the protocol.