Getting started with OpenTelemetry doesn't have to be painful. This guide walks through a reproducible path — deploy the Collector, configure the OTLP exporter, instrument your app, and verify traces in Jujing OBSERVE — with common errors and fixes.
Many teams want distributed tracing but hesitate because of the perceived onboarding cost. In practice, wiring Jujing OBSERVE with the OpenTelemetry (OTel) standard takes about 15 minutes from deployment to a rendered trace, once you know the steps. Let's walk through them in order.
The OTel Collector receives, processes, and forwards telemetry data. Run it as a separate service rather than embedding it in your app; Docker is the fastest way to get started:
docker run -d --name otel-collector -p 4317:4317 -p 4318:4318 -v $PWD/otel-collector-config.yaml:/etc/otelcol/config.yaml otel/opentelemetry-collector-contrib:latest
The config file is what matters, and its core is the receivers and exporters sections:
receivers:
otlp:
protocols:
grpc: { endpoint: 0.0.0.0:4317 }
http: { endpoint: 0.0.0.0:4318 }
exporters:
otlphttp:
endpoint: https://ob.jjhub.cn/otel
headers:
Authorization: "Bearer <your-access-token>"
service:
pipelines:
traces:
receivers: [otlp]
exporters: [otlphttp]
Port 4317 speaks gRPC and 4318 speaks HTTP; pick whichever your language prefers. Jujing receives telemetry over OTLP/HTTP, with authentication via the token header.
For Java, the Java Agent is the lowest-effort path — a single JVM flag and no code changes:
java -javaagent:opentelemetry-javaagent.jar -Dotel.service.name=order-svc -Dotel.exporter.otlp.endpoint=http://localhost:4318 -Dotel.traces.exporter=otlp -Dotel.metrics.exporter=otlp -jar order-svc.jar
Go, Python, and Node use their SDKs directly. Here's Go, initializing a TracerProvider that exports to the Collector:
ctx := context.Background()
exp, _ := otlptracehttp.New(ctx,
otlptracehttp.WithEndpoint("localhost:4318"),
otlptracehttp.WithInsecure())
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exp),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.ServiceName("order-svc"))))
otel.SetTracerProvider(tp)
For HTTP frameworks like gin, echo, and chi, attach the official instrumentation middleware — it creates a span per request automatically, so you avoid writing manual instrumentation for the most common paths.
First confirm the Collector isn't erroring: docker logs -f otel-collector should show the exporter flushing steadily. Then generate a few requests in your app, open the "Distributed Tracing" page in Jujing OBSERVE, filter by service name, and traces should appear. Open one and check three things:
Traces are most useful when they link to logs. The simplest way is to make the trace context available to your logger. In the Java Agent this works out of the box if your logging framework uses MDC; for Go, inject the context you got from the instrumentation middleware:
traceId := trace.SpanContextFromContext(ctx).TraceID().String()
logger.With("traceId", traceId).Info("order created")
Once the traceId field lands in your structured logs, Jujing automatically correlates it with the trace, and every span in the trace view becomes a clickable entry point into the logs it produced.
Before you scale out, set two things in the Collector config: a batch processor to reduce exporter calls, and a sampling policy. The batch processor groups spans before forwarding, which cuts the number of HTTP round-trips and smooths out bursty traffic:
processors:
batch:
timeout: 5s
send_batch_size: 512
For sampling, head-based sampling (decided at request start) is cheap but can drop slow traces entirely; tail-based sampling (decided after the trace completes) keeps every slow and error trace at the cost of buffering in the Collector. Start with the ratio sampler in the SDK, and only move to tail-based sampling once your volume genuinely justifies it. Sampling too aggressively early on is the most common reason people conclude "tracing doesn't show anything useful."
parentbased_traceidratio sampler that keeps all errors and samples a fraction of healthy traffic.memory_limiter errors under load, add a batch processor and set a reasonable memory limit in the config rather than tuning blindly.The value of tracing only becomes obvious once you're actually ingesting data and seeing complete traces. Follow the three steps above, get one trace end to end, then roll it out to more services gradually. Once the pipeline is proven, the marginal cost of adding the next service is close to zero.