Skip to content

Python library for better, more structured, logging

Notifications You must be signed in to change notification settings

gilbertozp/semilog

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

semilog: Simple structured logging library for Python

A semi-structured log is essentially a Python dictionary, serialized.

Design goals are minimal code, maximum flexibility, useful functionality.

The library follows the subject/observer pattern to send logs to destinations. The observers all have a 'severity' level that filters the logs that they receive.

Logs can be easily sent to local streams (files or stdout/stderr) or to remote ZeroMQ receivers. Built-in formats are user-specified text format, JSON, and Python's pickle.

Installation

  • From source: python setup.py install
  • COMING SOON: With pip: pip install semilog

Dependencies: see requirements.txt

Tests

The tests are written using the pytest framework. See http://pytest.org/ for details.

To run the tests (from the top level of the package):

py.test tests

Examples

Below are some examples of usage.

Basic

A log object is an instance of Subject, with one or more Observer subclasses attached to it. By default, a Stream observer, logging to stderr with a text format, is added to a new Subject instance.

The underlying method on all log objects is event(level, name, **kwd), but in keeping with other log libraries, there are methods for each level of the form <levelname>(name, **kwd).

from semilog import Subject
log = Subject()  # adds default Stream observer
log.info('hello', msg="Hello, world!")

Prints something like:

INFO 2014-12-03T20:24:07.493907 hello: msg="Hello, world!"

Syntactic sugar methods such as info can be created by the user simply by by modifying the Levelname and Severity dicts in the semilog.const module.

from semilog import Subject
from semilog.const import Levelname, Severity
# make "code_blue" a level past "trace"
Severity['B'], Levelname['B'] = Severity['T'] + 1, 'CODE_BLUE'
log = Subject(severity=Severity['B'])
log.code_blue('hello', msg="Hello, world!")

Prints something like:

CODE_BLUE 2014-12-03T20:25:10.993022 hello: msg="Hello, world!"

Multiple local destinations

Using the configure() method, or directly modifying the observers attribute of the Subject instance, you can add multiple log destinations. A severity filter is understood by the built-in Observers.

from semilog import Subject, Stream
log = Subject({})  # empty dict avoids default config
logfile = open("/tmp/mylog", "a")
fmt = "[{level}] {isotime} {event}: {msg}"
log.configure({'observers': {
    # Log anything at warning and above to stderr
    'console': Stream(fmt=fmt, severity='W'),
    # Log everything including traces to the file
    'logfile': Stream(fmt=fmt, stream=logfile, severity='T')}})
log.info('hello', msg="Hello, world!")  # only to file
log.warn('goodbye', msg="Later!")  # both

Remote destinations

Logging to remote destinations requires both a sender and receiver. The sender is created by adding a Remote observer to a subject. Then a Server needs to be run on the same port (and expecting the same message format, JSON by default). The underlying networking, and formatting, is handled by the ZeroMQ library.

from semilog import Subject, Remote, Server

localhost = '127.0.0.1'
count = 0

# create sender
sender = Subject({'observers': [Remote(localhost)]})
# create and start receiver
server = Server(lambda m: print("Got: {}".format(m)), host=localhost)
server.start()
# send some messages
for i in range(5):
    sender.info('hello', i=i)
# wait for messages to be processed
while count < 5:
    time.sleep(0.1)
# stop the receiver
server.stop()

Asynchronous logging

If you are worried about the logger blocking your application, you can use the async keyword to tell the Subject to buffer events and send them with a separate thread. This allows the event() calls to return immediately. The thread is a "daemon" thread, so it will be automatically killed when the main thread exits.

To show this in action, we can create a subclass of Observer that just sleeps every time it gets an event:

import time
from semilog import send
class Pokey(send.Observer):
    def event(self, event):
        time.sleep(1)

Then we can simply add the async=True keyword to the Subject constructor, and see that 5 log messages do not wait 5 seconds to complete, but complete in well under a second.

log = send.Subject({'observers':[Pokey()]}, async=True)
t0 = time.time()
for i in range(5):
    log.event('i', 'zoom-zoom')
dt = time.time() - t0
assert dt < 1.0

If you need to wait for all events to be processed, i.e. the queue to drain, then use the drain() method. To avoid hanging the application, a timeout of 10 seconds is the default; put a big number if you want to wait "forever".

# wait 30 sec. for log 's queue to drain
log.drain(30)
# wait Carl Sagan-like times for the queue to drain
log.drain(9e9)

Contact

Help improve this software! Contributions are welcome, although subject to review for quality and adherence to the design goals (and if you don't like that, then just go fork it, man!).

Contact the author at dkgunter@lbl.gov.

About

Python library for better, more structured, logging

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Python 100.0%