22CherryPy tool that uses the tracer to start a span for each request.
33"""
44import socket
5- from typing import Dict
5+ import json
6+ import logging
7+ from typing import Dict , Any
68
79import cherrypy
810
11+ from tracing .utils import swallow_exceptions
912from tracing .tracer import get_tracer
1013
14+ logger = logging .getLogger ("tracer" )
15+
16+ # Limit for raw JSON string preview (in characters)
17+ _PREVIEW_MAX_CHARS = 512
18+
19+ @swallow_exceptions
1120def _on_request_start () -> None :
1221 req = cherrypy .request
1322 tracer = get_tracer ()
@@ -30,32 +39,39 @@ def _on_request_start() -> None:
3039 else :
3140 span_name = f"{ method } { path } "
3241
33- ctx = tracer .start_as_current_span (span_name , kind = "SERVER" )
42+ attrs = {
43+ 'http.method' : getattr (req , 'method' , '' ),
44+ 'http.path' : getattr (req , 'path_info' , '' ),
45+ 'client.ip' : requester_host ,
46+ 'instance.hostname' : socket .gethostname (),
47+ }
48+ attrs .update (_record_incoming_json_preview (req ))
49+
50+ ctx = tracer .start_as_current_span (span_name , kind = "SERVER" , attributes = attrs )
3451 span = ctx .__enter__ ()
35- span .set_attribute ('http.method' , getattr (req , 'method' , '' ))
36- span .set_attribute ('http.path' , getattr (req , 'path_info' , '' ))
37- span .set_attribute ('client.ip' , requester_host )
38- span .set_attribute ('instance.hostname' , socket .gethostname ())
3952 safe_headers = {k : v for k , v in headers .items () if k .lower () not in {'authorization' , 'x-api-key' }}
4053 span .set_attribute ('sentry.incoming_headers' , safe_headers )
4154 req ._trace_span_ctx = ctx
4255 req ._trace_span = span
4356
4457 tracer .inject_traceparent (cherrypy .response .headers )
4558
46-
59+ @ swallow_exceptions
4760def _on_request_end () -> None :
4861 req = cherrypy .request
4962 try :
5063 status_str = str (cherrypy .response .status )
5164 status_code = int (status_str .split ()[0 ])
5265 except Exception :
5366 status_code = None
54- tracer = get_tracer ()
55- tracer .notify_request_finished (status_code )
67+
5668 span = getattr (req , "_trace_span" , None )
5769 if span is not None and status_code is not None :
5870 span .set_attribute ('http.status_code' , status_code )
71+
72+ tracer = get_tracer ()
73+ tracer .notify_request_finished (status_code )
74+
5975 ctx = getattr (req , "_trace_span_ctx" , None )
6076 if ctx is not None :
6177 try :
@@ -66,8 +82,27 @@ def _on_request_end() -> None:
6682
6783
6884def register_tracing_tools () -> None :
69- cherrypy .tools .trace = cherrypy .Tool ("on_start_resource" , _on_request_start , priority = 10 )
85+ cherrypy .tools .trace = cherrypy .Tool ("on_start_resource" , _on_request_start , priority = 70 )
7086 cherrypy .tools .trace_end = cherrypy .Tool ("on_end_resource" , _on_request_end , priority = 80 )
7187
88+ @swallow_exceptions
89+ def _record_incoming_json_preview (req ) -> dict [str , Any ]:
90+ """If request Content-Type is JSON, attach a compact preview to span.
91+ Returns dict of span attributes to set
92+ """
93+ attrs = {}
94+ content_type = str (getattr (req , 'headers' , {}).get ('Content-Type' , '' ) or '' )
95+ attrs ['http.request.content_type' ] = content_type
96+ if 'json' not in content_type .lower ():
97+ return attrs
7298
73-
99+ parsed_json = getattr (req , 'json' , None )
100+ if parsed_json is None :
101+ logger .debug ('request.json is not available' )
102+ return attrs
103+ normalized = json .dumps (parsed_json , ensure_ascii = False , sort_keys = True )
104+ if len (normalized ) > _PREVIEW_MAX_CHARS :
105+ normalized = normalized [:_PREVIEW_MAX_CHARS ] + '...<truncated>'
106+ attrs ['http.request.json' ] = normalized
107+ attrs ['http.request.body.size' ] = len (normalized )
108+ return attrs
0 commit comments