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#
- Go to Dashboard > Settings > API Keys
- Create a new key with the scopes you need:
traces:write,metrics:write,logs:write - 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#
- Generate some traffic to your application
- Navigate to Dashboard > Monitoring > Traces
- Filter by service name
- You should see traces appear within 30-60 seconds
Check Metrics#
- Navigate to Dashboard > Monitoring > Infrastructure
- Look for your custom metrics or default OTel metrics (e.g.,
http.server.duration)
Check Logs#
- Navigate to Dashboard > Monitoring > Logs
- Filter by service name
- 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#
- Check API key -- ensure the key has the correct scopes (
traces:write,metrics:write,logs:write) - Check endpoint -- use
https://api.justanalytics.app(no trailing slash) - Check headers -- the
x-api-keyheader must be present - Check network -- ensure your application can reach
api.justanalytics.appon port 443 - Enable debug logging -- see debug mode above
Traces Missing Spans#
- Check propagation -- ensure W3C trace context headers are propagated between services
- Check sampling -- if you've configured a sampler, some traces may be dropped
- Check batch timeout -- the BatchSpanProcessor may buffer spans; wait for the batch interval
Metrics Not Matching Expected Values#
- Temporality -- JustAnalytics expects cumulative temporality for counters. If your SDK sends delta, configure it to use cumulative.
- Aggregation -- histogram boundaries may differ between your SDK and what JA expects. Check the default bucket boundaries.
High Latency in Data Appearing#
- Batch size -- increase
send_batch_sizein the Collector or reduce the batch timeout - Network -- check latency between your infrastructure and
api.justanalytics.app - 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:
- Keep your existing OTel instrumentation -- no code changes needed
- Update the exporter endpoint to
https://api.justanalytics.app - Add the
x-api-keyheader to the exporter configuration - 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]