前言
经过上一节,opentelemetry的基本操作都已经融会贯通,但是有位老哥提出疑问?我的代码都已经写完了,为了添加全链路,还需要重构之前的代码吗?那这个代价太大了。那本章就来讨论一下opentelemetry的注入的问题
本小节主要关注python注入
使用装饰器
使用装饰器的好处就是非常灵活,并且对代码入侵很小,只需要装饰一下即可
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:
串联多个span
...
@traced("phase-1")
def views():
views_2()
@traced("phase-2")
def views_2():
pass
...
跨服务串联span
...
@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
...
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,跨服务已经串联
自动注入 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等,都可以自动采集。来验证一下
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
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
已经看到整个完整的链路追踪了
opentelemetry-instrument与装饰器结合使用
由于opentelemetry-instrument不能跟踪自定义的模块,可以结合装饰器跟踪重点函数
...
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
...
小结
本节介绍了2种注入的方法
- 其中装饰器的方法,提前将trace流程写好,函数只需要调用装饰即可完成注入,减少了代码入侵度
- 而opentelemetry-instrument则是采用aop思想,将目标模块(比如pymysql、requests等)动态替换成预定义的模块,从而实现trace的注入,该方法的优点就是对业务代码的无入侵
- 在python中,结合以上两种方法,可以很好的完成trace注入
联系我
- 联系我,做深入的交流
至此,本文结束
在下才疏学浅,有撒汤漏水的,请各位不吝赐教...