zlogging 0.1.3.post3__pp310-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.
- zlogging/__init__.py +35 -0
- zlogging/_aux.py +358 -0
- zlogging/_compat.py +95 -0
- zlogging/_data.py +98 -0
- zlogging/_exc.py +187 -0
- zlogging/_gen.py +330 -0
- zlogging/_typing.py +21 -0
- zlogging/dumper.py +704 -0
- zlogging/enum/__init__.py +1065 -0
- zlogging/enum/af_packet.py +55 -0
- zlogging/enum/broker.py +304 -0
- zlogging/enum/cluster.py +46 -0
- zlogging/enum/dce_rpc.py +101 -0
- zlogging/enum/http.py +38 -0
- zlogging/enum/input.py +78 -0
- zlogging/enum/intel.py +182 -0
- zlogging/enum/json.py +33 -0
- zlogging/enum/known.py +22 -0
- zlogging/enum/load_balancing.py +22 -0
- zlogging/enum/log.py +359 -0
- zlogging/enum/management.py +68 -0
- zlogging/enum/management_controller_runtime.py +30 -0
- zlogging/enum/management_log.py +28 -0
- zlogging/enum/mount3.py +88 -0
- zlogging/enum/mqtt.py +22 -0
- zlogging/enum/net_control.py +188 -0
- zlogging/enum/nfs3.py +221 -0
- zlogging/enum/notice.py +387 -0
- zlogging/enum/open_flow.py +148 -0
- zlogging/enum/pcap.py +26 -0
- zlogging/enum/protocol_detector.py +26 -0
- zlogging/enum/reporter.py +24 -0
- zlogging/enum/signatures.py +54 -0
- zlogging/enum/smb.py +50 -0
- zlogging/enum/socks.py +24 -0
- zlogging/enum/software.py +101 -0
- zlogging/enum/ssl.py +32 -0
- zlogging/enum/sum_stats.py +68 -0
- zlogging/enum/supervisor.py +30 -0
- zlogging/enum/telemetry.py +30 -0
- zlogging/enum/tunnel.py +63 -0
- zlogging/enum/weird.py +51 -0
- zlogging/enum/zeek.py +279 -0
- zlogging/enum/zeekygen_example.py +35 -0
- zlogging/loader.py +624 -0
- zlogging/model.py +301 -0
- zlogging/types.py +1709 -0
- zlogging/typing.py +256 -0
- zlogging-0.1.3.post3.dist-info/LICENSE +29 -0
- zlogging-0.1.3.post3.dist-info/METADATA +173 -0
- zlogging-0.1.3.post3.dist-info/RECORD +54 -0
- zlogging-0.1.3.post3.dist-info/WHEEL +5 -0
- zlogging-0.1.3.post3.dist-info/entry_points.txt +2 -0
- zlogging-0.1.3.post3.dist-info/top_level.txt +1 -0
zlogging/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Bro/Zeek logging framework."""
|
|
3
|
+
|
|
4
|
+
###############################################################################
|
|
5
|
+
# conda ``_extern`` module support
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
_extern = os.path.join(os.path.dirname(os.path.realpath(__file__)), '_extern')
|
|
11
|
+
if os.path.exists(_extern):
|
|
12
|
+
sys.path.append(_extern)
|
|
13
|
+
|
|
14
|
+
###############################################################################
|
|
15
|
+
|
|
16
|
+
from zlogging.dumper import dump, dumps, write
|
|
17
|
+
from zlogging.loader import load, loads, parse
|
|
18
|
+
from zlogging.model import Model, new_model
|
|
19
|
+
from zlogging.types import (AddrType, BoolType, CountType, DoubleType, EnumType, IntervalType,
|
|
20
|
+
IntType, PortType, RecordType, SetType, StringType, SubnetType,
|
|
21
|
+
TimeType, VectorType)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
'write', 'dump', 'dumps',
|
|
25
|
+
'parse', 'load', 'loads',
|
|
26
|
+
|
|
27
|
+
'Model', 'new_model',
|
|
28
|
+
|
|
29
|
+
'AddrType', 'BoolType', 'CountType', 'DoubleType', 'EnumType',
|
|
30
|
+
'IntervalType', 'IntType', 'PortType', 'RecordType', 'SetType',
|
|
31
|
+
'StringType', 'SubnetType', 'TimeType', 'VectorType',
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
# version string
|
|
35
|
+
__version__ = '0.1.3.post3'
|
zlogging/_aux.py
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Auxiliary functions."""
|
|
3
|
+
|
|
4
|
+
import collections
|
|
5
|
+
import decimal
|
|
6
|
+
import itertools
|
|
7
|
+
import math
|
|
8
|
+
import textwrap
|
|
9
|
+
from typing import TYPE_CHECKING, cast, overload
|
|
10
|
+
|
|
11
|
+
from typing_inspect import get_args, get_origin, is_generic_type, is_typevar
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from collections import OrderedDict
|
|
15
|
+
from decimal import Decimal
|
|
16
|
+
from io import BufferedReader as BinaryFile
|
|
17
|
+
from typing import Optional, Type, TypeVar, Union
|
|
18
|
+
|
|
19
|
+
from typing_extensions import Literal
|
|
20
|
+
|
|
21
|
+
from zlogging._typing import ExpandedTyping
|
|
22
|
+
from zlogging.model import Model
|
|
23
|
+
from zlogging.types import _VariadicType
|
|
24
|
+
|
|
25
|
+
__all__ = ['readline', 'decimal_toascii', 'float_toascii', 'unicode_escape', 'expand_typing']
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@overload
|
|
29
|
+
def readline(file: 'BinaryFile', seperator: 'bytes' = ..., maxsplit: 'int' = ..., # type: ignore[misc]
|
|
30
|
+
decode: 'Literal[False]' = ...) -> 'list[bytes]': ... # pylint: disable=redefined-outer-name
|
|
31
|
+
|
|
32
|
+
@overload
|
|
33
|
+
def readline(file: 'BinaryFile', seperator: 'bytes' = ..., maxsplit: 'int' = ...,
|
|
34
|
+
decode: 'Literal[True]' = ...) -> 'list[str]': ... # pylint: disable=redefined-outer-name
|
|
35
|
+
|
|
36
|
+
def readline(file: 'BinaryFile', separator: 'bytes' = b'\x09', # type: ignore[misc]
|
|
37
|
+
maxsplit: 'int' = -1, decode: 'bool' = False) -> 'Union[list[str], list[bytes]]': # pylint: disable=redefined-outer-name
|
|
38
|
+
"""Wrapper for :meth:`file.readline` function.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
file: Log file object opened in binary mode.
|
|
42
|
+
separator: Data separator.
|
|
43
|
+
maxsplit: Maximum number of splits to do; see :meth:`bytes.split`
|
|
44
|
+
and :meth:`str.split` for more information.
|
|
45
|
+
decode: If decide the buffered string with ``ascii`` encoding.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The splitted line as a :obj:`list` of :obj:`bytes`, or as :obj:`str` if
|
|
49
|
+
``decode`` if set to ``True``.
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
line = file.readline().strip()
|
|
53
|
+
if decode:
|
|
54
|
+
return line.decode('ascii').split(separator.decode('ascii'), maxsplit=maxsplit)
|
|
55
|
+
return line.split(separator, maxsplit)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def decimal_toascii(data: 'Decimal', infinite: 'Optional[str]' = None) -> 'str':
|
|
59
|
+
"""Convert :obj:`decimal.Decimal` to ASCII.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
data: A :obj:`decimal.Decimal` object.
|
|
63
|
+
infinite: The ASCII representation of infinite numbers (``NaN`` and infinity).
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
The converted ASCII string.
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
When converting a :obj:`decimal.Decimal` object, for example:
|
|
70
|
+
|
|
71
|
+
.. code-block:: python
|
|
72
|
+
|
|
73
|
+
>>> d = decimal.Decimal('-123.123456789')
|
|
74
|
+
|
|
75
|
+
the function will preserve only **6 digits** of its fractional part,
|
|
76
|
+
i.e.:
|
|
77
|
+
|
|
78
|
+
.. code-block:: python
|
|
79
|
+
|
|
80
|
+
>>> decimal_toascii(d)
|
|
81
|
+
'-123.123456'
|
|
82
|
+
|
|
83
|
+
Note:
|
|
84
|
+
Infinite numbers, i.e. ``NaN`` and infinity (``inf``), will be
|
|
85
|
+
converted as the value specified in ``infinite``, in default the string
|
|
86
|
+
representation of the number itself, i.e.:
|
|
87
|
+
|
|
88
|
+
* ``NaN`` -> ``'NaN'``
|
|
89
|
+
* Infinity -> ``'Infinity'``
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
if data.is_infinite():
|
|
93
|
+
if infinite is None:
|
|
94
|
+
return str(data)
|
|
95
|
+
return infinite
|
|
96
|
+
tpl = data.as_tuple() # type: decimal.DecimalTuple
|
|
97
|
+
|
|
98
|
+
exp = tpl.exponent
|
|
99
|
+
if exp >= 0:
|
|
100
|
+
return '%s%s%s.000000' % ('-' if tpl.sign else '', # pylint: disable=consider-using-f-string
|
|
101
|
+
''.join(map(str, tpl.digits)),
|
|
102
|
+
'0' * exp)
|
|
103
|
+
|
|
104
|
+
expabs = abs(exp)
|
|
105
|
+
dgtlen = len(tpl.digits)
|
|
106
|
+
if expabs >= dgtlen:
|
|
107
|
+
diff = expabs - dgtlen
|
|
108
|
+
if diff == 0:
|
|
109
|
+
diff = 1
|
|
110
|
+
return '%s%s.%s%s' % ('-' if tpl.sign else '', # pylint: disable=consider-using-f-string
|
|
111
|
+
'0' * diff,
|
|
112
|
+
''.join(map(str, tpl.digits[:6])),
|
|
113
|
+
'0' * (6 - dgtlen))
|
|
114
|
+
|
|
115
|
+
buf_int = ''
|
|
116
|
+
buf_flt = ''
|
|
117
|
+
for index, digit in enumerate(reversed(tpl.digits), start=1):
|
|
118
|
+
if index <= expabs:
|
|
119
|
+
buf_flt = str(digit) + buf_flt
|
|
120
|
+
else:
|
|
121
|
+
buf_int = str(digit) + buf_int
|
|
122
|
+
return '%s%s.%s%s' % ('-' if tpl.sign else '', # pylint: disable=consider-using-f-string
|
|
123
|
+
buf_int, buf_flt[:6],
|
|
124
|
+
'0' * (6 - len(buf_flt)))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def float_toascii(data: 'float', infinite: 'Optional[str]' = None) -> 'str':
|
|
128
|
+
"""Convert :obj:`float` to ASCII.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
data: A :obj:`float` number.
|
|
132
|
+
infinite: The ASCII representation of infinite numbers (``NaN`` and infinity).
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The converted ASCII string.
|
|
136
|
+
|
|
137
|
+
Example:
|
|
138
|
+
When converting a :obj:`float` number, for example:
|
|
139
|
+
|
|
140
|
+
.. code-block:: python
|
|
141
|
+
|
|
142
|
+
>>> f = -123.123456789
|
|
143
|
+
|
|
144
|
+
the function will preserve only **6 digits** of its fractional part,
|
|
145
|
+
i.e.:
|
|
146
|
+
|
|
147
|
+
.. code-block:: python
|
|
148
|
+
|
|
149
|
+
>>> float_toascii(f)
|
|
150
|
+
'-123.123456'
|
|
151
|
+
|
|
152
|
+
Note:
|
|
153
|
+
Infinite numbers, i.e. ``NaN`` and infinity (``inf``), will be
|
|
154
|
+
converted as the value specified in ``infinite``, in default the string
|
|
155
|
+
representation of the number itself, i.e.:
|
|
156
|
+
|
|
157
|
+
* ``NaN`` -> ``'nan'``
|
|
158
|
+
* Infinity -> ``'inf'``
|
|
159
|
+
|
|
160
|
+
"""
|
|
161
|
+
if not math.isfinite(data):
|
|
162
|
+
if infinite is None:
|
|
163
|
+
return str(data)
|
|
164
|
+
return infinite
|
|
165
|
+
int_part, flt_part = str(data).split('.')
|
|
166
|
+
return '%s.%s%s' % (int_part, # pylint: disable=consider-using-f-string
|
|
167
|
+
flt_part[:6],
|
|
168
|
+
'0' * (6 - len(flt_part)))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def unicode_escape(string: 'bytes') -> 'str':
|
|
172
|
+
"""Conterprocess of :meth:`bytes.decode('unicode_escape') <bytes.decode>`.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
string: The bytestring to be escaped.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
The escaped bytestring as an encoded string
|
|
179
|
+
|
|
180
|
+
Example:
|
|
181
|
+
|
|
182
|
+
>>> b'\\x09'.decode('unicode_escape')
|
|
183
|
+
'\\\\t'
|
|
184
|
+
>>> unicode_escape(b'\\t')
|
|
185
|
+
'\\\\x09'
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
return ''.join(map(lambda s: '\\x%s' % s, textwrap.wrap(string.hex(), 2))) # pylint: disable=consider-using-f-string
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def expand_typing(cls: 'Union[Model, Type[Model], _VariadicType, Type[_VariadicType]]',
|
|
192
|
+
exc: 'Optional[Type[ValueError]]' = None) -> 'ExpandedTyping':
|
|
193
|
+
"""Expand typing annotations.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
cls: a variadic class which supports :pep:`484` style attribute typing
|
|
197
|
+
annotations
|
|
198
|
+
exc: exception to be used in case of inconsistent values for ``unset_field``,
|
|
199
|
+
``empty_field`` and ``set_separator``
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
The returned dictionary contains the following directives:
|
|
203
|
+
|
|
204
|
+
* ``fields``: a mapping proxy of field names and their corresponding
|
|
205
|
+
data types, i.e. an instance of a :class:`~zlogging.types.BaseType`
|
|
206
|
+
subclass
|
|
207
|
+
|
|
208
|
+
* ``record_fields``: a mapping proxy for fields of ``record`` data type,
|
|
209
|
+
i.e. an instance of :class:`~zlogging.types.RecordType`
|
|
210
|
+
|
|
211
|
+
* ``unset_fields``: placeholder for unset field
|
|
212
|
+
|
|
213
|
+
* ``empty_fields``: placeholder for empty field
|
|
214
|
+
|
|
215
|
+
* ``set_separator``: separator for ``set``/``vector`` fields
|
|
216
|
+
|
|
217
|
+
Warns:
|
|
218
|
+
BroDeprecationWarning: Use of ``bro_*`` prefixed typing annotations.
|
|
219
|
+
|
|
220
|
+
Raises:
|
|
221
|
+
:exc:`ValueError`: In case of inconsistent values for ``unset_field``,
|
|
222
|
+
``empty_field`` and ``set_separator``.
|
|
223
|
+
|
|
224
|
+
Example:
|
|
225
|
+
Define a custom log data model from :class:`~zlogging.model.Model` using
|
|
226
|
+
the prefines Bro/Zeek data types, or subclasses of
|
|
227
|
+
:class:`~zlogging.types.BaseType`:
|
|
228
|
+
|
|
229
|
+
.. code-block:: python
|
|
230
|
+
|
|
231
|
+
class MyLog(Model):
|
|
232
|
+
field_one = StringType()
|
|
233
|
+
field_two = SetType(element_type=PortType)
|
|
234
|
+
|
|
235
|
+
Or you may use type annotations as :pep:`484` introduced when declaring
|
|
236
|
+
data models. All available type hints can be found in
|
|
237
|
+
:mod:`zlogging.typing`:
|
|
238
|
+
|
|
239
|
+
.. code-block:: python
|
|
240
|
+
|
|
241
|
+
class MyLog(Model):
|
|
242
|
+
field_one: zeek_string
|
|
243
|
+
field_two: zeek_set[zeek_port]
|
|
244
|
+
|
|
245
|
+
However, when mixing annotations and direct assignments, annotations
|
|
246
|
+
will take proceedings, i.e. the function shall process first typing
|
|
247
|
+
annotations then ``cls`` attribute assignments. Should there be any
|
|
248
|
+
conflicts, the ``exc`` will be raised.
|
|
249
|
+
|
|
250
|
+
Note:
|
|
251
|
+
Fields of :class:`zlogging.types.RecordType` type will be expanded as
|
|
252
|
+
plain fields of the ``cls``, i.e. for the variadic class as below:
|
|
253
|
+
|
|
254
|
+
.. code-block:: python
|
|
255
|
+
|
|
256
|
+
class MyLog(Model):
|
|
257
|
+
record = RecrodType(one=StringType(),
|
|
258
|
+
two=VectorType(element_type=CountType()))
|
|
259
|
+
|
|
260
|
+
will have the following fields:
|
|
261
|
+
|
|
262
|
+
* ``record.one`` -> ``string`` data type
|
|
263
|
+
* ``record.two`` -> ``vector[count]`` data type
|
|
264
|
+
|
|
265
|
+
"""
|
|
266
|
+
from zlogging.types import (BaseType, _GenericType, # pylint: disable=import-outside-toplevel
|
|
267
|
+
_SimpleType, _VariadicType)
|
|
268
|
+
|
|
269
|
+
if exc is None:
|
|
270
|
+
exc = ValueError
|
|
271
|
+
|
|
272
|
+
inited = False
|
|
273
|
+
unset_field = b'-'
|
|
274
|
+
empty_field = b'(empty)'
|
|
275
|
+
set_separator = b','
|
|
276
|
+
|
|
277
|
+
def register(name: str, field: 'Union[_SimpleType, _GenericType]') -> None:
|
|
278
|
+
"""Field registry."""
|
|
279
|
+
existed = fields.get(name)
|
|
280
|
+
if existed is not None and field.zeek_type != existed.zeek_type:
|
|
281
|
+
raise exc(f'inconsistent data type of {name!r} field: {field!r} and {existed!r}') # type: ignore[misc]
|
|
282
|
+
fields[name] = field
|
|
283
|
+
|
|
284
|
+
fields = collections.OrderedDict() # type: OrderedDict[str, Union[_SimpleType, _GenericType]]
|
|
285
|
+
record_fields = collections.OrderedDict() # type: OrderedDict[str, _VariadicType]
|
|
286
|
+
for name, attr in itertools.chain(getattr(cls, '__annotations__', {}).items(), cls.__dict__.items()):
|
|
287
|
+
# type instances
|
|
288
|
+
if isinstance(attr, BaseType):
|
|
289
|
+
if isinstance(attr, _VariadicType):
|
|
290
|
+
for elm_name, elm_field in attr.element_mapping.items():
|
|
291
|
+
register(f'{name}.{elm_name}', elm_field)
|
|
292
|
+
record_fields[name] = attr
|
|
293
|
+
else:
|
|
294
|
+
register(name, attr) # type: ignore[arg-type]
|
|
295
|
+
|
|
296
|
+
# uninitialised type classes
|
|
297
|
+
elif isinstance(attr, type) and issubclass(attr, BaseType):
|
|
298
|
+
attr = attr()
|
|
299
|
+
|
|
300
|
+
# simple typing types
|
|
301
|
+
elif is_typevar(attr):
|
|
302
|
+
if TYPE_CHECKING:
|
|
303
|
+
attr = cast('TypeVar', attr)
|
|
304
|
+
|
|
305
|
+
bound = attr.__bound__
|
|
306
|
+
if bound and issubclass(bound, _SimpleType):
|
|
307
|
+
attr = bound()
|
|
308
|
+
else:
|
|
309
|
+
continue
|
|
310
|
+
|
|
311
|
+
# generic typing types
|
|
312
|
+
elif is_generic_type(attr) and issubclass(attr, _GenericType):
|
|
313
|
+
origin = get_origin(attr)
|
|
314
|
+
parameter = get_args(attr)[0]
|
|
315
|
+
|
|
316
|
+
# uninitialised type classes
|
|
317
|
+
if isinstance(parameter, type) and issubclass(parameter, _SimpleType):
|
|
318
|
+
element_type = parameter()
|
|
319
|
+
|
|
320
|
+
# simple typing types
|
|
321
|
+
elif is_typevar(parameter):
|
|
322
|
+
if TYPE_CHECKING:
|
|
323
|
+
parameter = cast('TypeVar', parameter)
|
|
324
|
+
bound = parameter.__bound__
|
|
325
|
+
if bound and issubclass(bound, _SimpleType):
|
|
326
|
+
element_type = bound()
|
|
327
|
+
else:
|
|
328
|
+
element_type = bound # type: ignore[assignment]
|
|
329
|
+
|
|
330
|
+
else:
|
|
331
|
+
element_type = parameter # type: ignore[assignment]
|
|
332
|
+
attr = origin(element_type=element_type)\
|
|
333
|
+
|
|
334
|
+
else:
|
|
335
|
+
continue
|
|
336
|
+
|
|
337
|
+
if not inited:
|
|
338
|
+
unset_field = attr.unset_field
|
|
339
|
+
empty_field = attr.empty_field
|
|
340
|
+
set_separator = attr.set_separator
|
|
341
|
+
inited = True
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
if unset_field != attr.unset_field:
|
|
345
|
+
raise exc(f"inconsistent value of 'unset_field': {unset_field!r} and {attr.unset_field!r}")
|
|
346
|
+
if empty_field != attr.empty_field:
|
|
347
|
+
raise exc(f"inconsistent value of 'empty_field': {empty_field!r} and {attr.empty_field!r}")
|
|
348
|
+
if set_separator != attr.set_separator:
|
|
349
|
+
raise exc("inconsistent value of 'set_separator': {set_separator!r} and {attr.set_separator!r}")
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
'_inited': inited,
|
|
353
|
+
'fields': fields,
|
|
354
|
+
'record_fields': record_fields,
|
|
355
|
+
'unset_field': unset_field,
|
|
356
|
+
'empty_field': empty_field,
|
|
357
|
+
'set_separator': set_separator,
|
|
358
|
+
}
|
zlogging/_compat.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# pylint: disable=ungrouped-imports
|
|
3
|
+
"""Resolve compatibility issues."""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from typing_extensions import DefaultDict
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
'enum',
|
|
12
|
+
'GenericMeta',
|
|
13
|
+
'cached_property',
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from typing import Any, Callable, Optional, Type, Union
|
|
18
|
+
|
|
19
|
+
version_info = sys.version_info[:2]
|
|
20
|
+
|
|
21
|
+
# enum.Enum._ignore_ added in 3.7
|
|
22
|
+
if version_info < (3, 7):
|
|
23
|
+
import aenum as enum
|
|
24
|
+
else:
|
|
25
|
+
import enum # type: ignore[no-redef]
|
|
26
|
+
|
|
27
|
+
# 3.6 GenericMeta
|
|
28
|
+
# 3.7+ _GenericAlias
|
|
29
|
+
# 3.9+ _SpecialGenericAlias
|
|
30
|
+
GenericMeta = type(DefaultDict)
|
|
31
|
+
|
|
32
|
+
# functools.cached_property added in 3.8
|
|
33
|
+
if version_info >= (3, 8):
|
|
34
|
+
from functools import cached_property
|
|
35
|
+
else:
|
|
36
|
+
from _thread import RLock # type: ignore[attr-defined]
|
|
37
|
+
from typing import Generic, TypeVar, overload # isort: split
|
|
38
|
+
|
|
39
|
+
_T = TypeVar("_T")
|
|
40
|
+
_S = TypeVar("_S")
|
|
41
|
+
|
|
42
|
+
_NOT_FOUND = object()
|
|
43
|
+
|
|
44
|
+
class cached_property(Generic[_T]): # type: ignore[no-redef]
|
|
45
|
+
def __init__(self, func: 'Callable[[Any], _T]') -> None:
|
|
46
|
+
self.func = func # type: Callable[[Any], _T]
|
|
47
|
+
self.attrname = None # type: Optional[str]
|
|
48
|
+
self.__doc__ = func.__doc__
|
|
49
|
+
self.lock = RLock()
|
|
50
|
+
|
|
51
|
+
def __set_name__(self, owner: 'Type[Any]', name: str) -> None:
|
|
52
|
+
if self.attrname is None:
|
|
53
|
+
self.attrname = name
|
|
54
|
+
elif name != self.attrname:
|
|
55
|
+
raise TypeError(
|
|
56
|
+
"Cannot assign the same cached_property to two different names "
|
|
57
|
+
f"({self.attrname!r} and {name!r})."
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
@overload
|
|
61
|
+
def __get__(self, instance: None, owner: 'Optional[Type[Any]]' = ...) -> 'cached_property[_T]': ...
|
|
62
|
+
@overload
|
|
63
|
+
def __get__(self, instance: _S, owner: 'Optional[Type[Any]]' = ...) -> '_T': ...
|
|
64
|
+
|
|
65
|
+
def __get__(self, instance: 'Optional[_S]',
|
|
66
|
+
owner: 'Optional[Type[Any]]' = None) -> 'Union[cached_property[_T], _T]':
|
|
67
|
+
if instance is None:
|
|
68
|
+
return self # type: ignore[return-value]
|
|
69
|
+
if self.attrname is None:
|
|
70
|
+
raise TypeError(
|
|
71
|
+
"Cannot use cached_property instance without calling __set_name__ on it.")
|
|
72
|
+
try:
|
|
73
|
+
cache = instance.__dict__
|
|
74
|
+
except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
|
|
75
|
+
msg = (
|
|
76
|
+
f"No '__dict__' attribute on {type(instance).__name__!r} "
|
|
77
|
+
f"instance to cache {self.attrname!r} property."
|
|
78
|
+
)
|
|
79
|
+
raise TypeError(msg) from None
|
|
80
|
+
val = cache.get(self.attrname, _NOT_FOUND)
|
|
81
|
+
if val is _NOT_FOUND:
|
|
82
|
+
with self.lock:
|
|
83
|
+
# check if another thread filled cache while we awaited lock
|
|
84
|
+
val = cache.get(self.attrname, _NOT_FOUND)
|
|
85
|
+
if val is _NOT_FOUND:
|
|
86
|
+
val = self.func(instance)
|
|
87
|
+
try:
|
|
88
|
+
cache[self.attrname] = val
|
|
89
|
+
except TypeError:
|
|
90
|
+
msg = (
|
|
91
|
+
f"The '__dict__' attribute on {type(instance).__name__!r} instance "
|
|
92
|
+
f"does not support item assignment for caching {self.attrname!r} property."
|
|
93
|
+
)
|
|
94
|
+
raise TypeError(msg) from None
|
|
95
|
+
return val
|
zlogging/_data.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# pylint: disable=ungrouped-imports
|
|
3
|
+
"""Data classes for parsed logs."""
|
|
4
|
+
|
|
5
|
+
import abc
|
|
6
|
+
import dataclasses
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
'ASCIIInfo', 'JSONInfo'
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from datetime import datetime as DateTimeType
|
|
15
|
+
from os import PathLike
|
|
16
|
+
from typing import Literal
|
|
17
|
+
|
|
18
|
+
from zlogging.model import Model
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Info(metaclass=abc.ABCMeta):
|
|
22
|
+
"""Parsed log info.
|
|
23
|
+
|
|
24
|
+
The parsed log will be stored as in this :func:`dataclass <dataclasses.dataclass>`,
|
|
25
|
+
as introduced in :pep:`557`.
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
@abc.abstractmethod
|
|
31
|
+
def format(self) -> str:
|
|
32
|
+
"""Log file format."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclasses.dataclass(frozen=True)
|
|
36
|
+
class ASCIIInfo(Info):
|
|
37
|
+
"""Parsed log info for ASCII logs.
|
|
38
|
+
|
|
39
|
+
The ASCII log will be stored as in this :func:`dataclass <dataclasses.dataclass>`,
|
|
40
|
+
as introduced in :pep:`557`.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
path: The value is specified in the ASCII log file
|
|
44
|
+
under ``# path`` directive.
|
|
45
|
+
open: The value is specified in the ASCII log file
|
|
46
|
+
under ``# open`` directive.
|
|
47
|
+
close: The value is specified in the ASCII log file
|
|
48
|
+
under ``# close`` directive.
|
|
49
|
+
data: The log records parsed as a :obj:`list` of
|
|
50
|
+
:class:`~zlogging.model.Model` per line.
|
|
51
|
+
exit_with_error: When exit with error, the ASCII log
|
|
52
|
+
file doesn't has a ``# close`` directive.
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def format(self) -> 'Literal["ascii"]':
|
|
58
|
+
"""Log file format."""
|
|
59
|
+
return 'ascii'
|
|
60
|
+
|
|
61
|
+
#: Log path. The value is specified in the ASCII log file
|
|
62
|
+
#: under ``# path`` directive.
|
|
63
|
+
path: 'PathLike[str]'
|
|
64
|
+
#: Log open time. The value is specified in the ASCII log
|
|
65
|
+
#: file under ``# open`` directive.
|
|
66
|
+
open: 'DateTimeType'
|
|
67
|
+
#: Log close time. The value is specified in the ASCII log
|
|
68
|
+
#: file under ``# close`` directive.
|
|
69
|
+
close: 'DateTimeType'
|
|
70
|
+
#: Log records. The log records parsed as a :obj:`list` of
|
|
71
|
+
#: :class:`~zlogging.model.Model` per line.
|
|
72
|
+
data: 'list[Model]'
|
|
73
|
+
#: Log exit with error. When exit with error, the ASCII log
|
|
74
|
+
#: file doesn't has a ``# close`` directive.
|
|
75
|
+
exit_with_error: 'bool'
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclasses.dataclass(frozen=True)
|
|
79
|
+
class JSONInfo(Info):
|
|
80
|
+
"""Parsed log info for JSON logs.
|
|
81
|
+
|
|
82
|
+
The JSON log will be stored as in this :func:`dataclass <dataclasses.dataclass>`,
|
|
83
|
+
as introduced in :pep:`557`.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
data: The log records parsed as a :obj:`list` of
|
|
87
|
+
:class:`~zlogging.model.Model` per line.
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def format(self) -> 'Literal["json"]':
|
|
93
|
+
"""Log file format."""
|
|
94
|
+
return 'json'
|
|
95
|
+
|
|
96
|
+
#: Log records. The log records parsed as a :obj:`list` of
|
|
97
|
+
#: :class:`~zlogging.model.Model` per line.
|
|
98
|
+
data: 'list[Model]'
|