OpenTelemetry Setup

Send traces, metrics, and logs from any OpenTelemetry SDK to JustAnalytics via OTLP endpoints.

What Is OpenTelemetry?#

OpenTelemetry (OTel) is a vendor-neutral, open-source standard for collecting telemetry data -- traces, metrics, and logs. It provides APIs, SDKs, and tools for instrumenting your application in a way that isn't locked to any specific observability vendor.

JustAnalytics natively supports the OpenTelemetry Protocol (OTLP), so you can send data from any OTel-compatible SDK or collector without using the JustAnalytics SDK.

Why Use OpenTelemetry with JustAnalytics?#

  • Already using OTel? -- send your existing OTel data to JustAnalytics without changing your instrumentation
  • Multi-language support -- OTel has SDKs for Node.js, Python, Go, Java, Ruby, .NET, Rust, and more
  • Vendor flexibility -- instrument once, send to JustAnalytics and/or other backends
  • Community instrumentation -- leverage thousands of community-maintained auto-instrumentation libraries
  • Collector pipeline -- use the OTel Collector to transform, filter, and route data before it reaches JustAnalytics

OTLP Endpoints#

JustAnalytics exposes OTLP-compatible HTTP endpoints for all three signal types:

| Signal | Endpoint | Protocol | |--------|----------|----------| | Traces | https://api.justanalytics.app/v1/traces | OTLP/HTTP (protobuf or JSON) | | Metrics | https://api.justanalytics.app/v1/metrics | OTLP/HTTP (protobuf or JSON) | | Logs | https://api.justanalytics.app/v1/logs | OTLP/HTTP (protobuf or JSON) |

All endpoints accept both application/x-protobuf and application/json content types. Protobuf is recommended for lower bandwidth.

Authentication#

Authenticate using your JustAnalytics API key in the request headers:

x-api-key: YOUR_API_KEY

Or use the standard OTel header convention:

Authorization: Bearer YOUR_API_KEY

Both are accepted on all OTLP endpoints.

Finding Your API Key#

  1. Go to Dashboard > Settings > API Keys
  2. Create a new key with the scopes you need: traces:write, metrics:write, logs:write
  3. Copy the key (it's only shown once)

Configuration with Environment Variables#

The standard OTel environment variables configure where your SDK sends data:

# Required: endpoint and authentication
export OTEL_EXPORTER_OTLP_ENDPOINT="https://api.justanalytics.app"
export OTEL_EXPORTER_OTLP_HEADERS="x-api-key=YOUR_API_KEY"

# Recommended: identify your service
export OTEL_SERVICE_NAME="api-server"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production,service.version=1.2.3"

# Optional: protocol (default varies by SDK)
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"

These environment variables are respected by all official OTel SDKs. Set them in your deployment configuration and your application will send data to JustAnalytics without any code changes.

Per-Signal Endpoints#

If you want to send different signals to different destinations:

export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://api.justanalytics.app/v1/traces"
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT="https://api.justanalytics.app/v1/metrics"
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT="https://api.justanalytics.app/v1/logs"

Using with OTel SDKs (No JA SDK)#

You can use the official OpenTelemetry SDKs directly, sending data to JustAnalytics without installing @justanalyticsapp/node.

Node.js#

Install the OTel packages:

npm install @opentelemetry/sdk-node \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/exporter-trace-otlp-http \
  @opentelemetry/exporter-metrics-otlp-http \
  @opentelemetry/exporter-logs-otlp-http

Create a tracing setup file (tracing.ts):

import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';

const sdk = new NodeSDK({
  serviceName: 'api-server',
  traceExporter: new OTLPTraceExporter({
    url: 'https://api.justanalytics.app/v1/traces',
    headers: { 'x-api-key': process.env.JA_API_KEY! },
  }),
  metricReader: new PeriodicExportingMetricReader({
    exporter: new OTLPMetricExporter({
      url: 'https://api.justanalytics.app/v1/metrics',
      headers: { 'x-api-key': process.env.JA_API_KEY! },
    }),
    exportIntervalMillis: 60000,
  }),
  logRecordProcessor: undefined, // Configure separately if needed
  instrumentations: [
    getNodeAutoInstrumentations({
      '@opentelemetry/instrumentation-http': { enabled: true },
      '@opentelemetry/instrumentation-express': { enabled: true },
      '@opentelemetry/instrumentation-pg': { enabled: true },
    }),
  ],
});

sdk.start();
console.log('OpenTelemetry SDK started, exporting to JustAnalytics');

process.on('SIGTERM', () => {
  sdk.shutdown().then(() => process.exit(0));
});

Load it before your application:

node --require ./tracing.js ./app.js

Or with ts-node:

ts-node --require ./tracing.ts ./app.ts

Python#

Install the OTel packages:

pip install opentelemetry-sdk \
  opentelemetry-exporter-otlp-proto-http \
  opentelemetry-instrumentation-flask \
  opentelemetry-instrumentation-requests \
  opentelemetry-instrumentation-psycopg2

Configure and initialize:

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
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor
from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor
import os

# Configure the resource (service identity)
resource = Resource.create({
    "service.name": "api-server",
    "deployment.environment": "production",
    "service.version": "1.2.3",
})

# Configure the trace exporter
exporter = OTLPSpanExporter(
    endpoint="https://api.justanalytics.app/v1/traces",
    headers={"x-api-key": os.environ["JA_API_KEY"]},
)

# Set up the tracer provider
provider = TracerProvider(resource=resource)
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)

# Auto-instrument libraries
FlaskInstrumentor().instrument()
RequestsInstrumentor().instrument()
Psycopg2Instrumentor().instrument()

# Use the tracer in your code
tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("process-order") as span:
    span.set_attribute("order.id", "12345")
    # ... your code here

Go#

Install the OTel packages:

go get go.opentelemetry.io/otel \
  go.opentelemetry.io/otel/sdk/trace \
  go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp \
  go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp

Configure and initialize:

package main

import (
    "context"
    "log"
    "net/http"
    "os"

    "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.21.0"
    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

func initTracer() func() {
    ctx := context.Background()

    exporter, err := otlptracehttp.New(ctx,
        otlptracehttp.WithEndpoint("api.justanalytics.app"),
        otlptracehttp.WithHeaders(map[string]string{
            "x-api-key": os.Getenv("JA_API_KEY"),
        }),
    )
    if err != nil {
        log.Fatalf("failed to create exporter: %v", err)
    }

    res, _ := resource.New(ctx,
        resource.WithAttributes(
            semconv.ServiceName("api-server"),
            semconv.DeploymentEnvironment("production"),
        ),
    )

    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(res),
    )
    otel.SetTracerProvider(tp)

    return func() { tp.Shutdown(ctx) }
}

func main() {
    cleanup := initTracer()
    defer cleanup()

    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, World!"))
    })

    wrappedHandler := otelhttp.NewHandler(handler, "hello")
    http.ListenAndServe(":8080", wrappedHandler)
}

Using with the OTel Collector#

The OpenTelemetry Collector is a vendor-agnostic proxy that receives, processes, and exports telemetry data. It sits between your application and JustAnalytics, providing powerful data transformation capabilities.

Architecture#

Your App (OTel SDK)
  └─ OTLP → OTel Collector
              ├─ Processors (batch, filter, transform)
              └─ Exporters
                  ├─ JustAnalytics (OTLP)
                  └─ Other backends (optional)

Collector Configuration#

Create an otel-collector-config.yaml:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    send_batch_size: 1024
    timeout: 5s

  filter:
    traces:
      span:
        - 'attributes["http.route"] == "/health"'  # Drop health check spans

  resource:
    attributes:
      - key: deployment.environment
        value: production
        action: upsert

exporters:
  otlphttp/justanalytics:
    endpoint: https://api.justanalytics.app
    headers:
      x-api-key: ${env:JA_API_KEY}
    compression: gzip

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch, filter, resource]
      exporters: [otlphttp/justanalytics]
    metrics:
      receivers: [otlp]
      processors: [batch, resource]
      exporters: [otlphttp/justanalytics]
    logs:
      receivers: [otlp]
      processors: [batch, resource]
      exporters: [otlphttp/justanalytics]

Running the Collector#

# Docker
docker run -d \
  -p 4317:4317 \
  -p 4318:4318 \
  -v $(pwd)/otel-collector-config.yaml:/etc/otelcol/config.yaml \
  -e JA_API_KEY=your_api_key \
  otel/opentelemetry-collector-contrib:latest

# Kubernetes (Helm)
helm install otel-collector open-telemetry/opentelemetry-collector \
  --set config="$(cat otel-collector-config.yaml)"

Point your application's OTel SDK at the collector:

export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"

Benefits of Using the Collector#

  • Batching -- efficiently batch data before sending to reduce API calls
  • Filtering -- drop noisy spans (health checks, internal routes) before export
  • Enrichment -- add resource attributes (environment, region) centrally
  • Multi-export -- send to JustAnalytics and another backend simultaneously
  • Retry and buffering -- the collector handles transient failures

Data Mapping#

JustAnalytics maps OpenTelemetry data structures to its native models.

Traces: OTel Span to JA Span#

| OTel Field | JA Field | Notes | |-----------|----------|-------| | traceId | traceId | Hex-encoded, 32 characters | | spanId | spanId | Hex-encoded, 16 characters | | parentSpanId | parentSpanId | Empty for root spans | | name | operationName | Span name becomes the operation | | kind | spanKind | SERVER, CLIENT, INTERNAL, PRODUCER, CONSUMER | | startTimeUnixNano | startTime | Converted to ISO 8601 | | endTimeUnixNano | endTime | Converted to ISO 8601 | | status.code | status | OK=success, ERROR=error, UNSET=success | | status.message | statusMessage | Error description | | attributes | tags | Flattened to key-value pairs | | resource.attributes["service.name"] | serviceName | Required resource attribute |

Logs: OTel LogRecord to JA LogEntry#

| OTel Field | JA Field | Notes | |-----------|----------|-------| | timeUnixNano | timestamp | Converted to ISO 8601 | | severityNumber | level | Mapped: 1-8=debug, 9-12=info, 13-16=warn, 17-24=error | | severityText | level | Used if severityNumber is absent | | body.stringValue | message | Log message content | | attributes | metadata | Stored as JSON | | traceId | traceId | Links log to a trace | | spanId | spanId | Links log to a specific span | | resource.attributes["service.name"] | service | Service that emitted the log |

Metrics: OTel Metric to JA InfraMetric#

| OTel Field | JA Field | Notes | |-----------|----------|-------| | name | metricName | Metric name | | description | description | Human-readable description | | unit | unit | Unit of measurement | | Gauge dataPoints[].value | value | Point-in-time value | | Sum dataPoints[].value | value | Cumulative or delta counter | | Histogram dataPoints[] | Multiple values | Converted to p50, p95, p99 | | resource.attributes["service.name"] | service | Source service |

Verifying Your Setup#

After configuring your OTel SDK or Collector, verify data is flowing:

Check Traces#

  1. Generate some traffic to your application
  2. Navigate to Dashboard > Monitoring > Traces
  3. Filter by service name
  4. You should see traces appear within 30-60 seconds

Check Metrics#

  1. Navigate to Dashboard > Monitoring > Infrastructure
  2. Look for your custom metrics or default OTel metrics (e.g., http.server.duration)

Check Logs#

  1. Navigate to Dashboard > Monitoring > Logs
  2. Filter by service name
  3. Verify log levels are mapped correctly

Debug Mode#

Enable OTel SDK debug logging to troubleshoot:

# Node.js
export OTEL_LOG_LEVEL=debug

# Python
export OTEL_PYTHON_LOG_LEVEL=debug

# Go (in code)
otel.SetLogger(stdr.New(log.New(os.Stdout, "", log.LstdFlags)))

Troubleshooting#

No Data Appearing#

  1. Check API key -- ensure the key has the correct scopes (traces:write, metrics:write, logs:write)
  2. Check endpoint -- use https://api.justanalytics.app (no trailing slash)
  3. Check headers -- the x-api-key header must be present
  4. Check network -- ensure your application can reach api.justanalytics.app on port 443
  5. Enable debug logging -- see debug mode above

Traces Missing Spans#

  1. Check propagation -- ensure W3C trace context headers are propagated between services
  2. Check sampling -- if you've configured a sampler, some traces may be dropped
  3. Check batch timeout -- the BatchSpanProcessor may buffer spans; wait for the batch interval

Metrics Not Matching Expected Values#

  1. Temporality -- JustAnalytics expects cumulative temporality for counters. If your SDK sends delta, configure it to use cumulative.
  2. Aggregation -- histogram boundaries may differ between your SDK and what JA expects. Check the default bucket boundaries.

High Latency in Data Appearing#

  1. Batch size -- increase send_batch_size in the Collector or reduce the batch timeout
  2. Network -- check latency between your infrastructure and api.justanalytics.app
  3. Collector queuing -- if using the Collector, check its queue size and retry configuration

Migration from Other Vendors#

If you're migrating from another observability vendor that supports OTel:

  1. Keep your existing OTel instrumentation -- no code changes needed
  2. Update the exporter endpoint to https://api.justanalytics.app
  3. Add the x-api-key header to the exporter configuration
  4. Optional: dual-export -- use the OTel Collector to send to both your old vendor and JustAnalytics during migration
# Dual-export during migration
exporters:
  otlphttp/justanalytics:
    endpoint: https://api.justanalytics.app
    headers:
      x-api-key: ${env:JA_API_KEY}
  otlphttp/old-vendor:
    endpoint: https://ingest.old-vendor.com

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp/justanalytics, otlphttp/old-vendor]