博客 / 詳情

返回

opentelemetry全鏈路初探--python注入

前言

經過上一節,opentelemetry的基本操作都已經融會貫通,但是有位老哥提出疑問?我的代碼都已經寫完了,為了添加全鏈路,還需要重構之前的代碼嗎?那這個代價太大了。那本章就來討論一下opentelemetry的注入的問題

本小節主要關注python注入

使用裝飾器

使用裝飾器的好處就是非常靈活,並且對代碼入侵很小,只需要裝飾一下即可

decoration-s1.py

import tornado.httpserver as httpserver
import tornado.web
from tornado.ioloop import IOLoop
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.trace import get_tracer


trace.set_tracer_provider(
    TracerProvider(resource=Resource.create({SERVICE_NAME: "decoration-s1"}))
)
span_processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="http://127.0.0.1:4318/v1/traces"))
trace.get_tracer_provider().add_span_processor(span_processor)


def traced(name):
    def decorator(func):
        def wrapper(*args, **kwargs):
            tracer = get_tracer(__name__)
            with tracer.start_as_current_span(name):
                return func(*args, **kwargs)
        return wrapper
    return decorator

class TestFlow(tornado.web.RequestHandler):
    def get(self):
        views()
        self.finish('hello world')

@traced("phase-1")
def views():
    pass

def applications():
    urls = []
    urls.append([r'/', TestFlow])
    return tornado.web.Application(urls)

def main():
    app = applications()
    server = httpserver.HTTPServer(app)
    server.bind(10000, '0.0.0.0')
    server.start(1)
    IOLoop.current().start()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt as e:
        IOLoop.current().stop()
    finally:
        IOLoop.current().close()


查看jaeger:

watermarked-inject_1

串聯多個span

改造decoration-s1.py

...
@traced("phase-1")
def views():
    views_2()

@traced("phase-2")
def views_2():
    pass
...

watermarked-inject_2

跨服務串聯span

改造decoration-s1.py

...
@traced("phase-1")
def views():
    views_2()
    headers = {}
    inject(headers)
    requests.get("http://127.0.0.1:20000", headers=headers)

@traced("phase-2")
def views_2():
    pass

...

新增decoration-s2.py

import tornado.httpserver as httpserver
import tornado.web
from tornado.ioloop import IOLoop
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.trace import get_tracer
from opentelemetry.propagate import extract


trace.set_tracer_provider(
    TracerProvider(resource=Resource.create({SERVICE_NAME: "decoration-s2"}))
)
span_processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="http://127.0.0.1:4318/v1/traces"))
trace.get_tracer_provider().add_span_processor(span_processor)


def traced(name):
    def decorator(func):
        def wrapper(*args, **kwargs):
            tracer = get_tracer(__name__)
            ctx = extract(args[0])
            with tracer.start_as_current_span(name, context=ctx):
                return func(*args, **kwargs)
        return wrapper
    return decorator

class TestFlow(tornado.web.RequestHandler):
    def get(self):
        views(self.request.headers)
        self.finish('hello world')

@traced("phase-3")
def views(headers):
    pass

def applications():
    urls = []
    urls.append([r'/', TestFlow])
    return tornado.web.Application(urls)

def main():
    app = applications()
    server = httpserver.HTTPServer(app)
    server.bind(20000, '0.0.0.0')
    server.start(1)
    IOLoop.current().start()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt as e:
        IOLoop.current().stop()
    finally:
        IOLoop.current().close()

查看jaeger,跨服務已經串聯

watermarked-inject_3

自動注入 opentelemetry-instrumentation

opentelemetry-instrumentation可以自動採集在代碼在不同階段的trace

pip3 install opentelemetry-instrumentation opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install

查看支持的自動採集

▶ pip3 list | grep instrument
opentelemetry-instrumentation                0.56b0
opentelemetry-instrumentation-aiohttp-client 0.56b0
opentelemetry-instrumentation-aiohttp-server 0.56b0
opentelemetry-instrumentation-asyncio        0.56b0
opentelemetry-instrumentation-click          0.56b0
opentelemetry-instrumentation-dbapi          0.56b0
opentelemetry-instrumentation-elasticsearch  0.56b0
opentelemetry-instrumentation-flask          0.56b0
opentelemetry-instrumentation-grpc           0.56b0
opentelemetry-instrumentation-httpx          0.56b0
opentelemetry-instrumentation-jinja2         0.56b0
opentelemetry-instrumentation-kafka-python   0.56b0
opentelemetry-instrumentation-logging        0.56b0
opentelemetry-instrumentation-openai-v2      2.1b0
opentelemetry-instrumentation-pymysql        0.56b0
opentelemetry-instrumentation-redis          0.56b0
opentelemetry-instrumentation-requests       0.56b0
opentelemetry-instrumentation-sqlite3        0.56b0
opentelemetry-instrumentation-system-metrics 0.56b0
opentelemetry-instrumentation-threading      0.56b0
opentelemetry-instrumentation-tornado        0.56b0
opentelemetry-instrumentation-tortoiseorm    0.56b0
opentelemetry-instrumentation-urllib         0.56b0
opentelemetry-instrumentation-urllib3        0.56b0
opentelemetry-instrumentation-wsgi           0.56b0

可以看到,包括常見的mysql、redis、http requests等,都可以自動採集。來驗證一下

auto-s1.py

from tornado.ioloop import IOLoop
import tornado.httpserver as httpserver
import tornado.web
import redis
import pymysql
import requests

class TestFlow(tornado.web.RequestHandler):
    def get(self):
        views()
        self.finish('hello world')

def views():
    get_redis()
    get_db()
    get_s2()

def get_redis():
    r = redis.StrictRedis(host='localhost', port=6379, db=0)
    r.set('name', 'hello')

def get_db():
    db = pymysql.connect(host='localhost', user='root', password='123456')
    cursor = db.cursor()
    cursor.execute("SELECT VERSION()")
    data = cursor.fetchone()
    db.close()

def get_s2():
    requests.get('http://127.0.0.1:20000')

def applications():
    urls = []
    urls.append([r'/', TestFlow])
    return tornado.web.Application(urls)

def main():
    app = applications()
    server = httpserver.HTTPServer(app)
    server.bind(10000, '0.0.0.0')
    server.start(1)
    IOLoop.current().start()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt as e:
        IOLoop.current().stop()
    finally:
        IOLoop.current().close()

用opentelemetry-instrumentation啓動auto-s1.py

▶ opentelemetry-instrument \
    --traces_exporter otlp \
    --service_name auto-s1 \
    --exporter_otlp_endpoint http://0.0.0.0:4317 \
    python3 auto-s1.py

auto-s2.py

from tornado.ioloop import IOLoop
import tornado.httpserver as httpserver
import tornado.web

class TestFlow(tornado.web.RequestHandler):
    def get(self):
        views()
        self.finish('hello world')

def views():
    pass

def applications():
    urls = []
    urls.append([r'/', TestFlow])
    return tornado.web.Application(urls)

def main():
    app = applications()
    server = httpserver.HTTPServer(app)
    server.bind(20000, '0.0.0.0')
    server.start(1)
    IOLoop.current().start()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt as e:
        IOLoop.current().stop()
    finally:
        IOLoop.current().close()

同理,用opentelemetry-instrumentation啓動auto-s2.py

▶ opentelemetry-instrument \
    --traces_exporter otlp \
    --service_name auto-s2 \
    --exporter_otlp_endpoint http://0.0.0.0:4317 \
    python3 auto-s2.py

已經看到整個完整的鏈路追蹤了

watermarked-inject_4

opentelemetry-instrument與裝飾器結合使用

由於opentelemetry-instrument不能跟蹤自定義的模塊,可以結合裝飾器跟蹤重點函數

修改一下auto-s1.py

...
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer(__name__)
span_processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="http://127.0.0.1:4318/v1/traces"))
trace.get_tracer_provider().add_span_processor(span_processor)

def traced_function(func):
    def wrapper(*args, **kwargs):
        with tracer.start_as_current_span(func.__name__):
            return func(*args, **kwargs)
    return wrapper

...

def views():
    get_redis()
    get_db()
    get_important()
    get_s2()

@traced_function
def get_important():
    pass

...

watermarked-inject_5

小結

本節介紹了2種注入的方法

  • 其中裝飾器的方法,提前將trace流程寫好,函數只需要調用裝飾即可完成注入,減少了代碼入侵度
  • 而opentelemetry-instrument則是採用aop思想,將目標模塊(比如pymysql、requests等)動態替換成預定義的模塊,從而實現trace的注入,該方法的優點就是對業務代碼的無入侵
  • 在python中,結合以上兩種方法,可以很好的完成trace注入

聯繫我

  • 聯繫我,做深入的交流

至此,本文結束
在下才疏學淺,有撒湯漏水的,請各位不吝賜教...

user avatar u_16099271 頭像 Johny-zhao 頭像
2 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.