# OpenTelemetry Protocol (OTLP)

> 介绍如何使用 OpenTelemetry Protocol (OTLP) 将观测数据（如 metrics 和 logs）导出到 GreptimeDB，包括示例代码和数据模型的映射规则。

# OpenTelemetry Protocol (OTLP)

[OpenTelemetry](https://opentelemetry.io/) 是一个供应商中立的开源可观测性框架，用于检测、生成、收集和导出观测数据，例如 traces, metrics 和 logs。
OpenTelemetry Protocol (OTLP) 定义了观测数据在观测源和中间进程（例如收集器和观测后端）之间的编码、传输机制。

## OpenTelemetry Collectors

你可以很简单地将 GreptimeDB 配置为 OpenTelemetry 采集器写入的目标。
有关更多信息，请参阅 [OTel Collector](otel-collector.md) 和[Grafana Alloy](alloy.md) 示例。

## HTTP 基础端点

适用于所有信号类型的[HTTP 基础端点](https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/#otel_exporter_otlp_endpoint) URL：`http{s}://<host>/v1/otlp`

当需要将多种信号类型（指标、日志和链路追踪）发送到同一目标数据库时，这个统一端点非常有用，可以简化你的 OpenTelemetry 配置。

## Metrics

GreptimeDB 通过原生支持 [OTLP/HTTP](https://opentelemetry.io/docs/specs/otlp/#otlphttp) 协议，可以作为后端存储服务来接收 OpenTelemetry 指标数据。

### OTLP/HTTP API

使用下面的信息通过 Opentelemetry SDK 库发送 Metrics 到 GreptimeDB：

- URL: `https://<host>/v1/otlp/v1/metrics`
- Headers:
  - `X-Greptime-DB-Name`: `<dbname>`
- `Authorization`: `Basic` 认证，是 `<username>:<password>` 的 Base64 编码字符串。更多信息请参考 [鉴权](https://docs.greptime.cn/user-guide/deployments-administration/authentication/static/) 和 [HTTP API](https://docs.greptime.cn/user-guide/protocols/http#authentication)。

请求中使用 binary protobuf 编码 payload，因此你需要使用支持 `HTTP/protobuf` 的包。例如，在 Node.js 中，可以使用 [`@opentelemetry/exporter-metrics-otlp-proto`](https://www.npmjs.com/package/@opentelemetry/exporter-metrics-otlp-proto)；在 Go 中，可以使用 [`go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp)；在 Java 中，可以使用 [`io.opentelemetry:opentelemetry-exporter-otlp`](https://mvnrepository.com/artifact/io.opentelemetry/opentelemetry-exporter-otlp)；在 Python 中，可以使用 [`opentelemetry-exporter-otlp-proto-http`](https://pypi.org/project/opentelemetry-exporter-otlp-proto-http/)。

:::tip 注意
包名可能会根据 OpenTelemetry 的发展发生变化，因此建议你参考 OpenTelemetry 官方文档以获取最新信息。
:::

请参考 Opentelementry 的官方文档获取它所支持的编程语言的更多信息。

### 示例代码

下面是一些编程语言设置请求的示例代码：

<Tabs>

<TabItem value="TypeScript" label="TypeScript">

```ts
const auth = Buffer.from(`${username}:${password}`).toString('base64')
const exporter = new OTLPMetricExporter({
  url: `https://${dbHost}/v1/otlp/v1/metrics`,
  headers: {
    Authorization: `Basic ${auth}`,
    'X-Greptime-DB-Name': db,
  },
  timeoutMillis: 5000,
})
```

</TabItem>

<TabItem value="Go" label="Go">

```Go
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", *username, *password)))
exporter, err := otlpmetrichttp.New(
    context.Background(),
    otlpmetrichttp.WithEndpoint(*dbHost),
    otlpmetrichttp.WithURLPath("/v1/otlp/v1/metrics"),
    otlpmetrichttp.WithHeaders(map[string]string{
        "X-Greptime-DB-Name": *dbName,
        "Authorization":      "Basic " + auth,
    }),
    otlpmetrichttp.WithTimeout(time.Second*5),
)
```

</TabItem>

<TabItem value="Java" label="Java">

```Java
String endpoint = String.format("https://%s/v1/otlp/v1/metrics", dbHost);
String auth = username + ":" + password;
String b64Auth = new String(Base64.getEncoder().encode(auth.getBytes()));
OtlpHttpMetricExporter exporter = OtlpHttpMetricExporter.builder()
                .setEndpoint(endpoint)
                .addHeader("X-Greptime-DB-Name", db)
                .addHeader("Authorization", String.format("Basic %s", b64Auth))
                .setTimeout(Duration.ofSeconds(5))
                .build();
```

</TabItem>

<TabItem value="Python" label="Python">

```python
auth = f"{username}:{password}"
b64_auth = base64.b64encode(auth.encode()).decode("ascii")
endpoint = f"https://{host}/v1/otlp/v1/metrics"
exporter = OTLPMetricExporter(
    endpoint=endpoint,
    headers={"Authorization": f"Basic {b64_auth}", "X-Greptime-DB-Name": db},
    timeout=5)
```

</TabItem>

</Tabs>

关于示例代码，请参考 Opentelementry 的官方文档获取它所支持的编程语言获取更多信息。

### 兼容 Prometheus

GreptimeDB 支持以 Prometheus 兼容模式写入 OTLP 指标。
如果指标以这种兼容模式写入，你可以像查询 Prometheus 原生指标一样使用 PromQL 直接查询这些指标。

GreptimeDB 会为每个 OTLP 导出请求统一选择写入格式：

- 如果请求涉及的指标表都不存在，GreptimeDB 使用 Prometheus 兼容格式。
- 如果请求涉及的已有表都使用同一种格式，GreptimeDB 对请求中的所有指标使用该格式，包括由该请求新建的表。
- 如果请求同时涉及两种格式的已有表，GreptimeDB 会拒绝该请求。

如需同时写入旧格式和 Prometheus 兼容格式的指标，请将它们拆分到不同的 OTLP 导出请求中。

GreptimeDB 会首先对数据进行预处理，包括：
1. 将指标名（表名）和标签名转换成 Prometheus 风格的命名（例如：将 `.` 替换为 `_`）。默认情况下，GreptimeDB 还会根据指标单位和类型添加 Prometheus 风格的后缀。具体信息请参考[这里](https://opentelemetry.io/docs/specs/otel/compatibility/prometheus_and_openmetrics/#metric-metadata-1)

   以下是一些转换示例：

   | OTLP 指标 / 属性 | OTLP 类型 / 单位 | Prometheus 等效名称 |
   | :--- | :--- | :--- |
   | `cache.hit_ratio` | Gauge / `1` | `cache_hit_ratio` |
   | `memory.usage` | Gauge / `By` | `memory_usage_bytes` |
   | `queue.length` | Gauge / `{item}` | `queue_length` |
   | `http.server.request.duration` | Histogram / `s` | `http_server_request_duration_seconds` |
   | `rpc.server.duration` | Histogram / `ms` | `rpc_server_duration_milliseconds` |
   | `http.client.request.size` | Sum (Monotonic) / `By` | `http_client_request_size_bytes_total` |
   | `system.network.io` | Sum (Monotonic) / `By` | `system_network_io_bytes_total` |
   | `http.status_code` (属性) | - | `http_status_code` |
   | `service.name` (属性) | - | `service_name` |

2. 默认丢弃一些 resource 属性和全部的 scope 属性。默认保存的 resource 属性列表可以参考[这里](https://prometheus.io/docs/guides/opentelemetry/#promoting-resource-attributes)。你可以通过配置项对这个行为进行调整

注意： OTLP 的 `Sum` 和 `Histogram` 指标的数据可能是增量时序（delta temporality）类型的。
GreptimeDB 将会直接保存它们，不会进行累计值（cumulative value）的计算。
参考[这里](https://grafana.com/blog/2023/09/26/opentelemetry-metrics-a-guide-to-delta-vs.-cumulative-temporality-trade-offs/)获取更多背景信息。

你可以通过设置 HTTP 请求头来调整预处理的行为。以下是选项列表：
1. `x-greptime-otlp-metric-promote-all-resource-attrs`: 保存所有 resource 资源。默认是 `false`。
2. `x-greptime-otlp-metric-promote-resource-attrs`: 如果不保存所有 resource 资源，需要保存的资源名称列表，用 `;` 连接。
3. `x-greptime-otlp-metric-ignore-resource-attrs`: 如果保存所有的 resource 资源，需要丢弃的资源名称列表，用 `;` 连接。
4. `x-greptime-otlp-metric-promote-scope-attrs`: 是否需要保存 scope 资源。默认是 `false`。
5. `x-greptime-otlp-metric-translation-strategy`: 在保存前如何转换 OTLP 指标名（表名）和标签名（tag 列）。默认是 `UnderscoreEscapingWithSuffixes`。

`x-greptime-otlp-metric-translation-strategy` 请求头支持以下取值：

| 取值 | 指标名行为 | 标签名行为 | 原始名称 | 转换后名称 |
| :--- | :--- | :--- | :--- | :--- |
| `UnderscoreEscapingWithSuffixes` | 将不支持的字符转换为 `_`，并添加 Prometheus 风格的单位和类型后缀。 | 将不支持的字符转换为 `_`。 | 指标：`http.server.request-duration_total`（monotonic sum，单位 `ms`）标签：`_http.status-code` | 指标：`http_server_request_duration_milliseconds_total`标签：`key_http_status_code` |
| `UnderscoreEscapingWithoutSuffixes` | 将不支持的字符转换为 `_`，但不添加单位和类型后缀。 | 将不支持的字符转换为 `_`。 | 指标：`http.server.request-duration_total`（monotonic sum，单位 `ms`）标签：`_http.status-code` | 指标：`http_server_request_duration_total`标签：`key_http_status_code` |
| `NoUTF8EscapingWithSuffixes` | 保留指标名中的原始字符，并添加 Prometheus 风格的单位和类型后缀。 | 保留标签名中的原始字符。 | 指标：`http.server.request-duration_total`（monotonic sum，单位 `ms`）标签：`_http.status-code` | 指标：`http.server.request-duration_milliseconds_total`标签：`_http.status-code` |
| `NoTranslation` | 保留原始指标名，并且不添加后缀。 | 保留标签名中的原始字符。 | 指标：`http.server.request-duration_total`（monotonic sum，单位 `ms`）标签：`_http.status-code` | 指标：`http.server.request-duration_total`标签：`_http.status-code` |

请求头取值区分大小写。无效取值会被拒绝，并返回 `400 Bad Request`。
更多信息请参考 [OTel 规范](https://opentelemetry.io/docs/specs/otel/metrics/sdk_exporters/prometheus/#translation-strategy)和 [Prometheus 文档](https://prometheus.io/docs/guides/opentelemetry/#utf-8)。

### 数据模型

兼容 Prometheus 的 OTLP 指标数据模型按照下方的规则被映射到 GreptimeDB 数据模型中：

- Metric 的名称将被作为 GreptimeDB 表的名称，当表不存在时会自动创建。
- 只有特定 resource 属性会被默认保留。详情和配置选项见上一小节。属性在 GreptimeDB 表中会被作为 tag 列。
- 参考 [Prometheus 数据模型](./prometheus.md#数据模型)了解更多数据模型信息。
- ExponentialHistogram 暂时未被支持。

如果某张表是由旧版 OTLP 指标写入格式创建的，这张表会继续保留原有格式。以下是数据模型在映射上的差别：

- 所有的 Attribute，包含 resource 级别、scope 级别和 data_point 级别，都被作为 GreptimeDB 表的 tag 列。
- Summary 类型的每个 quantile 被作为单独的数据列，列名 `greptime_pxx`，其中 xx 是 quantile 的数据，如 90 / 99 等。

## Logs

GreptimeDB 是能够通过 [OTLP/HTTP](https://opentelemetry.io/docs/specs/otlp/#otlphttp) 协议原生地消费 OpenTelemetry 日志。

### OTLP/HTTP API

要通过 OpenTelemetry SDK 库将 OpenTelemetry 日志发送到 GreptimeDB，请使用以下信息：

- **URL:** `https://<host>/v1/otlp/v1/logs`
- **Headers:**
  - `X-Greptime-DB-Name`: `<dbname>`
  - `Authorization`: `Basic` 认证，这是一个 Base64 编码的 `<username>:<password>` 字符串。更多信息，请参考 [鉴权](/user-guide/deployments-administration/authentication/static.md) 和 [HTTP API](/user-guide/protocols/http.md#鉴权)。
  - `X-Greptime-Log-Table-Name`: `<table_name>`（可选）- 存储日志的表名。如果未提供，默认表名为 `opentelemetry_logs`。
  - `X-Greptime-Log-Extract-Keys`: `<extract_keys>`（可选）- 从属性中提取对应 key 的值到表的顶级字段。key 应以逗号（`,`）分隔。例如，`key1,key2,key3` 将从属性中提取 `key1`、`key2` 和 `key3`，并将它们提升到日志的顶层，设置为标签。key 匹配不区分大小写。如果同一个 key 同时存在于 log attributes、scope attributes 和 resource attributes 中，优先使用 log attributes 的值，其次是 scope attributes，最后是 resource attributes。可提取的值类型包括字符串、有符号整数、无符号整数和布尔值。如果提取的字段类型是数组、浮点数或对象，将返回错误。如果提供了 pipeline name，此设置将被忽略。
  - `X-Greptime-Pipeline-Name`: `<pipeline_name>`（可选）- 处理日志的 pipeline 名称。如果未提供，GreptimeDB 使用内置的 OTLP 日志映射，并在提供 `X-Greptime-Log-Extract-Keys` 时应用该配置。
  - `X-Greptime-Pipeline-Version`: `<pipeline_version>`（可选）- 处理日志的 pipeline 版本。如果未提供，将使用 pipeline 的最新版本。
  - `X-Greptime-Pipeline-Params`: `<pipeline_params>`（可选）- 使用自定义 pipeline 处理日志时传入的 pipeline 参数。

`X-Greptime-Log-Pipeline-Name` 和 `X-Greptime-Log-Pipeline-Version` 也可以作为通用 pipeline header 的旧别名使用。新的配置建议使用 `X-Greptime-Pipeline-Name` 和 `X-Greptime-Pipeline-Version`。

请求使用二进制 protobuf 编码负载，因此您需要使用支持 `HTTP/protobuf` 的包。

:::tip 提示
包名可能会根据 OpenTelemetry 的更新而变化，因此我们建议您参考官方 OpenTelemetry 文档以获取最新信息。
:::

有关 OpenTelemetry SDK 的更多信息，请参考您首选编程语言的官方文档。

### 示例代码

请参考 [OpenTelemetry Collector 文档](otel-collector.md)中的示例代码，里面包含了如何将 OpenTelemetry 日志发送到 GreptimeDB。
也可参考 [Alloy 文档](alloy.md#日志)中的示例代码，了解如何将 OpenTelemetry 日志发送到 GreptimeDB。

### 自定义 Pipeline 输入

当设置了 `X-Greptime-Pipeline-Name` 时，GreptimeDB 会把每条 OTLP 日志记录转换成一个 pipeline event。event 包含以下字段：

| 字段 | 说明 |
| --- | --- |
| `Timestamp` | OTLP 的 `time_unix_nano`。 |
| `ObservedTimestamp` | OTLP 的 `observed_time_unix_nano`。 |
| `TraceId`、`SpanId` | 十六进制编码后的 ID。 |
| `TraceFlags`、`SeverityText`、`SeverityNumber`、`Body` | 对应的 OTLP 日志字段。`Body` 会被转换成字符串。 |
| `ResourceSchemaUrl`、`ScopeSchemaUrl`、`ScopeName`、`ScopeVersion` | 对应的 resource 和 scope 字段。 |
| `ResourceAttributes`、`ScopeAttributes`、`LogAttributes` | 包含对应 OTLP attributes 的对象。 |

### 数据模型

OTLP 日志数据模型根据以下规则映射到 GreptimeDB 数据模型：

默认表结构：

```sql
+-----------------------+---------------------+------+------+---------+---------------+
| Column                | Type                | Key  | Null | Default | Semantic Type |
+-----------------------+---------------------+------+------+---------+---------------+
| timestamp             | TimestampNanosecond | PRI  | NO   |         | TIMESTAMP     |
| trace_id              | String              |      | YES  |         | FIELD         |
| span_id               | String              |      | YES  |         | FIELD         |
| severity_text         | String              |      | YES  |         | FIELD         |
| severity_number       | Int32               |      | YES  |         | FIELD         |
| body                  | String              |      | YES  |         | FIELD         |
| log_attributes        | Json                |      | YES  |         | FIELD         |
| trace_flags           | UInt32              |      | YES  |         | FIELD         |
| scope_name            | String              | PRI  | YES  |         | TAG           |
| scope_version         | String              |      | YES  |         | FIELD         |
| scope_attributes      | Json                |      | YES  |         | FIELD         |
| scope_schema_url      | String              |      | YES  |         | FIELD         |
| resource_attributes   | Json                |      | YES  |         | FIELD         |
| resource_schema_url   | String              |      | YES  |         | FIELD         |
+-----------------------+---------------------+------+------+---------+---------------+
14 rows in set (0.00 sec)
```

- 您可以使用 `X-Greptime-Log-Table-Name` 指定存储日志的表名。如果未提供，默认表名为 `opentelemetry_logs`。
- 所有属性，包括资源属性、范围属性和日志属性，将作为 JSON 列存储在 GreptimeDB 表中。
- `body` 列默认会创建 fulltext 索引。该索引使用默认的全文索引配置：
  `analyzer=English`、`case_sensitive=false` 和 `backend=bloom`。对于 Bloom
  后端，默认的 `granularity` 为 `10240`，默认的 `false_positive_rate` 为
  `0.01`。更多信息请参考[全文索引文档](/user-guide/manage-data/data-index.md#全文索引)。
- 日志的时间戳将用作 GreptimeDB 中的时间戳索引，列名为 `timestamp`。建议使用 `time_unix_nano` 作为时间戳列。如果未提供 `time_unix_nano`，将使用 `observed_time_unix_nano`。

### Append-only 模式

通过此接口创建的表，默认为[Append-only 模式](/user-guide/deployments-administration/performance-tuning/design-table.md#何时使用-append-only-表)。

## Traces

GreptimeDB 支持直接写入 OpenTelemetry 协议的 traces 数据，并内置 OpenTelemetry 的 traces 的表模型来让用户方便地查询和分析 traces 数据。

### OTLP/HTTP API

你可以使用 [OpenTelemetry SDK](https://opentelemetry.io/docs/languages/) 或其他类似的技术方案来为应用添加 traces 数据。你还可以用 [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) 来收集 traces 数据，并使用 GreptimeDB 作为后端存储。

要通过 OpenTelemetry SDK 库将 OpenTelemetry 的 traces 数据发送到 GreptimeDB，请使用以下信息：

- URL: `http{s}://<host>/v1/otlp/v1/traces`
- Headers:
  - `Content-Type`: 应配置为 `application/x-protobuf`
  - `Authorization`: `Basic` 认证。
  - `X-Greptime-DB-Name`: `<dbname>`
  - `X-Greptime-Trace-Table-Name`: `<table_name>`（可选）- 存储 traces 的表名。如果未提供，默认表名为 `opentelemetry_traces`。
  - `X-Greptime-Pipeline-Name`: `greptime_trace_v1`（必选）- 处理 traces 的 pipeline 名称。

GreptimeDB 会通过 **HTTP 协议** 接受 **protobuf 编码的 traces 数据**。

### 示例代码

你可以直接将 OpenTelemetry traces 数据发送到 GreptimeDB，也可以使用 OpenTelemetry Collector 来收集 traces 数据，并使用 GreptimeDB 作为后端存储，请参考 [OpenTelemetry Collector 文档](/user-guide/traces/read-write.md#opentelemetry-collector)中的示例代码，了解如何将 OpenTelemetry traces 数据发送到 GreptimeDB。

### 数据模型

GreptimeDB 将 OTLP traces 数据模型映射到表结构。默认情况下，Trace 数据存储在 `opentelemetry_traces` 表中。

```sql
+------------------------------------+---------------------+------+------+---------+---------------+
| Column                             | Type                | Key  | Null | Default | Semantic Type |
+------------------------------------+---------------------+------+------+---------+---------------+
| timestamp                          | TimestampNanosecond | PRI  | NO   |         | TIMESTAMP     |
| timestamp_end                      | TimestampNanosecond |      | YES  |         | FIELD         |
| duration_nano                      | UInt64              |      | YES  |         | FIELD         |
| parent_span_id                     | String              |      | YES  |         | FIELD         |
| trace_id                           | String              |      | YES  |         | FIELD         |
| span_id                            | String              |      | YES  |         | FIELD         |
| span_kind                          | String              |      | YES  |         | FIELD         |
| span_name                          | String              |      | YES  |         | FIELD         |
| span_status_code                   | String              |      | YES  |         | FIELD         |
| span_status_message                | String              |      | YES  |         | FIELD         |
| trace_state                        | String              |      | YES  |         | FIELD         |
| scope_name                         | String              |      | YES  |         | FIELD         |
| scope_version                      | String              |      | YES  |         | FIELD         |
| service_name                       | String              | PRI  | YES  |         | TAG           |
| span_attributes.net.sock.peer.addr | String              |      | YES  |         | FIELD         |
| span_attributes.peer.service       | String              |      | YES  |         | FIELD         |
| span_events                        | Json                |      | YES  |         | FIELD         |
| span_links                         | Json                |      | YES  |         | FIELD         |
+------------------------------------+---------------------+------+------+---------+---------------+
```

- 每一行代表一个单一的 span。
- `service_name` 用作 **Tag**（**主键**的一部分）。
- `timestamp` 用作 **时间索引**（Time Index）。
- Resource Attributes、Scope Attributes 和 Span Attributes 将被自动展平为单独的列。
  - 注意：`resource_attributes.service.name` 被排除在打平之外，因为它已经存储在 `service_name` 列中。
- `span_events` 和 `span_links` 默认存储为 `JSON` 数据类型。

有关数据模型和辅助表的更多详细信息，请参阅 [Trace 数据模型](/user-guide/traces/data-model.md)。

注意:
1. `greptime_trace_v1` 处理方式默认通过 `trace_id` 字段将数据切分成不同的分区以提升性能。**请确保 `trace_id` 的第一个字符是分布均匀的**。
2. 在非测试的场合下，可以通过设置 `ttl` 以避免持久化数据量过大。通过设置 `x-greptime-hints: ttl=7d` HTTP 请求头，在创建 trace 表时会添加一个 7 天的 `ttl` 表选项。见[此文档](/reference/sql/create.md#表选项)了解更多关于表选项 `ttl` 的信息。

### Schema 演进与 Partial Success

`greptime_trace_v1` 模型会自动添加新的打平 attribute 列。当 trace 表已经存在时，GreptimeDB 会根据已有表结构协调新写入 attribute 的值类型。兼容的标量值可以转换为已有列类型；当已有 `Int64` attribute 列后续收到整数和浮点数混合写入时，该列可能会被扩展为 `Float64`。

如果一次 trace 请求中有部分 span 因 schema 或值不兼容而无法写入，GreptimeDB 仍可能接受其他 span，并返回包含 `rejected_spans` 和 `error_message` 的 OTLP `partial_success` 响应。如果所有 span 都被拒绝，接口会返回 `400 Bad Request`。

### 辅助表

GreptimeDB 会自动创建辅助表（例如 `opentelemetry_traces_services` 和 `opentelemetry_traces_operations`），以便于搜索服务和操作。详情请参阅[辅助表](/user-guide/traces/data-model.md#辅助表)。

### Append-only 模式

通过此接口创建的表，默认为[Append-only 模式](/user-guide/deployments-administration/performance-tuning/design-table.md#何时使用-append-only-表)。
