Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #315 from ScatterHQ/migrating-to-eliot-292
Migrating to eliot
- Loading branch information
Showing
6 changed files
with
163 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ Generating Logs | |
actions | ||
errors | ||
loglevels | ||
migrating | ||
threads | ||
types | ||
types-testing | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
Integrating and Migrating Existing Logging | ||
========================================== | ||
|
||
If you have an existing code base, you likely have existing log messages. | ||
This document will explain how to migrate and integrate existing logging into your new Eliot log setup. | ||
In particular, this will focus on the Python standard library ``logging`` package, but the same principles apply to other logging libraries. | ||
|
||
|
||
.. _migrating: | ||
|
||
Route existing logs to Eliot | ||
---------------------------- | ||
|
||
Eliot includes a ``logging.Handler`` that can take standard library log messages and route them into Eliot. | ||
These log messages will *automatically* appear in the correct place in the action tree! | ||
Once you add actions to your code these log messages will automatically benefit from Eliot's causal information. | ||
|
||
To begin with, however, we'll just add routing of log messages to Eliot: | ||
|
||
.. code-block:: python | ||
# Add Eliot Handler to root Logger. You may wish to only route specific | ||
# Loggers to Eliot. | ||
import logging | ||
from eliot.stdlib import EliotHandler | ||
logging.getLogger().addHandler(EliotHandler()) | ||
Add actions at entry points and other key points | ||
------------------------------------------------ | ||
|
||
Simply by adding a few key actions—the entry points to the code, as well as key sub-actions—you can start getting value from Eliot's functionality while still getting information from your existing logs. | ||
You can leave existing log messages in place, replacing them with Eliot logging opportunistically; they will still be included in your output. | ||
|
||
.. literalinclude:: ../../../examples/stdlib.py | ||
|
||
The stdlib logging messages will be included in the correct part of the tree: | ||
|
||
.. code-block:: shell-session | ||
$ python examples/stdlib.py | eliot-tree | ||
3f465ee3-7fa9-40e2-8b20-9c0595612a8b | ||
└── mypackage:main/1 ⇒ started | ||
├── timestamp: 2018-07-15 16:50:39.230467 | ||
├── mypackage:do_a_thing/2/1 ⇒ started | ||
│ ├── timestamp: 2018-07-15 16:50:39.230709 | ||
│ └── mypackage:do_a_thing/2/2 ⇒ succeeded | ||
│ └── timestamp: 2018-07-15 16:50:39.230836 | ||
├── mypackage:do_a_thing/3/1 ⇒ started | ||
│ ├── timestamp: 2018-07-15 16:50:39.230980 | ||
│ ├── eliot:stdlib/3/2 | ||
│ │ ├── log_level: ERROR | ||
│ │ ├── logger: mypackage | ||
│ │ ├── message: The number 3 is a bad number, don't use it. | ||
│ │ └── timestamp: 2018-07-15 16:50:39.231157 | ||
│ └── mypackage:do_a_thing/3/3 ⇒ failed | ||
│ ├── exception: builtins.ValueError | ||
│ ├── reason: I hate the number 3 | ||
│ └── timestamp: 2018-07-15 16:50:39.231364 | ||
├── eliot:stdlib/4 | ||
│ ├── log_level: INFO | ||
│ ├── logger: mypackage | ||
│ ├── message: Number 3 was rejected. | ||
│ └── timestamp: 2018-07-15 16:50:39.231515 | ||
└── mypackage:main/5 ⇒ succeeded | ||
└── timestamp: 2018-07-15 16:50:39.231641 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
"""Integration with the standard library ``logging`` package.""" | ||
|
||
from logging import Handler | ||
from ._message import Message | ||
|
||
|
||
class EliotHandler(Handler): | ||
"""A C{logging.Handler} that routes log messages to Eliot.""" | ||
|
||
def emit(self, record): | ||
Message.log( | ||
message_type="eliot:stdlib", | ||
log_level=record.levelname, | ||
logger=record.name, | ||
message=record.getMessage() | ||
) | ||
|
||
|
||
__all__ = ["EliotHandler"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
"""Tests for standard library logging integration.""" | ||
|
||
from unittest import TestCase | ||
import logging | ||
|
||
from ..testing import assertContainsFields, capture_logging | ||
from ..stdlib import EliotHandler | ||
|
||
|
||
class StdlibTests(TestCase): | ||
"""Tests for stdlib integration.""" | ||
|
||
@capture_logging(None) | ||
def test_handler(self, logger): | ||
"""The EliotHandler routes messages to Eliot.""" | ||
stdlib_logger = logging.getLogger("eliot-test") | ||
stdlib_logger.setLevel(logging.DEBUG) | ||
handler = EliotHandler() | ||
stdlib_logger.addHandler(handler) | ||
stdlib_logger.info("hello") | ||
stdlib_logger.warn("ono") | ||
message = logger.messages[0] | ||
assertContainsFields(self, message, {"message_type": "eliot:stdlib", | ||
"log_level": "INFO", | ||
"message": "hello", | ||
"logger": "eliot-test"}) | ||
message = logger.messages[1] | ||
assertContainsFields(self, message, {"message_type": "eliot:stdlib", | ||
"log_level": "WARNING", | ||
"message": "ono", | ||
"logger": "eliot-test"}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
""" | ||
Example of routing standard library logging to Eliot. | ||
The assumption is you have legacy logging using stdlib, and are switching over | ||
to Eliot. | ||
""" | ||
|
||
import logging | ||
import sys | ||
|
||
from eliot.stdlib import EliotHandler | ||
from eliot import start_action, to_file | ||
|
||
# A Logger left over from before switch to Eliot | ||
LEGACY_LOGGER = logging.Logger("mypackage") | ||
|
||
|
||
def do_a_thing(i): | ||
with start_action(action_type="mypackage:do_a_thing"): | ||
# run your business logic.... | ||
if i == 3: | ||
LEGACY_LOGGER.error("The number 3 is a bad number, don't use it.") | ||
raise ValueError("I hate the number 3") | ||
|
||
|
||
def main(): | ||
with start_action(action_type="mypackage:main"): | ||
for i in [1, 3]: | ||
try: | ||
do_a_thing(i) | ||
except ValueError: | ||
LEGACY_LOGGER.info("Number {} was rejected.".format(i)) | ||
|
||
|
||
if __name__ == '__main__': | ||
# Hook up stdlib logging to Eliot: | ||
LEGACY_LOGGER.addHandler(EliotHandler()) | ||
# Write Eliot logs to stdout: | ||
to_file(sys.stdout) | ||
# Run the code: | ||
main() |