【Sentry使用】自定义transaction

在使用Sentry时,你会发现有两种颜色的柱形图,一个是紫色的,在上面;一个是灰色的,在下面。这两类柱形图分别代表error和transaction,而在Python脚本环境下,不会自动进行transaction的记录,也就是说只会在出现异常时进行记录,而正常情况不会。
【Sentry使用】自定义transaction_第1张图片
这时就要了解一下Sentry的transaction创建逻辑

  • 自动
  • 手动

首先是自动
【Sentry使用】自定义transaction_第2张图片
在使用一些框架的情况下会自动创建,官方给出了这些框架的列表

  • 基于WSGI的web框架,比如Django、Flask、Pyramid、Falcon、Bottle等
  • Celery
  • AIOHTTP web 应用
  • Redis Queue

spans就是在一个transaction中对下列类型操作的记录

  • 使用SQLAlchemy或者Django ORM对数据库进行检索
  • 使用requests或者stdlib进行HTTP请求
  • Spawn方式创建的子进程
  • Redis操作

以下面的一个爬虫transaction为例,可以看到spans记录的有http请求以及db操作,而数据处理的部分则是没有记录,显示为‘Missing instrumentation’
【Sentry使用】自定义transaction_第3张图片
如何将这些‘Missing instrumentation’变为自定义的呢?

from sentry_sdk import Hub

def process_item(item):
    transaction = Hub.current.scope.transaction
    # omitted code...
    with transaction.start_child(op="http", description="GET /") as span:
        response = my_custom_http_library.request("GET", "/")
        span.set_tag("http.status_code", response.status_code)
        span.set_data("http.foobarsessionid", get_foobar_sessionid())

参考:https://sentry-docs-git-sentry-ruby-40.sentry.dev/platforms/python/performance/#:~:text=op%20and%20description.-,Python,-Copied
如果想在span中创建span

from sentry_sdk import Hub

def process_item(item):
    parent_span = Hub.current.scope.span
    # omitted code...
    with parent_span.start_child(op="http", description="GET /") as span:
        response = my_custom_http_library.request("GET", "/")
        span.set_tag("http.status_code", response.status_code)
        span.set_data("http.foobarsessionid", get_foobar_sessionid())

参考:https://sentry-docs-git-sentry-ruby-40.sentry.dev/platforms/python/performance/#:~:text=tree%20of%20spans%3A-,Python,-Copied

那么如何手动创建transaction呢?
其实,上图中的transaction就是通过手动创建的

from sentry_sdk import start_transaction

while True:
  item = get_from_queue()

  with start_transaction(op="task", name=item.get_transaction_name()):
      # process_item may create more spans internally (see next examples)
      process_item(item)

这种就适合于那种没有使用框架的Python脚本使用

参考:
https://docs.sentry.io/platforms/python/performance/instrumentation/automatic-instrumentation/
https://docs.sentry.io/platforms/python/performance/instrumentation/custom-instrumentation/


根据官方说明:
status用来计算failure_rate,而failure_rate表示不成功事务的百分比。 Sentry 将状态不是“ok”、“cancelled”和“unknown”的事务视为失败。
我们看到,所有自定义transaction的状态都是unknown

如果想将其改为’ok’,可以将status参数设置为’ok’

with start_transaction(op='xxxx', name='xxxx', status='ok') as transaction:
    ...
    ...


参考:https://develop.sentry.dev/sdk/event-payloads/span/


首先是希望在多进程中使用,根据官方说明写出了如下代码:

# main.py
from sentry_sdk import start_transaction
from multiprocessing import Process
import zhuo
import long

with start_transaction(op='xxxx', name='xxxx', status='ok') as transaction:
    p1 = Process(target=zhuo.main)
    p2 = Process(target=long.main)
    p1.start()
    p2.start()
    p1.join()
    p2.join()
# zhuo.py
import sentry_sdk

transaction = sentry_sdk.Hub.current.scope.transaction

def main():
    with transaction.start_child(op="zhuo"):
        ...
        ...
# long.py
import sentry_sdk

transaction = sentry_sdk.Hub.current.scope.transaction

def main():
    with transaction.start_child(op="long"):
        ...
        ...

但是报错

AttributeError: 'NoneType' object has no attribute 'start_child'

也就是说

transaction = sentry_sdk.Hub.current.scope.transaction

总是为None
所以就将代码改为如下:

# main.py
from sentry_sdk import start_transaction
from multiprocessing import Process
import zhuo
import long

with start_transaction(op='xxxx', name='xxxx', status='ok') as transaction:
    p1 = Process(target=zhuo.main, args=(transaction, ))
    p2 = Process(target=long.main, args=(transaction, ))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
# zhuo.py
import sentry_sdk

def main(transaction: sentry_sdk.tracing.Transaction):
    with transaction.start_child(op="zhuo"):
        ...
        ...
# long.py
import sentry_sdk

transaction = sentry_sdk.Hub.current.scope.transaction

def main(transaction: sentry_sdk.tracing.Transaction):
    with transaction.start_child(op="long"):
        ...
        ...

但这时又报错

Traceback (most recent call last):
  File "main.py", line 6, in 
    process.start()
  File "/usr/local/Cellar/[email protected]/3.8.12_1/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/process.py", line 121, in start
    self._popen = self._Popen(self)
  File "/usr/local/Cellar/[email protected]/3.8.12_1/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/context.py", line 224, in _Popen
    return _default_context.get_context().Process._Popen(process_obj)
  File "/usr/local/Cellar/[email protected]/3.8.12_1/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/context.py", line 284, in _Popen
    return Popen(process_obj)
  File "/usr/local/Cellar/[email protected]/3.8.12_1/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/popen_spawn_posix.py", line 32, in __init__
    super().__init__(process_obj)
  File "/usr/local/Cellar/[email protected]/3.8.12_1/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/popen_fork.py", line 19, in __init__
    self._launch(process_obj)
  File "/usr/local/Cellar/[email protected]/3.8.12_1/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/popen_spawn_posix.py", line 47, in _launch
    reduction.dump(process_obj, fp)
  File "/usr/local/Cellar/[email protected]/3.8.12_1/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/reduction.py", line 60, in dump
    ForkingPickler(file, protocol).dump(obj)
TypeError: cannot pickle '_thread.RLock' object

原因是transaction无法被pickle序列化,解决思路是将transaction的构建放在目标函数中,这里只传Python自身的数据类型。但问题是transaction无法重新构建,每个进程必须要传入同一个transaction对象,故暂时无法解决。

参考:
https://blog.csdn.net/weixin_43064185/article/details/103213823
https://stackoverflow.com/questions/44144584/typeerror-cant-pickle-thread-lock-objects

多进程不行就改为多线程试试

# main.py
from sentry_sdk import start_transaction
from threading import Thread
import zhuo
import long

with start_transaction(op='xxxx', name='xxxx', status='ok') as transaction:
    p1 = Thread(target=zhuo.main, args=(transaction, ))
    p2 = Thread(target=long.main, args=(transaction, ))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
# zhuo.py
import sentry_sdk

def main(transaction: sentry_sdk.tracing.Transaction):
    with transaction.start_child(op="zhuo"):
        ...
        ...
# long.py
import sentry_sdk

transaction = sentry_sdk.Hub.current.scope.transaction

def main(transaction: sentry_sdk.tracing.Transaction):
    with transaction.start_child(op="long"):
        ...
        ...

一切正常了

transaction = sentry_sdk.Hub.current.scope.transaction

依旧为None
可以看到zhuo与long中的所有http请求以及数据库操作均被记录下来
所以这种方式仅适用于调试阶段,如果数据量过大,可能造成sentry服务器崩溃

你可能感兴趣的:(Python,Sentry,运维,python,sentry,运维)