wiki:logging
Warning: Can't synchronize with repository "(default)" (Unsupported version control system "svn": No module named svn). Look in the Trac log for more information.

Version 6 (modified by Kaan, 13 years ago) (diff)

Partially edited document, the rest should be done by tomorrow

Undestanding the Logger

As the  original post said TurboGears uses the Python standard library logger module, which you can read about in detail in the  official Python docs.

The default Turbogears logging is defined in two places:

  1. dev.cfg and prod.cfg - From your quickstart directory, contains configuration specific to your environment
  2. <your package>/config/log.cfg - Contains configuration for your logging that won't change based on your environment

Logging specific configuration is marked by the tag [logging] in both of these files.

Please note the <your package> used throughout this doc. Remember to substitute your app's packagename when you try these things out for yourself.

Using the Logger

To get the most out of this section, check out the  first page of the logging module docs.

Two lines in your controllers.py are all you need to get going. First, create a logger:

log = logging.getLogger("<your package>.controllers")

That is the prefered way to create a logger. The param string is the name of your new logger. Although this could be anything you want, it's good practice to follow a convention and keep your logs organized from the get go. Here we name the logger after the package/module since this is a unique name.

Next, send some info to our new logger object:

log.debug("Happy TurboGears Controller Responding For Duty")

Which, as you read in the official docs, is a log with level 10, the debug level of severity. You can probably just ignore the level numbers, unless you want to create log levels of your own.

Why not just use logging.debug(msg)?

Because then you'd be shorting yourself on the power and flexibility of the python logging module. We want to be able to send our logs to  Multiple Destinations, as defined by you, so you can later find out if the error is on your side or on the framework just by looking at the logs. You can also send different logs to different places, all without sprinkling your app code with needless boilerplate code, thanks to the config files.

In general is a better practice to do it the first way and take full advantage of the python logging module. If not, it's only one extra line of code, anwyways.

TurboGears .cfg files

if you are used to the "normal" way of creating loggers, either by logging.basicConfig or  config files you may get confused by this format.

So lets start with the format this is a ConfigObj? and its format is at  ConfigObj File Format for you the framework user the only diference from  ConfigParser is the extra nested tags and by now you should know that those generate a dict inside another. On the other hand if you want to explore the goodies of ConfigObj? Here are the  docs

by the way if you ever want to store python type in a config file  unrepr is what you need which is exactly what TG is doing and yes those are format strings and python lists on the default log.cfg

What is this *()

think of it as %()

Why change it?

http://trac.turbogears.org/turbogears/browser/tags/0.9a5/turbogears/config.py#L117 and if you still dont believe me http://trac.turbogears.org/turbogears/browser/tags/0.9a5/turbogears/config.py#L23 now can we go on?

logging format

So the file log.cfg file always starts with

[logging]

and there is a section in dev/prod.cfg with the same name, they are all load up into the same dict so in practice defining things in either is ok.

This is the head if the logging cfg, if we ever more this to other file the core will still work thanks to configObj

then you have 3 sections

[[formatters]]
[[handlers]]
[[loggers]]

where all are optional although you should at least have "handlers" and "loggers" after this all lvl 3 are just names which as all dict keys and therefore must be unique

Formatters

format key

each lvl 3 aggregate should have only one key call format which becomes the fmt param of a  Formatter Object, there you can see all the possible values this can have, as well as what happens went ommited

handlers

class key

each handler must have at least a class key which has to be one of  Handler Objects and yes you can subclass it but I think you will have enough with the defaults, also TG will error out since your class is not part of logging.dict

args key

the "optional" args key are the parameters pass to each subclass of Handler

level key

the level key indicates the loggin level of this handler this can be any of the default levels DEBUG, INFO, WARNING, ERROR and CRITICAL or a string with the number, i got a patch to fix this TODO summit patch

formatter key

formatter any lvl 3 name form formatters if level is ommited level=NOTSET if formatter is omitted the default is used '%(message)s'

...
[[[full_content]]]
format='*(asctime)s *(name)s *(levelname)s *(message)s'
...
[[[debug_out]]]
class='StreamHandler'
level='DEBUG'
args='(sys.stdout,)'
formatter='full_content'

So now this makes sence We are creating a StreamHandler? to sys.stdout with lvl debug using the formatted defined above

loggers

This is a another way to create  Logger Objects You can either define them here or do the calls in your code, it depends on your situation/taste there for I'll make a reference to the method call and you will know how to do it both ways and I'll type it ones

qualname

name of this logger

if qualname:
log=logging.getLogger(qualname)
else:
log=logging.getLogger()

level

log.setLevel(level)

handlers

handlers is a list so

for handler in handlers:
 log.addHandlers(handler)

propagate

log.propagate=propagate

And no those are "examples" it's not the way TG does it.

Putting it all together

in log.cfg

[logging]
[[formatters]]
[[[message_only]]]
format='*(message)s'

[[[full_content]]]
format='*(asctime)s *(name)s *(levelname)s *(message)s'

[[handlers]]
[[[debug_out]]]
class='StreamHandler'
level='DEBUG'
args='(sys.stdout,)'
formatter='full_content'

[[[access_out]]]
class='StreamHandler'
level='INFO'
args='(sys.stdout,)'
formatter='message_only'

[[[error_out]]]
class='StreamHandler'
level='ERROR'
args='(sys.stdout,)'

We define 2 formatters named "message_only" and "full_content", then we define 3 handlers which all Stream to stdout with diferent levels and using the two formaters we define.

In dev.cfg

[logging]

[[loggers]]
[[[your_project]]]
level='DEBUG'
qualname='<your package>'
handlers=['debug_out']

[[[allinfo]]]
level='INFO'
handlers=['debug_out']

[[[access]]]
level='INFO'
qualname='turbogears.access'
handlers=['access_out']
propagate=0

We define 3 loggers "your_project" which is the one we use at the start of this tutorial that prints DEBUG messages to stdout, "allinfo" which is the main logger (notice missing qualname) and "access" that prints INFO messages to stdout.

NOTE: "your_project" is no longer there as of r1367 and 0.9a6, it was a "typo"

What now

Now that you finally undestand how it works it's time to turn it around and do lots of stuff with it check out LoggingConfigurationExamples.

Custom level Logging

TODO add patch to add custom levels