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.
- logxpy/__init__.py +126 -0
- logxpy/_action.py +958 -0
- logxpy/_async.py +186 -0
- logxpy/_base.py +80 -0
- logxpy/_compat.py +71 -0
- logxpy/_config.py +45 -0
- logxpy/_dest.py +88 -0
- logxpy/_errors.py +58 -0
- logxpy/_fmt.py +68 -0
- logxpy/_generators.py +136 -0
- logxpy/_mask.py +23 -0
- logxpy/_message.py +195 -0
- logxpy/_output.py +517 -0
- logxpy/_pool.py +93 -0
- logxpy/_traceback.py +126 -0
- logxpy/_types.py +71 -0
- logxpy/_util.py +56 -0
- logxpy/_validation.py +486 -0
- logxpy/_version.py +21 -0
- logxpy/cli.py +61 -0
- logxpy/dask.py +172 -0
- logxpy/decorators.py +268 -0
- logxpy/filter.py +124 -0
- logxpy/journald.py +88 -0
- logxpy/json.py +149 -0
- logxpy/loggerx.py +253 -0
- logxpy/logwriter.py +84 -0
- logxpy/parse.py +191 -0
- logxpy/prettyprint.py +173 -0
- logxpy/serializers.py +36 -0
- logxpy/stdlib.py +23 -0
- logxpy/tai64n.py +45 -0
- logxpy/testing.py +472 -0
- logxpy/tests/__init__.py +9 -0
- logxpy/tests/common.py +36 -0
- logxpy/tests/strategies.py +231 -0
- logxpy/tests/test_action.py +1751 -0
- logxpy/tests/test_api.py +86 -0
- logxpy/tests/test_async.py +67 -0
- logxpy/tests/test_compat.py +13 -0
- logxpy/tests/test_config.py +21 -0
- logxpy/tests/test_coroutines.py +105 -0
- logxpy/tests/test_dask.py +211 -0
- logxpy/tests/test_decorators.py +54 -0
- logxpy/tests/test_filter.py +122 -0
- logxpy/tests/test_fmt.py +42 -0
- logxpy/tests/test_generators.py +292 -0
- logxpy/tests/test_journald.py +246 -0
- logxpy/tests/test_json.py +208 -0
- logxpy/tests/test_loggerx.py +44 -0
- logxpy/tests/test_logwriter.py +262 -0
- logxpy/tests/test_message.py +334 -0
- logxpy/tests/test_output.py +921 -0
- logxpy/tests/test_parse.py +309 -0
- logxpy/tests/test_pool.py +55 -0
- logxpy/tests/test_prettyprint.py +303 -0
- logxpy/tests/test_pyinstaller.py +35 -0
- logxpy/tests/test_serializers.py +36 -0
- logxpy/tests/test_stdlib.py +73 -0
- logxpy/tests/test_tai64n.py +66 -0
- logxpy/tests/test_testing.py +1051 -0
- logxpy/tests/test_traceback.py +251 -0
- logxpy/tests/test_twisted.py +814 -0
- logxpy/tests/test_util.py +45 -0
- logxpy/tests/test_validation.py +989 -0
- logxpy/twisted.py +265 -0
- logxpy-0.1.0.dist-info/METADATA +100 -0
- logxpy-0.1.0.dist-info/RECORD +72 -0
- logxpy-0.1.0.dist-info/WHEEL +5 -0
- logxpy-0.1.0.dist-info/entry_points.txt +2 -0
- logxpy-0.1.0.dist-info/licenses/LICENSE +201 -0
- 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)
|