Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions docs/reference/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ formatter = StdlibFormatter(

#### Limiting stack traces [_limiting_stack_traces]

The `StdlibLogger` automatically gathers `exc_info` into ECS `error.*` fields. If you’d like to control the number of stack frames that are included in `error.stack_trace` you can use the `stack_trace_limit` parameter (by default all frames are collected):
The `StdlibLogger` automatically gathers `exc_info` into ECS `error.*` fields. If you’d like to control the number of stack frames that are included in `error.stack_trace` you can use the `stack_trace_limit` parameter (by default all frames are collected). Positive values include frames starting from the caller's frame. Negative values include the last `N` frames (closest to the error):

```python
from ecs_logging import StdlibFormatter
Expand All @@ -94,6 +94,10 @@ formatter = StdlibFormatter(
# Only collects 3 stack frames
stack_trace_limit=3,
)
formatter = StdlibFormatter(
# Collects the last 2 frames (closest to the error)
stack_trace_limit=-2,
)
formatter = StdlibFormatter(
# Disable stack trace collection
stack_trace_limit=0,
Expand Down Expand Up @@ -322,4 +326,3 @@ labels:

:::::::
For more information, see the [Filebeat reference](beats://reference/filebeat/configuring-howto-filebeat.md).

14 changes: 5 additions & 9 deletions ecs_logging/_stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ def __init__(
:param int stack_trace_limit:
Specifies the maximum number of frames to include for stack
traces. Defaults to ``None`` which includes all available frames.
Setting this to zero will suppress stack traces.
Setting this to zero will suppress stack traces. Positive values
include frames starting from the caller's frame. Negative values
include the last ``N`` frames (closest to the error).
This setting doesn't affect ``LogRecord.stack_info`` because
this attribute is typically already pre-formatted.
:param Optional[Dict[str, Any]] extra:
Expand All @@ -116,13 +118,7 @@ def __init__(

if stack_trace_limit is not None:
if not isinstance(stack_trace_limit, int):
raise TypeError(
"'stack_trace_limit' must be None, or a non-negative integer"
)
elif stack_trace_limit < 0:
raise ValueError(
"'stack_trace_limit' must be None, or a non-negative integer"
)
raise TypeError("'stack_trace_limit' must be None or an integer")

if (
not isinstance(exclude_fields, collections.abc.Sequence)
Expand Down Expand Up @@ -283,7 +279,7 @@ def _record_error_stack_trace(self, record: logging.LogRecord) -> Optional[str]:
if (
record.exc_info
and record.exc_info[2] is not None
and (self._stack_trace_limit is None or self._stack_trace_limit > 0)
and (self._stack_trace_limit is None or self._stack_trace_limit != 0)
):
return (
"".join(format_tb(record.exc_info[2], limit=self._stack_trace_limit))
Expand Down
25 changes: 16 additions & 9 deletions tests/test_stdlib_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,16 @@ def test_exc_info_false_does_not_raise(logger):
assert "error" not in ecs


def test_stack_trace_limit_traceback(logger):
@pytest.mark.parametrize(
("stack_trace_limit", "expected_in", "expected_not_in"),
[
(2, ("f()", "g()"), ("h()",)),
(-2, ("h()",), ("f()", "g()")),
],
)
def test_stack_trace_limit_traceback(
stack_trace_limit, expected_in, expected_not_in, logger
):
def f():
g()

Expand All @@ -220,7 +229,9 @@ def h():

stream = StringIO()
handler = logging.StreamHandler(stream)
handler.setFormatter(ecs_logging.StdlibFormatter(stack_trace_limit=2))
handler.setFormatter(
ecs_logging.StdlibFormatter(stack_trace_limit=stack_trace_limit)
)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

Expand All @@ -231,8 +242,8 @@ def h():

ecs = json.loads(stream.getvalue().rstrip())
error_stack_trace = ecs["error"].pop("stack_trace")
assert all(x in error_stack_trace for x in ("f()", "g()"))
assert "h()" not in error_stack_trace
assert all(x in error_stack_trace for x in expected_in)
assert all(x not in error_stack_trace for x in expected_not_in)
assert ecs["error"] == {
"message": "error!",
"type": "ValueError",
Expand All @@ -245,11 +256,7 @@ def h():
def test_stack_trace_limit_types_and_values():
with pytest.raises(TypeError) as e:
ecs_logging.StdlibFormatter(stack_trace_limit="a")
assert str(e.value) == "'stack_trace_limit' must be None, or a non-negative integer"

with pytest.raises(ValueError) as e:
ecs_logging.StdlibFormatter(stack_trace_limit=-1)
assert str(e.value) == "'stack_trace_limit' must be None, or a non-negative integer"
assert str(e.value) == "'stack_trace_limit' must be None or an integer"


@pytest.mark.parametrize(
Expand Down