← Back to blog
Guide 4 min read 炬鲸团队

Onboarding OpenTelemetry Tracing into Observe: From Zero to Full Trace

A hands-on guide to onboarding OpenTelemetry tracing into Observe: getting your endpoint, configuring Java and Go SDKs, pointing the exporter, span granularity, sampling, and troubleshooting the most common failures.

Onboarding OpenTelemetry (OTel) into Observe tracing is the first step toward full-stack observability. This walks you from getting your endpoint to seeing a complete call tree, plus the spots where people most often get stuck.

Why OpenTelemetry and not a vendor SDK

Short version: rewriting instrumentation every time you switch backends is unacceptable. OTel’s value is decoupling instrumentation from the backend—you emit spans with one API and swap backends just by changing the exporter config. Observe speaks OTLP natively, so instrumentation you write today works against Observe, open-source Jaeger, or any OTLP-compatible backend tomorrow.

Before you start

Grab two things from the console (Integrations → Tracing):

  • Endpoint: the OTLP receiver address, e.g. https://collector.ob.jjhub.cn:4317
  • Token: your tenant credential, sent in the Authorization header

Also confirm the runtime: JDK 8+ or Go 1.19+, with outbound access on port 4317.

Java: the zero-code route

The OpenTelemetry Java agent is the fastest path—no code changes. Download the agent and add to your launch arguments:

java -javaagent:opentelemetry-javaagent.jar \
  -Dotel.exporter.otlp.endpoint=https://collector.ob.jjhub.cn:4317 \
  -Dotel.exporter.otlp.headers='Authorization=Bearer <your-token>' \
  -Dotel.service.name=order-service \
  -Dotel.traces.exporter=otlp \
  -Dotel.metrics.exporter=none \
  -jar your-app.jar

A few parameters worth explaining:

  • otel.service.name is the display name on the trace map. Follow your naming convention—don’t leave the default unknown_service.
  • otel.traces.exporter=otlp enables traces only. Turn off the metrics exporter if you aren’t collecting metrics yet.
  • In production add otel.propagators=tracecontext,baggage so trace context propagates across services.

Go: SDK-based instrumentation

For Go you instrument with the SDK or use auto-instrumentation libraries like otelhttp. The core is initializing a TracerProvider that points at Observe:

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    "go.opentelemetry.io/otel/sdk/trace"
)

func initTracer(ctx context.Context) (*trace.TracerProvider, error) {
    headers := map[string]string{"Authorization": "Bearer " + token}
    exp, err := otlptracehttp.New(ctx,
        otlptracehttp.WithEndpoint("collector.ob.jjhub.cn"),
        otlptracehttp.WithHeaders(headers),
        otlptracehttp.WithInsecure(),
    )
    if err != nil { return nil, err }
    tp := trace.NewTracerProvider(
        trace.WithBatcher(exp),
        trace.WithSampler(trace.ParentBased(trace.TraceIDRatioBased(0.1))),
    )
    otel.SetTracerProvider(tp)
    return tp, nil
}

Span granularity and attributes

“Connected” isn’t the same as “usable.” Three things make traces actually readable:

  1. Tag spans with business identifiers: every span should carry keys like order_id or user_id, so you can search by business dimension instead of staring at trace_ids.
  2. Don’t over-instrument: only span cross-service calls and key business steps. Spans inside every loop iteration produce oceans of meaningless data and slow down queries.
  3. Mark errors explicitly: call span.RecordError(err) and span.SetStatus(codes.Error) when you catch an exception—tail sampling and “errored traces only” filtering depend on it, otherwise broken traces look identical to healthy ones.

Other languages in brief

The idea is the same beyond Java and Go:

  • Python: use opentelemetry-distro or opentelemetry-instrument for auto-instrumentation, and set the OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS environment variables.
  • Node.js: use @opentelemetry/sdk-node with @opentelemetry/exporter-trace-otlp-http, and set OTEL_SERVICE_NAME.
  • Resource attributes: in every language, add deployment.environment=prod and service.version via OTEL_RESOURCE_ATTRIBUTES so you can filter traces by environment when investigating.

Sampling: don’t ship everything

Full trace volume is a disaster for high-QPS services—storage and bandwidth both buckle. Observe recommends tiered sampling:

  • Dev/test: AlwaysSample—full volume for easier debugging.
  • Production core paths: start with TraceIDRatioBased(0.1) (10%). Use ParentBased plus a custom sampler to keep every span marked with an error.
  • Tail sampling also works server-side, but controlling volume at the SDK is the simplest, most effective first step.

Don’t pick a ratio by gut feel: start at 10%, run a week, check storage cost and trace coverage, then adjust.

Troubleshooting the common failures

Nothing shows up in the console.
Check in order: ① is port 4317 reachable (curl -v https://collector.ob.jjhub.cn:4317); ② is the token correct and prefixed with Bearer; ③ is the exporter actually loaded (look for OTel lines in startup logs); ④ is the service name empty.

Traces are broken—only isolated spans.
Usually a propagation problem: make sure downstream services also have an SDK, and that propagators match (tracecontext everywhere). For HTTP calls, confirm the traceparent header is present. Mismatched propagator versions are the most common cause of broken chains.

Spans exist but latency looks inflated.
First check whether the exporter’s batch delay is the culprit, then whether spans carry oversized attributes. Trimming log and span attributes usually brings latency back down.

The easiest success check: log a line that carries the trace_id, then jump from that log into the trace view. If you see the full call tree, it’s working.

Pre-launch checklist

Run through these four before going live—they catch most “I thought it was wired up” problems:

  • Service name, endpoint, and token are all correct.
  • A request crossing two or more services shows a complete call tree.
  • The sampling ratio matches what you intend, and error spans are kept in full.
  • Resource attributes carry environment and version identifiers.

If all four pass, you're ready to point the service at production traffic.