これは、なにをしたくて書いたもの?
OpenTelemetry SDKで使えるExporterを確認しておこうかなと思いまして。
OpenTelemetry SDKの設定とExporterの設定
OpenTelemetry SDKは各言語ごとに設定がありますが、一般的なものやOTLP Exporterについてはこちらのページにまとめられています。
SDK Configuration | OpenTelemetry
一般的な設定はこちら。
General SDK Configuration | OpenTelemetry
この中に各シグナルで指定できるExporterが書かれています。
| シグナル | Exporterに指定する値 | 意味 |
|---|---|---|
| トレース(OTEL_TRACES_EXPORTER) | otlp | OTLP |
| jaeger | Jaegerのデータモデルでエクスポートする | |
| zipkin | Zipkinのデータモデルでエクスポートする | |
| console | 標準出力へエクスポートする | |
| none | トレースシグナルをエクスポートしないように自動構成する | |
| メトリクス(OTEL_METRICS_EXPORTER) | otlp | OTLP |
| prometheus | Prometheusのフォーマットでエクスポートする | |
| console | 標準出力へエクスポートする | |
| none | メトリクスシグナルをエクスポートしないように自動構成する | |
| ログ(OTEL_LOGS_EXPORTER) | otlp | OTLP |
| console | 標準出力へエクスポートする | |
| none | ログシグナルをエクスポートしないように自動構成する |
OTLP(OpenTelemetry Protocol)については共通なので、こちらに説明があります。
OTLP Exporter Configuration | OpenTelemetry
また標準出力へ書き出すconsoleについては、デバッグや学習目的で利用される想定のものとされています。
- Span Exporter - Standard output | OpenTelemetry
- Metrics Exporter - Standard output | OpenTelemetry
- Logs Exporter - Standard output | OpenTelemetry
トレースについては、これまでの経緯からか選択できるExporterが多いようです。
実際にOpenTelemetry SDKを使う時には、使用するExporterに対するライブラリーなどを導入することになります。
通常はOTLPを使うと思うのですが、ちょっとした確認などでOpenTelemetry Collectorやシグナルを収集するミドルウェアを用意するのも
大変なので、consoleの存在は便利な気がしますね。
種類は確認したので、consoleも少し試してみましょう。Python、FastAPIで行うことにします。
環境
今回の環境はこちら。
$ python3 --version Python 3.12.3 $ uv --version uv 0.8.14
サンプルアプリケーションを作成する
OpenTelemetry SDKを組み込んだ、簡単なFastAPIアプリケーションを作成します。
uvでプロジェクトを作成。
$ uv init --vcs none otel-exporter $ cd otel-exporter $ rm main.py
ライブラリーをインストール。
$ uv add 'fastapi[standard]' $ uv add --dev opentelemetry-distro $ uv run opentelemetry-bootstrap -a requirements | xargs uv add $ uv add --dev mypy ruff
ふつうはここでopentelemetry-exporter-otlpを追加する気がするのですが、今回は入れません。
pyproject.toml
[project]
name = "otel-exporter"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"fastapi[standard]>=0.116.1",
"opentelemetry-instrumentation-asyncio==0.57b0",
"opentelemetry-instrumentation-click==0.57b0",
"opentelemetry-instrumentation-dbapi==0.57b0",
"opentelemetry-instrumentation-fastapi==0.57b0",
"opentelemetry-instrumentation-httpx==0.57b0",
"opentelemetry-instrumentation-jinja2==0.57b0",
"opentelemetry-instrumentation-logging==0.57b0",
"opentelemetry-instrumentation-sqlite3==0.57b0",
"opentelemetry-instrumentation-starlette==0.57b0",
"opentelemetry-instrumentation-threading==0.57b0",
"opentelemetry-instrumentation-tortoiseorm==0.57b0",
"opentelemetry-instrumentation-urllib==0.57b0",
"opentelemetry-instrumentation-urllib3==0.57b0",
"opentelemetry-instrumentation-wsgi==0.57b0",
]
[dependency-groups]
dev = [
"mypy>=1.17.1",
"opentelemetry-distro>=0.57b0",
"ruff>=0.12.11",
]
[tool.mypy]
strict = true
disallow_any_unimported = true
disallow_any_expr = true
disallow_any_explicit = true
warn_unreachable = true
pretty = true
インストールした依存ライブラリーの一覧。
$ uv pip list Package Version ----------------------------------------- -------- annotated-types 0.7.0 anyio 4.10.0 asgiref 3.9.1 certifi 2025.8.3 click 8.2.1 dnspython 2.7.0 email-validator 2.3.0 fastapi 0.116.1 fastapi-cli 0.0.8 fastapi-cloud-cli 0.1.5 h11 0.16.0 httpcore 1.0.9 httptools 0.6.4 httpx 0.28.1 idna 3.10 importlib-metadata 8.7.0 jinja2 3.1.6 markdown-it-py 4.0.0 markupsafe 3.0.2 mdurl 0.1.2 mypy 1.17.1 mypy-extensions 1.1.0 opentelemetry-api 1.36.0 opentelemetry-distro 0.57b0 opentelemetry-instrumentation 0.57b0 opentelemetry-instrumentation-asgi 0.57b0 opentelemetry-instrumentation-asyncio 0.57b0 opentelemetry-instrumentation-click 0.57b0 opentelemetry-instrumentation-dbapi 0.57b0 opentelemetry-instrumentation-fastapi 0.57b0 opentelemetry-instrumentation-httpx 0.57b0 opentelemetry-instrumentation-jinja2 0.57b0 opentelemetry-instrumentation-logging 0.57b0 opentelemetry-instrumentation-sqlite3 0.57b0 opentelemetry-instrumentation-starlette 0.57b0 opentelemetry-instrumentation-threading 0.57b0 opentelemetry-instrumentation-tortoiseorm 0.57b0 opentelemetry-instrumentation-urllib 0.57b0 opentelemetry-instrumentation-urllib3 0.57b0 opentelemetry-instrumentation-wsgi 0.57b0 opentelemetry-sdk 1.36.0 opentelemetry-semantic-conventions 0.57b0 opentelemetry-util-http 0.57b0 packaging 25.0 pathspec 0.12.1 pydantic 2.11.7 pydantic-core 2.33.2 pygments 2.19.2 python-dotenv 1.1.1 python-multipart 0.0.20 pyyaml 6.0.2 rich 14.1.0 rich-toolkit 0.15.0 rignore 0.6.4 ruff 0.12.11 sentry-sdk 2.35.1 shellingham 1.5.4 sniffio 1.3.1 starlette 0.47.3 typer 0.16.1 typing-extensions 4.15.0 typing-inspection 0.4.1 urllib3 2.5.0 uvicorn 0.35.0 uvloop 0.21.0 watchfiles 1.1.0 websockets 15.0.1 wrapt 1.17.3 zipp 3.23.0
ソースコードを作成。
app.py
import logging import fastapi from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") # formatter = logging.Formatter("%(asctime)s - [trace_id=%(otelTraceID)s span_id=%(otelSpanID)s resource.service.name=%(otelServiceName)s trace_sampled=%(otelTraceSampled)s] - %(name)s - %(levelname)s - %(message)s") handler = logging.StreamHandler() handler.setLevel(logging.INFO) handler.setFormatter(formatter) logger.addHandler(handler) app = fastapi.FastAPI() @app.get("/hello") async def hello() -> dict[str, str]: logger.info("log message") return {"message": "hello world"} FastAPIInstrumentor.instrument_app(app)
起動。
$ uv run fastapi run app.py
確認。
$ curl localhost:8000/hello
{"message":"hello world"}
ログはこんな感じで出力されます。
2025-08-30 19:41:33,383 - app - INFO - log message
Exporterをconsoleに設定する
それでは、Exporterをconsoleに設定して動かしてみましょう。環境変数はこのように設定。
$ export OTEL_TRACES_EXPORTER=console $ export OTEL_METRICS_EXPORTER=console $ export OTEL_LOGS_EXPORTER=none $ export OTEL_SERVICE_NAME=app
ログシグナルはPythonはDevelopmentステータスで、ちょっとうまく動かせなかったので今回は除外しました。
起動。
$ uv run opentelemetry-instrument fastapi run app.py
アクセスしてみます。
$ curl localhost:8000/hello
すると、こんなトレースデータが標準出力に書き出されます。
{
"name": "GET /hello http send",
"context": {
"trace_id": "0x3490345f03a39adb8150c92234379f96",
"span_id": "0xf74762b6f3debe5e",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": "0xae4979247c04bd72",
"start_time": "2025-08-30T10:42:41.902792Z",
"end_time": "2025-08-30T10:42:41.902909Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"asgi.event.type": "http.response.start",
"http.status_code": 200
},
"events": [],
"links": [],
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.36.0",
"service.name": "app",
"telemetry.auto.version": "0.57b0"
},
"schema_url": ""
}
}
{
"name": "GET /hello http send",
"context": {
"trace_id": "0x3490345f03a39adb8150c92234379f96",
"span_id": "0xc5a8b6b3f464b441",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": "0xae4979247c04bd72",
"start_time": "2025-08-30T10:42:41.904584Z",
"end_time": "2025-08-30T10:42:41.904637Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"asgi.event.type": "http.response.body"
},
"events": [],
"links": [],
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.36.0",
"service.name": "app",
"telemetry.auto.version": "0.57b0"
},
"schema_url": ""
}
}
{
"name": "GET /hello",
"context": {
"trace_id": "0x3490345f03a39adb8150c92234379f96",
"span_id": "0xae4979247c04bd72",
"trace_state": "[]"
},
"kind": "SpanKind.SERVER",
"parent_id": null,
"start_time": "2025-08-30T10:42:41.901522Z",
"end_time": "2025-08-30T10:42:41.904739Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"http.scheme": "http",
"http.host": "127.0.0.1:8000",
"net.host.port": 8000,
"http.flavor": "1.1",
"http.target": "/hello",
"http.url": "http://127.0.0.1:8000/hello",
"http.method": "GET",
"http.server_name": "localhost:8000",
"http.user_agent": "curl/8.5.0",
"net.peer.ip": "127.0.0.1",
"net.peer.port": 37792,
"http.route": "/hello",
"http.status_code": 200
},
"events": [],
"links": [],
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.36.0",
"service.name": "app",
"telemetry.auto.version": "0.57b0"
},
"schema_url": ""
}
}
また、しばらく待っているとこんなメトリクスが標準出力に書き出されます。
{
"resource_metrics": [
{
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.36.0",
"service.name": "app",
"telemetry.auto.version": "0.57b0"
},
"schema_url": ""
},
"scope_metrics": [
{
"scope": {
"name": "opentelemetry.instrumentation.asyncio",
"version": "0.57b0",
"schema_url": "",
"attributes": null
},
"metrics": [
{
"name": "asyncio.process.duration",
"description": "Duration of asyncio process",
"unit": "s",
"data": {
"data_points": [
{
"attributes": {
"type": "future",
"state": "finished"
},
"start_time_unix_nano": 1756550547733977590,
"time_unix_nano": 1756550607302185036,
"count": 1,
"sum": 4.787299985764548e-05,
"bucket_counts": [
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"explicit_bounds": [
0.0,
5.0,
10.0,
25.0,
50.0,
75.0,
100.0,
250.0,
500.0,
750.0,
1000.0,
2500.0,
5000.0,
7500.0,
10000.0
],
"min": 4.787299985764548e-05,
"max": 4.787299985764548e-05,
"exemplars": []
}
],
"aggregation_temporality": 2
}
},
{
"name": "asyncio.process.created",
"description": "Number of asyncio process",
"unit": "{process}",
"data": {
"data_points": [
{
"attributes": {
"type": "future",
"state": "finished"
},
"start_time_unix_nano": 1756550547734021457,
"time_unix_nano": 1756550607302185036,
"value": 1,
"exemplars": []
}
],
"aggregation_temporality": 2,
"is_monotonic": true
}
}
],
"schema_url": ""
},
{
"scope": {
"name": "opentelemetry.instrumentation.fastapi",
"version": "0.57b0",
"schema_url": "https://opentelemetry.io/schemas/1.11.0",
"attributes": null
},
"metrics": [
{
"name": "http.server.active_requests",
"description": "Number of active HTTP server requests.",
"unit": "{request}",
"data": {
"data_points": [
{
"attributes": {
"http.scheme": "http",
"http.host": "127.0.0.1:8000",
"http.flavor": "1.1",
"http.method": "GET",
"http.server_name": "localhost:8000"
},
"start_time_unix_nano": 1756550561901675515,
"time_unix_nano": 1756550607302185036,
"value": 0,
"exemplars": []
}
],
"aggregation_temporality": 2,
"is_monotonic": false
}
},
{
"name": "http.server.duration",
"description": "Measures the duration of inbound HTTP requests.",
"unit": "ms",
"data": {
"data_points": [
{
"attributes": {
"http.scheme": "http",
"http.host": "127.0.0.1:8000",
"net.host.port": 8000,
"http.flavor": "1.1",
"http.method": "GET",
"http.server_name": "localhost:8000",
"http.status_code": 200,
"http.target": "/hello"
},
"start_time_unix_nano": 1756550561904944981,
"time_unix_nano": 1756550607302185036,
"count": 1,
"sum": 4,
"bucket_counts": [
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"explicit_bounds": [
0.0,
5.0,
10.0,
25.0,
50.0,
75.0,
100.0,
250.0,
500.0,
750.0,
1000.0,
2500.0,
5000.0,
7500.0,
10000.0
],
"min": 4,
"max": 4,
"exemplars": []
}
],
"aggregation_temporality": 2
}
},
{
"name": "http.server.response.size",
"description": "measures the size of HTTP response messages (compressed).",
"unit": "By",
"data": {
"data_points": [
{
"attributes": {
"http.scheme": "http",
"http.host": "127.0.0.1:8000",
"net.host.port": 8000,
"http.flavor": "1.1",
"http.method": "GET",
"http.server_name": "localhost:8000",
"http.status_code": 200,
"http.target": "/hello"
},
"start_time_unix_nano": 1756550561905066985,
"time_unix_nano": 1756550607302185036,
"count": 1,
"sum": 25,
"bucket_counts": [
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"explicit_bounds": [
0.0,
5.0,
10.0,
25.0,
50.0,
75.0,
100.0,
250.0,
500.0,
750.0,
1000.0,
2500.0,
5000.0,
7500.0,
10000.0
],
"min": 25,
"max": 25,
"exemplars": []
}
],
"aggregation_temporality": 2
}
}
],
"schema_url": "https://opentelemetry.io/schemas/1.11.0"
}
],
"schema_url": ""
}
]
}
よさそうですね。
ちなみにログについてはExporterはうまく動かせませんでしたが、loggingに対する計装は入っているので、以下のようにフォーマットを変更すると
# formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") formatter = logging.Formatter("%(asctime)s - [trace_id=%(otelTraceID)s span_id=%(otelSpanID)s resource.service.name=%(otelServiceName)s trace_sampled=%(otelTraceSampled)s] - %(name)s - %(levelname)s - %(message)s")
ログにトレースやスパンのIDを入れることができます。
2025-08-30 19:45:33,130 - [trace_id=e82ec3931b401d796a866024cb06dea2 span_id=0fb50c12c483b7e0 resource.service.name=app trace_sampled=True] - app - INFO - log message
OpenTelemetry Logging Instrumentation — OpenTelemetry Python Contrib documentation
今回はこんなところでしょうか。
おわりに
OpenTelemetry SDKのExporterの種類を確認してみました。
各シグナルでの出力結果を確認しようと思うとついついOpenTelemetry Collectorなどが必要になると思いがちですが、Console Exporterを
使ってデバッグしたり、OTLP以外のデータモデルが扱えることもドキュメントを見るとわかったりしますね。
基本的にはOTLPを使うことになるとは思いますが、知っておくと確認や構成の幅が広がってよさそうです。