A minimal path to OpenTelemetry from scratch: deploy the Collector, export OTLP to JUJING OBSERVE, instrument a Java or Go service, and verify your first full trace—plus the common errors you'll hit and how to fix them.
OpenTelemetry (OTel) has become the de facto standard for collecting observability data. It unifies traces, metrics, and logs under one reporting format, so you instrument your application once and swap backends freely. This post gets a complete trace into JUJING OBSERVE with minimal config in about fifteen minutes.
The flow has three hops: your app emits data via the OTel SDK, that data is sent to an OpenTelemetry Collector over OTLP, and the Collector forwards it to JUJING OBSERVE's OTLP endpoint.
Why not have the app report straight to the backend? The Collector centralizes receiving, redaction, sampling, and retries. Your app only needs to know the Collector's address, so switching backends never means touching application code. That indirection is the most valuable part of the OTel architecture.
Spin up a Collector with Docker. Here's the config:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 5s
exporters:
otlphttp:
endpoint: https://ob.jjhub.cn/otel/v1/traces
headers:
Authorization: "Bearer <your access token>"
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp]
Generate the access token from the "Data Ingestion" page in the JUJING OBSERVE console. Use separate tokens for production and staging so you can filter by source and track usage later.
docker run -d --name otel-collector -v $PWD/otel-collector.yaml:/etc/otelcol/config.yaml -p 4317:4317 -p 4318:4318 otel/opentelemetry-collector-contrib:latest
For Java, a single agent is all you need. It injects instrumentation via bytecode, so there's no change to your business code:
java -javaagent:opentelemetry-javaagent.jar -Dotel.exporter.otlp.endpoint=http://localhost:4317 -Dotel.service.name=order-service -jar app.jar
For Go, initialize a tracer in main:
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/sdk/trace"
)
exp, _ := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint("localhost:4317"),
otlptracegrpc.WithInsecure())
tp := trace.NewTracerProvider(trace.WithBatcher(exp))
otel.SetTracerProvider(tp)
The one thing that matters is service.name: it's the unique identifier that separates services in a trace, so make it meaningful. The automatic instrumentation for HTTP frameworks (Servlet in Java, net/http in Go) generates spans for you, and trace context propagates across service calls automatically — no manual header passing.
Fire a real request, open the "Tracing" page in JUJING OBSERVE, filter by service.name, and you should see the full call graph with per-service latency and dependencies laid out.
Three mistakes people hit most often:
tracecontext is fine; the problem is mixing in custom headers.timeout to something like 1s so you aren't staring at an empty screen.Once it's working, set batch.timeout back to 5s. In production, put a probabilistic_sampler in front of the Collector to keep your ingest volume from running away under load.