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

Connect to JJH OBSERVE with OpenTelemetry in Five Minutes

Route traces, metrics, and logs into JJH OBSERVE through an OpenTelemetry Collector. Includes a minimal collector config, a Java auto-instrumentation example, and a debug checklist for when data doesn't appear.

Prerequisites

Three things before you start:

  • A JJH OBSERVE instance; note the OTLP endpoint (e.g. https://ob.example.com/otlp)
  • An auth token (Console → Integration → Generate Token)
  • A running application, any language (Java, Go, Python, Node all work)

For local verification you can run a single-node Collector in Docker, covered below. If you already run an OpenTelemetry deployment, skip the Collector section and point your existing exporter at the platform directly.

Configure the OTel Collector

Use an OpenTelemetry Collector as the unified egress so your applications never talk to the platform directly. It also gives you one place to batch, retry, and redact data before it leaves your network. A minimal working config:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
processors:
  batch:
    timeout: 5s
    send_batch_size: 512
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
exporters:
  otlphttp:
    endpoint: https://ob.example.com/otlp
    headers:
      Authorization: "Bearer <token>"
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlphttp]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlphttp]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlphttp]

Three things matter here:

  • Put the batch processor after memory_limiter. If backpressure builds, the limiter drops or blocks before batch accumulates unbounded memory — reversing the order is the most common cause of Collector OOM in production.
  • Pass the token via header, never hardcode it in application code. Rotate tokens from the console and update only the Collector.
  • Keep the three pipelines separate. You can toggle logs off temporarily without affecting traces, or route metrics to a different exporter during a migration.

Instrument the Application

For Java, the automatic instrumentation agent gives you traces, metrics, and logs with zero code changes:

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

Two settings worth adding in production: otel.resource.attributes=service.version=<version> so you can diff latency by release, and otel.traces.sampler with a sensible ratio (start at 1.0 and lower it as volume grows).

Go and Python follow the same model but need explicit instrumentation. In Go you wrap handlers with otelhttp and add a trace provider at startup; in Python you run opentelemetry-instrument python app.py. Whatever the language, the one rule that matters is service.name: it must be globally unique and semantically clear (order-service, pay-gateway). That name is the join key for per-service aggregation and topology graphs, and renaming it later breaks your historical views.

Within about a minute, the Service Topology and Tracing pages should show data.

Production Checklist

Before you call the integration done, run through these:

  • Set resource attributes: service.name, service.version, and deployment.environment (prod/staging). They become the dimensions you filter on in every view.
  • Configure a sampler. Start at traceidratio 1.0 for low-traffic services, drop to 0.1 or 0.05 once volume grows. Head-based sampling is fine for most cases; if you need to keep slow or erroring traces, use tail-based sampling in the Collector.
  • Run a second Collector for high availability. Point both at the same OTLP endpoint; if one dies, the other keeps shipping.
  • Tune the batch size. send_batch_size 512 is a reasonable start; raise it for high-throughput services to cut exporter round trips, but keep memory_limiter in place.
  • Secure the endpoint. Put the OTLP endpoint behind TLS and, if your network requires it, IP-allowlist it.

These are the settings that separate a demo from something you can leave running unattended overnight.

Verify and Debug

If nothing appears, work through this checklist in order:

  1. Is the Collector receiving? docker logs should show otlp connections; if not, the app isn't reaching port 4317/4318.
  2. Is the exporter succeeding? 401 means the token is wrong or expired; a timeout means the endpoint is unreachable from the Collector's network.
  3. Is the app actually emitting? Enable DEBUG on the exporter and watch the send count — zero sends usually means the tracer provider was never initialized.
  4. Are the clocks aligned? If app, Collector, and platform drift by more than a few seconds, trace timelines render wrong. Keep everything on NTP.

To confirm end-to-end correlation, log a trace_id from your code and look it up in the platform — if the trace shows up but no logs attach to it, your logs pipeline isn't propagating trace context.

That's the whole onboarding path: one Collector, one agent flag, one token. Everything after that is tuning, which we cover in the sampling and retention guides.