Welcome to Python Log Indenter documentation¶
This module helps by allowing you to indent the lines in your log entries to make it easier to read when you are dealing with complex loops and calls (especially with debug level logging)
This subclasses a standard logging.LoggerAdapter
and allows you to control the indent level of your message.
Contents:
Installation and initial setup¶
To install the indented log helper, first get it using pip
:
pip install python_log_indenter
Since this is, in effect, a logging adapter, you simply wrap your logger in the IndentedLoggerAdapter class:
from python_log_indenter import IndentedLoggerAdapter
import logging
log = IndentedLoggerAdapter(logging.get_logger(__name__))
You can now use it as you would any other log:
log.debug('this is a debug log entry')
see the Basic usage page for examples of how to use this.
Basic usage¶
Loading¶
This subclasses a standard logging.LoggerAdapter
, so you simply wrap your standard logger object in this and
use it as normal. The IndentedLoggerAdapter
has the same methods as the normal logging.Logger
,
so you dont have to change any existing code.
Example:
from python_log_indenter import IndentedLoggerAdapter
import logging
logging.basicConfig(level=logging.DEBUG)
log = IndentedLoggerAdapter(logging.get_logger(__name__))
You can also chance the default number of spaces used for indenting, as well as chance the character used (see Advanced usage for more information)
Changing the indent level¶
Once you have loaded the IndentedLoggerAdapter, you can change the level of the indents as you go using the .add / .sub methods:
>>> log.info('test1')
>>> log.add()
>>> log.info('test2')
>>> log.sub()
>>> log.info('test3')
Will result in a log looking like:
INFO:root:test1
INFO:root: test2
INFO:root:test3
You can also add or subtract multiple levels by passing an int to IndentedLoggerAdapter.add()
or IndentedLoggerAdapter.sub()
:
>>> log.info('test1')
>>> log.add(3)
>>> log.info('test2')
>>> log.sub(2)
>>> log.info('test3')
# returning
INFO:root:test1
INFO:root: test2
INFO:root: test3
In addition, the IndentedLoggerAdapter.add()
and IndentedLoggerAdapter.sub()
:: are chainable (along
with several of the other methods). This means you can clean up your code to look like:
>>> log.info('test1')
>>> log.add().info('test2').sub()
>>> log.info('test3')
Other Features¶
There are several other features included in this library, these are documented in the Advanced usage section. These including:
- Push/Pop:
- The ability to push or pop indent levels from a FILO queue.
- Memories:
- The ability to store indent levels into a named memory location.
- Formatable as fields:
- The ability to add the indent as a field to the
logging.LogRecord
so that it can be included or not based on the format string and the handler. - Shortcuts:
- Shortcut methods for many of the fields for quicker usage.
Advanced usage¶
the py:class:IndentedLoggerAdapter has several other methods and usages for advanced usage.
Push / Pop¶
IndentedLoggerAdapter.push()
and IndentedLoggerAdapter.pop()
allow you to add an indent level to a
first in last out queue (FILO so that you can save an indent level and go back to it, even if you dont remember what it
was.
Example:
>>> log.info('test1').push().add()
>>> log.info('test2')
>>> log.add().info('test3')
>>> log.pop().info('test4')
# returning
INFO:root:test1
INFO:root: test2
INFO:root: test3
INFO:root:test4
This can be helpfull if you are changing indents across methods or functions:
def test1():
log.info('entering test function').add()
# do something ...
log.push() # save the location
log.add().debug('entering the loop')
for i in range(3):
sub_test(i)
log.pop() # getting the saved location
log.debug('leaving function')
def sub_test(cnt):
log.debug('sub_test_loop %d', cnt)
# do some loopy thing ...
log.a().debug('doing loopy stuff').s()
# returns
INFO:root:entering test function
DEBUG:root: entering the loop
DEBUG:root: sub_test_loop 0
DEBUG:root: doing loopy stuff
DEBUG:root: sub_test_loop 1
DEBUG:root: doing loopy stuff
DEBUG:root: sub_test_loop 2
DEBUG:root: doing loopy stuff
DEBUG:root: leaving the function
Especially if you forget to return to the same level as before. For example, if we run the above test1() function three times we would see:
INFO:root:entering test function
DEBUG:root: entering the loop
DEBUG:root: sub_test_loop 0
DEBUG:root: doing loopy stuff
DEBUG:root: sub_test_loop 1
DEBUG:root: doing loopy stuff
DEBUG:root: sub_test_loop 2
DEBUG:root: doing loopy stuff
DEBUG:root: leaving the function
INFO:root: entering test function
DEBUG:root: entering the loop
DEBUG:root: sub_test_loop 0
DEBUG:root: doing loopy stuff
DEBUG:root: sub_test_loop 1
DEBUG:root: doing loopy stuff
DEBUG:root: sub_test_loop 2
DEBUG:root: doing loopy stuff
DEBUG:root: leaving the function
INFO:root: entering test function
DEBUG:root: entering the loop
DEBUG:root: sub_test_loop 0
DEBUG:root: doing loopy stuff
DEBUG:root: sub_test_loop 1
DEBUG:root: doing loopy stuff
DEBUG:root: sub_test_loop 2
DEBUG:root: doing loopy stuff
DEBUG:root: leaving the function
if we had used IndentedLoggerAdapter.push()
and IndentedLoggerAdapter.pop()
at the beginning and end
of the method, we would have cleared out the building indent.
Push / Pop by name¶
You can also push and pop by name, this allows you to set a name while pushing an indent level, then return to that point in the queue without having to do multiple pop’s.
For example:
def test1():
log.push('test1_function')
log.info('entering test function').add()
# do something ...
log.add().debug('entering the loop')
log.push()
for i in range(3):
sub_test(i)
log.debug('leaving function')
# This pops TWO levels from the queue, the first one (Just above the "for / in") and returns to the first .push()
log.pop('test1_function')
def sub_test(cnt):
log.debug('sub_test_loop %d', cnt)
# do some loopy thing ...
log.a().debug('doing loopy stuff').s()
# returns
INFO:root:entering test function
DEBUG:root: entering the loop
DEBUG:root: sub_test_loop 0
DEBUG:root: doing loopy stuff
DEBUG:root: sub_test_loop 1
DEBUG:root: doing loopy stuff
DEBUG:root: sub_test_loop 2
DEBUG:root: doing loopy stuff
DEBUG:root: leaving the function
In addition, you can pass the indent level to the .push() (without changing the current level), and you can pass the number of levels to go back to the .pop():
>>> log.info('test1').push(2).add()
>>> log.info('test2')
>>> log.add().info('test3').push()
>>> log.info('test4')
>>> log.pop().info('test5')
# returning
INFO:root:test1
INFO:root: test2
INFO:root: test3
INFO:root: test4
INFO:root:test4
Memories¶
IndentedLoggerAdapter.mem_save()
, IndentedLoggerAdapter.mem()
, and
IndentedLoggerAdapter.mem_clear()
You also can store indent levels using named storage locations, this allows
you to setup indent levels for specific things and recall them as needed.:
>>> log.mem_save('level1',1)
>>> log.mem_save('level2',2)
>>> log.mem_save('level3',3)
>>> log.info('test0')
>>> log.mem('level1').info('test1')
>>> log.info('test2')
>>> log.mem('level2').info('test3')
>>> log.mem('level3').info('test4')
>>> log.mem('level1').info('test5')
# returning
INFO:root:test0
INFO:root: test1
INFO:root: test2
INFO:root: test3
INFO:root: test4
INFO:root:test5
If you do not pass an indent level to .mem_save() it will save the current level.
Formatting¶
By default the library will add the indent to the beginning of the message string, however if you want more control
over the formatting of the log string, you can change the behaivior to set the indent_str as a
logging.LogRecord
property, which can then be access by format strings set in the logging configuration.
This allows you to use the indenting for console logging, but not for log files (or any other mix you want). In addition, the indent_level is available as well if you want to pass that into the formatting string.
These are available using the “indent_str” and “indent_level” keywords in the formatting string.
As an example of a useless format:
logging.basicConfig(format='%(name)-8s: %(levelname)-8s : level %(indent_level) : indent <%(indent_str)s> : %(message)s')
log = IndentedLoggerAdapter(logging.getLogger(), spaces=1, indent_char='.', auto_add=False)
log.info('test1')
log.add(3)
log.info('test2')
log.sub(2)
log.info('test3')
# returning
root : INFO : level 0 : indent <> : test 1
root : INFO : level 3 : indent < > : test 1
root : INFO : level 1 : indent < > : test 1
for better examples, see the logging cookbook on the logging
Shortcuts¶
Shortcut methods have also been defined to assist in making these faster to enter (not that the names are very long to begin with).
Method | Shortcut |
---|---|
.indent_level() | .i() |
.add() | .a() |
.sub() | .s() |
.pop() | .po() |
.push() | .pu() |
.mem() | .m() |
.mem_save() | .ms() |
.mem_clear() | .mc() |
Also, you can access memory location using dictionary methods, for example:
>>> log['level1'] = 1
>>> log['level2'] = 2
>>> log['level3'] = 3
>>> log.info('test0')
>>> log['level1'].info('test1')
>>> log.info('test2')
>>> log['level2'].info('test3')
>>> log['level3'].info('test4')
>>> log['level1'].info('test5')
# returning
INFO:root:test0
INFO:root: test1
INFO:root: test2
INFO:root: test3
INFO:root: test4
INFO:root:test5
Changing indent size and indent character¶
When loading the IndentedLoggerAdapter
you can choose to set the size of the indent and the character used
to create the indent.
For example:
logging.basicConfig(level=logging.DEBUG)
log = IndentedLoggerAdapter(logging.getLogger(), spaces=1, indent_char='.')
log.a().info('test 1')
log.s().error('test 2')
log.a(3).debug('test 3')
log.push().warning('test 4')
log.a(1).critical('test 5')
log.pop().critical('test 6')
INFO:root:.test 1
ERROR:root:test 2
DEBUG:root:...test 3
WARNING:root:...test 4
CRITICAL:root:....test 5
CRITICAL:root:...test 6
See the IndentedLoggerAdapter API section for information on the api for specific parameters.
IndentedLoggerAdapter API¶
API Reference for the IndentedLoggerAdapter
This will allow you to turn logs that would normally look like this:
root DEBUG Loading system
root DEBUG Checking for the right record
root DEBUG Checking record 1
root DEBUG Checking name
root DEBUG Not the right record
root DEBUG Checking record 2
root DEBUG checking name
root DEBUG Name checks, checking phone number
root DEBUG Phone number checks, VALID record!
root DEBUG Returning record 2
Into something like this:
root DEBUG Loading system
root DEBUG Checking for the right record
root DEBUG Checking record 1
root DEBUG Checking name
root DEBUG Not the right record
root DEBUG Checking record 2
root DEBUG checking name
root DEBUG Name checks, checking phone number
root DEBUG Phone number checks, VALID record!
root DEBUG Returning record 2
Indices and tables¶
Note
Please feel feel free to make bug reports at https://github.com/dstrohl/Python_log_indenter/issues as well as forking the repo and issuing pull requests!