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

Instrument with OpenTelemetry in 5 Minutes and Send Traces to JJHub OBSERVE

A minimal-change walkthrough of auto-instrumenting a Java service with the OpenTelemetry SDK and configuring the Collector to push traces and logs into JJHub OBSERVE, with key config and common pitfalls.

Before you start

Make sure you have three things: a server with outbound network access, a JJHub OBSERVE account (grab the ingest endpoint and your token), and a running Java service. This post uses Spring Boot as the example; other languages follow the same steps, just swap the SDK and startup flags.

Why a local Collector instead of sending straight from the agent? Two reasons. First, the Collector gives you a single place to buffer, retry, and batch data, so a network blip doesn't lose spans. Second, it's where you add processors — sampling, redaction, environment tags — without touching every service.

Step 1: Auto-instrument without touching business code

OpenTelemetry ships a Java Agent that injects instrumentation at startup via a -javaagent flag, covering HTTP, databases, and message queues out of the box — no business code changes needed. This is the fastest way to get value: drop a jar, add a flag, restart. Download it:

curl -LO https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar

Then start your app with flags that send data to a local Collector:

java -javaagent:opentelemetry-javaagent.jar \
  -Dotel.service.name=order-service \
  -Dotel.traces.exporter=otlp \
  -Dotel.metrics.exporter=none \
  -Dotel.logs.exporter=none \
  -Dotel.exporter.otlp.endpoint=http://localhost:4317 \
  -jar app.jar

Set service.name to something stable and meaningful. Every search and alert depends on it later, so don't leave the default unknown_service. Pick a name that survives refactors and renames — order-service, not order-service-v2-fix. If you run multiple instances, the agent adds the instance ID automatically; you don't need to encode it in the name.

Step 2: Configure the Collector to forward to OBSERVE

The agent only collects; the Collector is what lands data in the platform. Install a Collector (the contrib distribution has the most receivers) and write this config:

receivers:
  otlp:
    protocols: { grpc: { endpoint: 0.0.0.0:4317 } }
processors:
  batch:
  resource:
    attributes:
      - key: deployment.environment
        value: production
        action: upsert
exporters:
  otlphttp:
    endpoint: https://ob.jjhub.cn/otel
    headers:
      authorization: Bearer <your-ingest-token>
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [resource, batch]
      exporters: [otlphttp]

The resource processor stamps a consistent environment label on everything, so each service doesn't reinvent its own. This matters more than it looks: once deployment.environment is uniform, you can filter prod vs staging everywhere. The batch processor groups spans to cut network overhead; without it, every span is its own HTTP request.

The authorization header carries your ingest token. Treat it like a secret: put it in an environment variable or a mounted secret, not plaintext committed to git.

Step 3: Verify data is flowing

Start the service and the Collector, fire a request, then open the Traces page in the platform and filter by service.name. You should see traces within tens of seconds; click one to inspect the span waterfall and latency breakdown. If nothing appears, check three places in order: whether the agent actually loaded (check startup logs for the OpenTelemetry Javaagent config summary), whether the Collector logs report exporter errors, and whether your token is correct.

A useful smoke test is to hit an endpoint that touches a database. If the trace shows an HTTP span but no DB span, the agent loaded but the database instrumentation didn't attach — usually a version mismatch between the agent and your driver.

Common pitfalls

  1. Port mix-ups: the agent speaks gRPC on 4317 and HTTP on 4318 — don't open only one in the firewall.
  2. Clock drift: unsynchronized NTP produces scrambled span timestamps; sync time before onboarding. A span that appears to start after its parent ends is a classic drift symptom.
  3. Sampling: under heavy traffic, configure tail_sampling in the Collector instead of storing every trace. A good starting policy: keep all traces with errors or latency over 1 second, sample 10% of the rest.

What you get at the end

Once the smoke test passes, you have the basics in place: every request is traced end to end, logs carry the trace ID, and you can jump from a slow span straight to the logs it produced. From here the natural next steps are enabling metrics export, turning on tail-based sampling for production traffic, and tagging business attributes like customer tier or region on the resource. Each is a small, independent change on top of the pipeline you just built.