これは、なにをしたくて書いたもの?
OpenTelemetryを使っている時に、ちょっとした動作確認みたいなことをしたい時があったりします。
この時に、アプリケーションを作って計装ライブラリーをインストールして…みたいなことをしていると手間な気がするので、簡単なスクリプトを
作ってこれができるようにしてみようかなと。
OpenTelemetry API、OpenTelemetry SDKの勉強がてらに。言語はPythonを使います。
OpenTelemetryの言語APIとSDK
OpenTelemetryの各言語向けのAPIやSDKに関するページはこちら。
Language APIs & SDKs | OpenTelemetry
Pythonに関するページはこちら。
通常はなんらかのフレームワークやライブラリーを使って開発したアプリケーションにOpenTelemetryを組み込むため、Getting Startedには
ゼロコード計装の例が紹介されています。
Getting Started | OpenTelemetry
今回参照するのはこちらですね。
Instrumentation | OpenTelemetry
アプリケーションに計装を組み込むためには、OpenTelemetry SDKを使います。各シグナルを操作する時にはOpenTelemetry APIを使うことに
なります。実際にシグナルを送信するためにはOpenTelemetry SDKが必要です。
計装に関しては、こちらも見るとよいでしょう。
Instrumentation | OpenTelemetry
ゼロコード計装、コードベース、ライブラリーが載っていますが、今回はコードベースの計装を行うことになります。
PythonのOpenTelemetry APIおよびSDKに関するリファレンスはこちら。
OpenTelemetry-Python API Reference — OpenTelemetry Python documentation
OpenTelemetry Python API — OpenTelemetry Python documentation
OpenTelemetry Python SDK — OpenTelemetry Python documentation
今回はメトリクスを扱ってみます。
送信先(Exporter)はコンソール(標準出力)とOTLPの2つにします。
最後に設定の自動構成までやってみましょう。
環境
今回の環境はこちら。
$ python3 --version Python 3.12.3 $ uv --version uv 0.8.15
OpenTelemetry Collector Contribはこちら。
$ otelcol-contrib --version otelcol-contrib version 0.134.0
OpenTelemetry Collector ContribのIPアドレスは172.17.0.2とします。
設定はデフォルトのままです。
/etc/otelcol-contrib/config.yaml
# To limit exposure to denial of service attacks, change the host in endpoints below from 0.0.0.0 to a specific network interface. # See https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/security-best-practices.md#safeguards-against-denial-of-service-attacks extensions: health_check: pprof: endpoint: 0.0.0.0:1777 zpages: endpoint: 0.0.0.0:55679 receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 # Collect own metrics prometheus: config: scrape_configs: - job_name: 'otel-collector' scrape_interval: 10s static_configs: - targets: ['0.0.0.0:8888'] jaeger: protocols: grpc: endpoint: 0.0.0.0:14250 thrift_binary: endpoint: 0.0.0.0:6832 thrift_compact: endpoint: 0.0.0.0:6831 thrift_http: endpoint: 0.0.0.0:14268 zipkin: endpoint: 0.0.0.0:9411 processors: batch: exporters: debug: verbosity: detailed service: pipelines: traces: receivers: [otlp, jaeger, zipkin] processors: [batch] exporters: [debug] metrics: receivers: [otlp, prometheus] processors: [batch] exporters: [debug] logs: receivers: [otlp] processors: [batch] exporters: [debug] extensions: [health_check, pprof, zpages]
準備
uvでプロジェクトを作成します。
$ uv init --vcs none simple-metrics-sender $ cd simple-metrics-sender $ rm main.py
必要なライブラリーをインストール。この後でまた追加します。
$ uv add opentelemetry-api opentelemetry-sdk $ uv add --dev mypy ruff
pyproject.toml
[project]
name = "simple-metrics-sender"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"opentelemetry-api>=1.36.0",
"opentelemetry-sdk>=1.36.0",
]
[dependency-groups]
dev = [
"mypy>=1.17.1",
"ruff>=0.12.12",
]
[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 ---------------------------------- ------- importlib-metadata 8.7.0 mypy 1.17.1 mypy-extensions 1.1.0 opentelemetry-api 1.36.0 opentelemetry-sdk 1.36.0 opentelemetry-semantic-conventions 0.57b0 pathspec 0.12.1 ruff 0.12.12 typing-extensions 4.15.0 zipp 3.23.0
ここからスタートです。
メトリクスを標準出力に書き出す
最初はメトリクスを標準出力に書き出してみましょう。
このあたりを見ながら
ソースコードを作成。
app.py
from opentelemetry import metrics from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import ( ConsoleMetricExporter, PeriodicExportingMetricReader, ) metric_reader = PeriodicExportingMetricReader(ConsoleMetricExporter()) provider = MeterProvider(metric_readers=[metric_reader]) metrics.set_meter_provider(provider) meter = metrics.get_meter("my.app.telemetry") gause = meter.create_gauge("my.gause", unit="value", description="Sample Gause") gause.set(15) counter = counter = meter.create_counter( "my.counter", unit="count", description="Sample Counter" ) counter.add(5) provider.shutdown()
今回はメトリクスのうちGaugeとCounterを使うことにしました。
service.nameは環境変数で設定しました。
$ export OTEL_SERVICE_NAME=app
実行。すぐに終了して、標準出力にメトリクスが書き出されます。
$ uv run app.py
{
"resource_metrics": [
{
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.36.0",
"service.name": "app"
},
"schema_url": ""
},
"scope_metrics": [
{
"scope": {
"name": "my.app.telemetry",
"version": "",
"schema_url": "",
"attributes": null
},
"metrics": [
{
"name": "my.gause",
"description": "Sample Gause",
"unit": "value",
"data": {
"data_points": [
{
"attributes": {},
"start_time_unix_nano": null,
"time_unix_nano": 1757169750858828777,
"value": 15,
"exemplars": []
}
]
}
},
{
"name": "my.counter",
"description": "Sample Counter",
"unit": "count",
"data": {
"data_points": [
{
"attributes": {},
"start_time_unix_nano": 1757169750858751308,
"time_unix_nano": 1757169750858828777,
"value": 5,
"exemplars": []
}
],
"aggregation_temporality": 2,
"is_monotonic": true
}
}
],
"schema_url": ""
}
],
"schema_url": ""
}
]
}
OKですね。
OTLPで送信する
次はOTLPで送信してみましょう。
opentelemetry-exporter-otlpを追加。
$ uv add opentelemetry-exporter-otlp
pyproject.tomlのdependenciesはこうなりました。
dependencies = [
"opentelemetry-api>=1.36.0",
"opentelemetry-exporter-otlp>=1.36.0",
"opentelemetry-sdk>=1.36.0",
]
インストールしたライブラリーの一覧。
$ uv pip list Package Version ---------------------------------------- -------- certifi 2025.8.3 charset-normalizer 3.4.3 googleapis-common-protos 1.70.0 grpcio 1.74.0 idna 3.10 importlib-metadata 8.7.0 mypy 1.17.1 mypy-extensions 1.1.0 opentelemetry-api 1.36.0 opentelemetry-exporter-otlp 1.36.0 opentelemetry-exporter-otlp-proto-common 1.36.0 opentelemetry-exporter-otlp-proto-grpc 1.36.0 opentelemetry-exporter-otlp-proto-http 1.36.0 opentelemetry-proto 1.36.0 opentelemetry-sdk 1.36.0 opentelemetry-semantic-conventions 0.57b0 pathspec 0.12.1 protobuf 6.32.0 requests 2.32.5 ruff 0.12.12 typing-extensions 4.15.0 urllib3 2.5.0 zipp 3.23.0
ソースコードはこのようなものを作成。
app_otlp.py
from opentelemetry import metrics from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter metric_reader = PeriodicExportingMetricReader( OTLPMetricExporter(endpoint="http://172.17.0.2:4318/v1/metrics") ) provider = MeterProvider(metric_readers=[metric_reader]) metrics.set_meter_provider(provider) meter = metrics.get_meter("my.app.telemetry") gause = meter.create_gauge("my.gause", unit="value", description="Sample Gause") gause.set(15) counter = counter = meter.create_counter( "my.counter", unit="count", description="Sample Counter" ) counter.add(5) provider.shutdown()
先ほどとの違いはExporterですね。
metric_reader = PeriodicExportingMetricReader(
OTLPMetricExporter(endpoint="http://172.17.0.2:4318/v1/metrics")
)
実行。
$ uv run app_otlp.py
今回はOpenTelemetry Collector Contrib側でこんな出力が得られます。
2025-09-06T15:05:46.781Z info Metrics {"resource": {"service.instance.id": "822bf5e0-e250-48f4-89fb-2df940d8f950", "service.name": "otelcol-contrib", "service.version": "0.134.0"}, "otelcol.component.id": "debug", "otelcol.component.kind": "exporter", "otelcol.signal": "metrics", "resource metrics": 1, "metrics": 2, "data points": 2}
2025-09-06T15:05:46.781Z info ResourceMetrics #0
Resource SchemaURL:
Resource attributes:
-> telemetry.sdk.language: Str(python)
-> telemetry.sdk.name: Str(opentelemetry)
-> telemetry.sdk.version: Str(1.36.0)
-> service.name: Str(app)
ScopeMetrics #0
ScopeMetrics SchemaURL:
InstrumentationScope my.app.telemetry
Metric #0
Descriptor:
-> Name: my.gause
-> Description: Sample Gause
-> Unit: value
-> DataType: Gauge
NumberDataPoints #0
StartTimestamp: 1970-01-01 00:00:00 +0000 UTC
Timestamp: 2025-09-06 15:05:46.737547332 +0000 UTC
Value: 15
Metric #1
Descriptor:
-> Name: my.counter
-> Description: Sample Counter
-> Unit: count
-> DataType: Sum
-> IsMonotonic: true
-> AggregationTemporality: Cumulative
NumberDataPoints #0
StartTimestamp: 2025-09-06 15:05:46.73741025 +0000 UTC
Timestamp: 2025-09-06 15:05:46.737547332 +0000 UTC
Value: 5
{"resource": {"service.instance.id": "822bf5e0-e250-48f4-89fb-2df940d8f950", "service.name": "otelcol-contrib", "service.version": "0.134.0"}, "otelcol.component.id": "debug", "otelcol.component.kind": "exporter", "otelcol.signal": "metrics"}
これはOpenTelemetry Collector Contribでの出力設定がdebugだからですね。
metrics: receivers: [otlp, prometheus] processors: [batch] exporters: [debug]
こちらもOKでした。
自動構成する
ここまで、Exporterを固定のクラス名で書いてきました。
実際に使う時は、環境変数で切り替えたいですね。
ここで、OpenTelemetry Distroを使います。
OpenTelemetry Distro | OpenTelemetry
正確には、opentelemetry-instrumentationに環境変数から設定を読み取って自動構成する処理が含まれているんですよね。
依存ライブラリーを追加。
$ uv add opentelemetry-distro[otlp] opentelemetry-instrumentation $ uv add --dev mypy ruff
pyproject.tomlのdependenciesは作り直しました。
dependencies = [
"opentelemetry-distro[otlp]>=0.57b0",
"opentelemetry-instrumentation>=0.57b0",
]
[dependency-groups]
dev = [
"mypy>=1.17.1",
"ruff>=0.12.12",
]
インストールしたライブラリーの一覧。
$ uv pip list Package Version ---------------------------------------- -------- certifi 2025.8.3 charset-normalizer 3.4.3 googleapis-common-protos 1.70.0 grpcio 1.74.0 idna 3.10 importlib-metadata 8.7.0 mypy 1.17.1 mypy-extensions 1.1.0 opentelemetry-api 1.36.0 opentelemetry-distro 0.57b0 opentelemetry-exporter-otlp 1.36.0 opentelemetry-exporter-otlp-proto-common 1.36.0 opentelemetry-exporter-otlp-proto-grpc 1.36.0 opentelemetry-exporter-otlp-proto-http 1.36.0 opentelemetry-instrumentation 0.57b0 opentelemetry-proto 1.36.0 opentelemetry-sdk 1.36.0 opentelemetry-semantic-conventions 0.57b0 packaging 25.0 pathspec 0.12.1 protobuf 6.32.0 requests 2.32.5 ruff 0.12.12 typing-extensions 4.15.0 urllib3 2.5.0 wrapt 1.17.3 zipp 3.23.0
ソースコードはこのように変更。
app_auto.py
from opentelemetry import metrics provider = metrics.get_meter_provider() meter = metrics.get_meter("my.app.telemetry") gause = meter.create_gauge("my.gause", unit="value", description="Sample Gause") gause.set(15) counter = counter = meter.create_counter( "my.counter", unit="count", description="Sample Counter" ) counter.add(5) provider.shutdown()
Exporterのクラスが固定されなくなりました。
設定を環境変数で行います。
$ export OTEL_TRACES_EXPORTER=none $ export OTEL_METRICS_EXPORTER=otlp $ export OTEL_LOGS_EXPORTER=none $ export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://172.17.0.2:4318/v1/metrics $ export OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=http/protobuf $ export OTEL_SERVICE_NAME=app
実行。
$ uv run opentelemetry-instrument python3 app_auto.py
OpenTelemetry Collector Contrib側にはこんな結果が得られます。先ほどと同じですね。
2025-09-06T15:33:50.904Z info Metrics {"resource": {"service.instance.id": "ec9f7f2a-240f-4ffa-9cf0-70dc78965996", "service.name": "otelcol-contrib", "service.version": "0.134.0"}, "otelcol.component.id": "debug", "otelcol.component.kind": "exporter", "otelcol.signal": "metrics", "resource metrics": 1, "metrics": 2, "data points": 2}
2025-09-06T15:33:50.905Z info ResourceMetrics #0
Resource SchemaURL:
Resource attributes:
-> telemetry.sdk.language: Str(python)
-> telemetry.sdk.name: Str(opentelemetry)
-> telemetry.sdk.version: Str(1.36.0)
-> service.name: Str(app)
-> telemetry.auto.version: Str(0.57b0)
ScopeMetrics #0
ScopeMetrics SchemaURL:
InstrumentationScope my.app.telemetry
Metric #0
Descriptor:
-> Name: my.gause
-> Description: Sample Gause
-> Unit: value
-> DataType: Gauge
NumberDataPoints #0
StartTimestamp: 1970-01-01 00:00:00 +0000 UTC
Timestamp: 2025-09-06 15:33:50.806934958 +0000 UTC
Value: 15
Metric #1
Descriptor:
-> Name: my.counter
-> Description: Sample Counter
-> Unit: count
-> DataType: Sum
-> IsMonotonic: true
-> AggregationTemporality: Cumulative
NumberDataPoints #0
StartTimestamp: 2025-09-06 15:33:50.806893705 +0000 UTC
Timestamp: 2025-09-06 15:33:50.806934958 +0000 UTC
Value: 5
{"resource": {"service.instance.id": "ec9f7f2a-240f-4ffa-9cf0-70dc78965996", "service.name": "otelcol-contrib", "service.version": "0.134.0"}, "otelcol.component.id": "debug", "otelcol.component.kind": "exporter", "otelcol.signal": "metrics"}
ここで出力先を標準出力にしてみましょう。
$ export OTEL_METRICS_EXPORTER=console
今度はメトリクスが標準出力に書き出されるようになりました。
$ uv run opentelemetry-instrument python3 app_auto.py
{
"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": "my.app.telemetry",
"version": "",
"schema_url": "",
"attributes": null
},
"metrics": [
{
"name": "my.gause",
"description": "Sample Gause",
"unit": "value",
"data": {
"data_points": [
{
"attributes": {},
"start_time_unix_nano": null,
"time_unix_nano": 1757172889317428348,
"value": 15,
"exemplars": []
}
]
}
},
{
"name": "my.counter",
"description": "Sample Counter",
"unit": "count",
"data": {
"data_points": [
{
"attributes": {},
"start_time_unix_nano": 1757172889317347929,
"time_unix_nano": 1757172889317428348,
"value": 5,
"exemplars": []
}
],
"aggregation_temporality": 2,
"is_monotonic": true
}
}
],
"schema_url": ""
}
],
"schema_url": ""
}
]
}
OKですね。
おわりに
PythonのOpenTelemetry SDKを使って、メトリクスを送信する単純なスクリプトを作成してみました。
Exporterを決め打ちするのならサンプルはあるのですが、opentelemetry-instrumentationを単体で使った例はなかったのでちょっと苦労しました。
まあ、前にOpenTelemetry Distroやその周辺を見ておいてよかったです。
PythonのOpenTelemetry計装ライブラリーの導入方法がよくわからなかったので、pipとuvを使ってFastAPIで試してみる - CLOVER🍀