Skip to content

Commit 2e55d9c

Browse files
authored
Merge pull request #323 from ScatterHQ/stdlib-tracebacks
Stdlib bridge logs tracebacks
2 parents 4f4d5cc + 2685251 commit 2e55d9c

File tree

13 files changed

+183
-109
lines changed

13 files changed

+183
-109
lines changed

docs/source/generating/errors.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ If you get a completely unexpected exception you may wish to log a traceback to
2222
write_traceback()
2323
2424
25+
You can also pass in the output of ``sys.exc_info()``:
26+
27+
.. code-block:: python
28+
29+
import sys
30+
from eliot import write_traceback
31+
32+
write_traceback(exc_info=sys.exc_info())
33+
34+
2535
.. _extract errors:
2636

2737
Custom Exception Logging

docs/source/news.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
What's New
22
==========
33

4+
1.5.0
5+
^^^^^
6+
7+
Bug fixes:
8+
9+
* The standard library ``logging`` bridge now logs tracebacks, not just messages.
10+
11+
Features:
12+
13+
* You can now pass in an explicit traceback tuple to ``write_traceback``.
14+
15+
Changes:
16+
17+
* The deprecated ``system`` argument to ``write_traceback`` and ``writeFailure`` has been removed.
18+
419
1.4.0
520
^^^^^
621

docs/source/quickstart.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,5 +119,6 @@ You can learn more by reading the rest of the documentation, including:
119119

120120
* The :doc:`motivation behind Eliot <introduction>`.
121121
* How to generate :doc:`actions <generating/actions>`, :doc:`standalone messages <generating/messages>`, and :doc:`handle errors <generating/errors>`.
122-
* How to integrate with :doc:`asyncio coroutines <generating/asyncio>`, :doc:`threads and processes <generating/threads>`, or :doc:`Twisted <generating/twisted>`.
122+
* How to integrate or migrate your :doc:`existing stdlib logging messages <generating/migrating>`.
123123
* How to output logs :doc:`to a file or elsewhere <outputting/output>`.
124+
* Using :doc:`asyncio coroutines <generating/asyncio>`, :doc:`threads and processes <generating/threads>`, or :doc:`Twisted <generating/twisted>`.

eliot/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
FileDestination,
1818
)
1919
from ._validation import Field, fields, MessageType, ActionType
20-
from ._traceback import writeTraceback, writeFailure
20+
from ._traceback import write_traceback, writeFailure
2121
from ._errors import register_exception_extractor
2222
from ._version import get_versions
2323

@@ -37,11 +37,11 @@ def add_destination(destination):
3737
addDestination = add_destination
3838
removeDestination = Logger._destinations.remove
3939
addGlobalFields = Logger._destinations.addGlobalFields
40+
writeTraceback = write_traceback
4041

4142
# PEP 8 variants:
4243
start_action = startAction
4344
start_task = startTask
44-
write_traceback = writeTraceback
4545
write_failure = writeFailure
4646
add_destinations = Logger._destinations.add
4747
remove_destination = removeDestination

eliot/_errors.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ def get_fields_for_exception(self, logger, exception):
4545
try:
4646
return extractor(exception)
4747
except:
48-
from ._traceback import writeTraceback
49-
writeTraceback(logger)
48+
from ._traceback import write_traceback
49+
write_traceback(logger)
5050
return {}
5151
return {}
5252

eliot/_output.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from . import _bytesjson as bytesjson
1515
from zope.interface import Interface, implementer
1616

17-
from ._traceback import writeTraceback, TRACEBACK_MESSAGE
17+
from ._traceback import write_traceback, TRACEBACK_MESSAGE
1818
from ._message import (
1919
Message,
2020
EXCEPTION_FIELD,
@@ -186,7 +186,7 @@ def write(self, dictionary, serializer=None):
186186
if serializer is not None:
187187
serializer.serialize(dictionary)
188188
except:
189-
writeTraceback(self)
189+
write_traceback(self)
190190
msg = Message(
191191
{
192192
MESSAGE_TYPE_FIELD: "eliot:serialization_failure",
@@ -252,7 +252,7 @@ class MemoryLogger(object):
252252
L{MemoryLogger.messages}. Do not mutate this list.
253253
254254
@ivar tracebackMessages: A C{list} of messages written to this logger for
255-
tracebacks using L{eliot.writeTraceback} or L{eliot.writeFailure}. Do
255+
tracebacks using L{eliot.write_traceback} or L{eliot.writeFailure}. Do
256256
not mutate this list.
257257
"""
258258

eliot/_traceback.py

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import traceback
99
import sys
10-
from warnings import warn
1110

1211
from ._message import EXCEPTION_FIELD, REASON_FIELD
1312
from ._util import safeunicode, load_module
@@ -74,7 +73,7 @@ def lazycache(self, *args, **kwargs):
7473
_traceback_no_io = _get_traceback_no_io()
7574

7675

77-
def writeTraceback(logger=None, system=None):
76+
def write_traceback(logger=None, exc_info=None):
7877
"""
7978
Write the latest traceback to the log.
8079
@@ -83,20 +82,19 @@ def writeTraceback(logger=None, system=None):
8382
try:
8483
dostuff()
8584
except:
86-
writeTraceback(logger)
87-
"""
88-
if system is not None:
89-
warn(
90-
"The `system` argument to writeTraceback is no longer used.",
91-
DeprecationWarning,
92-
stacklevel=2)
85+
write_traceback(logger)
9386
94-
typ, exception, tb = sys.exc_info()
87+
Or you can pass the result of C{sys.exc_info()} to the C{exc_info}
88+
parameter.
89+
"""
90+
if exc_info is None:
91+
exc_info = sys.exc_info()
92+
typ, exception, tb = exc_info
9593
traceback = "".join(_traceback_no_io.format_exception(typ, exception, tb))
9694
_writeTracebackMessage(logger, typ, exception, traceback)
9795

9896

99-
def writeFailure(failure, logger=None, system=None):
97+
def writeFailure(failure, logger=None):
10098
"""
10199
Write a L{twisted.python.failure.Failure} to the log.
102100
@@ -114,12 +112,6 @@ def writeFailure(failure, logger=None, system=None):
114112
115113
@return: None
116114
"""
117-
if system is not None:
118-
warn(
119-
"The `system` argument to writeFailure is no longer used.",
120-
DeprecationWarning,
121-
stacklevel=2)
122-
123115
# Failure.getBriefTraceback does not include source code, so does not do
124116
# I/O.
125117
_writeTracebackMessage(

eliot/stdlib.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
"""Integration with the standard library ``logging`` package."""
22

33
from logging import Handler
4+
45
from ._message import Message
6+
from ._traceback import write_traceback
57

68

79
class EliotHandler(Handler):
@@ -14,6 +16,8 @@ def emit(self, record):
1416
logger=record.name,
1517
message=record.getMessage()
1618
)
19+
if record.exc_info:
20+
write_traceback(exc_info=record.exc_info)
1721

1822

1923
__all__ = ["EliotHandler"]

eliot/tests/test_output.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
MemoryLogger, ILogger, Destinations, Logger, bytesjson as json, to_file,
2222
FileDestination, _DestinationsSendError)
2323
from .._validation import ValidationError, Field, _MessageSerializer
24-
from .._traceback import writeTraceback
24+
from .._traceback import write_traceback
2525
from ..testing import assertContainsFields
2626

2727

@@ -155,14 +155,14 @@ def test_serializeCopies(self):
155155
logger.serialize()
156156
self.assertEqual(logger.messages[0]["length"], "abc")
157157

158-
def writeTraceback(self, logger, exception):
158+
def write_traceback(self, logger, exception):
159159
"""
160160
Write an exception as a traceback to the logger.
161161
"""
162162
try:
163163
raise exception
164164
except:
165-
writeTraceback(logger)
165+
write_traceback(logger)
166166

167167
def test_tracebacksCauseTestFailure(self):
168168
"""
@@ -171,7 +171,7 @@ def test_tracebacksCauseTestFailure(self):
171171
"""
172172
logger = MemoryLogger()
173173
exception = Exception()
174-
self.writeTraceback(logger, exception)
174+
self.write_traceback(logger, exception)
175175
self.assertEqual(logger.tracebackMessages[0]["reason"], exception)
176176

177177
def test_flushTracebacksNoTestFailure(self):
@@ -182,7 +182,7 @@ def test_flushTracebacksNoTestFailure(self):
182182
"""
183183
logger = MemoryLogger()
184184
exception = RuntimeError()
185-
self.writeTraceback(logger, exception)
185+
self.write_traceback(logger, exception)
186186
logger.flushTracebacks(RuntimeError)
187187
self.assertEqual(logger.tracebackMessages, [])
188188

@@ -194,7 +194,7 @@ def test_flushTracebacksReturnsExceptions(self):
194194
logger = MemoryLogger()
195195
logger.write({"x": 1})
196196
for exc in exceptions:
197-
self.writeTraceback(logger, exc)
197+
self.write_traceback(logger, exc)
198198
logger.write({"x": 1})
199199
flushed = logger.flushTracebacks(ZeroDivisionError)
200200
self.assertEqual(flushed, logger.messages[1:3])
@@ -207,7 +207,7 @@ def test_flushTracebacksUnflushedTestFailure(self):
207207
"""
208208
logger = MemoryLogger()
209209
exception = RuntimeError()
210-
self.writeTraceback(logger, exception)
210+
self.write_traceback(logger, exception)
211211
logger.flushTracebacks(KeyError)
212212
self.assertEqual(logger.tracebackMessages[0]["reason"], exception)
213213

@@ -218,7 +218,7 @@ def test_flushTracebacksUnflushedUnreturned(self):
218218
"""
219219
logger = MemoryLogger()
220220
exception = RuntimeError()
221-
self.writeTraceback(logger, exception)
221+
self.write_traceback(logger, exception)
222222
self.assertEqual(logger.flushTracebacks(KeyError), [])
223223

224224
def test_reset(self):

eliot/tests/test_stdlib.py

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
from unittest import TestCase
44
import logging
5+
import traceback
56

67
from ..testing import assertContainsFields, capture_logging
78
from ..stdlib import EliotHandler
9+
from .test_traceback import assert_expected_traceback
810

911

1012
class StdlibTests(TestCase):
@@ -18,14 +20,48 @@ def test_handler(self, logger):
1820
handler = EliotHandler()
1921
stdlib_logger.addHandler(handler)
2022
stdlib_logger.info("hello")
21-
stdlib_logger.warn("ono")
23+
stdlib_logger.warning("ono")
2224
message = logger.messages[0]
23-
assertContainsFields(self, message, {"message_type": "eliot:stdlib",
24-
"log_level": "INFO",
25-
"message": "hello",
26-
"logger": "eliot-test"})
25+
assertContainsFields(
26+
self, message, {
27+
"message_type": "eliot:stdlib",
28+
"log_level": "INFO",
29+
"message": "hello",
30+
"logger": "eliot-test"
31+
}
32+
)
2733
message = logger.messages[1]
28-
assertContainsFields(self, message, {"message_type": "eliot:stdlib",
29-
"log_level": "WARNING",
30-
"message": "ono",
31-
"logger": "eliot-test"})
34+
assertContainsFields(
35+
self, message, {
36+
"message_type": "eliot:stdlib",
37+
"log_level": "WARNING",
38+
"message": "ono",
39+
"logger": "eliot-test"
40+
}
41+
)
42+
43+
@capture_logging(None)
44+
def test_traceback(self, logger):
45+
"""The EliotHandler routes tracebacks to Eliot."""
46+
stdlib_logger = logging.getLogger("eliot-test2")
47+
stdlib_logger.setLevel(logging.DEBUG)
48+
handler = EliotHandler()
49+
stdlib_logger.addHandler(handler)
50+
try:
51+
raise RuntimeError()
52+
except Exception as e:
53+
exception = e
54+
expected_traceback = traceback.format_exc()
55+
stdlib_logger.exception("ono")
56+
message = logger.messages[0]
57+
assertContainsFields(
58+
self, message, {
59+
"message_type": "eliot:stdlib",
60+
"log_level": "ERROR",
61+
"message": "ono",
62+
"logger": "eliot-test2"
63+
}
64+
)
65+
assert_expected_traceback(
66+
self, logger, logger.messages[1], exception, expected_traceback
67+
)

0 commit comments

Comments
 (0)