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.
Files changed (54) hide show
  1. zlogging/__init__.py +35 -0
  2. zlogging/_aux.py +358 -0
  3. zlogging/_compat.py +95 -0
  4. zlogging/_data.py +98 -0
  5. zlogging/_exc.py +187 -0
  6. zlogging/_gen.py +330 -0
  7. zlogging/_typing.py +21 -0
  8. zlogging/dumper.py +704 -0
  9. zlogging/enum/__init__.py +1065 -0
  10. zlogging/enum/af_packet.py +55 -0
  11. zlogging/enum/broker.py +304 -0
  12. zlogging/enum/cluster.py +46 -0
  13. zlogging/enum/dce_rpc.py +101 -0
  14. zlogging/enum/http.py +38 -0
  15. zlogging/enum/input.py +78 -0
  16. zlogging/enum/intel.py +182 -0
  17. zlogging/enum/json.py +33 -0
  18. zlogging/enum/known.py +22 -0
  19. zlogging/enum/load_balancing.py +22 -0
  20. zlogging/enum/log.py +359 -0
  21. zlogging/enum/management.py +68 -0
  22. zlogging/enum/management_controller_runtime.py +30 -0
  23. zlogging/enum/management_log.py +28 -0
  24. zlogging/enum/mount3.py +88 -0
  25. zlogging/enum/mqtt.py +22 -0
  26. zlogging/enum/net_control.py +188 -0
  27. zlogging/enum/nfs3.py +221 -0
  28. zlogging/enum/notice.py +387 -0
  29. zlogging/enum/open_flow.py +148 -0
  30. zlogging/enum/pcap.py +26 -0
  31. zlogging/enum/protocol_detector.py +26 -0
  32. zlogging/enum/reporter.py +24 -0
  33. zlogging/enum/signatures.py +54 -0
  34. zlogging/enum/smb.py +50 -0
  35. zlogging/enum/socks.py +24 -0
  36. zlogging/enum/software.py +101 -0
  37. zlogging/enum/ssl.py +32 -0
  38. zlogging/enum/sum_stats.py +68 -0
  39. zlogging/enum/supervisor.py +30 -0
  40. zlogging/enum/telemetry.py +30 -0
  41. zlogging/enum/tunnel.py +63 -0
  42. zlogging/enum/weird.py +51 -0
  43. zlogging/enum/zeek.py +279 -0
  44. zlogging/enum/zeekygen_example.py +35 -0
  45. zlogging/loader.py +624 -0
  46. zlogging/model.py +301 -0
  47. zlogging/types.py +1709 -0
  48. zlogging/typing.py +256 -0
  49. zlogging-0.1.3.post3.dist-info/LICENSE +29 -0
  50. zlogging-0.1.3.post3.dist-info/METADATA +173 -0
  51. zlogging-0.1.3.post3.dist-info/RECORD +54 -0
  52. zlogging-0.1.3.post3.dist-info/WHEEL +5 -0
  53. zlogging-0.1.3.post3.dist-info/entry_points.txt +2 -0
  54. 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]'