Codebase list python-eliot / fresh-snapshots/main
New upstream snapshot. Debian Janitor 1 year, 2 months ago
18 changed file(s) with 300 addition(s) and 262 deletion(s). Raw diff Collapse all Expand all
00 Metadata-Version: 2.1
11 Name: eliot
2 Version: 1.13.0
2 Version: 0+unknown
33 Summary: Logging library that tells you why it happened
44 Home-page: https://github.com/itamarst/eliot/
55 Maintainer: Itamar Turner-Trauring
66 Maintainer-email: itamar@itamarst.org
77 License: Apache 2.0
8 Description: Eliot: Logging that tells you *why* it happened
9 ================================================
10
11 .. image:: https://travis-ci.org/itamarst/eliot.png?branch=master
12 :target: http://travis-ci.org/itamarst/eliot
13 :alt: Build Status
14
15 Python's built-in ``logging`` and other similar systems output a stream of factoids: they're interesting, but you can't really tell what's going on.
16
17 * Why is your application slow?
18 * What caused this code path to be chosen?
19 * Why did this error happen?
20
21 Standard logging can't answer these questions.
22
23 But with a better model you could understand what and why things happened in your application.
24 You could pinpoint performance bottlenecks, you could understand what happened when, who called what.
25
26 That is what Eliot does.
27 ``eliot`` is a Python logging system that outputs causal chains of **actions**: actions can spawn other actions, and eventually they either **succeed or fail**.
28 The resulting logs tell you the story of what your software did: what happened, and what caused it.
29
30 Eliot supports a range of use cases and 3rd party libraries:
31
32 * Logging within a single process.
33 * Causal tracing across a distributed system.
34 * Scientific computing, with `built-in support for NumPy and Dask <https://eliot.readthedocs.io/en/stable/scientific-computing.html>`_.
35 * `Asyncio and Trio coroutines <https://eliot.readthedocs.io/en/stable/generating/asyncio.html>`_ and the `Twisted networking framework <https://eliot.readthedocs.io/en/stable/generating/twisted.html>`_.
36
37 Eliot is only used to generate your logs; you will might need tools like Logstash and ElasticSearch to aggregate and store logs if you are using multiple processes across multiple machines.
38
39 Eliot supports Python 3.6, 3.7, 3.8, and 3.9, as well as PyPy3.
40 It is maintained by Itamar Turner-Trauring, and released under the Apache 2.0 License.
41
42 Python 2.7 is in legacy support mode, with the last release supported being 1.7; see `here <https://eliot.readthedocs.io/en/stable/python2.html>`_ for details.
43
44 * `Read the documentation <https://eliot.readthedocs.io>`_.
45 * Download from `PyPI`_ or `conda-forge <https://anaconda.org/conda-forge/eliot>`_.
46 * Need help or have any questions? `File an issue <https://github.com/itamarst/eliot/issues/new>`_ on GitHub.
47 * **Commercial support** is available from `Python⇒Speed <https://pythonspeed.com/services/#eliot>`_.
48
49 Testimonials
50 ------------
51
52 "Eliot has made tracking down causes of failure (in complex external integrations and internal uses) tremendously easier. Our errors are logged to Sentry with the Eliot task UUID. That means we can go from a Sentry notification to a high-level trace of operations—with important metadata at each operation—in a few seconds. We immediately know which user did what in which part of the system."
53
54 —Jonathan Jacobs
55
56 .. _Github: https://github.com/itamarst/eliot
57 .. _PyPI: https://pypi.python.org/pypi/eliot
58
598 Keywords: logging
60 Platform: UNKNOWN
619 Classifier: Intended Audience :: Developers
6210 Classifier: License :: OSI Approved :: Apache Software License
6311 Classifier: Operating System :: OS Independent
6715 Classifier: Programming Language :: Python :: 3.7
6816 Classifier: Programming Language :: Python :: 3.8
6917 Classifier: Programming Language :: Python :: 3.9
18 Classifier: Programming Language :: Python :: 3.10
7019 Classifier: Programming Language :: Python :: Implementation :: CPython
7120 Classifier: Programming Language :: Python :: Implementation :: PyPy
7221 Classifier: Topic :: System :: Logging
7322 Requires-Python: >=3.6.0
23 Provides-Extra: dev
7424 Provides-Extra: journald
7525 Provides-Extra: test
76 Provides-Extra: dev
26 License-File: LICENSE
27
28 Eliot: Logging that tells you *why* it happened
29 ================================================
30
31 .. image:: https://travis-ci.org/itamarst/eliot.png?branch=master
32 :target: http://travis-ci.org/itamarst/eliot
33 :alt: Build Status
34
35 Python's built-in ``logging`` and other similar systems output a stream of factoids: they're interesting, but you can't really tell what's going on.
36
37 * Why is your application slow?
38 * What caused this code path to be chosen?
39 * Why did this error happen?
40
41 Standard logging can't answer these questions.
42
43 But with a better model you could understand what and why things happened in your application.
44 You could pinpoint performance bottlenecks, you could understand what happened when, who called what.
45
46 That is what Eliot does.
47 ``eliot`` is a Python logging system that outputs causal chains of **actions**: actions can spawn other actions, and eventually they either **succeed or fail**.
48 The resulting logs tell you the story of what your software did: what happened, and what caused it.
49
50 Eliot supports a range of use cases and 3rd party libraries:
51
52 * Logging within a single process.
53 * Causal tracing across a distributed system.
54 * Scientific computing, with `built-in support for NumPy and Dask <https://eliot.readthedocs.io/en/stable/scientific-computing.html>`_.
55 * `Asyncio and Trio coroutines <https://eliot.readthedocs.io/en/stable/generating/asyncio.html>`_ and the `Twisted networking framework <https://eliot.readthedocs.io/en/stable/generating/twisted.html>`_.
56
57 Eliot is only used to generate your logs; you will might need tools like Logstash and ElasticSearch to aggregate and store logs if you are using multiple processes across multiple machines.
58
59 Eliot supports Python 3.6, 3.7, 3.8, 3.9, and 3.10, as well as PyPy3.
60 It is maintained by Itamar Turner-Trauring, and released under the Apache 2.0 License.
61
62 * `Read the documentation <https://eliot.readthedocs.io>`_.
63 * Download from `PyPI`_ or `conda-forge <https://anaconda.org/conda-forge/eliot>`_.
64 * Need help or have any questions? `File an issue <https://github.com/itamarst/eliot/issues/new>`_ on GitHub.
65 * **Commercial support** is available from `Python⇒Speed <https://pythonspeed.com/services/#eliot>`_.
66
67 Testimonials
68 ------------
69
70 "Eliot has made tracking down causes of failure (in complex external integrations and internal uses) tremendously easier. Our errors are logged to Sentry with the Eliot task UUID. That means we can go from a Sentry notification to a high-level trace of operations—with important metadata at each operation—in a few seconds. We immediately know which user did what in which part of the system."
71
72 —Jonathan Jacobs
73
74 .. _Github: https://github.com/itamarst/eliot
75 .. _PyPI: https://pypi.python.org/pypi/eliot
2828
2929 Eliot is only used to generate your logs; you will might need tools like Logstash and ElasticSearch to aggregate and store logs if you are using multiple processes across multiple machines.
3030
31 Eliot supports Python 3.6, 3.7, 3.8, and 3.9, as well as PyPy3.
31 Eliot supports Python 3.6, 3.7, 3.8, 3.9, and 3.10, as well as PyPy3.
3232 It is maintained by Itamar Turner-Trauring, and released under the Apache 2.0 License.
33
34 Python 2.7 is in legacy support mode, with the last release supported being 1.7; see `here <https://eliot.readthedocs.io/en/stable/python2.html>`_ for details.
3533
3634 * `Read the documentation <https://eliot.readthedocs.io>`_.
3735 * Download from `PyPI`_ or `conda-forge <https://anaconda.org/conda-forge/eliot>`_.
0 python-eliot (1.13.0-2) UNRELEASED; urgency=medium
0 python-eliot (1.14.0+git20211224.1.74e0aa2-1) UNRELEASED; urgency=medium
11
22 * Update standards version to 4.6.2, no changes needed.
3 * New upstream snapshot.
34
4 -- Debian Janitor <janitor@jelmer.uk> Sun, 08 Jan 2023 18:34:28 -0000
5 -- Debian Janitor <janitor@jelmer.uk> Wed, 11 Jan 2023 04:11:56 -0000
56
67 python-eliot (1.13.0-1) unstable; urgency=medium
78
00 What's New
11 ==========
2
3 1.14.0
4 ^^^^^^
5
6 Features:
7
8 * ``Action.continue_task`` now takes ``action_task`` and extra fields to use for the action, so the default ``eliot:remote_task`` can be changed.
9 * Added support for Python 3.10.
10
11 Bug fixes:
12
13 * Fix infinite recursion when a logging destination raises exceptions forever. Thanks to to @alextatarinov.
214
315 1.13.0
416 ^^^^^^
33 ==================
44
55 The last version of Eliot to support Python 2.7 was release 1.7.
6
7 If you are using Eliot with Python 2, keep the following in mind:
8
9 * I will provide critical bug fixes for Python 2 until March 2020.
10 I will accept patches for critical bug fixes after that (or you can `pay for my services <https://pythonspeed.com/services/#eliot>`_ to do additional work).
11 * Make sure you use an up-to-date ``setuptools`` and ``pip``; in theory this should result in only downloading versions of the package that support Python 2.
12 * For extra safety, you can pin Eliot in ``setup.py`` or ``requirements.txt`` by setting: ``eliot < 1.8``.
13 * Critical bug fixes for Python 2 will be released as 1.7.1, 1.7.2, etc..
235235 ).encode("ascii")
236236
237237 @classmethod
238 def continue_task(cls, logger=None, task_id=_TASK_ID_NOT_SUPPLIED):
238 def continue_task(
239 cls,
240 logger=None,
241 task_id=_TASK_ID_NOT_SUPPLIED,
242 *,
243 action_type="eliot:remote_task",
244 _serializers=None,
245 **fields,
246 ):
239247 """
240248 Start a new action which is part of a serialized task.
241249
245253 @param task_id: A serialized task identifier, the output of
246254 L{Action.serialize_task_id}, either ASCII-encoded bytes or unicode
247255 string. Required.
256
257 @param action_type: The type of this action,
258 e.g. C{"yourapp:subsystem:dosomething"}.
259
260 @param _serializers: Either a L{eliot._validation._ActionSerializers}
261 instance or C{None}. In the latter case no validation or serialization
262 will be done for messages generated by the L{Action}.
263
264 @param fields: Additional fields to add to the start message.
248265
249266 @return: The new L{Action} instance.
250267 """
254271 task_id = task_id.decode("ascii")
255272 uuid, task_level = task_id.split("@")
256273 action = cls(
257 logger, uuid, TaskLevel.fromString(task_level), "eliot:remote_task"
274 logger, uuid, TaskLevel.fromString(task_level), action_type, _serializers
258275 )
259 action._start({})
276 action._start(fields)
260277 return action
261278
262279 # Backwards-compat variants:
422439 fields[TASK_UUID_FIELD] = self._identification[TASK_UUID_FIELD]
423440 fields[TASK_LEVEL_FIELD] = self._nextTaskLevel().as_list()
424441 fields[MESSAGE_TYPE_FIELD] = message_type
425 self._logger.write(fields, fields.pop("__eliot_serializer__", None))
442 # Loggers will hopefully go away...
443 logger = fields.pop("__eliot_logger__", self._logger)
444 logger.write(fields, fields.pop("__eliot_serializer__", None))
426445
427446
428447 class WrongTask(Exception):
933952
934953 If there is no current action, a new UUID will be generated.
935954 """
936 # Loggers will hopefully go away...
937 logger = fields.pop("__eliot_logger__", None)
938955 action = current_action()
939956 if action is None:
957 # Loggers will hopefully go away...
958 logger = fields.pop("__eliot_logger__", None)
940959 action = Action(logger, str(uuid4()), TaskLevel(level=[]), "")
941960 action.log(message_type, **fields)
942961
115115 Byte field names will be converted to Unicode.
116116
117117 @type logger: L{eliot.ILogger} or C{None} indicating the default one.
118 Should not be set if the action is also set.
118119
119 @param action: The L{Action} which is the context for this message. If
120 C{None}, the L{Action} will be deduced from the current call
121 stack.
120 @param action: The L{Action} which is the context for this message. If
121 C{None}, the L{Action} will be deduced from the current call stack.
122122 """
123123 fields = dict(self._contents)
124124 if "message_type" not in fields:
126126 if self._serializer is not None:
127127 fields["__eliot_serializer__"] = self._serializer
128128 if action is None:
129 fields["__eliot_logger__"] = logger
129 if logger is not None:
130 fields["__eliot_logger__"] = logger
130131 log_message(**fields)
131132 else:
132133 action.log(**fields)
11 Implementation of hooks and APIs for outputting log messages.
22 """
33
4 import sys
54 import traceback
65 import inspect
76 import json as pyjson
2120 from ._validation import ValidationError
2221
2322
24 class _DestinationsSendError(Exception):
25 """
26 An error occured sending to one or more destinations.
27
28 @ivar errors: A list of tuples output from C{sys.exc_info()}.
29 """
30
31 def __init__(self, errors):
32 self.errors = errors
33 Exception.__init__(self, errors)
23 # Action type for log messages due to a (hopefully temporarily) broken
24 # destination.
25 DESTINATION_FAILURE = "eliot:destination_failure"
3426
3527
3628 class BufferingDestination(object):
6961 """
7062 self._globalFields.update(fields)
7163
72 def send(self, message):
64 def send(self, message, logger=None):
7365 """
7466 Deliver a message to all destinations.
7567
7668 The passed in message might be mutated.
69
70 This should never raise an exception.
7771
7872 @param message: A message dictionary that can be serialized to JSON.
7973 @type message: L{dict}
74
75 @param logger: The ``ILogger`` that wrote the message, if any.
8076 """
8177 message.update(self._globalFields)
8278 errors = []
79 is_destination_error_message = (
80 message.get("message_type", None) == DESTINATION_FAILURE
81 )
8382 for dest in self._destinations:
8483 try:
8584 dest(message)
85 except Exception as e:
86 # If the destination is broken not because of a specific
87 # message, but rather continously, we will get a
88 # "eliot:destination_failure" log message logged, and so we
89 # want to ensure it doesn't do infinite recursion.
90 if not is_destination_error_message:
91 errors.append(e)
92
93 for exception in errors:
94 from ._action import log_message
95
96 try:
97 new_msg = {
98 MESSAGE_TYPE_FIELD: DESTINATION_FAILURE,
99 REASON_FIELD: safeunicode(exception),
100 EXCEPTION_FIELD: exception.__class__.__module__
101 + "."
102 + exception.__class__.__name__,
103 "message": _safe_unicode_dictionary(message),
104 }
105 if logger is not None:
106 # This is really only useful for testing, should really
107 # figure out way to get rid of this mechanism...
108 new_msg["__eliot_logger__"] = logger
109 log_message(**new_msg)
86110 except:
87 errors.append(sys.exc_info())
88 if errors:
89 raise _DestinationsSendError(errors)
111 # Nothing we can do here, raising exception to caller will
112 # break business logic, better to have that continue to
113 # work even if logging isn't.
114 pass
90115
91116 def add(self, *destinations):
92117 """
143168 """
144169
145170
171 def _safe_unicode_dictionary(dictionary):
172 """
173 Serialize a dictionary to a unicode string no matter what it contains.
174
175 The resulting dictionary will loosely follow Python syntax but it is
176 not expected to actually be a lossless encoding in all cases.
177
178 @param dictionary: A L{dict} to serialize.
179
180 @return: A L{unicode} string representing the input dictionary as
181 faithfully as can be done without putting in too much effort.
182 """
183 try:
184 return str(
185 dict(
186 (saferepr(key), saferepr(value)) for (key, value) in dictionary.items()
187 )
188 )
189 except:
190 return saferepr(dictionary)
191
192
146193 @implementer(ILogger)
147194 class Logger(object):
148195 """
154201 """
155202
156203 _destinations = Destinations()
157 _log_tracebacks = True
158
159 def _safeUnicodeDictionary(self, dictionary):
160 """
161 Serialize a dictionary to a unicode string no matter what it contains.
162
163 The resulting dictionary will loosely follow Python syntax but it is
164 not expected to actually be a lossless encoding in all cases.
165
166 @param dictionary: A L{dict} to serialize.
167
168 @return: A L{unicode} string representing the input dictionary as
169 faithfully as can be done without putting in too much effort.
170 """
171 try:
172 return str(
173 dict(
174 (saferepr(key), saferepr(value))
175 for (key, value) in dictionary.items()
176 )
177 )
178 except:
179 return saferepr(dictionary)
180204
181205 def write(self, dictionary, serializer=None):
182206 """
192216
193217 log_message(
194218 "eliot:serialization_failure",
195 message=self._safeUnicodeDictionary(dictionary),
219 message=_safe_unicode_dictionary(dictionary),
196220 __eliot_logger__=self,
197221 )
198222 return
199223
200 try:
201 self._destinations.send(dictionary)
202 except _DestinationsSendError as e:
203 from ._action import log_message
204
205 if self._log_tracebacks:
206 for (exc_type, exception, exc_traceback) in e.errors:
207 # Can't use same Logger as serialization errors because
208 # if destination continues to error out we will get
209 # infinite recursion. So instead we have to manually
210 # construct a Logger that won't retry.
211 logger = Logger()
212 logger._log_tracebacks = False
213 logger._destinations = self._destinations
214 msg = {
215 MESSAGE_TYPE_FIELD: "eliot:destination_failure",
216 REASON_FIELD: safeunicode(exception),
217 EXCEPTION_FIELD: exc_type.__module__ + "." + exc_type.__name__,
218 "message": self._safeUnicodeDictionary(dictionary),
219 "__eliot_logger__": logger,
220 }
221 log_message(**msg)
222 else:
223 # Nothing we can do here, raising exception to caller will
224 # break business logic, better to have that continue to
225 # work even if logging isn't.
226 pass
224 self._destinations.send(dictionary, self)
227225
228226
229227 def exclusively(f):
77
88 version_json = '''
99 {
10 "date": "2020-12-15T14:09:24-0500",
11 "dirty": false,
12 "error": null,
13 "full-revisionid": "e858c8ef7302e22ca05f37565d929db8e0fab153",
14 "version": "1.13.0"
10 "date": null,
11 "dirty": null,
12 "error": "unable to compute version",
13 "full-revisionid": null,
14 "version": "0+unknown"
1515 }
1616 ''' # END VERSION_JSON
1717
1818 return float(o)
1919 if isinstance(o, numpy.integer):
2020 return int(o)
21 if isinstance(o, (numpy.bool, numpy.bool_)):
21 if isinstance(o, numpy.bool_):
2222 return bool(o)
2323 if isinstance(o, numpy.ndarray):
2424 if o.size > 10000:
780780 },
781781 )
782782
783 def test_continueTaskCustomType(self):
784 """
785 L{Action.continue_task} uses the provided action type and extra fields.
786 """
787 originalAction = Action(None, "uniq456", TaskLevel(level=[3, 4]), "mytype")
788 taskId = originalAction.serializeTaskId()
789 logger = MemoryLogger()
790
791 Action.continue_task(logger, taskId, action_type="custom:action", field="value")
792 assertContainsFields(
793 self,
794 logger.messages[0],
795 {
796 "task_uuid": "uniq456",
797 "task_level": [3, 4, 1, 1],
798 "action_type": "custom:action",
799 "action_status": "started",
800 "field": "value",
801 },
802 )
803
783804 def test_continueTaskNoLogger(self):
784805 """
785806 L{Action.continue_task} can be called without a logger.
105105 top_action_name: [
106106 {"eliot:remote_task": ["dask:task", "mult"]},
107107 {"eliot:remote_task": ["dask:task", "mult"]},
108 {"eliot:remote_task": ["dask:task"]},
109 {"eliot:remote_task": ["dask:task"]},
110108 {"eliot:remote_task": ["dask:task", "finally"]},
111109 ]
112110 }
118116 (
119117 mult1_msg,
120118 mult2_msg,
121 reduce1_msg,
122 reduce2_msg,
123119 final_msg,
124120 ) = LoggedMessage.ofType(logger.messages, "dask:task")
125121 self.assertEqual(
126 reduce1_msg.message["dependencies"], [mult1_msg.message["key"]]
127 )
128 self.assertEqual(
129 reduce2_msg.message["dependencies"], [mult2_msg.message["key"]]
130 )
131 self.assertEqual(
132122 sorted(final_msg.message["dependencies"]),
133 sorted([reduce1_msg.message["key"], reduce2_msg.message["key"]]),
123 sorted([mult1_msg.message["key"], mult2_msg.message["key"]]),
134124 )
135125
136126 # Make sure dependencies are logically earlier in the logs:
137127 self.assertTrue(
138 mult1_msg.message["task_level"] < reduce1_msg.message["task_level"]
128 mult1_msg.message["task_level"] < final_msg.message["task_level"]
139129 )
140130 self.assertTrue(
141 mult2_msg.message["task_level"] < reduce2_msg.message["task_level"]
142 )
143 self.assertTrue(
144 reduce1_msg.message["task_level"] < final_msg.message["task_level"]
145 )
146 self.assertTrue(
147 reduce2_msg.message["task_level"] < final_msg.message["task_level"]
131 mult2_msg.message["task_level"] < final_msg.message["task_level"]
148132 )
149133
150134
2626 bytesjson as json,
2727 to_file,
2828 FileDestination,
29 _DestinationsSendError,
29 _safe_unicode_dictionary,
3030 )
31 from .._action import start_action
3132 from .._validation import ValidationError, Field, _MessageSerializer
3233 from .._traceback import write_traceback
3334 from ..testing import assertContainsFields
371372 self.assertEqual(dest2, [message])
372373 self.assertEqual(dest3, [message])
373374
374 def test_destinationExceptionMultipleDestinations(self):
375 def test_destination_exception_multiple_destinations(self):
375376 """
376377 If one destination throws an exception, other destinations still
377378 get the message.
385386 destinations.add(dest3.append)
386387
387388 message = {"hello": 123}
388 self.assertRaises(_DestinationsSendError, destinations.send, {"hello": 123})
389 self.assertEqual((dest, dest3), ([message], [message]))
390
391 def test_destinationExceptionContinue(self):
389 destinations.send(message)
390 self.assertIn(message, dest)
391 self.assertIn(message, dest3)
392
393 def test_destination_exception_continue(self):
392394 """
393395 If a destination throws an exception, future messages are still
394396 sent to it.
397399 dest = BadDestination()
398400 destinations.add(dest)
399401
400 self.assertRaises(_DestinationsSendError, destinations.send, {"hello": 123})
401 destinations.send({"hello": 200})
402 self.assertEqual(dest, [{"hello": 200}])
402 msg1 = {"hello": 123}
403 msg2 = {"world": 456}
404 destinations.send(msg1)
405 self.assertNotIn(msg1, dest)
406 destinations.send(msg2)
407 self.assertIn(msg2, dest)
403408
404409 def test_remove(self):
405410 """
559564 logger.write(d, serializer)
560565 self.assertEqual(d, original)
561566
562 def test_safeUnicodeDictionary(self):
563 """
564 L{Logger._safeUnicodeDictionary} converts the given dictionary's
567 def test_safe_unicode_dictionary(self):
568 """
569 L{_safe_unicode_dictionary} converts the given dictionary's
565570 values and keys to unicode using C{safeunicode}.
566571 """
567572
572577 dictionary = {badobject(): 123, 123: badobject()}
573578 badMessage = "eliot: unknown, unicode() raised exception"
574579 self.assertEqual(
575 eval(Logger()._safeUnicodeDictionary(dictionary)),
580 eval(_safe_unicode_dictionary(dictionary)),
576581 {badMessage: "123", "123": badMessage},
577582 )
578583
579 def test_safeUnicodeDictionaryFallback(self):
584 def test_safe_unicode_dictionary_fallback(self):
580585 """
581586 If converting the dictionary failed for some reason,
582 L{Logger._safeUnicodeDictionary} runs C{repr} on the object.
583 """
584 self.assertEqual(Logger()._safeUnicodeDictionary(None), "None")
585
586 def test_safeUnicodeDictionaryFallbackFailure(self):
587 """
588 If all else fails, L{Logger._safeUnicodeDictionary} just gives up.
587 L{_safe_unicode_dictionary} runs C{repr} on the object.
588 """
589 self.assertEqual(_safe_unicode_dictionary(None), "None")
590
591 def test_safe_unicode_dictionary_fallback_failure(self):
592 """
593 If all else fails, L{_safe_unicode_dictionary} just gives up.
589594 """
590595
591596 class badobject(object):
593598 raise TypeError()
594599
595600 self.assertEqual(
596 Logger()._safeUnicodeDictionary(badobject()),
601 _safe_unicode_dictionary(badobject()),
597602 "eliot: unknown, unicode() raised exception",
598603 )
599604
627632 },
628633 )
629634 self.assertIn("RuntimeError: oops", tracebackMessage["traceback"])
630 # Calling _safeUnicodeDictionary multiple times leads to
635 # Calling _safe_unicode_dictionary multiple times leads to
631636 # inconsistent results due to hash ordering, so compare contents:
632637 assertContainsFields(
633638 self, written[1], {"message_type": "eliot:serialization_failure"}
637642 dict((repr(key), repr(value)) for (key, value) in message.items()),
638643 )
639644
640 def test_destinationExceptionCaught(self):
645 def test_destination_exception_caught(self):
641646 """
642647 If a destination throws an exception, an appropriate error is
643648 logged.
654659 dest[0],
655660 {
656661 "message_type": "eliot:destination_failure",
657 "message": logger._safeUnicodeDictionary(message),
662 "message": _safe_unicode_dictionary(message),
658663 "reason": "ono",
659664 "exception": "eliot.tests.test_output.MyException",
660665 },
661666 )
662667
663 def test_destinationMultipleExceptionsCaught(self):
668 def test_destination_multiple_exceptions_caught(self):
664669 """
665670 If multiple destinations throw an exception, an appropriate error is
666671 logged for each.
704709 message,
705710 {
706711 "message_type": "eliot:destination_failure",
707 "message": logger._safeUnicodeDictionary(message),
712 "message": _safe_unicode_dictionary(message),
708713 "reason": "ono",
709714 "exception": "eliot.tests.test_output.MyException",
710715 },
711716 {
712717 "message_type": "eliot:destination_failure",
713 "message": logger._safeUnicodeDictionary(message),
718 "message": _safe_unicode_dictionary(message),
714719 "reason": zero_divide,
715720 "exception": zero_type,
716721 },
718723 ),
719724 )
720725
721 def test_destinationExceptionCaughtTwice(self):
726 def test_destination_exception_caught_twice(self):
722727 """
723728 If a destination throws an exception, and the logged error about
724729 it also causes an exception, then just drop that exception on the
732737
733738 logger._destinations.add(always_raise)
734739
735 # No exception raised; since everything is dropped no other
736 # assertions to be made.
740 # Just a message. No exception raised; since everything is dropped no
741 # other assertions to be made.
737742 logger.write({"hello": 123})
743
744 # With an action. No exception raised; since everything is dropped no
745 # other assertions to be made.
746 with start_action(logger, "sys:do"):
747 logger.write({"hello": 123})
738748
739749
740750 class PEP8Tests(TestCase):
00 Metadata-Version: 2.1
11 Name: eliot
2 Version: 1.13.0
2 Version: 0+unknown
33 Summary: Logging library that tells you why it happened
44 Home-page: https://github.com/itamarst/eliot/
55 Maintainer: Itamar Turner-Trauring
66 Maintainer-email: itamar@itamarst.org
77 License: Apache 2.0
8 Description: Eliot: Logging that tells you *why* it happened
9 ================================================
10
11 .. image:: https://travis-ci.org/itamarst/eliot.png?branch=master
12 :target: http://travis-ci.org/itamarst/eliot
13 :alt: Build Status
14
15 Python's built-in ``logging`` and other similar systems output a stream of factoids: they're interesting, but you can't really tell what's going on.
16
17 * Why is your application slow?
18 * What caused this code path to be chosen?
19 * Why did this error happen?
20
21 Standard logging can't answer these questions.
22
23 But with a better model you could understand what and why things happened in your application.
24 You could pinpoint performance bottlenecks, you could understand what happened when, who called what.
25
26 That is what Eliot does.
27 ``eliot`` is a Python logging system that outputs causal chains of **actions**: actions can spawn other actions, and eventually they either **succeed or fail**.
28 The resulting logs tell you the story of what your software did: what happened, and what caused it.
29
30 Eliot supports a range of use cases and 3rd party libraries:
31
32 * Logging within a single process.
33 * Causal tracing across a distributed system.
34 * Scientific computing, with `built-in support for NumPy and Dask <https://eliot.readthedocs.io/en/stable/scientific-computing.html>`_.
35 * `Asyncio and Trio coroutines <https://eliot.readthedocs.io/en/stable/generating/asyncio.html>`_ and the `Twisted networking framework <https://eliot.readthedocs.io/en/stable/generating/twisted.html>`_.
36
37 Eliot is only used to generate your logs; you will might need tools like Logstash and ElasticSearch to aggregate and store logs if you are using multiple processes across multiple machines.
38
39 Eliot supports Python 3.6, 3.7, 3.8, and 3.9, as well as PyPy3.
40 It is maintained by Itamar Turner-Trauring, and released under the Apache 2.0 License.
41
42 Python 2.7 is in legacy support mode, with the last release supported being 1.7; see `here <https://eliot.readthedocs.io/en/stable/python2.html>`_ for details.
43
44 * `Read the documentation <https://eliot.readthedocs.io>`_.
45 * Download from `PyPI`_ or `conda-forge <https://anaconda.org/conda-forge/eliot>`_.
46 * Need help or have any questions? `File an issue <https://github.com/itamarst/eliot/issues/new>`_ on GitHub.
47 * **Commercial support** is available from `Python⇒Speed <https://pythonspeed.com/services/#eliot>`_.
48
49 Testimonials
50 ------------
51
52 "Eliot has made tracking down causes of failure (in complex external integrations and internal uses) tremendously easier. Our errors are logged to Sentry with the Eliot task UUID. That means we can go from a Sentry notification to a high-level trace of operations—with important metadata at each operation—in a few seconds. We immediately know which user did what in which part of the system."
53
54 —Jonathan Jacobs
55
56 .. _Github: https://github.com/itamarst/eliot
57 .. _PyPI: https://pypi.python.org/pypi/eliot
58
598 Keywords: logging
60 Platform: UNKNOWN
619 Classifier: Intended Audience :: Developers
6210 Classifier: License :: OSI Approved :: Apache Software License
6311 Classifier: Operating System :: OS Independent
6715 Classifier: Programming Language :: Python :: 3.7
6816 Classifier: Programming Language :: Python :: 3.8
6917 Classifier: Programming Language :: Python :: 3.9
18 Classifier: Programming Language :: Python :: 3.10
7019 Classifier: Programming Language :: Python :: Implementation :: CPython
7120 Classifier: Programming Language :: Python :: Implementation :: PyPy
7221 Classifier: Topic :: System :: Logging
7322 Requires-Python: >=3.6.0
23 Provides-Extra: dev
7424 Provides-Extra: journald
7525 Provides-Extra: test
76 Provides-Extra: dev
26 License-File: LICENSE
27
28 Eliot: Logging that tells you *why* it happened
29 ================================================
30
31 .. image:: https://travis-ci.org/itamarst/eliot.png?branch=master
32 :target: http://travis-ci.org/itamarst/eliot
33 :alt: Build Status
34
35 Python's built-in ``logging`` and other similar systems output a stream of factoids: they're interesting, but you can't really tell what's going on.
36
37 * Why is your application slow?
38 * What caused this code path to be chosen?
39 * Why did this error happen?
40
41 Standard logging can't answer these questions.
42
43 But with a better model you could understand what and why things happened in your application.
44 You could pinpoint performance bottlenecks, you could understand what happened when, who called what.
45
46 That is what Eliot does.
47 ``eliot`` is a Python logging system that outputs causal chains of **actions**: actions can spawn other actions, and eventually they either **succeed or fail**.
48 The resulting logs tell you the story of what your software did: what happened, and what caused it.
49
50 Eliot supports a range of use cases and 3rd party libraries:
51
52 * Logging within a single process.
53 * Causal tracing across a distributed system.
54 * Scientific computing, with `built-in support for NumPy and Dask <https://eliot.readthedocs.io/en/stable/scientific-computing.html>`_.
55 * `Asyncio and Trio coroutines <https://eliot.readthedocs.io/en/stable/generating/asyncio.html>`_ and the `Twisted networking framework <https://eliot.readthedocs.io/en/stable/generating/twisted.html>`_.
56
57 Eliot is only used to generate your logs; you will might need tools like Logstash and ElasticSearch to aggregate and store logs if you are using multiple processes across multiple machines.
58
59 Eliot supports Python 3.6, 3.7, 3.8, 3.9, and 3.10, as well as PyPy3.
60 It is maintained by Itamar Turner-Trauring, and released under the Apache 2.0 License.
61
62 * `Read the documentation <https://eliot.readthedocs.io>`_.
63 * Download from `PyPI`_ or `conda-forge <https://anaconda.org/conda-forge/eliot>`_.
64 * Need help or have any questions? `File an issue <https://github.com/itamarst/eliot/issues/new>`_ on GitHub.
65 * **Commercial support** is available from `Python⇒Speed <https://pythonspeed.com/services/#eliot>`_.
66
67 Testimonials
68 ------------
69
70 "Eliot has made tracking down causes of failure (in complex external integrations and internal uses) tremendously easier. Our errors are logged to Sentry with the Eliot task UUID. That means we can go from a Sentry notification to a high-level trace of operations—with important metadata at each operation—in a few seconds. We immediately know which user did what in which part of the system."
71
72 —Jonathan Jacobs
73
74 .. _Github: https://github.com/itamarst/eliot
75 .. _PyPI: https://pypi.python.org/pypi/eliot
00 [console_scripts]
11 eliot-prettyprint = eliot.prettyprint:_main
2
0 boltons>=19.0.1
1 pyrsistent>=0.11.8
02 six
13 zope.interface
2 pyrsistent>=0.11.8
3 boltons>=19.0.1
44
55 [:python_version < "3.7" and python_version > "2.7"]
66 aiocontextvars
77
88 [dev]
9 black
10 coverage
11 flake8
912 setuptools>=40
10 twine>=1.12.1
11 coverage
1213 sphinx
1314 sphinx_rtd_theme
14 flake8
15 black
15 twine>=1.12.1
1616
1717 [journald]
1818 cffi>=1.1.2
1919
2020 [test]
2121 hypothesis>=1.14.0
22 testtools
2322 pytest
2423 pytest-xdist
24 testtools
44 license_file = LICENSE
55
66 [versioneer]
7 vcs = git
7 VCS = git
88 style = pep440
99 versionfile_source = eliot/_version.py
1010 versionfile_build = eliot/_version.py
1111 tag_prefix =
1212 parentdir_prefix = eliot-
1313
14 [flake8]
15 max-line-length = 88
16 extend-ignore = E203
17
1418 [egg_info]
1519 tag_build =
1620 tag_date = 0
2121 "Programming Language :: Python :: 3.7",
2222 "Programming Language :: Python :: 3.8",
2323 "Programming Language :: Python :: 3.9",
24 "Programming Language :: Python :: 3.10",
2425 "Programming Language :: Python :: Implementation :: CPython",
2526 "Programming Language :: Python :: Implementation :: PyPy",
2627 "Topic :: System :: Logging",