Skip to content

Commit 8054c06

Browse files
committed
Added MetricsLogger.add_stack_trace()
1 parent c85dcdd commit 8054c06

File tree

3 files changed

+138
-2
lines changed

3 files changed

+138
-2
lines changed

aws_embedded_metrics/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@
1313

1414
name = "aws_embedded_metrics"
1515

16-
from aws_embedded_metrics.metric_scope import metric_scope # noqa: F401
16+
from aws_embedded_metrics.metric_scope import metric_scope # noqa: F401 E402
17+
from aws_embedded_metrics.logger.metrics_logger import MetricsLogger # noqa: F401 E402
18+
from aws_embedded_metrics.logger.metrics_context import MetricsContext # noqa: F401 E402

aws_embedded_metrics/logger/metrics_logger.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
from aws_embedded_metrics.environment import Environment
1515
from aws_embedded_metrics.logger.metrics_context import MetricsContext
1616
from aws_embedded_metrics.config import get_config
17-
from typing import Any, Awaitable, Callable, Dict
17+
from typing import Any, Awaitable, Callable, Dict, Tuple
18+
import sys
19+
import traceback
1820

1921
Config = get_config()
2022

@@ -73,6 +75,35 @@ def put_metric(self, key: str, value: float, unit: str = "None") -> "MetricsLogg
7375
self.context.put_metric(key, value, unit)
7476
return self
7577

78+
def add_stack_trace(self, key: str, details: Any = None, exc_info: Tuple = None) -> "MetricsLogger":
79+
if not exc_info:
80+
exc_info = sys.exc_info()
81+
82+
err_cls, err, tb = exc_info
83+
84+
if err_cls is None:
85+
error_type = None
86+
error_str = None
87+
traceback_str = None
88+
else:
89+
if err_cls.__module__ == "builtins":
90+
error_type = err_cls.__name__
91+
else:
92+
error_type = "{module}.{name}".format(module=err_cls.__module__, name=err_cls.__name__)
93+
error_str = str(err)
94+
traceback_str = ''.join(traceback.format_tb(tb))
95+
96+
trace_value = {}
97+
if details:
98+
trace_value["details"] = details
99+
trace_value.update({
100+
"error_type": error_type,
101+
"error_str": error_str,
102+
"traceback": traceback_str,
103+
})
104+
self.set_property(key, trace_value)
105+
return self
106+
76107
def new(self) -> "MetricsLogger":
77108
return MetricsLogger(
78109
self.resolve_environment, self.context.create_copy_with_context()

tests/logger/test_metrics_logger.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from asyncio import Future
88
from importlib import reload
99
import os
10+
import sys
1011

1112
fake = Faker()
1213

@@ -50,6 +51,108 @@ async def test_can_put_metric(mocker):
5051
assert context.metrics[expected_key].unit == "None"
5152

5253

54+
@pytest.mark.asyncio
55+
async def test_can_add_stack_trace(mocker):
56+
# arrange
57+
expected_key = fake.word()
58+
expected_details = fake.word()
59+
expected_error_str = fake.word()
60+
61+
logger, sink, env = get_logger_and_sink(mocker)
62+
63+
from configparser import Error # Just some non-builtin exception
64+
65+
# act
66+
try:
67+
raise Error(expected_error_str)
68+
except Error:
69+
logger.add_stack_trace(expected_key, expected_details)
70+
await logger.flush()
71+
72+
# assert
73+
context = get_flushed_context(sink)
74+
value = context.properties[expected_key]
75+
assert isinstance(value, dict)
76+
assert value["details"] == expected_details
77+
assert value["error_type"] == "configparser.Error"
78+
assert value["error_str"] == expected_error_str
79+
assert value["traceback"].split("\n")[-2] == " raise Error(expected_error_str)"
80+
81+
82+
@pytest.mark.asyncio
83+
async def test_can_add_stack_trace_for_builtin(mocker):
84+
# arrange
85+
expected_key = fake.word()
86+
expected_details = fake.word()
87+
expected_error_str = fake.word()
88+
89+
logger, sink, env = get_logger_and_sink(mocker)
90+
91+
# act
92+
try:
93+
raise ValueError(expected_error_str)
94+
except ValueError:
95+
logger.add_stack_trace(expected_key, expected_details)
96+
await logger.flush()
97+
98+
# assert
99+
context = get_flushed_context(sink)
100+
value = context.properties[expected_key]
101+
assert isinstance(value, dict)
102+
assert value["details"] == expected_details
103+
assert value["error_type"] == "ValueError"
104+
assert value["error_str"] == expected_error_str
105+
assert value["traceback"].split("\n")[-2] == " raise ValueError(expected_error_str)"
106+
107+
108+
@pytest.mark.asyncio
109+
async def test_can_add_empty_stack_trace(mocker):
110+
# arrange
111+
expected_key = fake.word()
112+
113+
logger, sink, env = get_logger_and_sink(mocker)
114+
115+
# act
116+
logger.add_stack_trace(expected_key)
117+
await logger.flush()
118+
119+
# assert
120+
context = get_flushed_context(sink)
121+
value = context.properties[expected_key]
122+
assert isinstance(value, dict)
123+
assert "value" not in value
124+
assert value["error_type"] is None
125+
assert value["error_str"] is None
126+
assert value["traceback"] is None
127+
128+
129+
@pytest.mark.asyncio
130+
async def test_can_add_stack_trace_manually(mocker):
131+
# arrange
132+
expected_key = fake.word()
133+
expected_details = fake.word()
134+
expected_error_str = fake.word()
135+
136+
logger, sink, env = get_logger_and_sink(mocker)
137+
138+
# act
139+
try:
140+
raise ValueError(expected_error_str)
141+
except ValueError:
142+
exc_info = sys.exc_info()
143+
logger.add_stack_trace(expected_key, expected_details, exc_info=exc_info)
144+
await logger.flush()
145+
146+
# assert
147+
context = get_flushed_context(sink)
148+
value = context.properties[expected_key]
149+
assert isinstance(value, dict)
150+
assert value["details"] == expected_details
151+
assert value["error_type"] == "ValueError"
152+
assert value["error_str"] == expected_error_str
153+
assert value["traceback"].split("\n")[-2] == " raise ValueError(expected_error_str)"
154+
155+
53156
@pytest.mark.asyncio
54157
async def test_put_metric_appends_values_to_array(mocker):
55158
# arrange

0 commit comments

Comments
 (0)