Skip to content

Commit 1e387ee

Browse files
authored
Add logging Formatter class (#595)
* Add logging Formatter class We need a custom Formatter class in order to add error handling for any logs which happen before the Client is initialized (and our LogRecordFactory is put into place). * Remove some unused imports * Add leading space for consistency with recommended grok pattern * py2 compat * Update bullets to match sidebar
1 parent 1b8c834 commit 1e387ee

File tree

4 files changed

+78
-11
lines changed

4 files changed

+78
-11
lines changed

docs/advanced-topics.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* <<instrumenting-custom-code>>
55
* <<sanitizing-data>>
66
* <<run-tests-locally>>
7+
* <<logging-integrations>>
8+
* <<log-correlation>>
79

810
include::./custom-instrumentation.asciidoc[Custom Instrumentation]
911
include::./sanitizing-data.asciidoc[Sanitizing Data]

docs/logging.asciidoc

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,23 +96,33 @@ that looks like this:
9696
----
9797
import logging
9898
99+
fh = logging.FileHandler('spam.log')
99100
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
101+
fh.setFormatter(formatter)
100102
----
101103

102-
You can add the APM identifiers in an easy-to-parse manner:
104+
You can add the APM identifiers by simply switching out the `Formatter` object
105+
for the one that we provide:
103106

104107
[source,python]
105108
----
106109
import logging
110+
from elasticapm.handlers.logging import Formatter
107111
108-
format_string = (
109-
"%(asctime)s - %(name)s - %(levelname)s - %(message)s "
110-
"| elasticapm "
111-
"transaction.id=%(elasticapm_transaction_id) "
112-
"trace.id=%(elasticapm_trace_id) "
113-
"span.id=%(elasticapm_span_id)"
114-
)
115-
formatter = logging.Formatter(format_string)
112+
fh = logging.FileHandler('spam.log')
113+
formatter = Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
114+
fh.setFormatter(formatter)
115+
----
116+
117+
This will automatically append apm-specific fields to your format string:
118+
119+
[source,python]
120+
----
121+
formatstring = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
122+
formatstring = formatstring + " | elasticapm " \
123+
"transaction.id=%(elasticapm_transaction_id)s " \
124+
"trace.id=%(elasticapm_trace_id)s " \
125+
"span.id=%(elasticapm_span_id)s"
116126
----
117127

118128
Then, you could use a grok pattern like this (for the

elasticapm/handlers/logging.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,46 @@ def _add_attributes_to_log_record(record):
223223
record.elasticapm_labels = {"transaction.id": transaction_id, "trace.id": trace_id, "span.id": span_id}
224224

225225
return record
226+
227+
228+
class Formatter(logging.Formatter):
229+
"""
230+
Custom formatter to automatically append the elasticapm format string,
231+
as well as ensure that LogRecord objects actually have the required fields
232+
(so as to avoid errors which could occur for logs before we override the
233+
LogRecordFactory):
234+
235+
formatstring = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
236+
formatstring = formatstring + " | elasticapm " \
237+
"transaction.id=%(elasticapm_transaction_id)s " \
238+
"trace.id=%(elasticapm_trace_id)s " \
239+
"span.id=%(elasticapm_span_id)s"
240+
"""
241+
242+
def __init__(self, fmt=None, datefmt=None, style="%"):
243+
if fmt is None:
244+
fmt = "%(message)s"
245+
fmt = (
246+
fmt + " | elasticapm "
247+
"transaction.id=%(elasticapm_transaction_id)s "
248+
"trace.id=%(elasticapm_trace_id)s "
249+
"span.id=%(elasticapm_span_id)s"
250+
)
251+
if compat.PY3:
252+
super(Formatter, self).__init__(fmt=fmt, datefmt=datefmt, style=style)
253+
else:
254+
super(Formatter, self).__init__(fmt=fmt, datefmt=datefmt)
255+
256+
def format(self, record):
257+
if not hasattr(record, "elasticapm_transaction_id"):
258+
record.elasticapm_transaction_id = None
259+
record.elasticapm_trace_id = None
260+
record.elasticapm_span_id = None
261+
return super(Formatter, self).format(record=record)
262+
263+
def formatTime(self, record, datefmt=None):
264+
if not hasattr(record, "elasticapm_transaction_id"):
265+
record.elasticapm_transaction_id = None
266+
record.elasticapm_trace_id = None
267+
record.elasticapm_span_id = None
268+
return super(Formatter, self).formatTime(record=record, datefmt=datefmt)

tests/handlers/logging/logging_tests.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@
3636

3737
from elasticapm.conf import Config
3838
from elasticapm.conf.constants import ERROR
39-
from elasticapm.handlers.logging import LoggingFilter, LoggingHandler, log_record_factory
39+
from elasticapm.handlers.logging import Formatter, LoggingFilter, LoggingHandler
4040
from elasticapm.handlers.structlog import structlog_processor
41-
from elasticapm.traces import Tracer, capture_span, execution_context
41+
from elasticapm.traces import Tracer, capture_span
4242
from elasticapm.utils import compat
4343
from elasticapm.utils.stacks import iter_stack_frames
4444
from tests.fixtures import TempStoreClient
@@ -313,3 +313,15 @@ def test_automatic_log_record_factory_install(elasticapm_client):
313313
assert record.elasticapm_trace_id == transaction.trace_parent.trace_id
314314
assert record.elasticapm_span_id == span.id
315315
assert record.elasticapm_labels
316+
317+
318+
def test_formatter():
319+
record = logging.LogRecord(__name__, logging.DEBUG, __file__, 252, "dummy_msg", [], None)
320+
formatter = Formatter()
321+
formatted_record = formatter.format(record)
322+
assert "| elasticapm" in formatted_record
323+
assert hasattr(record, "elasticapm_transaction_id")
324+
record = logging.LogRecord(__name__, logging.DEBUG, __file__, 252, "dummy_msg", [], None)
325+
formatted_time = formatter.formatTime(record)
326+
assert formatted_time
327+
assert hasattr(record, "elasticapm_transaction_id")

0 commit comments

Comments
 (0)