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

> GreptimeDB's OpenTelemetry integration unifies metrics, logs, and traces ingestion via HTTP protobuf. Optimize your data pipeline with this powerful feature.

- Repository: [Greptime/greptimedb](https://github.com/greptimeteam/greptimedb)
- Tags: how-to-guide
- Published: 2026-03-02

---

**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`](https://github.com/greptimeteam/greptimedb/blob/main/src/servers/src/http/otlp.rs). Three async handlers register distinct routes for each signal type:

- `/v1/otlp/v1/metrics` → `metrics()` handler
- `/v1/otlp/v1/traces` → `traces()` handler  
- `/v1/otlp/v1/logs` → `logs()` 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`](https://github.com/greptimeteam/greptimedb/blob/main/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`](https://github.com/greptimeteam/greptimedb/blob/main/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`](https://github.com/greptimeteam/greptimedb/blob/main/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`](https://github.com/greptimeteam/greptimedb/blob/main/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`](https://github.com/greptimeteam/greptimedb/blob/main/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`](https://github.com/greptimeteam/greptimedb/blob/main/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

```bash
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

```bash
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

```yaml
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`](https://github.com/greptimeteam/greptimedb/blob/main/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`](https://github.com/greptimeteam/greptimedb/blob/main/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`](https://github.com/greptimeteam/greptimedb/blob/main/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`](https://github.com/greptimeteam/greptimedb/blob/main/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`](https://github.com/greptimeteam/greptimedb/blob/main/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.