Skip to content

Commit 9312971

Browse files
ndoornekampbasepi
andauthored
Move exception stack trace to ECS-compliant field for StructlogFormatter (elastic#97)
* Move exception stack trace to ECS-compliant field * refactor: patching time is not needed * Move event dicts to fixtures * CHANGELOG --------- Co-authored-by: Colton Myers <[email protected]>
1 parent 1193b91 commit 9312971

File tree

3 files changed

+45
-8
lines changed

3 files changed

+45
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 2.1.0 (unreleased)
44

55
- Add support for `service.environment` from APM log correlation ([#96](https://github.com/elastic/ecs-logging-python/pull/96))
6+
- Fix stack trace handling in StructLog for ECS compliance ([#97](https://github.com/elastic/ecs-logging-python/pull/97))
67

78
## 2.0.2 (2023-05-17)
89

ecs_logging/_structlog.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,13 @@ def format_to_ecs(self, event_dict):
4747
)[:-3]
4848
+ "Z"
4949
)
50+
51+
if "exception" in event_dict:
52+
stack_trace = event_dict.pop("exception")
53+
if "error" in event_dict:
54+
event_dict["error"]["stack_trace"] = stack_trace
55+
else:
56+
event_dict["error"] = {"stack_trace": stack_trace}
57+
5058
event_dict.setdefault("ecs", {}).setdefault("version", ECS_VERSION)
5159
return event_dict

tests/test_structlog_formatter.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,23 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717

18-
import ecs_logging
19-
import structlog
20-
from unittest import mock
18+
import json
2119
from io import StringIO
20+
from unittest import mock
2221

2322
import pytest
23+
import structlog
24+
25+
import ecs_logging
2426

2527

2628
class NotSerializable:
2729
def __repr__(self):
2830
return "<NotSerializable>"
2931

3032

31-
def make_event_dict():
33+
@pytest.fixture
34+
def event_dict():
3235
return {
3336
"event": "test message",
3437
"log.logger": "logger-name",
@@ -37,20 +40,29 @@ def make_event_dict():
3740
}
3841

3942

40-
def test_conflicting_event_dict():
43+
@pytest.fixture
44+
def event_dict_with_exception():
45+
return {
46+
"event": "test message",
47+
"log.logger": "logger-name",
48+
"foo": "bar",
49+
"exception": "<stack trace here>",
50+
}
51+
52+
53+
def test_conflicting_event_dict(event_dict):
4154
formatter = ecs_logging.StructlogFormatter()
42-
event_dict = make_event_dict()
4355
event_dict["foo.bar"] = "baz"
4456
with pytest.raises(TypeError):
4557
formatter(None, "debug", event_dict)
4658

4759

4860
@mock.patch("time.time")
49-
def test_event_dict_formatted(time, spec_validator):
61+
def test_event_dict_formatted(time, spec_validator, event_dict):
5062
time.return_value = 1584720997.187709
5163

5264
formatter = ecs_logging.StructlogFormatter()
53-
assert spec_validator(formatter(None, "debug", make_event_dict())) == (
65+
assert spec_validator(formatter(None, "debug", event_dict)) == (
5466
'{"@timestamp":"2020-03-20T16:16:37.187Z","log.level":"debug",'
5567
'"message":"test message",'
5668
'"baz":"<NotSerializable>",'
@@ -80,3 +92,19 @@ def test_can_be_set_as_processor(time, spec_validator):
8092
'"message":"test message","custom":"key","dot":{"ted":1},'
8193
'"ecs":{"version":"1.6.0"}}\n'
8294
)
95+
96+
97+
def test_exception_log_is_ecs_compliant_when_used_with_format_exc_info(
98+
event_dict_with_exception,
99+
):
100+
formatter = ecs_logging.StructlogFormatter()
101+
formatted_event_dict = json.loads(
102+
formatter(None, "debug", event_dict_with_exception)
103+
)
104+
105+
assert (
106+
"exception" not in formatted_event_dict
107+
), "The key 'exception' at the root of a log is not ECS-compliant"
108+
assert "error" in formatted_event_dict
109+
assert "stack_trace" in formatted_event_dict["error"]
110+
assert "<stack trace here>" in formatted_event_dict["error"]["stack_trace"]

0 commit comments

Comments
 (0)