Skip to content

Commit 5716ee0

Browse files
Merge pull request #3835 from mariadb-corporation/tracing-log-json-requests
Save json payload of request in tracing
2 parents 8fa84b4 + 33cb2e2 commit 5716ee0

File tree

3 files changed

+60
-19
lines changed

3 files changed

+60
-19
lines changed

cmapi/cmapi_server/__main__.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import logging
88
import os
9+
import sys
910
import threading
1011
import time
1112
from datetime import datetime
@@ -19,20 +20,20 @@
1920
from tracing.sentry import maybe_init_sentry
2021
from tracing.traceparent_backend import TraceparentBackend
2122
from tracing.tracer import get_tracer
22-
config_cmapi_server_logging()
23-
from tracing.trace_tool import register_tracing_tools
2423

24+
config_cmapi_server_logging()
2525
from cmapi_server import helpers
26-
from cmapi_server.constants import DEFAULT_MCS_CONF_PATH, CMAPI_CONF_PATH
27-
from cmapi_server.controllers.dispatcher import dispatcher, jsonify_error, jsonify_404
26+
from cmapi_server.constants import CMAPI_CONF_PATH, DEFAULT_MCS_CONF_PATH
27+
from cmapi_server.controllers.dispatcher import dispatcher, jsonify_404, jsonify_error
2828
from cmapi_server.failover_agent import FailoverAgent
29+
from cmapi_server.invariant_checks import run_invariant_checks
2930
from cmapi_server.managers.application import AppManager
30-
from cmapi_server.managers.process import MCSProcessManager
3131
from cmapi_server.managers.certificate import CertificateManager
32-
from cmapi_server.invariant_checks import run_invariant_checks
32+
from cmapi_server.managers.process import MCSProcessManager
3333
from failover.node_monitor import NodeMonitor
3434
from mcs_node_control.models.dbrm_socket import SOCK_TIMEOUT, DBRMSocketHandler
3535
from mcs_node_control.models.node_config import NodeConfig
36+
from tracing.trace_tool import register_tracing_tools
3637

3738

3839
def worker(app):

cmapi/tracing/trace_tool.py

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,21 @@
22
CherryPy tool that uses the tracer to start a span for each request.
33
"""
44
import socket
5-
from typing import Dict
5+
import json
6+
import logging
7+
from typing import Dict, Any
68

79
import cherrypy
810

11+
from tracing.utils import swallow_exceptions
912
from 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
1120
def _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
4760
def _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

6884
def 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

cmapi/tracing/tracer.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,12 @@ def clear_backends(self) -> None:
4646
self._backends.clear()
4747

4848
@contextmanager
49-
def start_as_current_span(self, name: str, kind: str = "INTERNAL") -> Iterator[TraceSpan]:
49+
def start_as_current_span(
50+
self,
51+
name: str,
52+
kind: str = "INTERNAL",
53+
attributes: Optional[Dict[str, Any]] = None,
54+
) -> Iterator[TraceSpan]:
5055
trace_id = _current_trace_id.get() or rand_16_hex()
5156
parent_span = _current_span_id.get()
5257
new_span_id = rand_8_hex()
@@ -63,7 +68,7 @@ def start_as_current_span(self, name: str, kind: str = "INTERNAL") -> Iterator[T
6368
trace_id=trace_id,
6469
span_id=new_span_id,
6570
parent_span_id=parent_span,
66-
attributes={"span.kind": kind, "span.name": name},
71+
attributes={"span.kind": kind, "span.name": name, **(attributes or {})},
6772
tracer=self,
6873
)
6974

0 commit comments

Comments
 (0)