Imported Upstream version 0.6.0
Agustin Henze
10 years ago
0 | .ropeproject | |
1 | .tox | |
2 | docs/_build | |
3 | logbook/_speedups.c | |
4 | logbook/_speedups.so | |
5 | Logbook.egg-info | |
6 | dist | |
7 | *.pyc | |
8 | env | |
9 | env* | |
10 | .coverage | |
11 | cover | |
12 | build | |
13 | .vagrant | |
14 | flycheck-* |
0 | language: python | |
1 | python: | |
2 | - "2.6" | |
3 | - "2.7" | |
4 | - "3.3" | |
5 | - "pypy" | |
6 | ||
7 | install: | |
8 | # this fixes SemLock issues on travis | |
9 | - "sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm" | |
10 | - "sudo apt-get install libzmq3-dev redis-server" | |
11 | - "python scripts/pypi_mirror_setup.py http://a.pypi.python.org/simple" | |
12 | - "pip install cython redis" | |
13 | - "make test_setup" | |
14 | - "python setup.py develop" | |
15 | ||
16 | env: | |
17 | - COMMAND="make test" | |
18 | - COMMAND="make cybuild test" | |
19 | ||
20 | script: "$COMMAND" | |
21 | ||
22 | matrix: | |
23 | exclude: | |
24 | - python: "pypy" | |
25 | env: COMMAND="make cybuild test" | |
26 | ||
27 | notifications: | |
28 | email: | |
29 | recipients: | |
30 | - vmalloc@gmail.com | |
31 | irc: | |
32 | channels: | |
33 | - "chat.freenode.net#pocoo" | |
34 | on_success: change | |
35 | on_failure: always | |
36 | use_notice: true | |
37 | skip_join: true |
0 | Logbook is written and maintained by the Logbook Team and various | |
1 | contributors: | |
2 | ||
3 | Lead Developers: | |
4 | ||
5 | - Armin Ronacher <armin.ronacher@active-4.com> | |
6 | - Georg Brandl | |
7 | ||
8 | Contributors: | |
9 | ||
10 | - Ronny Pfannschmidt | |
11 | - Daniel Neuhäuser | |
12 | - Kenneth Reitz | |
13 | - Valentine Svensson | |
14 | - Roman Valls Guimera | |
15 | - Guillermo Carrasco Hernández | |
16 | - Raphaël Vinot |
0 | Logbook Changelog | |
1 | ================= | |
2 | ||
3 | Here you can see the full list of changes between each Logbook release. | |
4 | ||
5 | Version 0.6.0 | |
6 | ------------- | |
7 | ||
8 | Released on October 3rd 2013. Codename "why_not_production_ready" | |
9 | ||
10 | - Added Redis handler (Thanks a lot @guillermo-carrasco for this PR) | |
11 | - Fixed email encoding bug (Thanks Raphaël Vinot) | |
12 | ||
13 | Version 0.5.0 | |
14 | ------------- | |
15 | ||
16 | Released on August 10th 2013. | |
17 | ||
18 | - Drop 2.5, 3.2 support, code cleanup | |
19 | - The exc_info argument now accepts `True`, like in the standard logging module | |
20 | ||
21 | Version 0.4.2 | |
22 | ------------- | |
23 | ||
24 | Released on June 2nd 2013. | |
25 | ||
26 | - Fixed Python 3.x compatibility, including speedups | |
27 | - Dropped Python 2.4 support. Python 2.4 support caused a lot of hacks in the code and introduced duplication to the test code. In addition, it is impossible to cover 2.4-3.x with a single tox installation, which may introduce unwitting code breakage. Travis also does not support Python 2.4 so the chances of accidentally breaking this support were very high as it was... | |
28 | ||
29 | ||
30 | Version 0.4.1 | |
31 | ------------- | |
32 | ||
33 | Released on December 12th. Codename "121212" | |
34 | ||
35 | - Fixed several outstanding encoding problems, thanks to @dvarazzo. | |
36 | - Merged in minor pull requests (see https://github.com/mitsuhiko/logbook/pulls?&state=closed) | |
37 | ||
38 | Version 0.4 | |
39 | ----------- | |
40 | ||
41 | Released on October 24th. Codename "Phoenix" | |
42 | ||
43 | - Added preliminary RabbitMQ and CouchDB support. | |
44 | - Added :class:`logbook.notifiers.NotifoHandler` | |
45 | - `channel` is now documented to be used for filtering purposes if | |
46 | wanted. Previously this was an opaque string that was not intended | |
47 | for filtering of any kind. | |
48 | ||
49 | Version 0.3 | |
50 | ----------- | |
51 | ||
52 | Released on October 23rd. Codename "Informant" | |
53 | ||
54 | - Added :class:`logbook.more.ColorizingStreamHandlerMixin` and | |
55 | :class:`logbook.more.ColorizedStderrHandler` | |
56 | - Deprecated :class:`logbook.RotatingFileHandlerBase` because the | |
57 | interface was not flexible enough. | |
58 | - Provided basic Python 3 compatibility. This did cause a few smaller | |
59 | API changes that caused minimal changes on Python 2 as well. The | |
60 | deprecation of the :class:`logbook.RotatingFileHandlerBase` was a | |
61 | result of this. | |
62 | - Added support for Python 2.4 | |
63 | - Added batch emitting support for handlers which now makes it possible | |
64 | to use the :class:`logbook.more.FingersCrossedHandler` with the | |
65 | :class:`logbook.MailHandler`. | |
66 | - Moved the :class:`~logbook.FingersCrossedHandler` handler into the | |
67 | base package. The old location stays importable for a few releases. | |
68 | - Added :class:`logbook.GroupHandler` that buffers records until the | |
69 | handler is popped. | |
70 | - Added :class:`logbook.more.ExternalApplicationHandler` that executes | |
71 | an external application for each log record emitted. | |
72 | ||
73 | Version 0.2.1 | |
74 | ------------- | |
75 | ||
76 | Bugfix release, Released on September 22nd. | |
77 | ||
78 | - Fixes Python 2.5 compatibility. | |
79 | ||
80 | Version 0.2 | |
81 | ----------- | |
82 | ||
83 | Released on September 21st. Codename "Walls of Text" | |
84 | ||
85 | - Implemented default with statement for handlers which is an | |
86 | alias for `threadbound`. | |
87 | - `applicationbound` and `threadbound` return the handler now. | |
88 | - Implemented channel recording on the log records. | |
89 | - The :class:`logbook.more.FingersCrossedHandler` now is set to | |
90 | `ERROR` by default and has the ability to create new loggers | |
91 | from a factory function. | |
92 | - Implemented maximum buffer size for the | |
93 | :class:`logbook.more.FingersCrossedHandler` as well as a lock | |
94 | for thread safety. | |
95 | - Added ability to filter for context. | |
96 | - Moved bubbling flags and filters to the handler object. | |
97 | - Moved context processors on their own stack. | |
98 | - Removed the `iter_context_handlers` function. | |
99 | - Renamed `NestedHandlerSetup` to :class:`~logbook.NestedSetup` | |
100 | because it can now also configure processors. | |
101 | - Added the :class:`logbook.Processor` class. | |
102 | - There is no difference between logger attached handlers and | |
103 | context specific handlers any more. | |
104 | - Added a function to redirect warnings to logbook | |
105 | (:func:`logbook.compat.redirected_warnings`). | |
106 | - Fixed and improved :class:`logbook.LoggerGroup`. | |
107 | - The :class:`logbook.TestHandler` now keeps the record open | |
108 | for further inspection. | |
109 | - The traceback is now removed from a log record when the record | |
110 | is closed. The formatted traceback is a cached property | |
111 | instead of a function. | |
112 | - Added ticketing handlers that send logs directly into a database. | |
113 | - Added MongoDB backend for ticketing handlers | |
114 | - Added a :func:`logbook.base.dispatch_record` function to dispatch | |
115 | records to handlers independently of a logger (uses the default | |
116 | record dispatching logic). | |
117 | - Renamed `logger_name` to `channel`. | |
118 | - Added a multi processing log handler | |
119 | (:class:`logbook.more.MultiProcessingHandler`). | |
120 | - Added a twitter handler. | |
121 | - Added a ZeroMQ handler. | |
122 | - Added a Growl handler. | |
123 | - Added a Libnotify handler. | |
124 | - Added a monitoring file handler. | |
125 | - Added a handler wrapper that moves the actual handling into a | |
126 | background thread. | |
127 | - The mail handler can now be configured to deliver each log record | |
128 | not more than n times in m seconds. | |
129 | - Added support for Python 2.5 | |
130 | - Added a :class:`logbook.queues.SubscriberGroup` to deal with multiple | |
131 | subscribers. | |
132 | - Added a :class:`logbook.compat.LoggingHandler` for redirecting logbook | |
133 | log calls to the standard library's :mod:`logging` module. | |
134 | ||
135 | Version 0.1 | |
136 | ----------- | |
137 | ||
138 | First public release. |
0 | Copyright (c) 2010 by the Logbook Team, see AUTHORS for more details. | |
1 | ||
2 | Some rights reserved. | |
3 | ||
4 | Redistribution and use in source and binary forms, with or without | |
5 | modification, are permitted provided that the following conditions are | |
6 | met: | |
7 | ||
8 | * Redistributions of source code must retain the above copyright | |
9 | notice, this list of conditions and the following disclaimer. | |
10 | ||
11 | * Redistributions in binary form must reproduce the above | |
12 | copyright notice, this list of conditions and the following | |
13 | disclaimer in the documentation and/or other materials provided | |
14 | with the distribution. | |
15 | ||
16 | * The names of the contributors may not be used to endorse or | |
17 | promote products derived from this software without specific | |
18 | prior written permission. | |
19 | ||
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
0 | include MANIFEST.in Makefile CHANGES logbook/_speedups.c logbook/_speedups.pyx tox.ini | |
1 | include scripts/test_setup.py | |
2 | recursive-include tests * | |
3 |
0 | all: clean-pyc test | |
1 | ||
2 | clean-pyc: | |
3 | find . -name '*.pyc' -exec rm -f {} + | |
4 | find . -name '*.pyo' -exec rm -f {} + | |
5 | find . -name '*~' -exec rm -f {} + | |
6 | ||
7 | test_setup: | |
8 | @python scripts/test_setup.py | |
9 | ||
10 | test: | |
11 | @nosetests -w tests | |
12 | ||
13 | toxtest: | |
14 | @tox | |
15 | ||
16 | vagrant_toxtest: | |
17 | @vagrant up | |
18 | @vagrant ssh --command "rsync -avP --delete --exclude=_build --exclude=.tox /vagrant/ ~/src/ && cd ~/src/ && tox" | |
19 | ||
20 | bench: | |
21 | @python benchmark/run.py | |
22 | ||
23 | upload-docs: docs | |
24 | python setup.py upload_docs | |
25 | ||
26 | docs: | |
27 | make -C docs html SPHINXOPTS=-Aonline=1 | |
28 | ||
29 | release: upload-docs | |
30 | python scripts/make-release.py | |
31 | ||
32 | logbook/_speedups.so: logbook/_speedups.pyx | |
33 | cython logbook/_speedups.pyx | |
34 | python setup.py build | |
35 | cp build/*/logbook/_speedups*.so logbook | |
36 | ||
37 | cybuild: logbook/_speedups.so | |
38 | ||
39 | .PHONY: test upload-docs clean-pyc cybuild bench all docs |
0 | Welcome to Logbook | |
1 | ================== | |
2 | ||
3 | .. image:: https://secure.travis-ci.org/mitsuhiko/logbook.png | |
4 | :target: https://travis-ci.org/mitsuhiko/logbook | |
5 | ||
6 | .. image:: https://pypip.in/d/Logbook/badge.png | |
7 | :target: https://crate.io/packages/Logbook | |
8 | ||
9 | .. image:: https://pypip.in/v/Logbook/badge.png | |
10 | :target: https://crate.io/packages/Logbook | |
11 | ||
12 | Logbook is a nice logging replacement. | |
13 | ||
14 | It should be easy to setup, use and configure and support web applications :) | |
15 | ||
16 | For more information look at http://logbook.pocoo.org/ |
0 | # -*- mode: ruby -*- | |
1 | # vi: set ft=ruby : | |
2 | PYTHON_VERSIONS = ["python2.6", "python2.7", "python3.3"] | |
3 | ||
4 | Vagrant::Config.run do |config| | |
5 | config.vm.define :box do |config| | |
6 | config.vm.box = "precise64" | |
7 | config.vm.box_url = "http://files.vagrantup.com/precise64.box" | |
8 | config.vm.host_name = "box" | |
9 | config.vm.provision :shell, :inline => "sudo apt-get -y update" | |
10 | config.vm.provision :shell, :inline => "sudo apt-get install -y python-software-properties" | |
11 | config.vm.provision :shell, :inline => "sudo add-apt-repository -y ppa:fkrull/deadsnakes" | |
12 | config.vm.provision :shell, :inline => "sudo apt-get update" | |
13 | PYTHON_VERSIONS.each { |python_version| | |
14 | config.vm.provision :shell, :inline => "sudo apt-get install -y " + python_version + " " + python_version + "-dev" | |
15 | } | |
16 | config.vm.provision :shell, :inline => "sudo apt-get install -y libzmq-dev wget libbluetooth-dev libsqlite3-dev" | |
17 | config.vm.provision :shell, :inline => "wget http://python-distribute.org/distribute_setup.py -O /tmp/distribute_setup.py" | |
18 | PYTHON_VERSIONS.each { |python_executable| | |
19 | config.vm.provision :shell, :inline => python_executable + " /tmp/distribute_setup.py" | |
20 | } | |
21 | config.vm.provision :shell, :inline => "sudo easy_install tox==1.2" | |
22 | config.vm.provision :shell, :inline => "sudo easy_install virtualenv==1.6.4" | |
23 | end | |
24 | end |
0 | """Tests with frame introspection disabled""" | |
1 | from logbook import Logger, NullHandler, Flags | |
2 | ||
3 | ||
4 | log = Logger('Test logger') | |
5 | ||
6 | ||
7 | class DummyHandler(NullHandler): | |
8 | blackhole = False | |
9 | ||
10 | ||
11 | def run(): | |
12 | with Flags(introspection=False): | |
13 | with DummyHandler() as handler: | |
14 | for x in xrange(500): | |
15 | log.warning('this is not handled') |
0 | """Tests with the whole logger disabled""" | |
1 | from logbook import Logger | |
2 | ||
3 | ||
4 | log = Logger('Test logger') | |
5 | log.disabled = True | |
6 | ||
7 | ||
8 | def run(): | |
9 | for x in xrange(500): | |
10 | log.warning('this is not handled') |
0 | """Tests with stack frame introspection enabled""" | |
1 | from logbook import Logger, NullHandler, Flags | |
2 | ||
3 | ||
4 | log = Logger('Test logger') | |
5 | ||
6 | ||
7 | class DummyHandler(NullHandler): | |
8 | blackhole = False | |
9 | ||
10 | ||
11 | def run(): | |
12 | with Flags(introspection=True): | |
13 | with DummyHandler() as handler: | |
14 | for x in xrange(500): | |
15 | log.warning('this is not handled') |
0 | """Benchmarks the file handler""" | |
1 | from logbook import Logger, FileHandler | |
2 | from tempfile import NamedTemporaryFile | |
3 | ||
4 | ||
5 | log = Logger('Test logger') | |
6 | ||
7 | ||
8 | def run(): | |
9 | f = NamedTemporaryFile() | |
10 | with FileHandler(f.name) as handler: | |
11 | for x in xrange(500): | |
12 | log.warning('this is handled') |
0 | """Benchmarks the file handler with unicode""" | |
1 | from logbook import Logger, FileHandler | |
2 | from tempfile import NamedTemporaryFile | |
3 | ||
4 | ||
5 | log = Logger('Test logger') | |
6 | ||
7 | ||
8 | def run(): | |
9 | f = NamedTemporaryFile() | |
10 | with FileHandler(f.name) as handler: | |
11 | for x in xrange(500): | |
12 | log.warning(u'this is handled \x6f') |
0 | """Test with no handler active""" | |
1 | from logbook import Logger | |
2 | ||
3 | ||
4 | def run(): | |
5 | for x in xrange(500): | |
6 | Logger('Test') |
0 | """Benchmarks too low logger levels""" | |
1 | from logbook import Logger, StreamHandler, ERROR | |
2 | from cStringIO import StringIO | |
3 | ||
4 | ||
5 | log = Logger('Test logger') | |
6 | log.level = ERROR | |
7 | ||
8 | ||
9 | def run(): | |
10 | out = StringIO() | |
11 | with StreamHandler(out): | |
12 | for x in xrange(500): | |
13 | log.warning('this is not handled') |
0 | """Tests logging file handler in comparison""" | |
1 | from logging import getLogger, FileHandler | |
2 | from tempfile import NamedTemporaryFile | |
3 | ||
4 | ||
5 | log = getLogger('Testlogger') | |
6 | ||
7 | ||
8 | def run(): | |
9 | f = NamedTemporaryFile() | |
10 | handler = FileHandler(f.name) | |
11 | log.addHandler(handler) | |
12 | for x in xrange(500): | |
13 | log.warning('this is handled') |
0 | """Tests logging file handler in comparison""" | |
1 | from logging import getLogger, FileHandler | |
2 | from tempfile import NamedTemporaryFile | |
3 | ||
4 | ||
5 | log = getLogger('Testlogger') | |
6 | ||
7 | ||
8 | def run(): | |
9 | f = NamedTemporaryFile() | |
10 | handler = FileHandler(f.name) | |
11 | log.addHandler(handler) | |
12 | for x in xrange(500): | |
13 | log.warning(u'this is handled \x6f') |
0 | """Test with no handler active""" | |
1 | from logging import getLogger | |
2 | ||
3 | ||
4 | root_logger = getLogger() | |
5 | ||
6 | ||
7 | def run(): | |
8 | for x in xrange(500): | |
9 | getLogger('Test') | |
10 | del root_logger.manager.loggerDict['Test'] |
0 | """Tests with a logging handler becoming a noop for comparison""" | |
1 | from logging import getLogger, StreamHandler, ERROR | |
2 | from cStringIO import StringIO | |
3 | ||
4 | ||
5 | log = getLogger('Testlogger') | |
6 | log.setLevel(ERROR) | |
7 | ||
8 | ||
9 | def run(): | |
10 | out = StringIO() | |
11 | handler = StreamHandler(out) | |
12 | log.addHandler(handler) | |
13 | for x in xrange(500): | |
14 | log.warning('this is not handled') |
0 | """Tests with a logging handler becoming a noop for comparison""" | |
1 | from logging import getLogger, StreamHandler, ERROR | |
2 | from cStringIO import StringIO | |
3 | ||
4 | ||
5 | log = getLogger('Testlogger') | |
6 | ||
7 | ||
8 | def run(): | |
9 | out = StringIO() | |
10 | handler = StreamHandler(out) | |
11 | handler.setLevel(ERROR) | |
12 | log.addHandler(handler) | |
13 | for x in xrange(500): | |
14 | log.warning('this is not handled') |
0 | """Tests with a filter disabling a handler for comparsion in logging""" | |
1 | from logging import getLogger, StreamHandler, Filter | |
2 | from cStringIO import StringIO | |
3 | ||
4 | ||
5 | log = getLogger('Testlogger') | |
6 | ||
7 | ||
8 | class DisableFilter(Filter): | |
9 | def filter(self, record): | |
10 | return False | |
11 | ||
12 | ||
13 | def run(): | |
14 | out = StringIO() | |
15 | handler = StreamHandler(out) | |
16 | handler.addFilter(DisableFilter()) | |
17 | log.addHandler(handler) | |
18 | for x in xrange(500): | |
19 | log.warning('this is not handled') |
0 | """Tests the stream handler in logging""" | |
1 | from logging import Logger, StreamHandler | |
2 | from cStringIO import StringIO | |
3 | ||
4 | ||
5 | log = Logger('Test logger') | |
6 | ||
7 | ||
8 | def run(): | |
9 | out = StringIO() | |
10 | log.addHandler(StreamHandler(out)) | |
11 | for x in xrange(500): | |
12 | log.warning('this is not handled') | |
13 | assert out.getvalue().count('\n') == 500 |
0 | """Test with no handler active""" | |
1 | from logbook import Logger, StreamHandler, NullHandler, ERROR | |
2 | from cStringIO import StringIO | |
3 | ||
4 | ||
5 | log = Logger('Test logger') | |
6 | ||
7 | ||
8 | def run(): | |
9 | out = StringIO() | |
10 | with NullHandler(): | |
11 | with StreamHandler(out, level=ERROR) as handler: | |
12 | for x in xrange(500): | |
13 | log.warning('this is not handled') | |
14 | assert not out.getvalue() |
0 | from logbook import Logger, StreamHandler, NullHandler | |
1 | from cStringIO import StringIO | |
2 | ||
3 | ||
4 | log = Logger('Test logger') | |
5 | ||
6 | ||
7 | def run(): | |
8 | out = StringIO() | |
9 | with NullHandler(): | |
10 | with StreamHandler(out, filter=lambda r, h: False) as handler: | |
11 | for x in xrange(500): | |
12 | log.warning('this is not handled') | |
13 | assert not out.getvalue() |
0 | """Like the filter test, but with the should_handle implemented""" | |
1 | from logbook import Logger, StreamHandler, NullHandler | |
2 | from cStringIO import StringIO | |
3 | ||
4 | ||
5 | log = Logger('Test logger') | |
6 | ||
7 | ||
8 | class CustomStreamHandler(StreamHandler): | |
9 | def should_handle(self, record): | |
10 | return False | |
11 | ||
12 | ||
13 | def run(): | |
14 | out = StringIO() | |
15 | with NullHandler(): | |
16 | with CustomStreamHandler(out) as handler: | |
17 | for x in xrange(500): | |
18 | log.warning('this is not handled') | |
19 | assert not out.getvalue() |
0 | """Tests redirects from logging to logbook""" | |
1 | from logging import getLogger | |
2 | from logbook import StreamHandler | |
3 | from logbook.compat import redirect_logging | |
4 | from cStringIO import StringIO | |
5 | ||
6 | ||
7 | redirect_logging() | |
8 | log = getLogger('Test logger') | |
9 | ||
10 | ||
11 | def run(): | |
12 | out = StringIO() | |
13 | with StreamHandler(out): | |
14 | for x in xrange(500): | |
15 | log.warning('this is not handled') | |
16 | assert out.getvalue().count('\n') == 500 |
0 | """Tests redirects from logging to logbook""" | |
1 | from logging import getLogger, StreamHandler | |
2 | from logbook.compat import LoggingHandler | |
3 | from cStringIO import StringIO | |
4 | ||
5 | ||
6 | log = getLogger('Test logger') | |
7 | ||
8 | ||
9 | def run(): | |
10 | out = StringIO() | |
11 | log.addHandler(StreamHandler(out)) | |
12 | with LoggingHandler(): | |
13 | for x in xrange(500): | |
14 | log.warning('this is not handled') | |
15 | assert out.getvalue().count('\n') == 500 |
0 | """Tests basic stack manipulation performance""" | |
1 | from logbook import Handler, NullHandler, StreamHandler, FileHandler, \ | |
2 | ERROR, WARNING | |
3 | from tempfile import NamedTemporaryFile | |
4 | from cStringIO import StringIO | |
5 | ||
6 | ||
7 | def run(): | |
8 | f = NamedTemporaryFile() | |
9 | out = StringIO() | |
10 | with NullHandler(): | |
11 | with StreamHandler(out, level=WARNING): | |
12 | with FileHandler(f.name, level=ERROR): | |
13 | for x in xrange(100): | |
14 | list(Handler.stack_manager.iter_context_objects()) |
0 | """Tests the stream handler""" | |
1 | from logbook import Logger, StreamHandler | |
2 | from cStringIO import StringIO | |
3 | ||
4 | ||
5 | log = Logger('Test logger') | |
6 | ||
7 | ||
8 | def run(): | |
9 | out = StringIO() | |
10 | with StreamHandler(out) as handler: | |
11 | for x in xrange(500): | |
12 | log.warning('this is not handled') | |
13 | assert out.getvalue().count('\n') == 500 |
0 | """Tests the test handler""" | |
1 | from logbook import Logger, TestHandler | |
2 | ||
3 | ||
4 | log = Logger('Test logger') | |
5 | ||
6 | ||
7 | def run(): | |
8 | with TestHandler() as handler: | |
9 | for x in xrange(500): | |
10 | log.warning('this is not handled') |
0 | #!/usr/bin/env python | |
1 | """ | |
2 | Runs the benchmarks | |
3 | """ | |
4 | import sys | |
5 | import os | |
6 | import re | |
7 | from subprocess import Popen | |
8 | ||
9 | try: | |
10 | from pkg_resources import get_distribution | |
11 | version = get_distribution('Logbook').version | |
12 | except Exception: | |
13 | version = 'unknown version' | |
14 | ||
15 | ||
16 | _filename_re = re.compile(r'^bench_(.*?)\.py$') | |
17 | bench_directory = os.path.abspath(os.path.dirname(__file__)) | |
18 | ||
19 | ||
20 | def list_benchmarks(): | |
21 | result = [] | |
22 | for name in os.listdir(bench_directory): | |
23 | match = _filename_re.match(name) | |
24 | if match is not None: | |
25 | result.append(match.group(1)) | |
26 | result.sort(key=lambda x: (x.startswith('logging_'), x.lower())) | |
27 | return result | |
28 | ||
29 | ||
30 | def run_bench(name): | |
31 | sys.stdout.write('%-32s' % name) | |
32 | sys.stdout.flush() | |
33 | Popen([sys.executable, '-mtimeit', '-s', | |
34 | 'from bench_%s import run' % name, | |
35 | 'run()']).wait() | |
36 | ||
37 | ||
38 | def main(): | |
39 | print '=' * 80 | |
40 | print 'Running benchmark with Logbook %s' % version | |
41 | print '-' * 80 | |
42 | os.chdir(bench_directory) | |
43 | for bench in list_benchmarks(): | |
44 | run_bench(bench) | |
45 | print '-' * 80 | |
46 | ||
47 | ||
48 | if __name__ == '__main__': | |
49 | main() |
0 | # Makefile for Sphinx documentation | |
1 | # | |
2 | ||
3 | # You can set these variables from the command line. | |
4 | SPHINXOPTS = | |
5 | SPHINXBUILD = sphinx-build | |
6 | PAPER = | |
7 | BUILDDIR = _build | |
8 | ||
9 | # Internal variables. | |
10 | PAPEROPT_a4 = -D latex_paper_size=a4 | |
11 | PAPEROPT_letter = -D latex_paper_size=letter | |
12 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . | |
13 | ||
14 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest | |
15 | ||
16 | help: | |
17 | @echo "Please use \`make <target>' where <target> is one of" | |
18 | @echo " html to make standalone HTML files" | |
19 | @echo " dirhtml to make HTML files named index.html in directories" | |
20 | @echo " singlehtml to make a single large HTML file" | |
21 | @echo " pickle to make pickle files" | |
22 | @echo " json to make JSON files" | |
23 | @echo " htmlhelp to make HTML files and a HTML help project" | |
24 | @echo " qthelp to make HTML files and a qthelp project" | |
25 | @echo " devhelp to make HTML files and a Devhelp project" | |
26 | @echo " epub to make an epub" | |
27 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" | |
28 | @echo " latexpdf to make LaTeX files and run them through pdflatex" | |
29 | @echo " text to make text files" | |
30 | @echo " man to make manual pages" | |
31 | @echo " changes to make an overview of all changed/added/deprecated items" | |
32 | @echo " linkcheck to check all external links for integrity" | |
33 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" | |
34 | ||
35 | clean: | |
36 | -rm -rf $(BUILDDIR)/* | |
37 | ||
38 | html: | |
39 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html | |
40 | @echo | |
41 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." | |
42 | ||
43 | dirhtml: | |
44 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml | |
45 | @echo | |
46 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." | |
47 | ||
48 | singlehtml: | |
49 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml | |
50 | @echo | |
51 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." | |
52 | ||
53 | pickle: | |
54 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle | |
55 | @echo | |
56 | @echo "Build finished; now you can process the pickle files." | |
57 | ||
58 | json: | |
59 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json | |
60 | @echo | |
61 | @echo "Build finished; now you can process the JSON files." | |
62 | ||
63 | htmlhelp: | |
64 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp | |
65 | @echo | |
66 | @echo "Build finished; now you can run HTML Help Workshop with the" \ | |
67 | ".hhp project file in $(BUILDDIR)/htmlhelp." | |
68 | ||
69 | qthelp: | |
70 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp | |
71 | @echo | |
72 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ | |
73 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" | |
74 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Logbook.qhcp" | |
75 | @echo "To view the help file:" | |
76 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Logbook.qhc" | |
77 | ||
78 | devhelp: | |
79 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp | |
80 | @echo | |
81 | @echo "Build finished." | |
82 | @echo "To view the help file:" | |
83 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Logbook" | |
84 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Logbook" | |
85 | @echo "# devhelp" | |
86 | ||
87 | epub: | |
88 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub | |
89 | @echo | |
90 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." | |
91 | ||
92 | latex: | |
93 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | |
94 | @echo | |
95 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." | |
96 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ | |
97 | "(use \`make latexpdf' here to do that automatically)." | |
98 | ||
99 | latexpdf: | |
100 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | |
101 | @echo "Running LaTeX files through pdflatex..." | |
102 | make -C $(BUILDDIR)/latex all-pdf | |
103 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." | |
104 | ||
105 | text: | |
106 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text | |
107 | @echo | |
108 | @echo "Build finished. The text files are in $(BUILDDIR)/text." | |
109 | ||
110 | man: | |
111 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man | |
112 | @echo | |
113 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." | |
114 | ||
115 | changes: | |
116 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes | |
117 | @echo | |
118 | @echo "The overview file is in $(BUILDDIR)/changes." | |
119 | ||
120 | linkcheck: | |
121 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck | |
122 | @echo | |
123 | @echo "Link check complete; look for any errors in the above output " \ | |
124 | "or in $(BUILDDIR)/linkcheck/output.txt." | |
125 | ||
126 | doctest: | |
127 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest | |
128 | @echo "Testing of doctests in the sources finished, look at the " \ | |
129 | "results in $(BUILDDIR)/doctest/output.txt." |
0 | Core Interface | |
1 | ============== | |
2 | ||
3 | This implements the core interface. | |
4 | ||
5 | .. module:: logbook | |
6 | ||
7 | .. autoclass:: Logger | |
8 | :members: | |
9 | :inherited-members: | |
10 | ||
11 | .. autoclass:: LoggerGroup | |
12 | :members: | |
13 | ||
14 | .. autoclass:: LogRecord | |
15 | :members: | |
16 | ||
17 | .. autoclass:: Flags | |
18 | :members: | |
19 | :inherited-members: | |
20 | ||
21 | .. autoclass:: Processor | |
22 | :members: | |
23 | :inherited-members: | |
24 | ||
25 | .. autofunction:: get_level_name | |
26 | ||
27 | .. autofunction:: lookup_level | |
28 | ||
29 | .. data:: CRITICAL | |
30 | ERROR | |
31 | WARNING | |
32 | INFO | |
33 | DEBUG | |
34 | NOTSET | |
35 | ||
36 | The log level constants |
0 | Compatibility | |
1 | ============= | |
2 | ||
3 | This documents compatibility support with existing systems such as | |
4 | :mod:`logging` and :mod:`warnings`. | |
5 | ||
6 | .. module:: logbook.compat | |
7 | ||
8 | Logging Compatibility | |
9 | --------------------- | |
10 | ||
11 | .. autofunction:: redirect_logging | |
12 | ||
13 | .. autofunction:: redirected_logging | |
14 | ||
15 | .. autoclass:: RedirectLoggingHandler | |
16 | :members: | |
17 | ||
18 | .. autoclass:: LoggingHandler | |
19 | :members: | |
20 | ||
21 | ||
22 | Warnings Compatibility | |
23 | ---------------------- | |
24 | ||
25 | .. autofunction:: redirect_warnings | |
26 | ||
27 | .. autofunction:: redirected_warnings |
0 | Handlers | |
1 | ======== | |
2 | ||
3 | This documents the base handler interface as well as the provided core | |
4 | handlers. There are additional handlers for special purposes in the | |
5 | :mod:`logbook.more`, :mod:`logbook.ticketing` and :mod:`logbook.queues` | |
6 | modules. | |
7 | ||
8 | .. module:: logbook | |
9 | ||
10 | Base Interface | |
11 | -------------- | |
12 | ||
13 | .. autoclass:: Handler | |
14 | :members: | |
15 | :inherited-members: | |
16 | ||
17 | .. autoclass:: NestedSetup | |
18 | :members: | |
19 | ||
20 | .. autoclass:: StringFormatter | |
21 | :members: | |
22 | ||
23 | Core Handlers | |
24 | ------------- | |
25 | ||
26 | .. autoclass:: StreamHandler | |
27 | :members: | |
28 | ||
29 | .. autoclass:: FileHandler | |
30 | :members: | |
31 | ||
32 | .. autoclass:: MonitoringFileHandler | |
33 | :members: | |
34 | ||
35 | .. autoclass:: StderrHandler | |
36 | :members: | |
37 | ||
38 | .. autoclass:: RotatingFileHandler | |
39 | :members: | |
40 | ||
41 | .. autoclass:: TimedRotatingFileHandler | |
42 | :members: | |
43 | ||
44 | .. autoclass:: TestHandler | |
45 | :members: | |
46 | ||
47 | .. autoclass:: MailHandler | |
48 | :members: | |
49 | ||
50 | .. autoclass:: GMailHandler | |
51 | :members: | |
52 | ||
53 | .. autoclass:: SyslogHandler | |
54 | :members: | |
55 | ||
56 | .. autoclass:: NTEventLogHandler | |
57 | :members: | |
58 | ||
59 | .. autoclass:: NullHandler | |
60 | :members: | |
61 | ||
62 | .. autoclass:: WrapperHandler | |
63 | :members: | |
64 | ||
65 | .. autofunction:: create_syshandler | |
66 | ||
67 | Special Handlers | |
68 | ---------------- | |
69 | ||
70 | .. autoclass:: FingersCrossedHandler | |
71 | :members: | |
72 | ||
73 | .. autoclass:: GroupHandler | |
74 | :members: | |
75 | ||
76 | Mixin Classes | |
77 | ------------- | |
78 | ||
79 | .. autoclass:: StringFormatterHandlerMixin | |
80 | :members: | |
81 | ||
82 | .. autoclass:: HashingHandlerMixin | |
83 | :members: | |
84 | ||
85 | .. autoclass:: LimitingHandlerMixin | |
86 | :members: |
0 | API Documentation | |
1 | ================= | |
2 | ||
3 | This part of the documentation documents all the classes and functions | |
4 | provided by Logbook. | |
5 | ||
6 | .. toctree:: | |
7 | ||
8 | base | |
9 | handlers | |
10 | utilities | |
11 | queues | |
12 | ticketing | |
13 | more | |
14 | notifiers | |
15 | compat | |
16 | internal |
0 | Internal API | |
1 | ============ | |
2 | ||
3 | This documents the internal API that might be useful for more advanced | |
4 | setups or custom handlers. | |
5 | ||
6 | .. module:: logbook.base | |
7 | ||
8 | .. autofunction:: dispatch_record | |
9 | ||
10 | .. autoclass:: StackedObject | |
11 | :members: | |
12 | ||
13 | .. autoclass:: RecordDispatcher | |
14 | :members: | |
15 | ||
16 | .. autoclass:: LoggerMixin | |
17 | :members: | |
18 | :inherited-members: | |
19 | ||
20 | .. module:: logbook.handlers | |
21 | ||
22 | .. autoclass:: RotatingFileHandlerBase | |
23 | :members: | |
24 | ||
25 | .. autoclass:: StringFormatterHandlerMixin | |
26 | :members: |
0 | The More Module | |
1 | =============== | |
2 | ||
3 | The more module implements special handlers and other things that are | |
4 | beyond the scope of Logbook itself or depend on external libraries. | |
5 | Additionally there are some handlers in :mod:`logbook.ticketing`, | |
6 | :mod:`logbook.queues` and :mod:`logbook.notifiers`. | |
7 | ||
8 | .. module:: logbook.more | |
9 | ||
10 | Tagged Logging | |
11 | -------------- | |
12 | ||
13 | .. autoclass:: TaggingLogger | |
14 | :members: | |
15 | :inherited-members: | |
16 | ||
17 | .. autoclass:: TaggingHandler | |
18 | :members: | |
19 | ||
20 | Special Handlers | |
21 | ---------------- | |
22 | ||
23 | .. autoclass:: TwitterHandler | |
24 | :members: | |
25 | ||
26 | .. autoclass:: ExternalApplicationHandler | |
27 | :members: | |
28 | ||
29 | .. autoclass:: ExceptionHandler | |
30 | :members: | |
31 | ||
32 | Colorized Handlers | |
33 | ------------------ | |
34 | ||
35 | .. versionadded:: 0.3 | |
36 | ||
37 | .. autoclass:: ColorizedStderrHandler | |
38 | ||
39 | .. autoclass:: ColorizingStreamHandlerMixin | |
40 | :members: | |
41 | ||
42 | Other | |
43 | ----- | |
44 | ||
45 | .. autoclass:: JinjaFormatter | |
46 | :members: |
0 | .. _notifiers: | |
1 | ||
2 | The Notifiers Module | |
3 | ==================== | |
4 | ||
5 | The notifiers module implements special handlers for various platforms | |
6 | that depend on external libraries. | |
7 | The more module implements special handlers and other things that are | |
8 | beyond the scope of Logbook itself or depend on external libraries. | |
9 | ||
10 | .. module:: logbook.notifiers | |
11 | ||
12 | .. autofunction:: create_notification_handler | |
13 | ||
14 | OSX Specific Handlers | |
15 | --------------------- | |
16 | ||
17 | .. autoclass:: GrowlHandler | |
18 | :members: | |
19 | ||
20 | Linux Specific Handlers | |
21 | ----------------------- | |
22 | ||
23 | .. autoclass:: LibNotifyHandler | |
24 | :members: | |
25 | ||
26 | Other Services | |
27 | -------------- | |
28 | ||
29 | .. autoclass:: BoxcarHandler | |
30 | :members: | |
31 | ||
32 | .. autoclass:: NotifoHandler | |
33 | :members: | |
34 | ||
35 | Base Interface | |
36 | -------------- | |
37 | ||
38 | .. autoclass:: NotificationBaseHandler | |
39 | :members: |
0 | Queue Support | |
1 | ============= | |
2 | ||
3 | The queue support module makes it possible to add log records to a queue | |
4 | system. This is useful for distributed setups where you want multiple | |
5 | processes to log to the same backend. Currently supported are ZeroMQ as | |
6 | well as the :mod:`multiprocessing` :class:`~multiprocessing.Queue` class. | |
7 | ||
8 | .. module:: logbook.queues | |
9 | ||
10 | ZeroMQ | |
11 | ------ | |
12 | ||
13 | .. autoclass:: ZeroMQHandler | |
14 | :members: | |
15 | ||
16 | .. autoclass:: ZeroMQSubscriber | |
17 | :members: | |
18 | :inherited-members: | |
19 | ||
20 | Redis | |
21 | ----- | |
22 | ||
23 | .. autoclass:: RedisHandler | |
24 | :members: | |
25 | ||
26 | MultiProcessing | |
27 | --------------- | |
28 | ||
29 | .. autoclass:: MultiProcessingHandler | |
30 | :members: | |
31 | ||
32 | .. autoclass:: MultiProcessingSubscriber | |
33 | :members: | |
34 | :inherited-members: | |
35 | ||
36 | Other | |
37 | ----- | |
38 | ||
39 | .. autoclass:: ThreadedWrapperHandler | |
40 | :members: | |
41 | ||
42 | .. autoclass:: SubscriberGroup | |
43 | :members: | |
44 | ||
45 | Base Interface | |
46 | -------------- | |
47 | ||
48 | .. autoclass:: SubscriberBase | |
49 | :members: | |
50 | ||
51 | .. autoclass:: ThreadController | |
52 | :members: | |
53 | ||
54 | .. autoclass:: TWHThreadController | |
55 | :members: |
0 | Ticketing Support | |
1 | ================= | |
2 | ||
3 | This documents the support classes for ticketing. With ticketing handlers | |
4 | log records are categorized by location and for every emitted log record a | |
5 | count is added. That way you know how often certain messages are | |
6 | triggered, at what times and when the last occurrence was. | |
7 | ||
8 | .. module:: logbook.ticketing | |
9 | ||
10 | .. autoclass:: TicketingBaseHandler | |
11 | :members: | |
12 | ||
13 | .. autoclass:: TicketingHandler | |
14 | :members: | |
15 | ||
16 | .. autoclass:: BackendBase | |
17 | :members: | |
18 | ||
19 | .. autoclass:: SQLAlchemyBackend | |
20 | ||
21 | .. autoclass:: MongoDBBackend |
0 | Utilities | |
1 | ========= | |
2 | ||
3 | This documents general purpose utility functions available in Logbook. | |
4 | ||
5 | .. module:: logbook | |
6 | ||
7 | .. autofunction:: debug | |
8 | ||
9 | .. autofunction:: info | |
10 | ||
11 | .. autofunction:: warn | |
12 | ||
13 | .. autofunction:: warning | |
14 | ||
15 | .. autofunction:: notice | |
16 | ||
17 | .. autofunction:: error | |
18 | ||
19 | .. autofunction:: exception | |
20 | ||
21 | .. autofunction:: catch_exceptions | |
22 | ||
23 | .. autofunction:: critical | |
24 | ||
25 | .. autofunction:: log | |
26 | ||
27 | .. autofunction:: set_datetime_format |
0 | .. include:: ../CHANGES |
0 | .. _logging-compat: | |
1 | ||
2 | Logging Compatibility | |
3 | ===================== | |
4 | ||
5 | Logbook provides backwards compatibility with the logging library. When | |
6 | activated, the logging library will transparently redirect all the logging calls | |
7 | to your Logbook logging setup. | |
8 | ||
9 | Basic Setup | |
10 | ----------- | |
11 | ||
12 | If you import the compat system and call the | |
13 | :func:`~logbook.compat.redirect_logging` function, all logging calls that happen | |
14 | after this call will transparently be redirected to Logbook:: | |
15 | ||
16 | from logbook.compat import redirect_logging | |
17 | redirect_logging() | |
18 | ||
19 | This also means you don't have to call :func:`logging.basicConfig`: | |
20 | ||
21 | >>> from logbook.compat import redirect_logging | |
22 | >>> redirect_logging() | |
23 | >>> from logging import getLogger | |
24 | >>> log = getLogger('My Logger') | |
25 | >>> log.warn('This is a warning') | |
26 | [2010-07-25 00:24] WARNING: My Logger: This is a warning | |
27 | ||
28 | Advanced Setup | |
29 | -------------- | |
30 | ||
31 | The way this is implemented is with a | |
32 | :class:`~logbook.compat.RedirectLoggingHandler`. This class is a handler for | |
33 | the old logging system that sends records via an internal logbook logger to the | |
34 | active logbook handlers. This handler can then be added to specific logging | |
35 | loggers if you want: | |
36 | ||
37 | >>> from logging import getLogger | |
38 | >>> mylog = getLogger('My Log') | |
39 | >>> from logbook.compat import RedirectLoggingHandler | |
40 | >>> mylog.addHandler(RedirectLoggingHandler()) | |
41 | >>> otherlog = getLogger('Other Log') | |
42 | >>> otherlog.warn('logging is deprecated') | |
43 | No handlers could be found for logger "Other Log" | |
44 | >>> mylog.warn('but logbook is awesome') | |
45 | [2010-07-25 00:29] WARNING: My Log: but logbook is awesome | |
46 | ||
47 | Reverse Redirects | |
48 | ----------------- | |
49 | ||
50 | You can also redirect logbook records to logging, so the other way round. | |
51 | For this you just have to activate the | |
52 | :class:`~logbook.compat.LoggingHandler` for the thread or application:: | |
53 | ||
54 | from logbook import Logger | |
55 | from logbook.compat import LoggingHandler | |
56 | ||
57 | log = Logger('My app') | |
58 | with LoggingHandler(): | |
59 | log.warn('Going to logging') |
0 | # -*- coding: utf-8 -*- | |
1 | # | |
2 | # Logbook documentation build configuration file, created by | |
3 | # sphinx-quickstart on Fri Jul 23 16:54:49 2010. | |
4 | # | |
5 | # This file is execfile()d with the current directory set to its containing dir. | |
6 | # | |
7 | # Note that not all possible configuration values are present in this | |
8 | # autogenerated file. | |
9 | # | |
10 | # All configuration values have a default; values that are commented out | |
11 | # serve to show the default. | |
12 | ||
13 | import sys, os | |
14 | ||
15 | # If extensions (or modules to document with autodoc) are in another directory, | |
16 | # add these directories to sys.path here. If the directory is relative to the | |
17 | # documentation root, use os.path.abspath to make it absolute, like shown here. | |
18 | sys.path.extend((os.path.abspath('.'), os.path.abspath('..'))) | |
19 | ||
20 | # -- General configuration ----------------------------------------------------- | |
21 | ||
22 | # If your documentation needs a minimal Sphinx version, state it here. | |
23 | #needs_sphinx = '1.0' | |
24 | ||
25 | # Add any Sphinx extension module names here, as strings. They can be extensions | |
26 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. | |
27 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] | |
28 | ||
29 | # Add any paths that contain templates here, relative to this directory. | |
30 | templates_path = ['_templates'] | |
31 | ||
32 | # The suffix of source filenames. | |
33 | source_suffix = '.rst' | |
34 | ||
35 | # The encoding of source files. | |
36 | #source_encoding = 'utf-8-sig' | |
37 | ||
38 | # The master toctree document. | |
39 | master_doc = 'index' | |
40 | ||
41 | # General information about the project. | |
42 | project = u'Logbook' | |
43 | copyright = u'2010, Armin Ronacher, Georg Brandl' | |
44 | ||
45 | # The version info for the project you're documenting, acts as replacement for | |
46 | # |version| and |release|, also used in various other places throughout the | |
47 | # built documents. | |
48 | # | |
49 | # The short X.Y version. | |
50 | version = '0.6.1-dev' | |
51 | # The full version, including alpha/beta/rc tags. | |
52 | release = '0.6.1-dev' | |
53 | ||
54 | # The language for content autogenerated by Sphinx. Refer to documentation | |
55 | # for a list of supported languages. | |
56 | #language = None | |
57 | ||
58 | # There are two options for replacing |today|: either, you set today to some | |
59 | # non-false value, then it is used: | |
60 | #today = '' | |
61 | # Else, today_fmt is used as the format for a strftime call. | |
62 | #today_fmt = '%B %d, %Y' | |
63 | ||
64 | # List of patterns, relative to source directory, that match files and | |
65 | # directories to ignore when looking for source files. | |
66 | exclude_patterns = ['_build'] | |
67 | ||
68 | # The reST default role (used for this markup: `text`) to use for all documents. | |
69 | #default_role = None | |
70 | ||
71 | # If true, '()' will be appended to :func: etc. cross-reference text. | |
72 | #add_function_parentheses = True | |
73 | ||
74 | # If true, the current module name will be prepended to all description | |
75 | # unit titles (such as .. function::). | |
76 | #add_module_names = True | |
77 | ||
78 | # If true, sectionauthor and moduleauthor directives will be shown in the | |
79 | # output. They are ignored by default. | |
80 | #show_authors = False | |
81 | ||
82 | # The name of the Pygments (syntax highlighting) style to use. | |
83 | pygments_style = 'sphinx' | |
84 | ||
85 | # A list of ignored prefixes for module index sorting. | |
86 | #modindex_common_prefix = [] | |
87 | ||
88 | ||
89 | # -- Options for HTML output --------------------------------------------------- | |
90 | ||
91 | # The theme to use for HTML and HTML Help pages. See the documentation for | |
92 | # a list of builtin themes. | |
93 | html_theme = 'sheet' | |
94 | ||
95 | # Theme options are theme-specific and customize the look and feel of a theme | |
96 | # further. For a list of options available for each theme, see the | |
97 | # documentation. | |
98 | html_theme_options = { | |
99 | 'nosidebar': True, | |
100 | } | |
101 | ||
102 | # Add any paths that contain custom themes here, relative to this directory. | |
103 | html_theme_path = ['.'] | |
104 | ||
105 | # The name for this set of Sphinx documents. If None, it defaults to | |
106 | # "<project> v<release> documentation". | |
107 | html_title = "Logbook" | |
108 | ||
109 | # A shorter title for the navigation bar. Default is the same as html_title. | |
110 | html_short_title = "Logbook " + release | |
111 | ||
112 | # The name of an image file (relative to this directory) to place at the top | |
113 | # of the sidebar. | |
114 | #html_logo = None | |
115 | ||
116 | # The name of an image file (within the static path) to use as favicon of the | |
117 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 | |
118 | # pixels large. | |
119 | #html_favicon = None | |
120 | ||
121 | # Add any paths that contain custom static files (such as style sheets) here, | |
122 | # relative to this directory. They are copied after the builtin static files, | |
123 | # so a file named "default.css" will overwrite the builtin "default.css". | |
124 | #html_static_path = ['_static'] | |
125 | ||
126 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, | |
127 | # using the given strftime format. | |
128 | #html_last_updated_fmt = '%b %d, %Y' | |
129 | ||
130 | # If true, SmartyPants will be used to convert quotes and dashes to | |
131 | # typographically correct entities. | |
132 | #html_use_smartypants = True | |
133 | ||
134 | # Custom sidebar templates, maps document names to template names. | |
135 | #html_sidebars = {} | |
136 | ||
137 | # Additional templates that should be rendered to pages, maps page names to | |
138 | # template names. | |
139 | #html_additional_pages = {} | |
140 | ||
141 | # If false, no module index is generated. | |
142 | #html_domain_indices = True | |
143 | ||
144 | # If false, no index is generated. | |
145 | #html_use_index = True | |
146 | ||
147 | # If true, the index is split into individual pages for each letter. | |
148 | #html_split_index = False | |
149 | ||
150 | # If true, links to the reST sources are added to the pages. | |
151 | #html_show_sourcelink = True | |
152 | ||
153 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. | |
154 | #html_show_sphinx = True | |
155 | ||
156 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. | |
157 | #html_show_copyright = True | |
158 | ||
159 | html_add_permalinks = False | |
160 | ||
161 | # If true, an OpenSearch description file will be output, and all pages will | |
162 | # contain a <link> tag referring to it. The value of this option must be the | |
163 | # base URL from which the finished HTML is served. | |
164 | #html_use_opensearch = '' | |
165 | ||
166 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). | |
167 | #html_file_suffix = '' | |
168 | ||
169 | # Output file base name for HTML help builder. | |
170 | htmlhelp_basename = 'Logbookdoc' | |
171 | ||
172 | ||
173 | # -- Options for LaTeX output -------------------------------------------------- | |
174 | ||
175 | # The paper size ('letter' or 'a4'). | |
176 | #latex_paper_size = 'letter' | |
177 | ||
178 | # The font size ('10pt', '11pt' or '12pt'). | |
179 | #latex_font_size = '10pt' | |
180 | ||
181 | # Grouping the document tree into LaTeX files. List of tuples | |
182 | # (source start file, target name, title, author, documentclass [howto/manual]). | |
183 | latex_documents = [ | |
184 | ('index', 'Logbook.tex', u'Logbook Documentation', | |
185 | u'Armin Ronacher, Georg Brandl', 'manual'), | |
186 | ] | |
187 | ||
188 | # The name of an image file (relative to this directory) to place at the top of | |
189 | # the title page. | |
190 | #latex_logo = None | |
191 | ||
192 | # For "manual" documents, if this is true, then toplevel headings are parts, | |
193 | # not chapters. | |
194 | #latex_use_parts = False | |
195 | ||
196 | # If true, show page references after internal links. | |
197 | #latex_show_pagerefs = False | |
198 | ||
199 | # If true, show URL addresses after external links. | |
200 | #latex_show_urls = False | |
201 | ||
202 | # Additional stuff for the LaTeX preamble. | |
203 | #latex_preamble = '' | |
204 | ||
205 | # Documents to append as an appendix to all manuals. | |
206 | #latex_appendices = [] | |
207 | ||
208 | # If false, no module index is generated. | |
209 | #latex_domain_indices = True | |
210 | ||
211 | ||
212 | # -- Options for manual page output -------------------------------------------- | |
213 | ||
214 | # One entry per manual page. List of tuples | |
215 | # (source start file, name, description, authors, manual section). | |
216 | man_pages = [ | |
217 | ('index', 'logbook', u'Logbook Documentation', | |
218 | [u'Armin Ronacher, Georg Brandl'], 1) | |
219 | ] | |
220 | ||
221 | intersphinx_mapping = { | |
222 | 'http://docs.python.org': None | |
223 | } |
0 | Design Principles | |
1 | ================= | |
2 | ||
3 | .. currentmodule:: logbook | |
4 | ||
5 | Logbook is a logging library that breaks many expectations people have in | |
6 | logging libraries to support paradigms we think are more suitable for | |
7 | modern applications than the traditional Java inspired logging system that | |
8 | can also be found in the Python standard library and many more programming | |
9 | languages. | |
10 | ||
11 | This section of the documentation should help you understand the design of | |
12 | Logbook and why it was implemented like this. | |
13 | ||
14 | No Logger Registry | |
15 | ------------------ | |
16 | ||
17 | Logbook is unique in that it has the concept of logging channels but that | |
18 | it does not keep a global registry of them. In the standard library's | |
19 | logging module a logger is attached to a tree of loggers that are stored | |
20 | in the logging module itself as global state. | |
21 | ||
22 | In logbook a logger is just an opaque object that might or might not have | |
23 | a name and attached information such as log level or customizations, but | |
24 | the lifetime and availability of that object is controlled by the person | |
25 | creating that logger. | |
26 | ||
27 | The registry is necessary for the logging library to give the user the | |
28 | ability to configure these loggers. | |
29 | ||
30 | Logbook has a completely different concept of dispatching from loggers to | |
31 | the actual handlers which removes the requirement and usefulness of such a | |
32 | registry. The advantage of the logbook system is that it's a cheap | |
33 | operation to create a logger and that a logger can easily be garbage | |
34 | collected to remove all traces of it. | |
35 | ||
36 | Instead Logbook moves the burden of delivering a log record from the log | |
37 | channel's attached log to an independent entity that looks at the context | |
38 | of the execution to figure out where to deliver it. | |
39 | ||
40 | Context Sensitive Handler Stack | |
41 | ------------------------------- | |
42 | ||
43 | Python has two builtin ways to express implicit context: processes and | |
44 | threads. What this means is that if you have a function that is passed no | |
45 | arguments at all, you can figure out what thread called the function and | |
46 | what process you are sitting in. Logbook supports this context | |
47 | information and lets you bind a handler (or more!) for such a context. | |
48 | ||
49 | This is how this works: there are two stacks available at all times in | |
50 | Logbook. The first stack is the process wide stack. It is manipulated | |
51 | with :class:`Handler.push_application` and | |
52 | :class:`Handler.pop_application` (and of course the context manager | |
53 | :class:`Handler.applicationbound`). Then there is a second stack which is | |
54 | per thread. The manipulation of that stack happens with | |
55 | :class:`Handler.push_thread`, :class:`Handler.pop_thread` and the | |
56 | :class:`Handler.threadbound` contextmanager. | |
57 | ||
58 | Let's take a WSGI web application as first example. When a request comes | |
59 | in your WSGI server will most likely do one of the following two things: | |
60 | either spawn a new Python process (or reuse a process in a pool), or | |
61 | create a thread (or again, reuse something that already exists). Either | |
62 | way, we can now say that the context of process id and thread id is our | |
63 | playground. For this context we can define a log handler that is active | |
64 | in this context only for a certain time. In pseudocode this would look | |
65 | like this:: | |
66 | ||
67 | def my_application(environ, start_response): | |
68 | my_handler = FileHandler(...) | |
69 | my_handler.push_thread() | |
70 | try: | |
71 | # whatever happens here in terms of logging is handled | |
72 | # by the `my_handler` handler. | |
73 | ... | |
74 | finally: | |
75 | my_handler.pop_thread() | |
76 | ||
77 | Because this is a lot to type, you can also use the `with` statement to do | |
78 | the very same:: | |
79 | ||
80 | def my_application(environ, start_response): | |
81 | with FileHandler(...).threadbound() as my_handler: | |
82 | # whatever happens here in terms of logging is handled | |
83 | # by the `my_handler` handler. | |
84 | ... | |
85 | ||
86 | Additionally there is another place where you can put handlers: directly | |
87 | onto a logging channel (for example on a :class:`Logger`). | |
88 | ||
89 | This stack system might seem like overkill for a traditional system, but | |
90 | it allows complete decoupling from the log handling system and other | |
91 | systems that might log messages. | |
92 | ||
93 | Let's take a GUI application rather than a web application. You have an | |
94 | application that starts up, shuts down and at any point in between might | |
95 | fail or log messages. The typical default behaviour here would be to log | |
96 | into a logfile. Fair enough, that's how these applications work. | |
97 | ||
98 | But what's the point in logging if not even a single warning happened? | |
99 | The traditional solution with the logging library from Python is to set | |
100 | the level high (like `ERROR` or `WARNING`) and log into a file. When | |
101 | things break, you have a look at the file and hope it contains enough | |
102 | information. | |
103 | ||
104 | When you are in full control of the context of execution with a stack based | |
105 | system like Logbook has, there is a lot more you can do. | |
106 | ||
107 | For example you could immediately after your application boots up | |
108 | instanciate a :class:`~logbook.FingersCrossedHandler`. This handler | |
109 | buffers *all* log records in memory and does not emit them at all. What's | |
110 | the point? That handler activates when a certain threshold is reached. | |
111 | For example, when the first warning occurs you can write the buffered | |
112 | messages as well as the warning that just happened into a logfile and | |
113 | continue logging from that point. Because there is no point in logging | |
114 | when you will never look at that file anyways. | |
115 | ||
116 | But that alone is not the killer feature of a stack. In a GUI application | |
117 | there is the point where we are still initializing the windowing system. | |
118 | So a file is the best place to log messages. But once we have the GUI | |
119 | initialized, it would be very helpful to show error messages to a user in | |
120 | a console window or a dialog. So what we can do is to initialize at that | |
121 | point a new handler that logs into a dialog. | |
122 | ||
123 | When then a long running tasks in the GUI starts we can move that into a | |
124 | separate thread and intercept all the log calls for that thread into a | |
125 | separate window until the task succeeded. | |
126 | ||
127 | Here such a setup in pseudocode:: | |
128 | ||
129 | from logbook import FileHandler, WARNING | |
130 | from logbook import FingersCrossedHandler | |
131 | ||
132 | def main(): | |
133 | # first we set up a handler that logs everything (including debug | |
134 | # messages, but only starts doing that when a warning happens | |
135 | default_handler = FingersCrossedHandler(FileHandler(filename, | |
136 | delay=True), | |
137 | WARNING) | |
138 | # this handler is now activated as the default handler for the | |
139 | # whole process. We do not bubble up to the default handler | |
140 | # that logs to stderr. | |
141 | with default_handler.applicationbound(bubble=False): | |
142 | # now we initialize the GUI of the application | |
143 | initialize_gui() | |
144 | # at that point we can hook our own logger in that intercepts | |
145 | # errors and displays them in a log window | |
146 | with gui.log_handler.applicationbound(): | |
147 | # run the gui mainloop | |
148 | gui.mainloop() | |
149 | ||
150 | This stack can also be used to inject additional information automatically | |
151 | into log records. This is also used to replace the need for custom log | |
152 | levels. | |
153 | ||
154 | No Custom Log Levels | |
155 | -------------------- | |
156 | ||
157 | This change over logging was controversial, even under the two original | |
158 | core developers. There clearly are use cases for custom log levels, but | |
159 | there is an inherent problem with then: they require a registry. If you | |
160 | want custom log levels, you will have to register them somewhere or parts | |
161 | of the system will not know about them. Now we just spent a lot of time | |
162 | ripping out the registry with a stack based approach to solve delivery | |
163 | problems, why introduce a global state again just for log levels? | |
164 | ||
165 | Instead we looked at the cases where custom log levels are useful and | |
166 | figured that in most situations custom log levels are used to put | |
167 | additional information into a log entry. For example it's not uncommon to | |
168 | have separate log levels to filter user input out of a logfile. | |
169 | ||
170 | We instead provide powerful tools to inject arbitrary additional data into | |
171 | log records with the concept of log processors. | |
172 | ||
173 | So for example if you want to log user input and tag it appropriately you | |
174 | can override the :meth:`Logger.process_record` method:: | |
175 | ||
176 | class InputLogger(Logger): | |
177 | def process_record(self, record): | |
178 | record.extra['kind'] = 'input' | |
179 | ||
180 | A handler can then use this information to filter out input:: | |
181 | ||
182 | def no_input(record, handler): | |
183 | return record.extra.get('kind') != 'input' | |
184 | ||
185 | with MyHandler().threadbound(filter=no_input): | |
186 | ... | |
187 | ||
188 | Injecting Context-Sensitive Information | |
189 | --------------------------------------- | |
190 | ||
191 | For many situations it's not only necessary to inject information on a | |
192 | per-channel basis but also for all logging calls from a given context. | |
193 | This is best explained for web applications again. If you have some | |
194 | libraries doing logging in code that is triggered from a request you might | |
195 | want to record the URL of that request for each log record so that you get | |
196 | an idea where a specific error happened. | |
197 | ||
198 | This can easily be accomplished by registering a custom processor when | |
199 | binding a handler to a thread:: | |
200 | ||
201 | def my_application(environ, start_reponse): | |
202 | def inject_request_info(record, handler): | |
203 | record.extra['path'] = environ['PATH_INFO'] | |
204 | with Processor(inject_request_info).threadbound(): | |
205 | with my_handler.threadbound(): | |
206 | # rest of the request code here | |
207 | ... | |
208 | ||
209 | Logging Compatibility | |
210 | --------------------- | |
211 | ||
212 | The last pillar of logbook's design is the compatibility with the standard | |
213 | libraries logging system. There are many libraries that exist currently | |
214 | that log information with the standard libraries logging module. Having | |
215 | two separate logging systems in the same process is countrproductive and | |
216 | will cause separate logfiles to appear in the best case or complete chaos | |
217 | in the worst. | |
218 | ||
219 | Because of that, logbook provides ways to transparently redirect all | |
220 | logging records into the logbook stack based record delivery system. That | |
221 | way you can even continue to use the standard libraries logging system to | |
222 | emit log messages and can take the full advantage of logbook's powerful | |
223 | stack system. | |
224 | ||
225 | If you are curious, have a look at :ref:`logging-compat`. |
0 | The Design Explained | |
1 | ==================== | |
2 | ||
3 | This part of the documentation explains the design of Logbook in detail. | |
4 | This is not strictly necessary to make use of Logbook but might be helpful | |
5 | when writing custom handlers for Logbook or when using it in a more | |
6 | complex environment. | |
7 | ||
8 | Dispatchers and Channels | |
9 | ------------------------ | |
10 | ||
11 | Logbook does not use traditional loggers, instead a logger is internally | |
12 | named as :class:`~logbook.base.RecordDispatcher`. While a logger also has | |
13 | methods to create new log records, the base class for all record | |
14 | dispatchers itself only has ways to dispatch :class:`~logbook.LogRecord`\s | |
15 | to the handlers. A log record itself might have an attribute that points | |
16 | to the dispatcher that was responsible for dispatching, but it does not | |
17 | have to be. | |
18 | ||
19 | If a log record was created from the builtin :class:`~logbook.Logger` it | |
20 | will have the channel set to the name of the logger. But that itself is | |
21 | no requirement. The only requirement for the channel is that it's a | |
22 | string with some human readable origin information. It could be | |
23 | ``'Database'`` if the database issued the log record, it could be | |
24 | ``'Process-4223'`` if the process with the pid 4223 issued it etc. | |
25 | ||
26 | For example if you are logging from the :func:`logbook.log` function they | |
27 | will have a cannel set, but no dispatcher: | |
28 | ||
29 | >>> from logbook import TestHandler, warn | |
30 | >>> handler = TestHandler() | |
31 | >>> handler.push_application() | |
32 | >>> warn('This is a warning') | |
33 | >>> handler.records[0].channel | |
34 | 'Generic' | |
35 | >>> handler.records[0].dispatcher is None | |
36 | True | |
37 | ||
38 | If you are logging from a custom logger, the channel attribute points to | |
39 | the logger for as long this logger class is not garbage collected: | |
40 | ||
41 | >>> from logbook import Logger, TestHandler | |
42 | >>> logger = Logger('Console') | |
43 | >>> handler = TestHandler() | |
44 | >>> handler.push_application() | |
45 | >>> logger.warn('A warning') | |
46 | >>> handler.records[0].dispatcher is logger | |
47 | True | |
48 | ||
49 | You don't need a record dispatcher to dispatch a log record though. The | |
50 | default dispatching can be triggered from a function | |
51 | :func:`~logbook.base.dispatch_record`: | |
52 | ||
53 | >>> from logbook import dispatch_record, LogRecord, INFO | |
54 | >>> record = LogRecord('My channel', INFO, 'Hello World!') | |
55 | >>> dispatch_record(record) | |
56 | [2010-09-04 15:56] INFO: My channel: Hello World! | |
57 | ||
58 | It is pretty common for log records to be created without a dispatcher. | |
59 | Here some common use cases for log records without a dispatcher: | |
60 | ||
61 | - log records that were redirected from a different logging system | |
62 | such as the standard library's :mod:`logging` module or the | |
63 | :mod:`warnings` module. | |
64 | - log records that came from different processes and do not have a | |
65 | dispatcher equivalent in the current process. | |
66 | - log records that came from over the network. | |
67 | ||
68 | The Log Record Container | |
69 | ------------------------ | |
70 | ||
71 | The :class:`~logbook.LogRecord` class is a simple container that | |
72 | holds all the information necessary for a log record. Usually they are | |
73 | created from a :class:`~logbook.Logger` or one of the default log | |
74 | functions (:func:`logbook.warn` etc.) and immediately dispatched to the | |
75 | handlers. The logger will apply some additional knowledge to figure out | |
76 | where the record was created from and if a traceback information should be | |
77 | attached. | |
78 | ||
79 | Normally if log records are dispatched they will be closed immediately | |
80 | after all handlers had their chance to write it down. On closing, the | |
81 | interpreter frame and traceback object will be removed from the log record | |
82 | to break up circular dependencies. | |
83 | ||
84 | Sometimes however it might be necessary to keep log records around for a | |
85 | longer time. Logbook provides three different ways to accomplish that: | |
86 | ||
87 | 1. Handlers can set the :attr:`~logbook.LogRecord.keep_open` attribute of | |
88 | a log record to `True` so that the record dispatcher will not close | |
89 | the object. This is for example used by the | |
90 | :class:`~logbook.TestHandler` so that unittests can still access | |
91 | interpreter frames and traceback objects if necessary. | |
92 | 2. Because some information on the log records depends on the interpreter | |
93 | frame (such as the location of the log call) it is possible to pull | |
94 | that related information directly into the log record so that it can | |
95 | safely be closed without losing that information (see | |
96 | :meth:`~logbook.LogRecord.pull_information`). | |
97 | 3. Last but not least, log records can be converted to dictionaries and | |
98 | recreated from these. It is also possible to make these dictionaries | |
99 | safe for JSON export which is used by the | |
100 | :class:`~logbook.ticketing.TicketingHandler` to store information in a | |
101 | database or the :class:`~logbook.more.MultiProcessingHandler` to send | |
102 | information between processes. |
0 | What does it do? | |
1 | ================ | |
2 | ||
3 | Although the Python standard library provides a logging system, you should | |
4 | consider having a look at Logbook for your applications. | |
5 | ||
6 | We think it will work out for you and be fun to use :) | |
7 | ||
8 | Logbook leverages some features of Python that are not available in older Python releases. | |
9 | Logbook currently requires Python 2.7 or higher including Python 3 (3.1 or | |
10 | higher, 3.0 is not supported). | |
11 | ||
12 | Core Features | |
13 | ------------- | |
14 | ||
15 | - Logbook is based on the concept of loggers that are extensible by the | |
16 | application. | |
17 | - Each logger and handler, as well as other parts of the system, may inject | |
18 | additional information into the logging record that improves the usefulness | |
19 | of log entries. | |
20 | - Handlers can be set on an application-wide stack as well as a thread-wide | |
21 | stack. Setting a handler does not replace existing handlers, but gives it | |
22 | higher priority. Each handler has the ability to prevent records from | |
23 | propagating to lower-priority handlers. | |
24 | - Logbook comes with a useful default configuration that spits all the | |
25 | information to stderr in a useful manner. | |
26 | - All of the built-in handlers have a useful default configuration applied with | |
27 | formatters that provide all the available information in a format that | |
28 | makes the most sense for the given handler. For example, a default stream | |
29 | handler will try to put all the required information into one line, whereas | |
30 | an email handler will split it up into nicely formatted ASCII tables that | |
31 | span multiple lines. | |
32 | - Logbook has built-in handlers for streams, arbitrary files, files with time | |
33 | and size based rotation, a handler that delivers mails, a handler for the | |
34 | syslog daemon as well as the NT log file. | |
35 | - There is also a special "fingers crossed" handler that, in combination with | |
36 | the handler stack, has the ability to accumulate all logging messages and | |
37 | will deliver those in case a severity level was exceeded. For example, it | |
38 | can withhold all logging messages for a specific request to a web | |
39 | application until an error record appears, in which case it will also send | |
40 | all withheld records to the handler it wraps. This way, you can always log | |
41 | lots of debugging records, but only get see them when they can actually | |
42 | tell you something of interest. | |
43 | - It is possible to inject a handler for testing that records messages for | |
44 | assertions. | |
45 | - Logbook was designed to be fast and with modern Python features in mind. | |
46 | For example, it uses context managers to handle the stack of handlers as | |
47 | well as new-style string formatting for all of the core log calls. | |
48 | - Builtin support for ZeroMQ, RabbitMQ, Redis and other means to distribute | |
49 | log messages between heavily distributed systems and multiple processes. | |
50 | - The Logbook system does not depend on log levels. In fact, custom log | |
51 | levels are not supported, instead we strongly recommend using logging | |
52 | subclasses or log processors that inject tagged information into the log | |
53 | record for this purpose. | |
54 | - :pep:`8` naming and code style. | |
55 | ||
56 | Advantages over Logging | |
57 | ----------------------- | |
58 | ||
59 | If properly configured, Logbook's logging calls will be very cheap and | |
60 | provide a great performance improvement over an equivalent configuration | |
61 | of the standard library's logging module. While for some parts we are not | |
62 | quite at performance we desire, there will be some further performance | |
63 | improvements in the upcoming versions. | |
64 | ||
65 | It also supports the ability to inject additional information for all | |
66 | logging calls happening in a specific thread or for the whole application. | |
67 | For example, this makes it possible for a web application to add | |
68 | request-specific information to each log record such as remote address, | |
69 | request URL, HTTP method and more. | |
70 | ||
71 | The logging system is (besides the stack) stateless and makes unit testing | |
72 | it very simple. If context managers are used, it is impossible to corrupt | |
73 | the stack, so each test can easily hook in custom log handlers. | |
74 | ||
75 | Cooperation | |
76 | ----------- | |
77 | ||
78 | Logbook is an addon library to Python and working in an area where there | |
79 | are already a couple of contestants. First of all there is the standard | |
80 | library's :mod:`logging` module, secondly there is also the | |
81 | :mod:`warnings` module which is used internally in Python to warn about | |
82 | invalid uses of APIs and more. We know that there are many situations | |
83 | where you want to use either of them. Be it that they are integrated into | |
84 | a legacy system, part of a library outside of your control or just because | |
85 | they are a better choice. | |
86 | ||
87 | Because of that, Logbook is two-way compatible with :mod:`logging` and | |
88 | one-way compatible with :mod:`warnings`. If you want, you can let all | |
89 | logging calls redirect to the logbook handlers or the other way round, | |
90 | depending on what your desired setup looks like. That way you can enjoy | |
91 | the best of both worlds. | |
92 | ||
93 | It should be Fun | |
94 | ---------------- | |
95 | ||
96 | Logging should be fun. A good log setup makes debugging easier when | |
97 | things go rough. For good results you really have to start using logging | |
98 | before things actually break. Logbook comes with a couple of unusual log | |
99 | handlers to bring the fun back to logging. You can log to your personal | |
100 | twitter feed, you can log to mobile devices, your desktop notification | |
101 | system and more. | |
102 | ||
103 | Logbook in a Nutshell | |
104 | --------------------- | |
105 | ||
106 | This is how easy it is to get started with Logbook:: | |
107 | ||
108 | from logbook import warn | |
109 | warn('This is a warning') | |
110 | ||
111 | That will use the default logging channel. But you can create as many as | |
112 | you like:: | |
113 | ||
114 | from logbook import Logger | |
115 | log = Logger('My Logger') | |
116 | log.warn('This is a warning') | |
117 | ||
118 | Roadmap | |
119 | ------- | |
120 | ||
121 | Here a list of things you can expect in upcoming versions: | |
122 | ||
123 | - c implementation of the internal stack management and record | |
124 | dispatching for higher performance. | |
125 | - a ticketing log handler that creates tickets in trac and redmine. | |
126 | - a web frontend for the ticketing database handler. |
0 | Welcome to Logbook | |
1 | ================== | |
2 | ||
3 | Logbook is a logging sytem for Python that replaces the standard library's | |
4 | logging module. It was designed with both complex and simple applications | |
5 | in mind and the idea to make logging fun: | |
6 | ||
7 | >>> from logbook import Logger | |
8 | >>> log = Logger('Logbook') | |
9 | >>> log.info('Hello, World!') | |
10 | [2010-07-23 16:34] INFO: Logbook: Hello, World! | |
11 | ||
12 | What makes it fun? What about getting log messages on your phone or | |
13 | desktop notification system? :ref:`Logbook can do that <notifiers>`. | |
14 | ||
15 | Feedback is appreciated. The docs here only show a tiny, | |
16 | tiny feature set and can be incomplete. We will have better docs | |
17 | soon, but until then we hope this gives a sneak peak about how cool | |
18 | Logbook is. If you want more, have a look at the comprehensive suite of tests. | |
19 | ||
20 | Documentation | |
21 | ------------- | |
22 | ||
23 | .. toctree:: | |
24 | :maxdepth: 2 | |
25 | ||
26 | features | |
27 | quickstart | |
28 | setups | |
29 | stacks | |
30 | performance | |
31 | libraries | |
32 | unittesting | |
33 | ticketing | |
34 | compat | |
35 | api/index | |
36 | designexplained | |
37 | designdefense | |
38 | changelog | |
39 | ||
40 | Project Information | |
41 | ------------------- | |
42 | ||
43 | .. cssclass:: toctree-l1 | |
44 | ||
45 | * `Download from PyPI`_ | |
46 | * `Master repository on GitHub`_ | |
47 | * `Mailing list`_ | |
48 | * IRC: ``#pocoo`` on freenode | |
49 | ||
50 | .. _Download from PyPI: http://pypi.python.org/pypi/Logbook | |
51 | .. _Master repository on GitHub: https://github.com/mitsuhiko/logbook | |
52 | .. _Mailing list: http://groups.google.com/group/pocoo-libs |
0 | Logbook in Libraries | |
1 | ==================== | |
2 | ||
3 | Logging becomes more useful the higher the number of components in a | |
4 | system that are using it. Logbook itself is not a widely supported | |
5 | library so far, but a handful of libraries are using the :mod:`logging` | |
6 | already which can be redirected to Logbook if necessary. | |
7 | ||
8 | Logbook itself is easier to support for libraries than logging because it | |
9 | does away with the central logger registry and can easily be mocked in | |
10 | case the library is not available. | |
11 | ||
12 | Mocking Logbook | |
13 | --------------- | |
14 | ||
15 | If you want to support Logbook in your library but not depend on it you | |
16 | can copy/paste the following piece of code. It will attempt to import | |
17 | logbook and create a :class:`~logbook.Logger` and if it fails provide a | |
18 | class that just swallows all calls:: | |
19 | ||
20 | try: | |
21 | from logbook import Logger | |
22 | except ImportError: | |
23 | class Logger(object): | |
24 | def __init__(self, name, level=0): | |
25 | self.name = name | |
26 | self.level = level | |
27 | debug = info = warn = warning = notice = error = exception = \ | |
28 | critical = log = lambda *a, **kw: None | |
29 | ||
30 | log = Logger('My library') | |
31 | ||
32 | Best Practices | |
33 | -------------- | |
34 | ||
35 | - A library that wants to log to the Logbook system should generally be | |
36 | designed to provide an interface to the record dispatchers it is | |
37 | using. That does not have to be a reference to the record dispatcher | |
38 | itself, it is perfectly fine if there is a toggle to switch it on or | |
39 | off. | |
40 | ||
41 | - The channel name should be readable and descriptive. | |
42 | ||
43 | - For example, if you are a database library that wants to use the | |
44 | logging system to log all SQL statements issued in debug mode, you can | |
45 | enable and disable your record dispatcher based on that debug flag. | |
46 | ||
47 | - Libraries should never set up log setups except temporarily on a | |
48 | per-thread basis if it never changes the stack for a longer duration | |
49 | than a function call in a library. For example, hooking in a null | |
50 | handler for a call to a noisy function is fine, changing the global | |
51 | stack in a function and not reverting it at the end of the function is | |
52 | bad. | |
53 | ||
54 | Debug Loggers | |
55 | ------------- | |
56 | ||
57 | Sometimes you want to have loggers in place that are only really good for | |
58 | debugging. For example you might have a library that does a lot of | |
59 | server/client communication and for debugging purposes it would be nice if | |
60 | you can enable/disable that log output as necessary. | |
61 | ||
62 | In that case it makes sense to create a logger and disable that by default | |
63 | and give people a way to get hold of the logger to flip the flag. | |
64 | Additionally you can override the :attr:`~logbook.Logger.disabled` flag to | |
65 | automatically set it based on another value:: | |
66 | ||
67 | class MyLogger(Logger): | |
68 | @property | |
69 | def disabled(self): | |
70 | return not database_connection.debug | |
71 | database_connection.logger = MyLogger('mylibrary.dbconnection') |
0 | @ECHO OFF | |
1 | ||
2 | REM Command file for Sphinx documentation | |
3 | ||
4 | if "%SPHINXBUILD%" == "" ( | |
5 | set SPHINXBUILD=sphinx-build | |
6 | ) | |
7 | set BUILDDIR=_build | |
8 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . | |
9 | if NOT "%PAPER%" == "" ( | |
10 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% | |
11 | ) | |
12 | ||
13 | if "%1" == "" goto help | |
14 | ||
15 | if "%1" == "help" ( | |
16 | :help | |
17 | echo.Please use `make ^<target^>` where ^<target^> is one of | |
18 | echo. html to make standalone HTML files | |
19 | echo. dirhtml to make HTML files named index.html in directories | |
20 | echo. singlehtml to make a single large HTML file | |
21 | echo. pickle to make pickle files | |
22 | echo. json to make JSON files | |
23 | echo. htmlhelp to make HTML files and a HTML help project | |
24 | echo. qthelp to make HTML files and a qthelp project | |
25 | echo. devhelp to make HTML files and a Devhelp project | |
26 | echo. epub to make an epub | |
27 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter | |
28 | echo. text to make text files | |
29 | echo. man to make manual pages | |
30 | echo. changes to make an overview over all changed/added/deprecated items | |
31 | echo. linkcheck to check all external links for integrity | |
32 | echo. doctest to run all doctests embedded in the documentation if enabled | |
33 | goto end | |
34 | ) | |
35 | ||
36 | if "%1" == "clean" ( | |
37 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i | |
38 | del /q /s %BUILDDIR%\* | |
39 | goto end | |
40 | ) | |
41 | ||
42 | if "%1" == "html" ( | |
43 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html | |
44 | echo. | |
45 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. | |
46 | goto end | |
47 | ) | |
48 | ||
49 | if "%1" == "dirhtml" ( | |
50 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml | |
51 | echo. | |
52 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. | |
53 | goto end | |
54 | ) | |
55 | ||
56 | if "%1" == "singlehtml" ( | |
57 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml | |
58 | echo. | |
59 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. | |
60 | goto end | |
61 | ) | |
62 | ||
63 | if "%1" == "pickle" ( | |
64 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle | |
65 | echo. | |
66 | echo.Build finished; now you can process the pickle files. | |
67 | goto end | |
68 | ) | |
69 | ||
70 | if "%1" == "json" ( | |
71 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json | |
72 | echo. | |
73 | echo.Build finished; now you can process the JSON files. | |
74 | goto end | |
75 | ) | |
76 | ||
77 | if "%1" == "htmlhelp" ( | |
78 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp | |
79 | echo. | |
80 | echo.Build finished; now you can run HTML Help Workshop with the ^ | |
81 | .hhp project file in %BUILDDIR%/htmlhelp. | |
82 | goto end | |
83 | ) | |
84 | ||
85 | if "%1" == "qthelp" ( | |
86 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp | |
87 | echo. | |
88 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ | |
89 | .qhcp project file in %BUILDDIR%/qthelp, like this: | |
90 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Logbook.qhcp | |
91 | echo.To view the help file: | |
92 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Logbook.ghc | |
93 | goto end | |
94 | ) | |
95 | ||
96 | if "%1" == "devhelp" ( | |
97 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp | |
98 | echo. | |
99 | echo.Build finished. | |
100 | goto end | |
101 | ) | |
102 | ||
103 | if "%1" == "epub" ( | |
104 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub | |
105 | echo. | |
106 | echo.Build finished. The epub file is in %BUILDDIR%/epub. | |
107 | goto end | |
108 | ) | |
109 | ||
110 | if "%1" == "latex" ( | |
111 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex | |
112 | echo. | |
113 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. | |
114 | goto end | |
115 | ) | |
116 | ||
117 | if "%1" == "text" ( | |
118 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text | |
119 | echo. | |
120 | echo.Build finished. The text files are in %BUILDDIR%/text. | |
121 | goto end | |
122 | ) | |
123 | ||
124 | if "%1" == "man" ( | |
125 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man | |
126 | echo. | |
127 | echo.Build finished. The manual pages are in %BUILDDIR%/man. | |
128 | goto end | |
129 | ) | |
130 | ||
131 | if "%1" == "changes" ( | |
132 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes | |
133 | echo. | |
134 | echo.The overview file is in %BUILDDIR%/changes. | |
135 | goto end | |
136 | ) | |
137 | ||
138 | if "%1" == "linkcheck" ( | |
139 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck | |
140 | echo. | |
141 | echo.Link check complete; look for any errors in the above output ^ | |
142 | or in %BUILDDIR%/linkcheck/output.txt. | |
143 | goto end | |
144 | ) | |
145 | ||
146 | if "%1" == "doctest" ( | |
147 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest | |
148 | echo. | |
149 | echo.Testing of doctests in the sources finished, look at the ^ | |
150 | results in %BUILDDIR%/doctest/output.txt. | |
151 | goto end | |
152 | ) | |
153 | ||
154 | :end |
0 | Performance Tuning | |
1 | ================== | |
2 | ||
3 | The more logging calls you add to your application and libraries, the more | |
4 | overhead will you introduce. There are a couple things you can do to | |
5 | remedy this behavior. | |
6 | ||
7 | Debug-Only Logging | |
8 | ------------------ | |
9 | ||
10 | There are debug log calls, and there are debug log calls. Some debug log | |
11 | calls would sometimes be interesting in a production environment, others | |
12 | really only if you are on your local machine fiddling around with the | |
13 | code. Logbook internally makes sure to process as little of your logging | |
14 | call as necessary, but it will still have to walk the current stack to | |
15 | figure out if there are any active handlers or not. Depending on the | |
16 | number of handlers on the stack, the kind of handler etc, there will be | |
17 | more or less processed. | |
18 | ||
19 | Generally speaking a not-handled logging call is cheap enough that you | |
20 | don't have to care about it. However there is not only your logging call, | |
21 | there might also be some data you have to process for the record. This | |
22 | will always be processed, even if the log record ends up being discarded. | |
23 | ||
24 | This is where the Python ``__debug__`` feature comes in handy. This | |
25 | variable is a special flag that is evaluated at the time where Python | |
26 | processes your script. It can elliminate code completely from your script | |
27 | so that it does not even exist in the compiled bytecode (requires Python | |
28 | to be run with the ``-O`` switch):: | |
29 | ||
30 | if __debug__: | |
31 | info = get_wallcalculate_debug_info() | |
32 | logger.debug("Call to response() failed. Reason: {0}", info) | |
33 | ||
34 | Keep the Fingers Crossed | |
35 | ------------------------ | |
36 | ||
37 | Do you really need the debug info? In case you find yourself only looking | |
38 | at the logfiles when errors occurred it would be an option to put in the | |
39 | :class:`~logbook.FingersCrossedHandler`. Logging into memory is always | |
40 | cheaper than logging on a filesystem. | |
41 | ||
42 | Keep the Stack Static | |
43 | --------------------- | |
44 | ||
45 | Whenever you do a push or pop from one of the stacks you will invalidate | |
46 | an internal cache that is used by logbook. This is an implementation | |
47 | detail, but this is how it works for the moment. That means that the | |
48 | first logging call after a push or pop will have a higher impact on the | |
49 | performance than following calls. That means you should not attempt to | |
50 | push or pop from a stack for each logging call. Make sure to do the | |
51 | pushing and popping only as needed. (start/end of application/request) | |
52 | ||
53 | Disable Introspection | |
54 | --------------------- | |
55 | ||
56 | By default Logbook will try to pull in the interpreter frame of the caller | |
57 | that invoked a logging function. While this is a fast operation that | |
58 | usually does not slow down the execution of your script it also means that | |
59 | for certain Python implementations it invalidates assumptions a JIT | |
60 | compiler might have made of the function body. Currently this for example | |
61 | is the case for applications running on pypy. If you would be using a | |
62 | stock logbook setup on pypy, the JIT wouldn't be able to work properly. | |
63 | ||
64 | In case you don't need the frame based information (name of module, | |
65 | calling function, filename, line number) you can disable the introspection | |
66 | feature:: | |
67 | ||
68 | from logbook import Flags | |
69 | ||
70 | with Flags(introspection=False): | |
71 | # all logging calls here will not use introspection | |
72 | ... |
0 | Quickstart | |
1 | ========== | |
2 | ||
3 | .. currentmodule:: logbook | |
4 | ||
5 | Logbook makes it very easy to get started with logging. Just import the logger | |
6 | class, create yourself a logger and you are set: | |
7 | ||
8 | >>> from logbook import Logger | |
9 | >>> log = Logger('My Awesome Logger') | |
10 | >>> log.warn('This is too cool for stdlib') | |
11 | [2010-07-23 16:34] WARNING: My Awesome Logger: This is too cool for stdlib | |
12 | ||
13 | A logger is a so-called :class:`~logbook.base.RecordDispatcher`, which is | |
14 | commonly referred to as a "logging channel". The name you give such a channel | |
15 | is up to you and need not be unique although it's a good idea to keep it | |
16 | unique so that you can filter by it if you want. | |
17 | ||
18 | The basic interface is similar to what you may already know from the standard | |
19 | library's :mod:`logging` module. | |
20 | ||
21 | There are several logging levels, available as methods on the logger. The | |
22 | levels -- and their suggested meaning -- are: | |
23 | ||
24 | * ``critical`` -- for errors that lead to termination | |
25 | * ``error`` -- for errors that occur, but are handled | |
26 | * ``warning`` -- for exceptional circumstances that might not be errors | |
27 | * ``notice`` -- for non-error messages you usually want to see | |
28 | * ``info`` -- for messages you usually don't want to see | |
29 | * ``debug`` -- for debug messages | |
30 | ||
31 | Each of these levels is available as method on the :class:`Logger`. | |
32 | Additionally the ``warning`` level is aliased as :meth:`~Logger.warn`. | |
33 | ||
34 | Alternatively, there is the :meth:`~Logger.log` method that takes the logging | |
35 | level (string or integer) as an argument. | |
36 | ||
37 | Handlers | |
38 | -------- | |
39 | ||
40 | Each call to a logging method creates a log *record* which is then passed to | |
41 | *handlers*, which decide how to store or present the logging info. There are a | |
42 | multitude of available handlers, and of course you can also create your own: | |
43 | ||
44 | * :class:`StreamHandler` for logging to arbitrary streams | |
45 | * :class:`StderrHandler` for logging to stderr | |
46 | * :class:`FileHandler`, :class:`MonitoringFileHandler`, | |
47 | :class:`RotatingFileHandler` and :class:`TimedRotatingFileHandler` for | |
48 | logging to files | |
49 | * :class:`MailHandler` and :class:`GMailHandler` for logging via e-mail | |
50 | * :class:`SyslogHandler` for logging to the syslog daemon | |
51 | * :class:`NTEventLogHandler` for logging to the Windows NT event log | |
52 | ||
53 | On top of those there are a couple of handlers for special use cases: | |
54 | ||
55 | * :class:`logbook.FingersCrossedHandler` for logging into memory and | |
56 | delegating information to another handler when a certain level was | |
57 | exceeded, otherwise discarding all buffered records. | |
58 | * :class:`logbook.more.TaggingHandler` for dispatching log records that | |
59 | are tagged (used in combination with a | |
60 | :class:`logbook.more.TaggingLogger`) | |
61 | * :class:`logbook.queues.ZeroMQHandler` for logging to ZeroMQ | |
62 | * :class:`logbook.queues.RedisHandler` for logging to Redis | |
63 | * :class:`logbook.queues.MultiProcessingHandler` for logging from a child | |
64 | process to a handler from the outer process. | |
65 | * :class:`logbook.queues.ThreadedWrapperHandler` for moving the actual | |
66 | handling of a handler into a background thread and using a queue to | |
67 | deliver records to that thread. | |
68 | * :class:`logbook.notifiers.GrowlHandler` and | |
69 | :class:`logbook.notifiers.LibNotifyHandler` for logging to the OS X Growl | |
70 | or the linux notification daemon. | |
71 | * :class:`logbook.notifiers.BoxcarHandler` for logging to `boxcar`_. | |
72 | * :class:`logbook.more.TwitterHandler` for logging to twitter. | |
73 | * :class:`logbook.more.ExternalApplicationHandler` for logging to an | |
74 | external application such as the OS X ``say`` command. | |
75 | * :class:`logbook.ticketing.TicketingHandler` for creating tickets from | |
76 | log records in a database or other data store. | |
77 | ||
78 | .. _boxcar: http://boxcar.io/ | |
79 | ||
80 | Registering Handlers | |
81 | -------------------- | |
82 | ||
83 | So how are handlers registered? If you are used to the standard Python logging | |
84 | system, it works a little bit differently here. Handlers can be registered for | |
85 | a thread or for a whole process or individually for a logger. However, it is | |
86 | strongly recommended not to add handlers to loggers unless there is a very good | |
87 | use case for that. | |
88 | ||
89 | If you want errors to go to syslog, you can set up logging like this:: | |
90 | ||
91 | from logbook import SyslogHandler | |
92 | ||
93 | error_handler = SyslogHandler('logbook example', level='ERROR') | |
94 | with error_handler.applicationbound(): | |
95 | # whatever is executed here and an error is logged to the | |
96 | # error handler | |
97 | ... | |
98 | ||
99 | This will send all errors to the syslog but warnings and lower record | |
100 | levels still to stderr. This is because the handler is not bubbling by | |
101 | default which means that if a record is handled by the handler, it will | |
102 | not bubble up to a higher handler. If you want to display all records on | |
103 | stderr, even if they went to the syslog you can enable bubbling by setting | |
104 | *bubble* to ``True``:: | |
105 | ||
106 | from logbook import SyslogHandler | |
107 | ||
108 | error_handler = SyslogHandler('logbook example', level='ERROR', bubble=True) | |
109 | with error_handler.applicationbound(): | |
110 | # whatever is executed here and an error is logged to the | |
111 | # error handler but it will also bubble up to the default | |
112 | # stderr handler. | |
113 | ... | |
114 | ||
115 | So what if you want to only log errors to the syslog and nothing to | |
116 | stderr? Then you can combine this with a :class:`NullHandler`:: | |
117 | ||
118 | from logbook import SyslogHandler, NullHandler | |
119 | ||
120 | error_handler = SyslogHandler('logbook example', level='ERROR') | |
121 | null_handler = NullHandler() | |
122 | ||
123 | with null_handler.applicationbound(): | |
124 | with error_handler.applicationbound(): | |
125 | # errors now go to the error_handler and everything else | |
126 | # is swallowed by the null handler so nothing ends up | |
127 | # on the default stderr handler | |
128 | ... | |
129 | ||
130 | Record Processors | |
131 | ----------------- | |
132 | ||
133 | What makes logbook interesting is the ability to automatically process log | |
134 | records. This is handy if you want additional information to be logged for | |
135 | everything you do. A good example use case is recording the IP of the current | |
136 | request in a web application. Or, in a daemon process you might want to log | |
137 | the user and working directory of the process. | |
138 | ||
139 | A context processor can be injected at two places: you can either bind a | |
140 | processor to a stack like you do with handlers or you can override the | |
141 | override the :meth:`.RecordDispatcher.process_record` method. | |
142 | ||
143 | Here an example that injects the current working directory into the | |
144 | `extra` dictionary of a log record:: | |
145 | ||
146 | import os | |
147 | from logbook import Processor | |
148 | ||
149 | def inject_cwd(record): | |
150 | record.extra['cwd'] = os.getcwd() | |
151 | ||
152 | with my_handler.applicationbound(): | |
153 | with Processor(inject_cwd).applicationbound(): | |
154 | # everything logged here will have the current working | |
155 | # directory in the log record. | |
156 | ... | |
157 | ||
158 | The alternative is to inject information just for one logger in which case | |
159 | you might want to subclass it:: | |
160 | ||
161 | import os | |
162 | ||
163 | class MyLogger(logbook.Logger): | |
164 | ||
165 | def process_record(self, record): | |
166 | logbook.Logger.process_record(self, record) | |
167 | record.extra['cwd'] = os.getcwd() | |
168 | ||
169 | ||
170 | Configuring the Logging Format | |
171 | ------------------------------ | |
172 | ||
173 | All handlers have a useful default log format you don't have to change to use | |
174 | logbook. However if you start injecting custom information into log records, | |
175 | it makes sense to configure the log formatting so that you can see that | |
176 | information. | |
177 | ||
178 | There are two ways to configure formatting: you can either just change the | |
179 | format string or hook in a custom format function. | |
180 | ||
181 | All the handlers that come with logbook and that log into a string use the | |
182 | :class:`~logbook.StringFormatter` by default. Their constructors accept a | |
183 | format string which sets the :attr:`logbook.Handler.format_string` attribute. | |
184 | You can override this attribute in which case a new string formatter is set: | |
185 | ||
186 | >>> from logbook import StderrHandler | |
187 | >>> handler = StderrHandler() | |
188 | >>> handler.format_string = '{record.channel}: {record.message}' | |
189 | >>> handler.formatter | |
190 | <logbook.handlers.StringFormatter object at 0x100641b90> | |
191 | ||
192 | Alternatively you can also set a custom format function which is invoked | |
193 | with the record and handler as arguments: | |
194 | ||
195 | >>> def my_formatter(record, handler): | |
196 | ... return record.message | |
197 | ... | |
198 | >>> handler.formatter = my_formatter | |
199 | ||
200 | The format string used for the default string formatter has one variable called | |
201 | `record` available which is the log record itself. All attributes can be | |
202 | looked up using the dotted syntax, and items in the `extra` dict looked up | |
203 | using brackets. Note that if you are accessing an item in the extra dict that | |
204 | does not exist, an empty string is returned. | |
205 | ||
206 | Here is an example configuration that shows the current working directory from | |
207 | the example in the previous section:: | |
208 | ||
209 | handler = StderrHandler(format_string= | |
210 | '{record.channel}: {record.message) [{record.extra[cwd]}]') | |
211 | ||
212 | In the :mod:`~logbook.more` module there is a formatter that uses the Jinja2 | |
213 | template engine to format log records, especially useful for multi-line log | |
214 | formatting such as mails (:class:`~logbook.more.JinjaFormatter`). |
0 | Common Logbook Setups | |
1 | ===================== | |
2 | ||
3 | This part of the documentation shows how you can configure Logbook for | |
4 | different kinds of setups. | |
5 | ||
6 | ||
7 | Desktop Application Setup | |
8 | ------------------------- | |
9 | ||
10 | If you develop a desktop application (command line or GUI), you probably have a line | |
11 | like this in your code:: | |
12 | ||
13 | if __name__ == '__main__': | |
14 | main() | |
15 | ||
16 | This is what you should wrap with a ``with`` statement that sets up your log | |
17 | handler:: | |
18 | ||
19 | from logbook import FileHandler | |
20 | log_handler = FileHandler('application.log') | |
21 | ||
22 | if __name__ == '__main__': | |
23 | with log_handler.applicationbound(): | |
24 | main() | |
25 | ||
26 | Alternatively you can also just push a handler in there:: | |
27 | ||
28 | from logbook import FileHandler | |
29 | log_handler = FileHandler('application.log') | |
30 | log_handler.push_application() | |
31 | ||
32 | if __name__ == '__main__': | |
33 | main() | |
34 | ||
35 | Please keep in mind that you will have to pop the handlers in reverse order if | |
36 | you want to remove them from the stack, so it is recommended to use the context | |
37 | manager API if you plan on reverting the handlers. | |
38 | ||
39 | Web Application Setup | |
40 | --------------------- | |
41 | ||
42 | Typical modern web applications written in Python have two separate contexts | |
43 | where code might be executed: when the code is imported, as well as when a | |
44 | request is handled. The first case is easy to handle, just push a global file | |
45 | handler that writes everything into a file. | |
46 | ||
47 | But Logbook also gives you the ability to improve upon the logging. For | |
48 | example, you can easily create yourself a log handler that is used for | |
49 | request-bound logging that also injects additional information. | |
50 | ||
51 | For this you can either subclass the logger or you can bind to the handler with | |
52 | a function that is invoked before logging. The latter has the advantage that it | |
53 | will also be triggered for other logger instances which might be used by a | |
54 | different library. | |
55 | ||
56 | Here is a simple WSGI example application that showcases sending error mails for | |
57 | errors happened during a WSGI application:: | |
58 | ||
59 | from logbook import MailHandler | |
60 | ||
61 | mail_handler = MailHandler('errors@example.com', | |
62 | ['admin@example.com'], | |
63 | format_string=u'''\ | |
64 | Subject: Application Error at {record.extra[url]} | |
65 | ||
66 | Message type: {record.level_name} | |
67 | Location: {record.filename}:{record.lineno} | |
68 | Module: {record.module} | |
69 | Function: {record.func_name} | |
70 | Time: {record.time:%Y-%m-%d %H:%M:%S} | |
71 | Remote IP: {record.extra[ip]} | |
72 | Request: {record.extra[url]} [{record.extra[method]}] | |
73 | ||
74 | Message: | |
75 | ||
76 | {record.message} | |
77 | ''', bubble=True) | |
78 | ||
79 | def application(environ, start_response): | |
80 | request = Request(environ) | |
81 | ||
82 | def inject_info(record, handler): | |
83 | record.extra.update( | |
84 | ip=request.remote_addr, | |
85 | method=request.method, | |
86 | url=request.url | |
87 | ) | |
88 | ||
89 | with mail_handler.threadbound(processor=inject_info): | |
90 | # standard WSGI processing happens here. If an error | |
91 | # is logged, a mail will be sent to the admin on | |
92 | # example.com | |
93 | ... | |
94 | ||
95 | Deeply Nested Setups | |
96 | -------------------- | |
97 | ||
98 | If you want deeply nested logger setups, you can use the | |
99 | :class:`~logbook.NestedSetup` class which simplifies that. This is best | |
100 | explained using an example:: | |
101 | ||
102 | import os | |
103 | from logbook import NestedSetup, NullHandler, FileHandler, \ | |
104 | MailHandler, Processor | |
105 | ||
106 | def inject_information(record): | |
107 | record.extra['cwd'] = os.getcwd() | |
108 | ||
109 | # a nested handler setup can be used to configure more complex setups | |
110 | setup = NestedSetup([ | |
111 | # make sure we never bubble up to the stderr handler | |
112 | # if we run out of setup handling | |
113 | NullHandler(), | |
114 | # then write messages that are at least warnings to to a logfile | |
115 | FileHandler('application.log', level='WARNING'), | |
116 | # errors should then be delivered by mail and also be kept | |
117 | # in the application log, so we let them bubble up. | |
118 | MailHandler('servererrors@example.com', | |
119 | ['admin@example.com'], | |
120 | level='ERROR', bubble=True), | |
121 | # while we're at it we can push a processor on its own stack to | |
122 | # record additional information. Because processors and handlers | |
123 | # go to different stacks it does not matter if the processor is | |
124 | # added here at the bottom or at the very beginning. Same would | |
125 | # be true for flags. | |
126 | Processor(inject_information) | |
127 | ]) | |
128 | ||
129 | Once such a complex setup is defined, the nested handler setup can be used as if | |
130 | it was a single handler:: | |
131 | ||
132 | with setup.threadbound(): | |
133 | # everything here is handled as specified by the rules above. | |
134 | ... | |
135 | ||
136 | ||
137 | Distributed Logging | |
138 | ------------------- | |
139 | ||
140 | For applications that are spread over multiple processes or even machines | |
141 | logging into a central system can be a pain. Logbook supports ZeroMQ to | |
142 | deal with that. You can set up a :class:`~logbook.queues.ZeroMQHandler` | |
143 | that acts as ZeroMQ publisher and will send log records encoded as JSON | |
144 | over the wire:: | |
145 | ||
146 | from logbook.queues import ZeroMQHandler | |
147 | handler = ZeroMQHandler('tcp://127.0.0.1:5000') | |
148 | ||
149 | Then you just need a separate process that can receive the log records and | |
150 | hand it over to another log handler using the | |
151 | :class:`~logbook.queues.ZeroMQSubscriber`. The usual setup is this:: | |
152 | ||
153 | from logbook.queues import ZeroMQSubscriber | |
154 | subscriber = ZeroMQSubscriber('tcp://127.0.0.1:5000') | |
155 | with my_handler: | |
156 | subscriber.dispatch_forever() | |
157 | ||
158 | You can also run that loop in a background thread with | |
159 | :meth:`~logbook.queues.ZeroMQSubscriber.dispatch_in_background`:: | |
160 | ||
161 | from logbook.queues import ZeroMQSubscriber | |
162 | subscriber = ZeroMQSubscriber('tcp://127.0.0.1:5000') | |
163 | subscriber.dispatch_in_background(my_handler) | |
164 | ||
165 | If you just want to use this in a :mod:`multiprocessing` environment you | |
166 | can use the :class:`~logbook.queues.MultiProcessingHandler` and | |
167 | :class:`~logbook.queues.MultiProcessingSubscriber` instead. They work the | |
168 | same way as the ZeroMQ equivalents but are connected through a | |
169 | :class:`multiprocessing.Queue`:: | |
170 | ||
171 | from multiprocessing import Queue | |
172 | from logbook.queues import MultiProcessingHandler, \ | |
173 | MultiProcessingSubscriber | |
174 | queue = Queue(-1) | |
175 | handler = MultiProcessingHandler(queue) | |
176 | subscriber = MultiProcessingSubscriber(queue) | |
177 | ||
178 | There is also the possibility to log into a Redis instance using the | |
179 | :class:`~logbook.queues.RedisHandler`. To do so, you just need to create an | |
180 | instance of this handler as follows:: | |
181 | ||
182 | import logbook | |
183 | from logbook.queues import RedisHandler | |
184 | ||
185 | handler = RedisHandler() | |
186 | l = logbook.Logger() | |
187 | with handler: | |
188 | l.info('Your log message') | |
189 | ||
190 | With the default parameters, this will send a message to redis under the key redis. | |
191 | ||
192 | ||
193 | Redirecting Single Loggers | |
194 | -------------------------- | |
195 | ||
196 | If you want to have a single logger go to another logfile you have two | |
197 | options. First of all you can attach a handler to a specific record | |
198 | dispatcher. So just import the logger and attach something:: | |
199 | ||
200 | from yourapplication.yourmodule import logger | |
201 | logger.handlers.append(MyHandler(...)) | |
202 | ||
203 | Handlers attached directly to a record dispatcher will always take | |
204 | precedence over the stack based handlers. The bubble flag works as | |
205 | expected, so if you have a non-bubbling handler on your logger and it | |
206 | always handles, it will never be passed to other handlers. | |
207 | ||
208 | Secondly you can write a handler that looks at the logging channel and | |
209 | only accepts loggers of a specific kind. You can also do that with a | |
210 | filter function:: | |
211 | ||
212 | handler = MyHandler(filter=lambda r: r.channel == 'app.database') | |
213 | ||
214 | Keep in mind that the channel is intended to be a human readable string | |
215 | and is not necessarily unique. If you really need to keep loggers apart | |
216 | on a central point you might want to introduce some more meta information | |
217 | into the extra dictionary. | |
218 | ||
219 | You can also compare the dispatcher on the log record:: | |
220 | ||
221 | from yourapplication.yourmodule import logger | |
222 | handler = MyHandler(filter=lambda r: r.dispatcher is logger) | |
223 | ||
224 | This however has the disadvantage that the dispatcher entry on the log | |
225 | record is a weak reference and might go away unexpectedly and will not be | |
226 | there if log records are sent to a different process. | |
227 | ||
228 | Last but not least you can check if you can modify the stack around the | |
229 | execution of the code that triggers that logger For instance if the | |
230 | logger you are interested in is used by a specific subsystem, you can | |
231 | modify the stacks before calling into the system. |
0 | {% extends "basic/layout.html" %} | |
1 | ||
2 | {% block extrahead %} | |
3 | {% if online %} | |
4 | <link href='http://fonts.googleapis.com/css?family=OFL+Sorts+Mill+Goudy+TT|Inconsolata' | |
5 | rel='stylesheet' type='text/css'> | |
6 | {% endif %} | |
7 | {% endblock %} | |
8 | ||
9 | {% block header %} | |
10 | <div class="book"> | |
11 | <div class="banner"> | |
12 | <a href="{{ pathto(master_doc) }}"> | |
13 | <!-- <img src="{{ pathto('_static/' + logo, 1) }}" alt="{{ project }} logo"></img> --> | |
14 | <h1>{{ project }} </h1> | |
15 | </a> | |
16 | </div> | |
17 | {% endblock %} | |
18 | ||
19 | {% block footer %} | |
20 | {% if online %} | |
21 | <a href="http://github.com/mitsuhiko/logbook"> | |
22 | <img style="position: fixed; top: 0; right: 0; border: 0;" | |
23 | src="http://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png" | |
24 | alt="Fork me on GitHub"> | |
25 | </a> | |
26 | {% endif %} | |
27 | {{ super() }} | |
28 | </div> | |
29 | {% endblock %} |
Binary diff not shown
0 | /* | |
1 | * sheet.css | |
2 | * ~~~~~~~~~ | |
3 | * | |
4 | * Sphinx stylesheet for the sheet theme. | |
5 | * | |
6 | * :copyright: Copyright 2010 by Armin Ronacher, Georg Brandl. | |
7 | * :license: BSD, see LICENSE for details. | |
8 | * | |
9 | */ | |
10 | ||
11 | @import url("basic.css"); | |
12 | ||
13 | body { | |
14 | text-align: center; | |
15 | font-family: {{ theme_bodyfont }}; | |
16 | margin: 0; | |
17 | padding: 0; | |
18 | background: #d5dde2; | |
19 | } | |
20 | ||
21 | .book { | |
22 | padding: 15px 25px 25px 85px; | |
23 | margin: 0 auto; | |
24 | width: 695px; | |
25 | text-align: left; | |
26 | background: white url("background.png") repeat-y; | |
27 | } | |
28 | ||
29 | a { | |
30 | font-weight: bold; | |
31 | color: #003366; | |
32 | } | |
33 | ||
34 | h1, h2, h3, h4, h5, h6 { | |
35 | font-family: {{ theme_seriffont }}; | |
36 | font-weight: normal; | |
37 | } | |
38 | ||
39 | h1 { font-size: 2.8em; } | |
40 | h2 { font-size: 2.2em; } | |
41 | ||
42 | .document { | |
43 | border-bottom: 1px solid #837D7C; | |
44 | border-top: 1px solid #837D7C; | |
45 | margin: 12px 0px; | |
46 | line-height: 1.5em; | |
47 | } | |
48 | ||
49 | .related a { | |
50 | margin: 0; | |
51 | font-size: 1.1em; | |
52 | font-weight: normal; | |
53 | letter-spacing: 3px; | |
54 | text-transform: uppercase; | |
55 | text-decoration: none; | |
56 | border-bottom: 1px dashed #ddd; | |
57 | color: #837D7C; | |
58 | } | |
59 | ||
60 | div.related ul { | |
61 | margin-right: -10px; | |
62 | padding: 0px; | |
63 | } | |
64 | ||
65 | .banner h1 { | |
66 | font-size: 4.5em; | |
67 | font-weight: normal; | |
68 | line-height: 1; | |
69 | letter-spacing: -3px; | |
70 | margin-top: 12px; | |
71 | margin-bottom: 12px; | |
72 | } | |
73 | ||
74 | .banner { | |
75 | color: #000000; | |
76 | letter-spacing: -1px; | |
77 | font-family: {{ theme_seriffont }}; | |
78 | margin-bottom: 24px; | |
79 | } | |
80 | ||
81 | .banner a { | |
82 | color: #000000; | |
83 | text-decoration: none; | |
84 | } | |
85 | ||
86 | .footer { | |
87 | color: #000000; | |
88 | font-size: 0.7em; | |
89 | text-align: center; | |
90 | line-height: 1; | |
91 | margin-top: 20px; | |
92 | font-family: {{ theme_monofont }}; | |
93 | font-weight: normal; | |
94 | letter-spacing: 2px; | |
95 | text-transform: uppercase; | |
96 | text-decoration: none; | |
97 | color: #837D7C; | |
98 | } | |
99 | ||
100 | .highlight pre { | |
101 | background-color: #f8f8f8; | |
102 | border-top: 1px solid #c8c8c8; | |
103 | border-bottom: 1px solid #c8c8c8; | |
104 | line-height: 120%; | |
105 | padding: 10px 6px; | |
106 | } | |
107 | ||
108 | .highlighttable .highlight pre { | |
109 | margin: 0px; | |
110 | } | |
111 | ||
112 | div.sphinxsidebar { | |
113 | margin-left: 0px; | |
114 | float: none; | |
115 | width: 100%; | |
116 | font-size: 0.8em; | |
117 | } | |
118 | ||
119 | .toctree-l1 a { | |
120 | text-decoration: none; | |
121 | } | |
122 | ||
123 | img.align-right { | |
124 | margin-left: 24px; | |
125 | } | |
126 | ||
127 | pre, tt { | |
128 | font-family: {{ theme_monofont }}; | |
129 | font-size: 15px!important; | |
130 | } | |
131 | ||
132 | dl.class dt { | |
133 | padding-left: 60px; | |
134 | text-indent: -60px; | |
135 | } | |
136 | ||
137 | tt.descname { | |
138 | font-size: 1em; | |
139 | } | |
140 | ||
141 | p.output-caption { | |
142 | font-size: small; | |
143 | margin: 0px; | |
144 | } |
0 | [theme] | |
1 | inherit = basic | |
2 | stylesheet = sheet.css | |
3 | pygments_style = tango | |
4 | ||
5 | [options] | |
6 | bodyfont = 'Cantarell', 'Lucida Grande', sans-serif | |
7 | seriffont = 'OFL Sorts Mill Goudy TT', 'Georgia', 'Bitstream Vera Serif', serif | |
8 | monofont = 'Consolas', 'Inconsolata', 'Bitstream Vera Sans Mono', monospace |
0 | Stacks in Logbook | |
1 | ================= | |
2 | ||
3 | Logbook keeps three stacks internally currently: | |
4 | ||
5 | - one for the :class:`~logbook.Handler`\s: each handler is handled from | |
6 | stack top to bottom. When a record was handled it depends on the | |
7 | :attr:`~logbook.Handler.bubble` flag of the handler if it should still | |
8 | be processed by the next handler on the stack. | |
9 | - one for the :class:`~logbook.Processor`\s: each processor in the stack | |
10 | is applied on a record before the log record is handled by the | |
11 | handler. | |
12 | - one for the :class:`~logbook.Flags`: this stack manages simple flags | |
13 | such as how errors during logging should be processed or if stackframe | |
14 | introspection should be used etc. | |
15 | ||
16 | General Stack Management | |
17 | ------------------------ | |
18 | ||
19 | Generally all objects that are management by stacks have a common | |
20 | interface (:class:`~logbook.base.StackedObject`) and can be used in | |
21 | combination with the :class:`~logbook.NestedSetup` class. | |
22 | ||
23 | Commonly stacked objects are used with a context manager (`with` | |
24 | statement):: | |
25 | ||
26 | with context_object.threadbound(): | |
27 | # this is managed for this thread only | |
28 | ... | |
29 | ||
30 | with context_object.applicationbound(): | |
31 | # this is managed for all applications | |
32 | ... | |
33 | ||
34 | Alternatively you can also use `try`/`finally`:: | |
35 | ||
36 | context_object.push_thread() | |
37 | try: | |
38 | # this is managed for this thread only | |
39 | ... | |
40 | finally: | |
41 | context_object.pop_thread() | |
42 | ||
43 | context_object.push_application() | |
44 | try: | |
45 | # this is managed for all applications | |
46 | ... | |
47 | finally: | |
48 | context_object.pop_application() | |
49 | ||
50 | It's very important that you will always pop from the stack again unless | |
51 | you rea |