Skip to content

Commit 8a48fae

Browse files
authored
Add support for LogRecord.stack_info
1 parent 748f74d commit 8a48fae

File tree

2 files changed

+52
-6
lines changed

2 files changed

+52
-6
lines changed

ecs_logging/_stdlib.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ def __init__(self, stack_trace_limit=None, exclude_fields=()):
6161
Specifies the maximum number of frames to include for stack
6262
traces. Defaults to ``None`` which includes all available frames.
6363
Setting this to zero will suppress stack traces.
64+
This setting doesn't affect ``LogRecord.stack_info`` because
65+
this attribute is typically already pre-formatted.
6466
:param Sequence[str] exclude_fields:
6567
Specifies any fields that should be suppressed from the resulting
6668
fields, expressed with dot notation::
@@ -203,13 +205,20 @@ def _record_attribute(self, attribute):
203205

204206
def _record_error_stack_trace(self, record):
205207
# type: (logging.LogRecord) -> Optional[str]
206-
if not (
208+
# Using stack_info=True will add 'error.stack_trace' even
209+
# if the type is not 'error', exc_info=True only gathers
210+
# when there's an active exception.
211+
if (
207212
record.exc_info
208213
and record.exc_info[2] is not None
209214
and (self._stack_trace_limit is None or self._stack_trace_limit > 0)
210215
):
211-
return None
212-
return (
213-
"".join(format_tb(record.exc_info[2], limit=self._stack_trace_limit))
214-
or None
215-
)
216+
return (
217+
"".join(format_tb(record.exc_info[2], limit=self._stack_trace_limit))
218+
or None
219+
)
220+
# LogRecord only has 'stack_info' if it's passed via .log(..., stack_info=True)
221+
stack_info = getattr(record, "stack_info", None)
222+
if stack_info:
223+
return str(stack_info)
224+
return None

tests/test_stdlib_formatter.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@
44
import json
55
import time
66
import random
7+
import sys
78
import ecs_logging
89
from .compat import StringIO
910

1011

12+
requires_py3 = pytest.mark.skipif(
13+
sys.version_info[0] < 3, reason="Test requires Python 3.x+"
14+
)
15+
16+
1117
@pytest.fixture(scope="function")
1218
def logger():
1319
return logging.getLogger("test-logger-%f-%f" % (time.time(), random.random()))
@@ -258,3 +264,34 @@ def test_exclude_fields_type_and_values():
258264
with pytest.raises(TypeError) as e:
259265
ecs_logging.StdlibFormatter(exclude_fields=[1])
260266
assert str(e.value) == "'exclude_fields' must be a sequence of strings"
267+
268+
269+
@requires_py3
270+
def test_stack_info(logger):
271+
stream = StringIO()
272+
handler = logging.StreamHandler(stream)
273+
handler.setFormatter(ecs_logging.StdlibFormatter())
274+
logger.addHandler(handler)
275+
logger.setLevel(logging.DEBUG)
276+
277+
logger.info("stack info!", stack_info=True)
278+
279+
ecs = json.loads(stream.getvalue().rstrip())
280+
assert list(ecs["error"].keys()) == ["stack_trace"]
281+
error_stack_trace = ecs["error"].pop("stack_trace")
282+
assert "test_stack_info" in error_stack_trace and __file__ in error_stack_trace
283+
284+
285+
@requires_py3
286+
@pytest.mark.parametrize("exclude_fields", [["error"], ["error.stack_trace"]])
287+
def test_stack_info_excluded(logger, exclude_fields):
288+
stream = StringIO()
289+
handler = logging.StreamHandler(stream)
290+
handler.setFormatter(ecs_logging.StdlibFormatter(exclude_fields=exclude_fields))
291+
logger.addHandler(handler)
292+
logger.setLevel(logging.DEBUG)
293+
294+
logger.info("stack info!", stack_info=True)
295+
296+
ecs = json.loads(stream.getvalue().rstrip())
297+
assert "error" not in ecs

0 commit comments

Comments
 (0)