PythonのOpenTelemetryを触ってみた
初めに
この記事を書いているのは2025年の3月で2024年度が終わろうとしています。
4月にOpenTelemetryを勉強しようと思っていたのですが、
ずるずる着手しなかった反省をこめて滑りこみでお勉強した内容をまとめました。
OpenTelemetryとは
Gemini先生に100文字で要約してもらうと以下の回答になりました。
様々な環境で動作するアプリケーションの監視に必要な、分散トレーシング、メトリクス、ログといったテレメトリーデータを収集・管理するためのオープンソースプロジェクトです。ベンダーに依存せず、オブザーバビリティを高めるための標準的な仕組みを提供します
一つ注意が必要なのは、OpenTelemetryはテレメトリーデータを収集が役割ですので、
収集したデータを描画するためのツールは別で準備する必要があります。
触ってみた
OpenTelemetryのサイトにあるサンプルを触ってみました。
このサンプルは、Flaskのサーバー用のpythonスクリプトとそこにリクエストするpythonスクリプトを準備して、クライントからの通信がトレースできることを確認するものです。
またこの記事では追加で
サンプルを少し弄ってローカルのDockerで起動するJaegerに表示できるようにします。
pythonの環境を準備
仮想環境を作成
python3 -m venv venv
source ./venv/bin/activate
必要なライブラリをインストール
pip install opentelemetry-distro
pip install flask requests
opentelemetry-bootstrap -a install
ソースコードの準備
以下のclient.pyとserver_manual.pyをお借りします。
まずサンプルどおりに実行
サーバー側のserver_manual.pyをまず実行
source ./venv/bin/activate
python server_manual.py
別のターミナルを動かしてクライアント側のclient.pyを実行
source ./venv/bin/activate
python client.py testing
クライアントからサーバーへリクエストが一件飛びます。
それに合わせてOpenTelemetryのトレース結果がクライアント側とサーバー側に一件ずつ出力されます。
クライアント側の出力
{
"name": "client-server",
"context": {
"trace_id": "0xec05ef8aa5297d2414149afd9ffed128",
"span_id": "0x20fe72c60bbd9f5a",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": "0x329fe1907aef791a",
"start_time": "2025-03-21T06:59:19.488264Z",
"end_time": "2025-03-21T06:59:19.505840Z",
"status": {
"status_code": "UNSET"
},
"attributes": {},
"events": [],
"links": [],
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.31.1",
"service.name": "unknown_service"
},
"schema_url": ""
}
}
{
"name": "client",
"context": {
"trace_id": "0xec05ef8aa5297d2414149afd9ffed128",
"span_id": "0x329fe1907aef791a",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": null,
"start_time": "2025-03-21T06:59:19.488190Z",
"end_time": "2025-03-21T06:59:19.505876Z",
"status": {
"status_code": "UNSET"
},
"attributes": {},
"events": [],
"links": [],
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.31.1",
"service.name": "unknown_service"
},
"schema_url": ""
}
}
サーバー側出力
{
"name": "server_request",
"context": {
"trace_id": "0xec05ef8aa5297d2414149afd9ffed128",
"span_id": "0x5cf8fe2ab289e0b3",
"trace_state": "[]"
},
"kind": "SpanKind.SERVER",
"parent_id": "0x20fe72c60bbd9f5a",
"start_time": "2025-03-21T06:59:19.503744Z",
"end_time": "2025-03-21T06:59:19.503906Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"http.method": "GET",
"http.server_name": "127.0.0.1",
"http.scheme": "http",
"net.host.name": "localhost:8082",
"http.host": "localhost:8082",
"net.host.port": 8082,
"http.target": "/server_request?param=testing",
"net.peer.ip": "127.0.0.1",
"net.peer.port": 61331,
"http.user_agent": "python-requests/2.32.3",
"http.flavor": "1.1"
},
"events": [],
"links": [],
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.31.1",
"service.name": "unknown_service"
},
"schema_url": ""
}
}
Jaegerに出力されるように修正
ライブラリのインストール
OTLPSpanExporterを使えるようにするためのライブラリを追加
source ./venv/bin/activate
pip install opentelemetry.exporter.otlp
クライアント側のclient.pyを修正
OTLPSpanExporterを設定します。
ついでにSERVICE_NAMEとして"client"を設定します。
--- client.py.org 2025-03-21 16:36:28.000000000 +0900
+++ client.py 2025-03-21 17:50:08.000000000 +0900
@@ -24,11 +24,21 @@
ConsoleSpanExporter,
)
-trace.set_tracer_provider(TracerProvider())
+from opentelemetry.sdk.resources import SERVICE_NAME, Resource
+from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
+
+
+# Service name is required for most backends
+resource = Resource(attributes={
+ SERVICE_NAME: "client"
+})
+
+
+trace.set_tracer_provider(TracerProvider(resource=resource))
tracer = trace.get_tracer_provider().get_tracer(__name__)
trace.get_tracer_provider().add_span_processor(
- BatchSpanProcessor(ConsoleSpanExporter())
+ BatchSpanProcessor(OTLPSpanExporter())
)
clinet.pyの全文
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from sys import argv
from requests import get
from opentelemetry import trace
from opentelemetry.propagate import inject
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
BatchSpanProcessor,
ConsoleSpanExporter,
)
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
# Service name is required for most backends
resource = Resource(attributes={
SERVICE_NAME: "client"
})
trace.set_tracer_provider(TracerProvider(resource=resource))
tracer = trace.get_tracer_provider().get_tracer(__name__)
trace.get_tracer_provider().add_span_processor(
BatchSpanProcessor(OTLPSpanExporter())
)
assert len(argv) == 2
with tracer.start_as_current_span("client"):
with tracer.start_as_current_span("client-server"):
headers = {}
inject(headers)
requested = get(
"http://localhost:8082/server_request",
params={"param": argv[1]},
headers=headers,
)
assert requested.status_code == 200
サーバー側のserver_manual.pyを修正
こちらもOTLPSpanExporterを設定します。
ついでにSERVICE_NAMEとして"server"を設定します。
+
+from opentelemetry.sdk.resources import SERVICE_NAME, Resource
+from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
+
+# Service name is required for most backends
+resource = Resource(attributes={
+ SERVICE_NAME: "server"
+})
+
app = Flask(__name__)
-set_tracer_provider(TracerProvider())
+set_tracer_provider(TracerProvider(resource=resource))
tracer = get_tracer_provider().get_tracer(__name__)
get_tracer_provider().add_span_processor(
- BatchSpanProcessor(ConsoleSpanExporter())
+ BatchSpanProcessor(OTLPSpanExporter())
)
server_manual.pyの全文
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from flask import Flask, request
from opentelemetry.instrumentation.wsgi import collect_request_attributes
from opentelemetry.propagate import extract
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
BatchSpanProcessor,
ConsoleSpanExporter,
)
from opentelemetry.trace import (
SpanKind,
get_tracer_provider,
set_tracer_provider,
)
app = Flask(__name__)
set_tracer_provider(TracerProvider())
tracer = get_tracer_provider().get_tracer(__name__)
get_tracer_provider().add_span_processor(
BatchSpanProcessor(ConsoleSpanExporter())
)
@app.route("/server_request")
def server_request():
with tracer.start_as_current_span(
"server_request",
context=extract(request.headers),
kind=SpanKind.SERVER,
attributes=collect_request_attributes(request.environ),
):
print(request.args.get("param"))
return "served"
if __name__ == "__main__":
app.run(port=8082)
Jaegerの起動
以下のコマンドでJaegerをdockerで起動します。
docker run -d --name jaeger \
-e COLLECTOR_OTLP_ENABLED=true \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
jaegertracing/all-in-one:1.41
再度リクエストをサーバーにリクエストしてみる。
サーバー側のserver_manual.pyをまず実行
source ./venv/bin/activate
python server_manual.py
別のターミナルを動かしてクライアント側のclient.pyを実行
source ./venv/bin/activate
python client.py testing
今回はターミナルにはログには出てきません。
Jaegerに転送がうまくいったようです。
Jaegerの確認
Jaegerは http://localhost:16686/ で起動しているのでブラウザでアクセスします。
するとちゃんとトレースとしてJaegerにとりこまれていることが確認できます。
感想
正直な話Jaegerの表示させるまで色々つまづいてやっと表示できた感じです。
まだまだ学ぶことは多そうです。。
今回は触らなかったですがOpenTelemetryにはコレクターという機能もあるので
そこを使えるともっと幅が広がりそうだなとは感じています。
Discussion