Tree-like logging util for python
pip install pytreelog==194rc0.post1
This tool allows python users to log entries in layers of blocks that are displayed in a tree-like structure:
-start
.-----------------------
|* f(x="test")
| -x = test
| .-----------------------
| :* Start loop:
| : -i=0
| : -i=1
| : -i=2
| :* end loop
| '-----------------------
'-----------------------
-done
Features:
Note: this README.md
file is both a doc and the source of doctest (see section Test for details).
Simply
pip install pytreelog
Note that this lib has only one file, pytreelog/pytreelog.py
, which can be copied to whichever folder of choice.
Use this approach only when testing is not intented (this lib use this file, pytreelog/README.md
, as both the doc and source of tests).
Or for likely the most updated one:
pip install git+https://gitlab.com/runsun/pytreelog.git
from pytreelog.pytreelog import TreeLog
tl = TreeLog()
tl(x) # log x
tl.b(x) # Beging a new block. 'x' is optional. If not given,
# x will be assigned the function call arg values. As of
# 2012.12.19, this can be achieved much more easily for
# functions by the Decorator Approach described below.
tl.e(x) # End the current block. 'x' is optional. If x not given
# for a function that return something, it will show the
# returned value
tl.data # list of lines of the logged data
tl.text() # returns the data as a string
tl.reset() # reset the data and block info to start anew
You can turn logging on/off with either :
tl.on()
tl.off()
or
tl._on = True
tl._on = False
default: on.t
>>> from pytreelog.pytreelog import TreeLog
>>> tl = TreeLog()
>>> def f(x):
... tl.b('BEG') ## <== New block
... tl("test line "+str(x)) ## <== Any string
... tl.e('END') ## <== End of a block
>>> f(1)
>>> print(tl.text()) # doctest: +NORMALIZE_WHITESPACE
=============================================
TreeLog output
=============================================
.-----------------------
|* BEG
| -test line 1
|* END
'-----------------------
tl.b()
, tl.e()
>>> def f(x):
... tl.b()
... tl("test line "+str(x))
... tl.e()
>>> f(2)
>>> print(tl.text()) # doctest: +NORMALIZE_WHITESPACE
=============================================
TreeLog output
=============================================
.-----------------------
|* BEG
| -test line 1
|* END
'-----------------------
.-----------------------
|* f(x=2)
| -test line 2
'-----------------------
Note: tl.b()
logs function name and input value, tl.e()
logs nothing. Also note that the output is appended to previous result.
Another example:
>>> tl.reset()
>>> tl('start test') ## log a single line
>>> def f(x):
... a=0
... tl.b() ## Begin a block. This logs func name and input arg value(s)
... tl('x = '+str(x)) ## Log a line
... tl.b('Starting loop:') ## Begin of another block
...
... for i in range(3):
... tl('i='+str(i)) ## Log i
...
... tl.e('end loop') ## End of block
... tl.e() ## End of block
>>> f('test')
>>> tl('done')
>>> print(tl.text()) # doctest: +NORMALIZE_WHITESPACE
=============================================
TreeLog output
=============================================
-start test
.-----------------------
|* f(x="test")
| -x = test
| .-----------------------
| :* Starting loop:
| : -i=0
| : -i=1
| : -i=2
| :* end loop
| '-----------------------
'-----------------------
-done
The logging approach below to indicate a function block:
def ff():
tl.b()
...
tl.e()
can be done in a Decorator Aproach by using the TreeLog instance, tl
, as the decorator to track the entry/exit of the function ff
below:
@tl
def ff():
...
This allows:
The Decorator Approach automatically logs the return of a function:
>>> tl.reset()
>>> @tl
... def test(a,b=0):
... tl.b('User-block beg')
... tl('Inside test()')
... tl.e('User-block end')
... return a+b
>>> test(3,4)
7
>>> print(tl.text()) # doctest: +NORMALIZE_WHITESPACE
=============================================
TreeLog output
=============================================
.-----------------------
|> test(a=3, b=4)
| .-----------------------
| :* User-block beg
| : -Inside test()
| :* User-block end
| '-----------------------
|< 7
'-----------------------
Note that, with Decorator Arrpoach, the symbols of function block head/tail (i.e., >
and <
) are different from those logged with tl.b()
and tl.e()
(i.e., *
). This allows the log to tell function block and user-defined block apart:
>
and <
*
and *
Both of them can be customized (See Customizable API below).
If just want to track nothing but entry/exit of a function, use an unbound instance for one-time use:
>>> @TreeLog()
... def test3():
... ...
More example:
>>> tl.reset()
>>> @tl
... def test(a,b=0):
... tl.l('Inside test()')
... @tl
... def inside(a,b):
... return a+b
... return inside(a,b)*2
>>> test(3,4)
14
>>> print(tl.text()) # doctest: +NORMALIZE_WHITESPACE
=============================================
TreeLog output
=============================================
.-----------------------
|> test(a=3, b=4)
| -Inside test()
| .-----------------------
| :> a.inside(b=4)
| :< 7
| '-----------------------
|< 14
'-----------------------
Example of using TreeLog
in classes:
>>> log= TreeLog()
>>> class Cls(object):
... @log
... def __init__(self):
... self.clsname = 'CLS'
... self.setname( prefix='I am ') ## run inside __init__
... @log
... def setname(self, prefix):
... self.name= prefix + self.clsname
... @log
... def getname(self):
... return self.name
>>> cc = Cls() ## run __init__, trigger the logging of __init__ and setname.
>>> cc.getname()
'I am CLS'
>>> cc.setname('This is ') ## run outside __init__. Note the user arg ('This is ') is logged.
>>> cc.getname()
'This is CLS'
>>> print(BR.join( log.data )) # doctest: +NORMALIZE_WHITESPACE
=============================================
TreeLog output
=============================================
.-----------------------
|> self.__init__()
| .-----------------------
| :> self.setname(prefix="I am ")
| '-----------------------
'-----------------------
.-----------------------
|> cc.getname()
|< "I am CLS"
'-----------------------
.-----------------------
|> cc.setname(prefix="This is ")
'-----------------------
.-----------------------
|> cc.getname()
|< "This is CLS"
'-----------------------
Note that:
tl.data
is a list containing the logged strings. tl.text()
returns a single string.
You can also log to external variable/file :
Use the external
argument for TreeLog
to save output to a variable, which
is a dict contains a data
key, {'data':[]}
:
>>> external={'data':[]}
>>> p = TreeLog(external=external)
>>> p.b('start')
>>> @p
... def g(name):
... p.l('test')
>>> g('g')
>>> p.e('stop')
>>> for x in external['data']: print(x) # doctest: +NORMALIZE_WHITESPACE
=============================================
TreeLog output
=============================================
.-----------------------
|* start
| .-----------------------
| :> g(name="g")
| : -test
| '-----------------------
|* stop
'-----------------------
You can log to a file:
pp= TreeLog(logfile='./pytreelog.log')
Defaults of the TreeLog()
module:
TreeLog( header = '='*45+BR+'TreeLog output'+ BR+'='*45
, _on = True
, block_beg = '* '
, block_end = '* '
, func_beg = '> '
, func_end = '< '
, indent = 1
, blockline = '-----------------------'
, blocksymbols= '|:'
, external = None # External var ( {'data':[]} ) to which the data is logged
, logfile = ''
)
blocksymbols
: a str containing symbols of the left-border of all layers. The default is |:
, means the symbols of left-border will be |
and :
, alternatively.
The following example shows alternative symbols of 3 (i.e., [=!
):
>>> tl= TreeLog(blocksymbols= '[=!')
>>> @tl
... def f():
... tl.b('1st layer')
... for i in (1,2):
... tl.b('2nd layer')
... for j in (3,4):
... tl.b('3rd layer')
... for k in (5,6):
... tl( str( (i,j,k) ) )
... tl.e()
... tl.e()
... tl.e()
>>> f()
>>> print(BR.join( tl.data )) # doctest: +NORMALIZE_WHITESPACE
=============================================
TreeLog output
=============================================
.-----------------------
[> f()
[ .-----------------------
[ =* 1st layer
[ = .-----------------------
[ = !* 2nd layer
[ = ! .-----------------------
[ = ! [* 3rd layer
[ = ! [ -(1, 3, 5)
[ = ! [ -(1, 3, 6)
[ = ! '-----------------------
[ = ! .-----------------------
[ = ! [* 3rd layer
[ = ! [ -(1, 4, 5)
[ = ! [ -(1, 4, 6)
[ = ! '-----------------------
[ = '-----------------------
[ = .-----------------------
[ = !* 2nd layer
[ = ! .-----------------------
[ = ! [* 3rd layer
[ = ! [ -(2, 3, 5)
[ = ! [ -(2, 3, 6)
[ = ! '-----------------------
[ = ! .-----------------------
[ = ! [* 3rd layer
[ = ! [ -(2, 4, 5)
[ = ! [ -(2, 4, 6)
[ = ! '-----------------------
[ = '-----------------------
[ '-----------------------
'-----------------------
All tests are written in this README.md
(which means, this README.md
is both a doc and tests). The content of this file is loaded by pytreelog.test()
and assigned to TreeLog.__doc__
then tests are carried out by doctest.testmod()
(See the code in pytreelog.test()
for details).
\>>> from pytreelog import pytreelog
\>>> pytreelog.test()
--- Loading "C:\python37\lib\site-packages\pytreelog\README.md" for doctest:
--- Tests done.