Instrumentation
Observability

Instrumentation

Connect your application to JuhJuh observability. Create API keys, install OpenTelemetry SDKs for Python, Node.js, Go, or Java, or send simple JSON over HTTP. Step-by-step setup for every language.

Instrumentation is how your application sends observability data to JuhJuh. You have two options: use an OpenTelemetry SDK for automatic instrumentation, or send simple JSON over HTTP for quick integration without dependencies.

This guide walks through authentication, SDK setup for four languages, and the simple JSON format. Pick the approach that fits your stack.

Authentication

Every request to the ingestion endpoints requires an API key.

Create an API key

  1. Open your organization's Settings
  2. Go to API Keys
  3. Click Create API Key
  4. Copy the key immediately. It starts with jjk_ and you will not see the full key again after this screen
  5. Store the key in your application's environment configuration

How authentication works

Include the key in the Authorization header on every request:

Authorization: Bearer jjk_YOUR_API_KEY

Keys are hashed before storage. JuhJuh never stores your raw key. Each key can be deactivated or set to expire on a specific date from the API Keys settings page.

Key management

Action How
Deactivate Toggle the key's active status. Deactivated keys are rejected immediately
Set expiry Choose an expiration date. Expired keys stop working at midnight UTC on that date
Delete Remove the key permanently. Any application still using it will get 401 errors

Ingestion endpoints

Three endpoints accept observability data. All three support both OTLP and simple JSON formats, detected automatically from the request body.

Signal Endpoint
Logs POST /api/ingest/logs/
Metrics POST /api/ingest/metrics/
Traces POST /api/ingest/traces/

An additional OTLP-standard path is available for traces: POST /api/ingest/v1/traces/. Both trace endpoints accept the same payloads.

Common behavior

Compression. All endpoints accept gzip and deflate compression via the Content-Encoding header. Compressed payloads are decompressed up to a 2 MB limit. Payloads exceeding 2 MB return a 413 status.

Batch processing. Requests with 1 to 50 items are processed synchronously and return 200. Requests with more than 50 items return 202 and are processed asynchronously in the background. Async processing retries up to 3 times with 10-second delays on failure.

Rate limits. Each API key allows 200 requests per 60 seconds per endpoint. Exceeding this returns 429. Batch more items per request to stay within limits.


OpenTelemetry SDK setup

OpenTelemetry is the recommended approach. It provides automatic instrumentation for HTTP frameworks, database clients, and cache libraries with minimal code changes.

Python

Install the SDK and OTLP exporter:

pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-http

Configure the exporter in your application startup:

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource

resource = Resource.create({"service.name": "my-python-service"})

provider = TracerProvider(resource=resource)
exporter = OTLPSpanExporter(
    endpoint="https://your-org.juhjuh.com/api/ingest/v1/traces/",
    headers={"Authorization": "Bearer jjk_YOUR_API_KEY"},
)
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)

Add auto-instrumentation for your framework:

pip install opentelemetry-instrumentation-django  # or flask, fastapi, etc.
from opentelemetry.instrumentation.django import DjangoInstrumentor

DjangoInstrumentor().instrument()

For metrics and logs, add the corresponding exporters:

pip install opentelemetry-exporter-otlp-proto-http
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter

metric_exporter = OTLPMetricExporter(
    endpoint="https://your-org.juhjuh.com/api/ingest/metrics/",
    headers={"Authorization": "Bearer jjk_YOUR_API_KEY"},
)
reader = PeriodicExportingMetricReader(metric_exporter, export_interval_millis=60000)
meter_provider = MeterProvider(resource=resource, metric_readers=[reader])

Node.js

Install the packages:

npm install @opentelemetry/api @opentelemetry/sdk-node \
  @opentelemetry/exporter-trace-otlp-proto \
  @opentelemetry/exporter-metrics-otlp-proto \
  @opentelemetry/auto-instrumentations-node

Create a tracing.js file and require it before your application starts:

const { NodeSDK } = require("@opentelemetry/sdk-node");
const { OTLPTraceExporter } = require("@opentelemetry/exporter-trace-otlp-proto");
const { OTLPMetricExporter } = require("@opentelemetry/exporter-metrics-otlp-proto");
const { PeriodicExportingMetricReader } = require("@opentelemetry/sdk-metrics");
const { getNodeAutoInstrumentations } = require("@opentelemetry/auto-instrumentations-node");

const traceExporter = new OTLPTraceExporter({
  url: "https://your-org.juhjuh.com/api/ingest/v1/traces/",
  headers: { Authorization: "Bearer jjk_YOUR_API_KEY" },
});

const metricExporter = new OTLPMetricExporter({
  url: "https://your-org.juhjuh.com/api/ingest/metrics/",
  headers: { Authorization: "Bearer jjk_YOUR_API_KEY" },
});

const sdk = new NodeSDK({
  serviceName: "my-node-service",
  traceExporter,
  metricReader: new PeriodicExportingMetricReader({
    exporter: metricExporter,
    exportIntervalMillis: 60000,
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();

Start your application with the tracing file loaded first:

node -r ./tracing.js app.js

Go

Install the packages:

go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/sdk/trace
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp

Configure the exporter:

package main

import (
    "context"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)

func initTracer() (*sdktrace.TracerProvider, error) {
    exporter, err := otlptracehttp.New(
        context.Background(),
        otlptracehttp.WithEndpointURL("https://your-org.juhjuh.com/api/ingest/v1/traces/"),
        otlptracehttp.WithHeaders(map[string]string{
            "Authorization": "Bearer jjk_YOUR_API_KEY",
        }),
    )
    if err != nil {
        return nil, err
    }

    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceNameKey.String("my-go-service"),
        )),
    )
    otel.SetTracerProvider(tp)
    return tp, nil
}

Java

Add the dependencies to your build file:

<!-- Maven -->
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>

Configure the exporter:

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.ResourceAttributes;

Resource resource = Resource.getDefault()
    .merge(Resource.create(io.opentelemetry.api.common.Attributes.of(
        ResourceAttributes.SERVICE_NAME, "my-java-service"
    )));

OtlpHttpSpanExporter exporter = OtlpHttpSpanExporter.builder()
    .setEndpoint("https://your-org.juhjuh.com/api/ingest/v1/traces/")
    .addHeader("Authorization", "Bearer jjk_YOUR_API_KEY")
    .build();

SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
    .addSpanProcessor(BatchSpanProcessor.builder(exporter).build())
    .setResource(resource)
    .build();

OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
    .setTracerProvider(tracerProvider)
    .build();

Simple JSON (no SDK)

If you prefer not to use an SDK, send simple JSON directly over HTTP. This works from any language, any script, or any tool that can make HTTP requests.

Sending logs

curl -X POST https://your-org.juhjuh.com/api/ingest/logs/ \
  -H "Authorization: Bearer jjk_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "logs": [
      {
        "message": "User login successful",
        "level": "info",
        "service": "auth-service",
        "timestamp": "2026-04-09T14:30:00Z",
        "attributes": {
          "user_id": "usr_12345",
          "method": "oauth"
        }
      }
    ]
  }'

Sending metrics

curl -X POST https://your-org.juhjuh.com/api/ingest/metrics/ \
  -H "Authorization: Bearer jjk_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "metrics": [
      {
        "name": "http.request.duration",
        "value": 142.5,
        "metric_type": "gauge",
        "unit": "ms",
        "tags": {
          "service": "api-gateway",
          "endpoint": "/api/users",
          "method": "GET"
        }
      }
    ]
  }'

Sending traces

curl -X POST https://your-org.juhjuh.com/api/ingest/traces/ \
  -H "Authorization: Bearer jjk_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "spans": [
      {
        "trace_id": "abc123def456",
        "span_id": "span_001",
        "parent_span_id": "",
        "operation_name": "GET /api/users",
        "service_name": "api-gateway",
        "span_kind": "server",
        "status_code": "ok",
        "duration_ms": 142,
        "started_at": "2026-04-09T14:30:00Z",
        "http_method": "GET",
        "http_url": "/api/users",
        "http_status_code": 200,
        "attributes": {
          "user.id": "usr_12345"
        }
      }
    ]
  }'

OpenTelemetry Collector

For applications that already use an OpenTelemetry Collector, point the OTLP HTTP exporter at JuhJuh:

exporters:
  otlphttp/juhjuh:
    endpoint: "https://your-org.juhjuh.com/api/ingest"
    headers:
      Authorization: "Bearer jjk_YOUR_API_KEY"
    compression: gzip
    retry_on_failure:
      enabled: true
      max_elapsed_time: 300s

service:
  pipelines:
    traces:
      exporters: [otlphttp/juhjuh]
    metrics:
      exporters: [otlphttp/juhjuh]
    logs:
      exporters: [otlphttp/juhjuh]

The Collector handles batching, retry, and compression automatically.


Compression

All ingestion endpoints accept compressed payloads. Set the Content-Encoding header to gzip or deflate. This reduces network bandwidth significantly for large batches.

Most OpenTelemetry SDKs and the Collector support gzip compression out of the box. For simple JSON, compress with your language's standard library:

import gzip
import json
import requests

payload = json.dumps({"logs": [{"message": "compressed log", "level": "info"}]})
compressed = gzip.compress(payload.encode())

requests.post(
    "https://your-org.juhjuh.com/api/ingest/logs/",
    data=compressed,
    headers={
        "Authorization": "Bearer jjk_YOUR_API_KEY",
        "Content-Type": "application/json",
        "Content-Encoding": "gzip",
    },
)

The decompressed payload must be under 2 MB. Requests exceeding this limit return 413.


Error responses

Status Meaning
200 Data accepted and processed
202 Data accepted, processing asynchronously (batch > 50 items)
400 Invalid JSON or missing required fields
401 Missing, invalid, expired, or deactivated API key
413 Decompressed payload exceeds 2 MB
429 Rate limit exceeded (200 requests per 60 seconds per key per endpoint)

Next steps

With your application connected, explore each signal:

  • Logging for searching and filtering your log data
  • Metrics for understanding metric types and building dashboards
  • Tracing for distributed request tracing and service dependency maps
  • Alerts for setting up threshold-based notifications
  • Observability for an overview of the observability platform
  • Logging for log ingestion formats and search
  • Metrics for metric types and dashboards
  • Tracing for distributed tracing and service maps
  • Alerts for alert configuration and notification channels
  • API for the full API reference