From 05c3cd0da9e56031f75f740da25dcda42aac4b85 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Sun, 15 Jul 2018 12:40:05 -0400 Subject: [PATCH 1/3] logging.Handler that routes to Eliot. --- eliot/stdlib.py | 19 +++++++++++++++++++ eliot/tests/test_stdlib.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 eliot/stdlib.py create mode 100644 eliot/tests/test_stdlib.py diff --git a/eliot/stdlib.py b/eliot/stdlib.py new file mode 100644 index 0000000..eff8120 --- /dev/null +++ b/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"] diff --git a/eliot/tests/test_stdlib.py b/eliot/tests/test_stdlib.py new file mode 100644 index 0000000..8166cc0 --- /dev/null +++ b/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"}) From d6605e265bb2b968bdc9bfeaf83468a9f1d14204 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Sun, 15 Jul 2018 12:54:01 -0400 Subject: [PATCH 2/3] Add documentation for migrating/integration stdlib logging. --- docs/source/generating/index.rst | 1 + docs/source/generating/migrating.rst | 64 ++++++++++++++++++++++++++++ examples/stdlib.py | 41 ++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 docs/source/generating/migrating.rst create mode 100644 examples/stdlib.py diff --git a/docs/source/generating/index.rst b/docs/source/generating/index.rst index 3dbd578..ca69acd 100644 --- a/docs/source/generating/index.rst +++ b/docs/source/generating/index.rst @@ -6,6 +6,7 @@ Generating Logs actions errors loglevels + migrating threads types types-testing diff --git a/docs/source/generating/migrating.rst b/docs/source/generating/migrating.rst new file mode 100644 index 0000000..19aa823 --- /dev/null +++ b/docs/source/generating/migrating.rst @@ -0,0 +1,64 @@ +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. + + +Step 1: 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()) + + +Step 2: 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 diff --git a/examples/stdlib.py b/examples/stdlib.py new file mode 100644 index 0000000..496e7b6 --- /dev/null +++ b/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() From 3a1d87dc5f1cd806e75b3f4cb3a709d6a0b34cb1 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Sun, 15 Jul 2018 12:57:25 -0400 Subject: [PATCH 3/3] News entry. --- docs/source/generating/migrating.rst | 10 ++++++---- docs/source/news.rst | 5 +++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/source/generating/migrating.rst b/docs/source/generating/migrating.rst index 19aa823..189fb1b 100644 --- a/docs/source/generating/migrating.rst +++ b/docs/source/generating/migrating.rst @@ -6,8 +6,10 @@ This document will explain how to migrate and integrate existing logging into yo In particular, this will focus on the Python standard library ``logging`` package, but the same principles apply to other logging libraries. -Step 1: Route existing logs to Eliot ------------------------------------- +.. _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! @@ -24,8 +26,8 @@ To begin with, however, we'll just add routing of log messages to Eliot: logging.getLogger().addHandler(EliotHandler()) -Step 2: Add actions at entry points and other key points --------------------------------------------------------- +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. diff --git a/docs/source/news.rst b/docs/source/news.rst index 7f30c04..5abf78b 100644 --- a/docs/source/news.rst +++ b/docs/source/news.rst @@ -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 ^^^^^