How GreptimeDB OpenTelemetry Integration Supports Metrics, Logs, and Traces Ingestion

GreptimeDB exposes a unified OpenTelemetry (OTEL) ingestion API that accepts metrics, logs, and traces over HTTP using protobuf, converting them into optimized row insertions via the OpenTelemetryProtocolHandler trait.

The OpenTelemetry integration in greptimeteam/greptimedb enables seamless observability data ingestion through three dedicated HTTP endpoints. This implementation decodes OTEL protobuf messages and transforms them into GreptimeDB's internal storage format, supporting both direct insertion and configurable pipeline processing for telemetry normalization.

HTTP Entry Points and Protocol Routing

The ingestion flow begins at the axum HTTP layer in src/servers/src/http/otlp.rs. Three async handlers register distinct routes for each signal type:

  • /v1/otlp/v1/metricsmetrics() handler
  • /v1/otlp/v1/tracestraces() handler
  • /v1/otlp/v1/logslogs() handler

Each handler validates the Content-Type header, rejects JSON payloads (enforcing binary protobuf), and initializes a protocol context via ProtocolCtx::OtlpMetric. The request body undergoes prost::Message::decode conversion (lines 90-95) to transform raw bytes into OTEL gRPC structs: ExportMetricsServiceRequest, ExportTraceServiceRequest, or ExportLogsServiceRequest.

The decoded requests pass to the OpenTelemetryProtocolHandler trait implementation in src/frontend/src/instance/otlp.rs, where the Instance struct handles authorization, request-level interceptor plugins, and signal-specific routing.

Metrics Ingestion Pipeline

Metrics conversion occurs in src/servers/src/otlp/metrics.rs through the to_grpc_insert_requests function. The processor iterates the nested structure ResourceMetrics → ScopeMetrics → Metric, extracting time-series data into RowInsertRequests.

Key normalization steps include:

  • Resource-attribute promotion: Elevates curated resource attributes (service → job/instance) or all attributes when promote_all_resource_attrs is enabled
  • Name normalization: Applies legacy_normalize_otlp_name to sanitize metric names
  • Type mapping: Converts OTEL gauges to value columns, sums to _count/_sum table variants, and handles unit suffixes via UNIT_MAP and PER_UNIT_MAP

The Instance::metrics method (lines 40-88) determines execution path based on OtlpMetricCtx flags. When is_legacy is false and with_metric_engine is true, requests route to the optimized metric engine via handle_metric_row_inserts; otherwise, standard row insertion applies.

Logs Ingestion and Pipeline Processing

Logs ingestion in src/servers/src/otlp/logs.rs supports dual processing modes via the PipelineWay enum:

  • PipelineWay::OtlpLogDirect: Builds a single RowInsertRequest targeting the opentelemetry_logs table (constant LOG_TABLE_NAME) using an identity schema
  • PipelineWay::Pipeline: Parses logs into a VRL array and executes user-defined pipelines via run_pipeline for custom transformation

The to_grpc_insert_requests function (lines 60-73) dispatches based on pipeline configuration, enabling either immediate storage or structured preprocessing before insertion.

Traces Ingestion: v0 and v1 Models

Traces ingestion supports schema evolution through versioned converters in src/servers/src/otlp/trace.rs. The dispatcher to_grpc_insert_requests (lines 67-94) selects between:

  • v0 model (v0_to_grpc_insert_requests): Legacy flat table structure for backward compatibility
  • v1 model (v1_to_grpc_insert_requests): Modern column-rich schema implemented in src/servers/src/otlp/trace/v1.rs

The v1 converter writes three distinct tables per trace dataset:

  1. Primary span table (one row per span)
  2. trace_services table (deduplicated service names)
  3. trace_operations table (service and operation combinations)

Configuration via HTTP Headers

GreptimeDB exposes fine-grained control through custom HTTP headers parsed in src/servers/src/http/otlp.rs and stored in OtlpMetricCtx:

  • x-greptime-otlp-metric-promote-scope-attrs – Promote all scope attributes as columns
  • x-greptime-otlp-metric-promote-all-resource-attrs – Promote every resource attribute (not just curated defaults)
  • x-greptime-otlp-metric-ignore-resource-attrs – Exclude specific attributes from promotion
  • x-greptime-otlp-legacy-mode – Force legacy metric handling with disabled metric-engine and attribute promotion

Query-string parameters like pipeline and db further customize request routing and target database selection.

Practical Code Examples

Sending Metrics with otel-cli

otel-cli metric add \
  --name "http_requests_total" \
  --type counter \
  --value 1 \
  --attrs "service.name=web,job=frontend" \
  --endpoint http://localhost:4000/v1/otlp/v1/metrics \
  --proto

The metrics() handler decodes this into an ExportMetricsServiceRequest, then otlp::metrics::to_grpc_insert_requests generates rows for tables named http_requests_total (or _count/_sum variants for cumulative metrics).

Ingesting Logs via cURL

cat logs.pb | curl -X POST \
  -H "Content-Type: application/x-protobuf" \
  --data-binary @- \
  http://localhost:4000/v1/otlp/v1/logs?db=public

When no pipeline parameter is specified, logs() creates direct insertions to the opentelemetry_logs table. Adding ?pipeline=my_pipeline triggers VRL processing before storage.

Configuring OpenTelemetry Collector for Traces

exporters:
  otlp/greptime:
    endpoint: "http://localhost:4000/v1/otlp/v1/traces"
    compression: none

The collector exports ExportTraceServiceRequest protobuf to the traces endpoint. By default, the v1 converter creates the three-table schema (spans, services, operations) in src/servers/src/otlp/trace/v1.rs.

Summary

  • Unified HTTP API: Three endpoints (/v1/otlp/v1/metrics, /v1/otlp/v1/logs, /v1/otlp/v1/traces) in src/servers/src/http/otlp.rs handle all OTEL signals via protobuf decoding
  • Pluggable Processing: The OpenTelemetryProtocolHandler trait in src/frontend/src/instance/otlp.rs delegates to signal-specific converters for metrics, logs, and traces
  • Flexible Logs: Supports both direct insertion to opentelemetry_logs and VRL pipeline processing via PipelineWay configuration
  • Versioned Traces: Automatic dispatch between v0 (legacy flat) and v1 (normalized multi-table) trace models based on request context
  • Configurable Ingestion: HTTP headers control resource attribute promotion, legacy mode toggling, and metric-engine selection

Frequently Asked Questions

Does GreptimeDB support JSON payloads for OpenTelemetry ingestion?

No. According to the source code in src/servers/src/http/otlp.rs, the HTTP handlers explicitly reject JSON payloads and require Content-Type: application/x-protobuf. All OTEL requests must use binary protobuf encoding via prost::Message::decode.

How does the metric engine differ from standard row insertion for OTEL metrics?

When x-greptime-otlp-metric-promote-all-resource-attrs headers indicate modern mode (non-legacy) and with_metric_engine is true, the Instance::metrics method routes requests through handle_metric_row_inserts. This path optimizes storage for high-cardinality time-series data compared to the standard row insertion fallback used when legacy mode is enabled.

Can I customize which log fields become columns versus attributes?

Yes. For logs sent through the pipeline path (PipelineWay::Pipeline), you can define VRL (Vector Remap Language) transformations that parse the OTEL log record and map specific fields to columns. The direct path (PipelineWay::OtlpLogDirect) uses a fixed identity schema for the opentelemetry_logs table.

What tables are created when ingesting OpenTelemetry traces?

For the default v1 model, the trace converter in src/servers/src/otlp/trace/v1.rs creates three tables: the primary span table (containing full trace data), a trace_services table with deduplicated service names, and a trace_operations table tracking service and operation combinations. The legacy v0 model uses a single flat table structure instead.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →