Skip to content

Commit

Permalink
Merge pull request #315 from ScatterHQ/migrating-to-eliot-292
Browse files Browse the repository at this point in the history
Migrating to eliot
  • Loading branch information
itamarst committed Jul 15, 2018
2 parents 9bd2967 + 3a1d87d commit 88422dc
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/source/generating/index.rst
Expand Up @@ -6,6 +6,7 @@ Generating Logs
actions
errors
loglevels
migrating
threads
types
types-testing
Expand Down
66 changes: 66 additions & 0 deletions docs/source/generating/migrating.rst
@@ -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
5 changes: 5 additions & 0 deletions docs/source/news.rst
Expand Up @@ -4,10 +4,15 @@ What's New
1.4.0
^^^^^

Features:

* Added support for routing standard library logging into Eliot; see :ref:`migrating` for details.

Documentation:

* Documented how to add log levels, and how to filter Eliot logs.
* Logstash configuration is closer to modern version's options, though still untested.
* Explained how to integrate/migrate existing logging with Eliot.

1.3.0
^^^^^
Expand Down
19 changes: 19 additions & 0 deletions eliot/stdlib.py
@@ -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"]
31 changes: 31 additions & 0 deletions eliot/tests/test_stdlib.py
@@ -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"})
41 changes: 41 additions & 0 deletions examples/stdlib.py
@@ -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()

0 comments on commit 88422dc

Please sign in to comment.