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.
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.
Grab two things from the console (Integrations → Tracing):
https://collector.ob.jjhub.cn:4317Authorization headerAlso confirm the runtime: JDK 8+ or Go 1.19+, with outbound access on port 4317.
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.otel.propagators=tracecontext,baggage so trace context propagates across services.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
}
“Connected” isn’t the same as “usable.” Three things make traces actually readable:
order_id or user_id, so you can search by business dimension instead of staring at trace_ids.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.The idea is the same beyond Java and Go:
opentelemetry-distro or opentelemetry-instrument for auto-instrumentation, and set the OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS environment variables.@opentelemetry/sdk-node with @opentelemetry/exporter-trace-otlp-http, and set OTEL_SERVICE_NAME.deployment.environment=prod and service.version via OTEL_RESOURCE_ATTRIBUTES so you can filter traces by environment when investigating.Full trace volume is a disaster for high-QPS services—storage and bandwidth both buckle. Observe recommends tiered sampling:
AlwaysSample—full volume for easier debugging.TraceIDRatioBased(0.1) (10%). Use ParentBased plus a custom sampler to keep every span marked with an error.Don’t pick a ratio by gut feel: start at 10%, run a week, check storage cost and trace coverage, then adjust.
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.
Run through these four before going live—they catch most “I thought it was wired up” problems:
If all four pass, you're ready to point the service at production traffic.