Giter Club home page Giter Club logo

mflog's Introduction

mflog

Status (master branch)

GitHub CI Maintenance

What is it ?

It is an opinionated python (structured) logging library built on structlog for the MetWork Framework (but it can be used in any context).

Structured logging means that you don’t write hard-to-parse and hard-to-keep-consistent prose in your logs but that you log events that happen in a context instead.

Example:

from mflog import get_logger

# Get a logger
log = get_logger("foo.bar")

# Bind some attributes to the logger depending on the context
log = log.bind(user="john")
log = log.bind(user_id=123)

# [...]

# Log something
log.warning("user logged in", happy=True, another_key=42)

On stderr, you will get:

2019-01-28T07:52:42.903067Z  [WARNING] (foo.bar#7343) user logged in {another_key=42 happy=True user=john user_id=123}

On json output file, you will get:

{
    "timestamp": "2019-01-28T08:16:40.047710Z",
    "level": "warning",
    "name": "foo.bar",
    "pid": 29317,
    "event": "user logged in",
    "another_key": 42,
    "happy": true,
    "user": "john",
    "user_id": 123
}

If the python/rich library is installed (this is not a mandatory requirement) and if the output is a real terminal (and not a redirection or a pipe), the library will automatically configure a fancy color output (of course you can disable it if you don't like):

With following demo python program:

import mflog

# Get a logger
logger = mflog.get_logger("foobar")

# Bind two context variables to this logger
logger = logger.bind(user_id=1234, is_logged=True)

# Log something
logger.info("This is an info message", special_value="foo")
logger.critical("This is a very interesting critical message")

# Let's play with exception
try:
    # Just set a variable to get a demo of locals variable dump
    var = {"key1": [1, 2, 3], "key2": "foobar"}
    1/0
except Exception:
    logger.exception("exception raised (a variables dump should follow)")

You will get this color ouput:

color output

(opinionated) Choices and Features

  • we use main ideas from structlog library
  • we log [DEBUG] and [INFO] messages on stdout (in a human friendly way)
  • we log [WARNING], [ERROR] and [CRITICAL] on stderr (in a human friendly way)
  • (and optionally) we log all messages (worse than a minimal configurable level) in a configurable file in JSON (for easy automatic parsing)
  • (and optionally) we send all messages (worse than a minimal configurable level) to an UDP syslog server (in JSON or in plain text)
  • we can configure a global minimal level to ignore all messages below
  • we reconfigure automatically python standard logging library to use mflog
  • Unicode and Bytes messages are supported (in Python2 and Python3)
  • good support for exceptions (with backtraces)
  • override easily minimal levels (for patterns of logger names) programmatically or with plain text configuration files
  • if the python/rich library is installed (this is not a mandatory requirement) and if the output is a real terminal (and not a redirection), the library will automatically configure a fancy color output (can be really useful but of course you can disable this feature if you don't like it)

How to use ?

A mflog logger can be used as a standard logging logger.

For example:

# Import
from mflog import get_logger

# Get a logger
x = get_logger("foo.bar")

# Usage
x.warning("basic message")
x.critical("message with templates: %i, %s", 2, "foo")
x.debug("message with key/values", foo=True, bar="string")

try:
    1/0
except Exception:
    x.exception("we catched an exception with automatic traceback")

x = x.bind(context1="foo")
x = x.bind(context2="bar")
x.info("this is a contexted message", extra_var=123)

How to configure ?

In python

import mflog

# Configure
mflog.set_config(minimal_level="DEBUG", json_minimal_level="WARNING",
                 json_file="/foo/bar/my_output.json")

# Get a logger
x = mflog.get_logger("foo.bar")

# [...]

With environment variables

$ export MFLOG_MINIMAL_LEVEL="DEBUG"
$ export MFLOG_JSON_MINIMAL_LEVEL="WARNING"
$ export MFLOG_JSON_FILE="/foo/bar/my_output.json"

$ python

>>> import mflog
>>>
>>> # Get a logger
>>> x = mflog.get_logger("foo.bar")
>>>
>>> # [...]

Note

When you get a mflog logger, if default configuration is applied automatically if not set manually before.

How to override minimal level for a specific logger

If you have a "noisy" specific logger, you can override its minimal log level.

The idea is to configure this in a file like this:

# lines beginning with # are comments

# this line say 'foo.bar' logger will have a minimal level of WARNING
foo.bar => WARNING

# this line say 'foo.*' loggers will have a minimal level of DEBUG
# (see python fnmatch for accepted wildcards)
foo.* => DEBUG

# The first match wins

Then, you can use

# yes we use a list here because you can use several files
# (the first match wins)
mflog.set_config([...], override_files=["/full/path/to/your/override.conf"])

or

# if you want to provide multiple files, use ';' as a separator
export MFLOG_MINIMAL_LEVEL_OVERRIDE_FILES=/full/path/to/your/override.conf

Link with standard python logging library

When you get a mflog logger or when you call set_config() function, the standard python logging library is reconfigured to use mflog.

Example:

import logging
import mflog

# standard use of logging library
x = logging.getLogger("standard.logger")
print("<output of the standard logging library>")
x.warning("foo bar")
print("</output of the standard logging library>")

# we set the mflog configuration
mflog.set_config()

# now logging library use mflog
print()
print("<output of the standard logging library through mflog>")
x.warning("foo bar")
print("</output of the standard logging library through mflog>")

Output:

<output of the standard logging library>
foo bar
</output of the standard logging library>

<output of the standard logging library through mflog>
2019-01-29T09:32:37.093240Z  [WARNING] (standard.logger#15809) foo bar
</output of the standard logging library through mflog>

mflog loggers API

.debug(message, *args, **kwargs)

Log the given message as [DEBUG].

  • *args can be used for placeholders (to format the given message)
  • **kwargs can be used for key/values (log context).

Examples:

from mflog import get_logger

x = get_logger('my.logger')
x.debug("my debug message with placeholders: %s and %i", "foo", 123,
        key1="value1, key2=True, key5=123)

.info(message, *args, **kwargs)

Same as .debug but with [INFO] severity level.

.warning(message, *args, **kwargs)

Same as .debug but with [WARNING] severity level.

.error(message, *args, **kwargs)

Same as .debug but with [ERROR] severity level.

.critical(message, *args, **kwargs)

Same as .debug but with [CRITICAL] severity level.

.exception(message, *args, **kwargs)

Same as .error (so with [ERROR] severity level) but we automatically add the current stacktrace in the message through special key/values.

.bind(**new_values)

Return a new logger with **new_values added to the existing ones (see examples at the beginning).

.unbind(*keys)

Return a new logger with *keys removed from the context. It raises KeyError if the key is not part of the context.

.try_unbind(*keys)

Like .unbind but best effort: missing keys are ignored.

.die(optional_message, *args, **kwargs)

Same as .exception() but also do a .dump_locals() call and exit the program with sys.exit(1).

.dump_locals()

Dump locals variables on stderr (for debugging).

mflog.*

All previous loggers method are also available in mflog module.

Example:

import mflog

mflog.warning("this is a warning message", context1="foobar", user_id=123)

FAQ

If I want to use mflog inside my library ?

If you write a library and if you want to use mflog, use mflog normally. You just should avoid to call set_config() inside your library.

Do you have "thread local context mode" ?

This mode is explained here.

You have to understand what you are doing.

If you want to use it, just add thread_local_context=True to your set_config() call. And you can use .new(**new_values) on mflog loggers to clear context and binds some initial values.

Can I globally add an extra context to each log line ?

If you add extra_context_func=your_python_func to your set_config() call, and if your_python_func returns a dict of key/values as strings when called with no argument, these key/values will be added to your log context.

Another way to do that without even calling set_config() is to define an environment variable called MFLOG_EXTRA_CONTEXT_FUNC containing the full path to your python func.

Full example:

# in shell
export MFLOG_EXTRA_CONTEXT_FUNC="mflog.unittests.extra_context"

then, in your python interpreter:

>>> from mflog import get_logger
>>> get_logger("foo").info("bar")
2019-04-11T07:32:53.517260Z     [INFO] (foo#15379) bar {extra_context_key1=extra_context_value1 extra_context_key2=extra_context_value2}

Here is the code of mflog.unittests.extra_context:

def extra_context():
    return {"extra_context_key1": "extra_context_value1",
            "extra_context_key2": "extra_context_value2"}

Can I filter some context keys in stdout/stderr output (but keep them in json output) ?

Yes, add json_only_keys=["key1", "key2"] to your set_config() call or use MFLOG_JSON_ONLY_KEYS=key1,key2 environment variable.

What about if I don't want to redirect standard python logging to mflog ?

You can add standard_logging_redirect=False in your set_config() call of set MFLOG_STANDARD_LOGGING_REDIRECT=0 environment variable.

Can I silent a specific noisy logger?

You can use override_files feature to do that or you can also use the mflog.add_override function.

For example:

import mflog

# for all mylogger.* loggers (fnmatch pattern), the minimal level is CRITICAL
mflog.add_override("mylogger.*", CRITICAL)

# Not very interesting but this call will be ignored
mflog.get_logger("mylogger.foo").warning("foo")

How can I use syslog logging?

You can configure it with these keyword arguments during set_config() call:

  • syslog_minimal_level: WARNING, CRITICAL...
  • syslog_address: null (no syslog (defaut)), 127.0.0.1:514 (send packets to 127.0.0.1:514), /dev/log (unix socket)...
  • syslog_format: msg_only (default) or json

or with corresponding env vars:

  • MFLOG_SYSLOG_MINIMAL_LEVEL
  • MFLOG_SYSLOG_ADDRESS
  • MFLOG_SYSLOG_FORMAT

How to disable the fancy color output?

This feature is automatically enabled when:

  • python/rich library is installed
  • the corresponding output (stdout, stderr) is a real terminal (and not a redirection to a file)

But you can manually disable it by adding fancy_output=False to your set_config().

Coverage

See Coverage report

Contributing guide

See CONTRIBUTING.md file.

Code of Conduct

See CODE_OF_CONDUCT.md file.

Sponsors

(If you are officially paid to work on MetWork Framework, please contact us to add your company logo here!)

logo

mflog's People

Contributors

metworkbot avatar thebaptiste avatar thefab avatar vincentchabot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

vincentchabot

mflog's Issues

can't give exception object to mflog.exception method

# With standard python logging
>>> import logging
>>> try:
...     1/0
... except Exception as e:
...     logging.exception(e)
... 
ERROR:root:division by zero
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

=> ok

# With mflog
>>> from mflog import get_logger
>>> x = get_logger("plop")
>>> try:
...     1/0
... except Exception as e:
...     logging.exception(e)
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/logging/__init__.py", line 1819, in exception
    error(msg, *args, exc_info=exc_info, **kwargs)
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/logging/__init__.py", line 1811, in error
    root.error(msg, *args, **kwargs)
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/logging/__init__.py", line 1314, in error
    self._log(ERROR, msg, args, **kwargs)
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/logging/__init__.py", line 1421, in _log
    self.handle(record)
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/logging/__init__.py", line 1431, in handle
    self.callHandlers(record)
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/logging/__init__.py", line 1493, in callHandlers
    hdlr.handle(record)
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/logging/__init__.py", line 861, in handle
    self.emit(record)
  File "/home/fab/metwork/mfext/build/opt/python3/lib/python3.5/site-packages/mflog/__init__.py", line 50, in emit
    logger.error(record.msg, *(record.args), **kwargs)
  File "/home/fab/metwork/mfext/build/opt/python3/lib/python3.5/site-packages/structlog/stdlib.py", line 83, in error
    return self._proxy_to_logger("error", event, *args, **kw)
  File "/home/fab/metwork/mfext/build/opt/python3/lib/python3.5/site-packages/structlog/stdlib.py", line 119, in _proxy_to_logger
    method_name, event=event, **event_kw
  File "/home/fab/metwork/mfext/build/opt/python3/lib/python3.5/site-packages/structlog/_base.py", line 192, in _proxy_to_logger
    return getattr(self._logger, method_name)(*args, **kw)
  File "/home/fab/metwork/mfext/build/opt/python3/lib/python3.5/site-packages/mflog/__init__.py", line 88, in _msg_stderr
    self._json(**event_dict)
  File "/home/fab/metwork/mfext/build/opt/python3/lib/python3.5/site-packages/mflog/__init__.py", line 97, in _json
    self._json_logger.msg(json.dumps(event_dict))
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/json/__init__.py", line 230, in dumps
    return _default_encoder.encode(obj)
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/json/encoder.py", line 198, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/json/encoder.py", line 256, in iterencode
    return _iterencode(o, 0)
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/json/encoder.py", line 179, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: ZeroDivisionError('division by zero',) is not JSON serializable

=> not ok

problem with gunicorn logs when debug is enabled and mflog loaded

2019-04-11T16:34:44.988636Z    [ERROR] (gunicorn.error#28290) Traceback (most recent call last):
  File "/home/fab/metwork/mfserv/build/opt/python3/lib/python3.5/site-packages/gunicorn/glogging.py", line 353, in access
    self.access_log.info(self.cfg.access_log_format, safe_atoms)
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/logging/__init__.py", line 1285, in info
    self._log(INFO, msg, args, **kwargs)
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/logging/__init__.py", line 1421, in _log
    self.handle(record)
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/logging/__init__.py", line 1431, in handle
    self.callHandlers(record)
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/logging/__init__.py", line 1493, in callHandlers
    hdlr.handle(record)
  File "/home/fab/metwork/mfext/build/opt/python3_core/lib/python3.5/logging/__init__.py", line 861, in handle
    self.emit(record)
  File "/home/fab/metwork/mfext/build/opt/python3/lib/python3.5/site-packages/mflog/__init__.py", line 48, in emit
    logger.info(record.msg, *(record.args), **kwargs)
  File "/home/fab/metwork/mfext/build/opt/python3/lib/python3.5/site-packages/structlog/stdlib.py", line 69, in info
    return self._proxy_to_logger("info", event, *args, **kw)
  File "/home/fab/metwork/mfext/build/opt/python3/lib/python3.5/site-packages/structlog/stdlib.py", line 119, in _proxy_to_logger
    method_name, event=event, **event_kw
  File "/home/fab/metwork/mfext/build/opt/python3/lib/python3.5/site-packages/structlog/_base.py", line 191, in _proxy_to_logger
    args, kw = self._process_event(method_name, event, event_kw)
  File "/home/fab/metwork/mfext/build/opt/python3/lib/python3.5/site-packages/structlog/_base.py", line 151, in _process_event
    event_dict = proc(self._logger, method_name, event_dict)
  File "/home/fab/metwork/mfext/build/opt/python3/lib/python3.5/site-packages/structlog/stdlib.py", line 280, in __call__
    event_dict["event"] = event_dict["event"] % args
TypeError: format requires a mapping

TypeError: Object of type ... is not JSON serializable

When mflog is not able to json serialize a provided log message (for example if we pass it an object), we face a "fat" stack trace in our plugin log:

919353 --- Logging error ---
919354 Traceback (most recent call last):
...
919362 During handling of the above exception, another exception occurred:
919363
919364 Traceback (most recent call last):
919365   File "/opt/metwork-mfext-2.1/opt/python3_core/lib/python3.10/logging/handlers.py", line 971, in emit
919366     msg = self.format(record)
919367   File "/opt/metwork-mfext-2.1/opt/python3_core/lib/python3.10/logging/__init__.py", line 943, in format
919368     return fmt.format(record)
919369   File "/opt/metwork-mfext-2.1/opt/python3/lib/python3.10/site-packages/mflog/syslog.py", line 15, in format
919370     return json.dumps(record.msg)
919371   File "/opt/metwork-mfext-2.1/opt/python3_core/lib/python3.10/json/__init__.py", line 231, in dumps
919372     return _default_encoder.encode(obj)
919373   File "/opt/metwork-mfext-2.1/opt/python3_core/lib/python3.10/json/encoder.py", line 199, in encode
919374     chunks = self.iterencode(o, _one_shot=True)
919375   File "/opt/metwork-mfext-2.1/opt/python3_core/lib/python3.10/json/encoder.py", line 257, in iterencode
919376     return _iterencode(o, 0)
919377   File "/opt/metwork-mfext-2.1/opt/python3_core/lib/python3.10/json/encoder.py", line 179, in default
919378     raise TypeError(f'Object of type {o.__class__.__name__} '
919379 TypeError: Object of type UndefinedTable is not JSON serializable
919380 Call stack:
919381   File "/home/mfdata/var/plugins/my_plugin/image_importer.py", line 304, in <module>
919382     x.run()
919383   File "/opt/metwork-mfext-2.1/opt/python3/lib/python3.10/site-packages/acquisition/step.py", line 544, in run
919384     self.__run_in_daemon_mode(
919385   File "/opt/metwork-mfext-2.1/opt/python3/lib/python3.10/site-packages/acquisition/step.py", line 329, in __run_in_daemon_mode
919386     self._process(xaf)
919387   File "/opt/metwork-mfext-2.1/opt/python3/lib/python3.10/site-packages/acquisition/step.py", line 153, in _process
919388     process_status = self._exception_safe_call(
919389   File "/opt/metwork-mfext-2.1/opt/python3/lib/python3.10/site-packages/acquisition/step.py", line 129, in _exception_safe_call
919390     return func(*args, **kwargs)
919391   File "/home/mfdata/var/plugins/my_plugin/image_importer.py", line 295, in process
919392     self.save_image_ref(self.layer_id, self.new_filename)
919393   File "/home/mfdata/var/plugins/my_plugin/image_importer.py", line 229, in save_image_ref
919394     self.error(error)
919395   File "/opt/metwork-mfext-2.1/opt/python3/lib/python3.10/site-packages/acquisition/base.py", line 164, in error
919396     logger.error(msg, *args, **kwargs)
919397   File "/opt/metwork-mfext-2.1/opt/python3/lib/python3.10/site-packages/structlog/stdlib.py", line 200, in error
919398     return self._proxy_to_logger("error", event, *args, **kw)
919399   File "/opt/metwork-mfext-2.1/opt/python3/lib/python3.10/site-packages/structlog/stdlib.py", line 247, in _proxy_to_logger
919400     return super()._proxy_to_logger(method_name, event=event, **event_kw)
919401   File "/opt/metwork-mfext-2.1/opt/python3/lib/python3.10/site-packages/structlog/_base.py", line 206, in _proxy_to_logger
919402     return getattr(self._logger, method_name)(*args, **kw)
919403   File "/opt/metwork-mfext-2.1/opt/python3/lib/python3.10/site-packages/mflog/__init__.py", line 216, in _msg_stderr
919404     self._msg(self._stderr_print_logger, **event_dict)
919405   File "/opt/metwork-mfext-2.1/opt/python3/lib/python3.10/site-packages/mflog/__init__.py", line 139, in _msg
919406     self._syslog(**event_dict)
919407   File "/opt/metwork-mfext-2.1/opt/python3/lib/python3.10/site-packages/mflog/__init__.py", line 233, in _syslog
919408     self._syslog_logger.msg(event_dict)
919409   File "/opt/metwork-mfext-2.1/opt/python3/lib/python3.10/site-packages/mflog/syslog.py", line 39, in msg
919410     self.__syslog_handler.emit(record)
919411 Message: {'name': 'mfdata......main', 'event': UndefinedTable('relation "..." does not exist\nLINE 2:                 INSERT INTO ....(....), 'level': 'error', 'pid': 2622228, 'plugin': '....', 'timestamp': '2023-02-01T11:21:24.256635Z'}

This stack trace is produced with below code:

except (Exception) as error:
        LOGGER.error(error)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.