-
Notifications
You must be signed in to change notification settings - Fork 424
/
Copy pathpatch.py
146 lines (113 loc) · 4.76 KB
/
patch.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import logging
import attr
import ddtrace
from ddtrace import config
from ...internal.utils import get_argument_value
from ...vendor.wrapt import wrap_function_wrapper as _w
from ..trace_utils import unwrap as _u
RECORD_ATTR_TRACE_ID = "dd.trace_id"
RECORD_ATTR_SPAN_ID = "dd.span_id"
RECORD_ATTR_ENV = "dd.env"
RECORD_ATTR_VERSION = "dd.version"
RECORD_ATTR_SERVICE = "dd.service"
RECORD_ATTR_VALUE_ZERO = "0"
RECORD_ATTR_VALUE_EMPTY = ""
_LOG_SPAN_KEY = "__datadog_log_span"
config._add(
"logging",
dict(
tracer=None,
),
) # by default, override here for custom tracer
def get_version():
# type: () -> str
return getattr(logging, "__version__", "")
@attr.s(slots=True)
class DDLogRecord(object):
trace_id = attr.ib(type=int)
span_id = attr.ib(type=int)
service = attr.ib(type=str)
version = attr.ib(type=str)
env = attr.ib(type=str)
def _get_current_span(tracer=None):
"""Helper to get the currently active span"""
if not tracer:
# With the addition of a custom ddtrace logger in _logger.py, logs that happen on startup
# don't have access to `ddtrace.tracer`. Checking that this exists prevents an error
# if log injection is enabled.
if not getattr(ddtrace, "tracer", False):
return None
tracer = ddtrace.tracer
# We might be calling this during library initialization, in which case `ddtrace.tracer` might
# be the `tracer` module and not the global tracer instance.
if not getattr(tracer, "enabled", False):
return None
return tracer.current_span()
def _w_makeRecord(func, instance, args, kwargs):
# Get the LogRecord instance for this log
record = func(*args, **kwargs)
setattr(record, RECORD_ATTR_VERSION, config.version or "")
setattr(record, RECORD_ATTR_ENV, config.env or "")
setattr(record, RECORD_ATTR_SERVICE, config.service or "")
# logs from internal logger may explicitly pass the current span to
# avoid deadlocks in getting the current span while already in locked code.
span_from_log = getattr(record, _LOG_SPAN_KEY, None)
if isinstance(span_from_log, ddtrace.Span):
span = span_from_log
else:
span = _get_current_span(tracer=config.logging.tracer)
if span:
trace_id = span.trace_id
if config._128_bit_trace_id_enabled and not config._128_bit_trace_id_logging_enabled:
trace_id = span._trace_id_64bits
setattr(record, RECORD_ATTR_TRACE_ID, str(trace_id))
setattr(record, RECORD_ATTR_SPAN_ID, str(span.span_id))
else:
setattr(record, RECORD_ATTR_TRACE_ID, RECORD_ATTR_VALUE_ZERO)
setattr(record, RECORD_ATTR_SPAN_ID, RECORD_ATTR_VALUE_ZERO)
return record
def _w_StrFormatStyle_format(func, instance, args, kwargs):
# The format string "dd.service={dd.service}" expects
# the record to have a "dd" property which is an object that
# has a "service" property
# PercentStyle, and StringTemplateStyle both look for
# a "dd.service" property on the record
record = get_argument_value(args, kwargs, 0, "record")
record.dd = DDLogRecord(
trace_id=getattr(record, RECORD_ATTR_TRACE_ID, RECORD_ATTR_VALUE_ZERO),
span_id=getattr(record, RECORD_ATTR_SPAN_ID, RECORD_ATTR_VALUE_ZERO),
service=getattr(record, RECORD_ATTR_SERVICE, ""),
version=getattr(record, RECORD_ATTR_VERSION, ""),
env=getattr(record, RECORD_ATTR_ENV, ""),
)
try:
return func(*args, **kwargs)
finally:
# We need to remove this extra attribute so it does not pollute other formatters
# For example: if we format with StrFormatStyle and then a JSON logger
# then the JSON logger will have `dd.{service,version,env,trace_id,span_id}` as
# well as the `record.dd` `DDLogRecord` instance
del record.dd
def patch():
"""
Patch ``logging`` module in the Python Standard Library for injection of
tracer information by wrapping the base factory method ``Logger.makeRecord``
"""
if getattr(logging, "_datadog_patch", False):
return
logging._datadog_patch = True
_w(logging.Logger, "makeRecord", _w_makeRecord)
if hasattr(logging, "StrFormatStyle"):
if hasattr(logging.StrFormatStyle, "_format"):
_w(logging.StrFormatStyle, "_format", _w_StrFormatStyle_format)
else:
_w(logging.StrFormatStyle, "format", _w_StrFormatStyle_format)
def unpatch():
if getattr(logging, "_datadog_patch", False):
logging._datadog_patch = False
_u(logging.Logger, "makeRecord")
if hasattr(logging, "StrFormatStyle"):
if hasattr(logging.StrFormatStyle, "_format"):
_u(logging.StrFormatStyle, "_format")
else:
_u(logging.StrFormatStyle, "format")