CLOVER🍀

That was when it all began.

OpenTelemetry SDKのExporterの種類を確認する(+Console Exporterを試す)

これは、なにをしたくて書いたもの?

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については、デバッグや学習目的で利用される想定のものとされています。

トレースについては、これまでの経緯からか選択できる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

https://github.com/open-telemetry/opentelemetry-python-contrib/tree/v0.57b0/instrumentation/opentelemetry-instrumentation-logging

OpenTelemetry Logging Instrumentation — OpenTelemetry Python Contrib documentation

今回はこんなところでしょうか。

おわりに

OpenTelemetry SDKのExporterの種類を確認してみました。

各シグナルでの出力結果を確認しようと思うとついついOpenTelemetry Collectorなどが必要になると思いがちですが、Console Exporterを
使ってデバッグしたり、OTLP以外のデータモデルが扱えることもドキュメントを見るとわかったりしますね。

基本的にはOTLPを使うことになるとは思いますが、知っておくと確認や構成の幅が広がってよさそうです。