logxpy 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. logxpy/__init__.py +126 -0
  2. logxpy/_action.py +958 -0
  3. logxpy/_async.py +186 -0
  4. logxpy/_base.py +80 -0
  5. logxpy/_compat.py +71 -0
  6. logxpy/_config.py +45 -0
  7. logxpy/_dest.py +88 -0
  8. logxpy/_errors.py +58 -0
  9. logxpy/_fmt.py +68 -0
  10. logxpy/_generators.py +136 -0
  11. logxpy/_mask.py +23 -0
  12. logxpy/_message.py +195 -0
  13. logxpy/_output.py +517 -0
  14. logxpy/_pool.py +93 -0
  15. logxpy/_traceback.py +126 -0
  16. logxpy/_types.py +71 -0
  17. logxpy/_util.py +56 -0
  18. logxpy/_validation.py +486 -0
  19. logxpy/_version.py +21 -0
  20. logxpy/cli.py +61 -0
  21. logxpy/dask.py +172 -0
  22. logxpy/decorators.py +268 -0
  23. logxpy/filter.py +124 -0
  24. logxpy/journald.py +88 -0
  25. logxpy/json.py +149 -0
  26. logxpy/loggerx.py +253 -0
  27. logxpy/logwriter.py +84 -0
  28. logxpy/parse.py +191 -0
  29. logxpy/prettyprint.py +173 -0
  30. logxpy/serializers.py +36 -0
  31. logxpy/stdlib.py +23 -0
  32. logxpy/tai64n.py +45 -0
  33. logxpy/testing.py +472 -0
  34. logxpy/tests/__init__.py +9 -0
  35. logxpy/tests/common.py +36 -0
  36. logxpy/tests/strategies.py +231 -0
  37. logxpy/tests/test_action.py +1751 -0
  38. logxpy/tests/test_api.py +86 -0
  39. logxpy/tests/test_async.py +67 -0
  40. logxpy/tests/test_compat.py +13 -0
  41. logxpy/tests/test_config.py +21 -0
  42. logxpy/tests/test_coroutines.py +105 -0
  43. logxpy/tests/test_dask.py +211 -0
  44. logxpy/tests/test_decorators.py +54 -0
  45. logxpy/tests/test_filter.py +122 -0
  46. logxpy/tests/test_fmt.py +42 -0
  47. logxpy/tests/test_generators.py +292 -0
  48. logxpy/tests/test_journald.py +246 -0
  49. logxpy/tests/test_json.py +208 -0
  50. logxpy/tests/test_loggerx.py +44 -0
  51. logxpy/tests/test_logwriter.py +262 -0
  52. logxpy/tests/test_message.py +334 -0
  53. logxpy/tests/test_output.py +921 -0
  54. logxpy/tests/test_parse.py +309 -0
  55. logxpy/tests/test_pool.py +55 -0
  56. logxpy/tests/test_prettyprint.py +303 -0
  57. logxpy/tests/test_pyinstaller.py +35 -0
  58. logxpy/tests/test_serializers.py +36 -0
  59. logxpy/tests/test_stdlib.py +73 -0
  60. logxpy/tests/test_tai64n.py +66 -0
  61. logxpy/tests/test_testing.py +1051 -0
  62. logxpy/tests/test_traceback.py +251 -0
  63. logxpy/tests/test_twisted.py +814 -0
  64. logxpy/tests/test_util.py +45 -0
  65. logxpy/tests/test_validation.py +989 -0
  66. logxpy/twisted.py +265 -0
  67. logxpy-0.1.0.dist-info/METADATA +100 -0
  68. logxpy-0.1.0.dist-info/RECORD +72 -0
  69. logxpy-0.1.0.dist-info/WHEEL +5 -0
  70. logxpy-0.1.0.dist-info/entry_points.txt +2 -0
  71. logxpy-0.1.0.dist-info/licenses/LICENSE +201 -0
  72. logxpy-0.1.0.dist-info/top_level.txt +1 -0
logxpy/prettyprint.py ADDED
@@ -0,0 +1,173 @@
1
+ """
2
+ API and command-line support for human-readable Eliot messages.
3
+ """
4
+
5
+ import pprint
6
+ import argparse
7
+ from datetime import datetime
8
+ from sys import stdin, stdout
9
+ from collections import OrderedDict
10
+ from json import dumps
11
+
12
+ from json import loads
13
+
14
+ from ._message import (
15
+ TIMESTAMP_FIELD,
16
+ TASK_UUID_FIELD,
17
+ TASK_LEVEL_FIELD,
18
+ MESSAGE_TYPE_FIELD,
19
+ )
20
+ from ._action import ACTION_TYPE_FIELD, ACTION_STATUS_FIELD
21
+
22
+
23
+ # Ensure binary stdin, since we expect specifically UTF-8 encoded
24
+ # messages, not platform-encoding messages.
25
+ stdin = stdin.buffer
26
+
27
+
28
+ # Fields that all Eliot messages are expected to have:
29
+ REQUIRED_FIELDS = {TASK_LEVEL_FIELD, TASK_UUID_FIELD, TIMESTAMP_FIELD}
30
+
31
+ # Fields that get treated specially when formatting.
32
+ _skip_fields = {
33
+ TIMESTAMP_FIELD,
34
+ TASK_UUID_FIELD,
35
+ TASK_LEVEL_FIELD,
36
+ MESSAGE_TYPE_FIELD,
37
+ ACTION_TYPE_FIELD,
38
+ ACTION_STATUS_FIELD,
39
+ }
40
+
41
+ # First fields to render:
42
+ _first_fields = [ACTION_TYPE_FIELD, MESSAGE_TYPE_FIELD, ACTION_STATUS_FIELD]
43
+
44
+
45
+ def _render_timestamp(message: dict, local_timezone: bool) -> str:
46
+ """Convert a message's timestamp to a string."""
47
+ # If we were returning or storing the datetime we'd want to use an
48
+ # explicit timezone instead of a naive datetime, but since we're
49
+ # just using it for formatting we needn't bother.
50
+ if local_timezone:
51
+ dt = datetime.fromtimestamp(message[TIMESTAMP_FIELD])
52
+ else:
53
+ dt = datetime.utcfromtimestamp(message[TIMESTAMP_FIELD])
54
+ result = dt.isoformat(sep="T")
55
+ if not local_timezone:
56
+ result += "Z"
57
+ return result
58
+
59
+
60
+ def pretty_format(message: dict, local_timezone: bool = False) -> str:
61
+ """
62
+ Convert a message dictionary into a human-readable string.
63
+
64
+ @param message: Message to parse, as dictionary.
65
+
66
+ @return: Unicode string.
67
+ """
68
+
69
+ def add_field(previous, key, value):
70
+ value = (
71
+ pprint.pformat(value, width=40).replace("\\n", "\n ").replace("\\t", "\t")
72
+ )
73
+ # Reindent second line and later to match up with first line's
74
+ # indentation:
75
+ lines = value.split("\n")
76
+ # indent lines are " <key length>| <value>"
77
+ indent = "{}| ".format(" " * (2 + len(key)))
78
+ value = "\n".join([lines[0]] + [indent + l for l in lines[1:]])
79
+ return " %s: %s\n" % (key, value)
80
+
81
+ remaining = ""
82
+ for field in _first_fields:
83
+ if field in message:
84
+ remaining += add_field(remaining, field, message[field])
85
+ for key, value in sorted(message.items()):
86
+ if key not in _skip_fields:
87
+ remaining += add_field(remaining, key, value)
88
+
89
+ level = "/" + "/".join(map(str, message[TASK_LEVEL_FIELD]))
90
+ return "%s -> %s\n%s\n%s" % (
91
+ message[TASK_UUID_FIELD],
92
+ level,
93
+ _render_timestamp(message, local_timezone),
94
+ remaining,
95
+ )
96
+
97
+
98
+ def compact_format(message: dict, local_timezone: bool = False) -> str:
99
+ """Format an Eliot message into a single line.
100
+
101
+ The message is presumed to be JSON-serializable.
102
+ """
103
+ ordered_message = OrderedDict()
104
+ for field in _first_fields:
105
+ if field in message:
106
+ ordered_message[field] = message[field]
107
+ for key, value in sorted(message.items()):
108
+ if key not in _skip_fields:
109
+ ordered_message[key] = value
110
+ # drop { and } from JSON:
111
+ rendered = " ".join(
112
+ "{}={}".format(key, dumps(value, separators=(",", ":")))
113
+ for (key, value) in ordered_message.items()
114
+ )
115
+
116
+ return "%s%s %s %s" % (
117
+ message[TASK_UUID_FIELD],
118
+ "/" + "/".join(map(str, message[TASK_LEVEL_FIELD])),
119
+ _render_timestamp(message, local_timezone),
120
+ rendered,
121
+ )
122
+
123
+
124
+ _CLI_HELP = """\
125
+ Convert Eliot messages into more readable format.
126
+
127
+ Reads JSON lines from stdin, write out pretty-printed results on stdout.
128
+ """
129
+
130
+
131
+ def _main():
132
+ """
133
+ Command-line program that reads in JSON from stdin and writes out
134
+ pretty-printed messages to stdout.
135
+ """
136
+ parser = argparse.ArgumentParser(
137
+ description=_CLI_HELP, usage="cat messages | %(prog)s [options]"
138
+ )
139
+ parser.add_argument(
140
+ "-c",
141
+ "--compact",
142
+ action="store_true",
143
+ dest="compact",
144
+ help="Compact format, one message per line.",
145
+ )
146
+ parser.add_argument(
147
+ "-l",
148
+ "--local-timezone",
149
+ action="store_true",
150
+ dest="local_timezone",
151
+ help="Use local timezone instead of UTC.",
152
+ )
153
+
154
+ args = parser.parse_args()
155
+ if args.compact:
156
+ formatter = compact_format
157
+ else:
158
+ formatter = pretty_format
159
+
160
+ for line in stdin:
161
+ try:
162
+ message = loads(line)
163
+ except ValueError:
164
+ stdout.write("Not JSON: {}\n\n".format(line.rstrip(b"\n")))
165
+ continue
166
+ if REQUIRED_FIELDS - set(message.keys()):
167
+ stdout.write("Not an Eliot message: {}\n\n".format(line.rstrip(b"\n")))
168
+ continue
169
+ result = formatter(message, args.local_timezone) + "\n"
170
+ stdout.write(result)
171
+
172
+
173
+ __all__ = ["pretty_format", "compact_format"]
logxpy/serializers.py ADDED
@@ -0,0 +1,36 @@
1
+ """
2
+ Standardized serialization code.
3
+ """
4
+
5
+ from hashlib import md5
6
+
7
+ _TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
8
+
9
+
10
+ def timestamp(dt):
11
+ """
12
+ Convert a UTC datetime to a string.
13
+
14
+ @param dt: A C{datetime.datetime} in UTC timezone.
15
+
16
+ @return: C{unicode}
17
+ """
18
+ return dt.strftime(_TIME_FORMAT)
19
+
20
+
21
+ def identity(value):
22
+ """
23
+ Return the passed in object.
24
+ """
25
+ return value
26
+
27
+
28
+ def md5hex(data):
29
+ """
30
+ Return hex MD5 of the input bytes.
31
+
32
+ @param data: Some C{bytes}.
33
+
34
+ @return: Hex-encoded MD5 of the data.
35
+ """
36
+ return md5(data).hexdigest()
logxpy/stdlib.py ADDED
@@ -0,0 +1,23 @@
1
+ """Integration with the standard library ``logging`` package."""
2
+
3
+ from logging import Handler
4
+
5
+ from ._action import log_message
6
+ from ._traceback import write_traceback
7
+
8
+
9
+ class EliotHandler(Handler):
10
+ """A C{logging.Handler} that routes log messages to Eliot."""
11
+
12
+ def emit(self, record):
13
+ log_message(
14
+ message_type="eliot:stdlib",
15
+ log_level=record.levelname,
16
+ logger=record.name,
17
+ message=record.getMessage(),
18
+ )
19
+ if record.exc_info:
20
+ write_traceback(exc_info=record.exc_info)
21
+
22
+
23
+ __all__ = ["EliotHandler"]
logxpy/tai64n.py ADDED
@@ -0,0 +1,45 @@
1
+ """
2
+ TAI64N encoding and decoding.
3
+
4
+ TAI64N encodes nanosecond-accuracy timestamps and is supported by logstash.
5
+
6
+ @see: U{http://cr.yp.to/libtai/tai64.html}.
7
+ """
8
+
9
+ import struct
10
+ from binascii import b2a_hex, a2b_hex
11
+
12
+ _STRUCTURE = b">QI"
13
+ _OFFSET = (2**62) + 10 # last 10 are leap seconds
14
+
15
+
16
+ def encode(timestamp):
17
+ """
18
+ Convert seconds since epoch to TAI64N string.
19
+
20
+ @param timestamp: Seconds since UTC Unix epoch as C{float}.
21
+
22
+ @return: TAI64N-encoded time, as C{unicode}.
23
+ """
24
+ seconds = int(timestamp)
25
+ nanoseconds = int((timestamp - seconds) * 1000000000)
26
+ seconds = seconds + _OFFSET
27
+ encoded = b2a_hex(struct.pack(_STRUCTURE, seconds, nanoseconds))
28
+ return "@" + encoded.decode("ascii")
29
+
30
+
31
+ def decode(tai64n):
32
+ """
33
+ Convert TAI64N string to seconds since epoch.
34
+
35
+ Note that dates before 2013 may not decode accurately due to leap second
36
+ issues. If you need correct decoding for earlier dates you can try the
37
+ tai64n package available from PyPI (U{https://pypi.python.org/pypi/tai64n}).
38
+
39
+ @param tai64n: TAI64N-encoded time, as C{unicode}.
40
+
41
+ @return: Seconds since UTC Unix epoch as C{float}.
42
+ """
43
+ seconds, nanoseconds = struct.unpack(_STRUCTURE, a2b_hex(tai64n[1:]))
44
+ seconds -= _OFFSET
45
+ return seconds + (nanoseconds / 1000000000.0)