Skip to content

Commit

Permalink
Refactor "parser" object into the "parse()" logger method
Browse files Browse the repository at this point in the history
  • Loading branch information
Delgan committed Dec 8, 2018
1 parent 7e9879c commit c639a5d
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 253 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Unreleased
==========

- Remove the ``parser`` and refactor it into the ``logger.parse()`` method
- Remove the ``notifier`` and its dependencies, just ``pip install notifiers`` if user needs it


Expand Down
14 changes: 6 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,6 @@ Take the tour
.. |logger| replace:: ``logger``
.. _logger: https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger

.. |parser| replace:: ``parser``
.. _parser: https://loguru.readthedocs.io/en/stable/api/parser.html#loguru._parser.Parser

.. |start| replace:: ``start()``
.. _start: https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.start

Expand Down Expand Up @@ -104,6 +101,9 @@ Take the tour
.. |enable| replace:: ``enable()``
.. _enable: https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.enable

.. |parse| replace:: ``parse()``
.. _parse: https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.parse

.. _sinks: https://loguru.readthedocs.io/en/stable/api/logger.html#sink
.. _record dict: https://loguru.readthedocs.io/en/stable/api/logger.html#record
.. _log messages: https://loguru.readthedocs.io/en/stable/api/logger.html#message
Expand Down Expand Up @@ -377,16 +377,14 @@ Don't like the default logger formatting? Would prefer another ``DEBUG`` color?
Convenient parser
^^^^^^^^^^^^^^^^^

It is often useful to extract specific information from generated logs, this is why `Loguru` provides a |parser|_ which helps dealing with logs and regexes.
It is often useful to extract specific information from generated logs, this is why `Loguru` provides a |parse|_ method which helps dealing with logs and regexes.

::

from loguru import parser

pattern = r"(?P<time>.*) - (?P<level>[0-9]+) - (?P<message>.*)"
caster_dict = dict(time=time.strptime, level=int)

for groups in parser.parse("file.log", pattern):
groups = parser.cast(groups, time=time.strptime, level=int)
for groups in logger.parse("file.log", pattern, cast=caster_dict):
print("Parsed message at {} with severity {}".format(groups["time"], groups["level"]))


Expand Down
1 change: 0 additions & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,3 @@ API Reference
:includehidden:

api/logger.rst
api/parser.rst
5 changes: 0 additions & 5 deletions docs/api/parser.rst

This file was deleted.

6 changes: 2 additions & 4 deletions loguru/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
"""
The Loguru library provides pre-instanced objects to facilitate dealing with logging in Python.
The Loguru library provides a pre-instanced logger to facilitate dealing with logging in Python.
Pick one: ``from loguru import logger, parser``
Just ``from loguru import logger``.
"""
import atexit as _atexit
import sys as _sys

from . import _defaults
from ._logger import Logger as _Logger
from ._parser import Parser as _Parser

__version__ = "0.1.0"

logger = _Logger({}, None, False, False, False, False, 0)
parser = _Parser()

if _defaults.LOGURU_AUTOINIT and _sys.stderr:
logger.start(_sys.stderr)
Expand Down
113 changes: 113 additions & 0 deletions loguru/_logger.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import functools
import itertools
import logging
import re
import threading
from collections import namedtuple
from inspect import isclass
Expand Down Expand Up @@ -1220,6 +1221,118 @@ def key_sort(x):

self._activation_list[:] = activation_list

@staticmethod
def parse(file, pattern, *, cast={}, chunk=2 ** 16):
"""
Parse raw logs and extract each entry as a |dict|.
The logging format has to be specified as the regex ``pattern``, it will then be
used to parse the ``file`` and retrieve each entries based on the named groups present
in the regex.
Parameters
----------
file : |str|, |Path| or |file-like object|_
The path of the log file to be parsed, or alternatively an already opened file object.
pattern : |str| or |re.Pattern|_
The regex to use for logs parsing, it should contain named groups which will be included
in the returned dict.
cast : |function|_ or |dict|, optional
A function that should convert in-place the regex groups parsed (a dict of string
values) to more appropiate types. If a dict is passed, its should be a mapping between
keys of parsed log dict and the function that should be used to convert the associated
value.
chunk : |int|, optional
The number of bytes read while iterating through the logs, this avoid having to load the
whole file in memory.
Yields
------
:class:`dict`
The dict mapping regex named groups to matched values, as returned by |match.groupdict|
and optionally converted according to ``cast`` argument.
Examples
--------
>>> reg = r"(?P<lvl>[0-9]+): (?P<msg>.*)" # If log format is "{level.no} - {message}"
>>> for e in logger.parse("file.log", reg): # A file line could be "10 - A debug message"
... print(e) # => {'lvl': '10', 'msg': 'A debug message'}
...
>>> caster = dict(lvl=int) # Parse 'lvl' key as an integer
>>> for e in logger.parse("file.log", reg, cast=caster):
... print(e) # => {'lvl': 10, 'msg': 'A debug message'}
>>> def cast(groups):
... if "date" in groups:
... groups["date"] = datetime.strptime(groups["date"], "%Y-%m-%d %H:%M:%S")
...
>>> with open("file.log") as file:
... for log in logger.parse(file, reg, cast=cast):
... print(log["date"], log["something_else"])
"""
if isinstance(file, (str, PathLike)):
should_close = True
fileobj = open(str(file))
elif hasattr(file, "read") and callable(file.read):
should_close = False
fileobj = file
else:
raise ValueError(
"Invalid file, it should be a string path or a file object, not: '%s'"
% type(file).__name__
)

if isinstance(cast, dict):

def cast_function(groups):
for key, converter in cast.items():
if key in groups:
groups[key] = converter(groups[key])

elif callable(cast):
cast_function = cast
else:
raise ValueError(
"Invalid cast, it should be a function or a dict, not: '%s'" % type(cast).__name__
)

try:
regex = re.compile(pattern)
except TypeError:
raise ValueError(
"Invalid pattern, it should be a string or a compiled regex, not: '%s'"
% type(pattern).__name__
)

matches = Logger._find_iter(fileobj, regex, chunk)

for match in matches:
groups = match.groupdict()
cast_function(groups)
yield groups

if should_close:
fileobj.close()

@staticmethod
def _find_iter(fileobj, regex, chunk):
buffer = fileobj.read(0)

while 1:
text = fileobj.read(chunk)
buffer += text
matches = list(regex.finditer(buffer))

if not text:
yield from matches
break

if len(matches) > 1:
end = matches[-2].end()
buffer = buffer[end:]
yield from matches[:-1]

@staticmethod
@functools.lru_cache()
def _make_log_function(level, decorated=False):
Expand Down
136 changes: 0 additions & 136 deletions loguru/_parser.py

This file was deleted.

0 comments on commit c639a5d

Please sign in to comment.