The log service

It’s often useful to log some information during the execution of a Web application to trace the execution of the requests and to provide useful information for debugging when something goes wrong. The most common use-case is to log the exceptions tracebacks to a file on the server that provide the context where the error occurred so that we can fix the problem without disclosing sensitive information to the end-users.

Python ships with a logging module that provides everything we need in a Web development context. However, it is relatively complex to configure and it’s configuration is a singleton, which is problematic in a multi-applications setup (that is, when the same nagare-admin serve process serves more than one application).

In order to solve these problems, Nagare provides its own nagare.log service , which is built on top of the logging module but greatly simplifies its usage and configuration for common use-cases.

Basic Usages

Using The default logger

By default, when you serve Web applications with the nagare-admin serve utility, Nagare creates a dedicated logger, handler and formatter for each application, and activates them: this is the purpose of the nagare.log.configure and nagare.log.activate functions.

You can use the module level functions of the log module to write messages to the default logger of the currently running application, as shown in the example below:

from nagare import presentation, log

class Root(object):
    pass

@presentation.render_for(Root)
def render(self, h, *args):
    log.info('Rendering the Root component')
    # some rendering code...
    return h.root

app = Root

Then, each time the default view of the Root component is rendered, this line should appear in the console shell (which is the output of the default logger):

2012-06-14 10:22:38,379 - nagare.application.myapp - INFO - Rendering the root component

As you see, the messages are issued in the nagare.application.myapp namespace, which is the namespace of the messages coming from the Nagare application called myapp.

Here is the full listing of the module-level functions of the nagare.log module:

Function Effect
log.debug(msg, *args, **kw) Logs a message with DEBUG level on the application logger
log.info(msg, *args, **kw) Logs a message with INFO level on the application logger
log.warning(msg, *args, **kw) Logs a message with WARNING level on the application logger
log.error(msg, *args, **kw) Logs a message with ERROR level on the application logger
log.critical(msg, *args, **kw) Logs a message with CRITICAL level on the application logger
log.exception(msg, *args, **kw) Logs a message with ERROR level on the application logger, and also logs the current exception information
log.log(level, msg, *args, **kw) Logs a message with the specified level on the application logger

All these functions accept variable arguments and keyword arguments, which are documented in the logging module, here.

Overriding the default configuration

The [[logger]], [[hander]] and [[formatter]] sub-sections of the [logging] section in the application configuration file are dedicated to configure the default application logger.

The default logging setup is equivalent to the following [logging] section, where <name> is replaced by the name of the application (please refer to the logging’s module configuration file format for a better understanding of the configuration of loggers, handlers and formatters):

[logging]

[[logger]]
qualname=nagare.application.<name>
level=INFO
propagate=1

[[handler]]
class=StreamHandler
args=(sys.stderr,)

[[formatter]]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

As you can see, by default, the log messages with a level greater or equal to INFO are printed to sys.stderr via a StreamHandler.

It’s possible to override this default logging configuration by adding your own [logging] section to the application configuration file. Here, the logging level of the application is set to DEBUG:

[logging]
[[logger]]
level=DEBUG

Here, a new handler is defined to log the messages to the file /tmp/myapp.log. We use a RotatingFileHandler instead of the classical FileHandler since we want the file to be log-rotated in order to prevent it from becoming too large:

[logging]
[[handler]]
class=handlers.RotatingFileHandler
args="('/tmp/myapp.log', 'a', 10485760, 10, 'UTF-8')"

Using the Nagare IDE

When the Nagare IDE is launched at the same time than your application (i.e nagare-admin serve <name> ide), a second handler is bound to the default logger so that all the messages are also sent to the IDE log panel.

Advanced Usages

Creating other application loggers

As seen above, Nagare makes it easy to log messages in an application. However, as your application becomes more complex and is broken down in several modules and packages, it may become hard to track down where you sent a particular message or to analyze the log file which is now filled with numerous log messages.

In this situation, it’s generally useful to organize the log messages into namespaces, so that we can enable/disable the logging of messages in some namespaces or send messages with different namespaces to different handlers.

For this purpose, Nagare offers a log.get_logger(namespace) function that creates a logger which puts the messages into the specified namespace. To create a new logger for your application, use a relative name starting with a dot. In this case, the new namespace is relative to the application namespace, e.g. nagare.application.myapp.

The logger object offers the same functions as the log module for writing messages, that is logger.debug, logger.info, logger.warning, logger.error, logger.critical, logger.exception and logger.log, with the same parameters.

In this example, we will log all the messages generated in the views to a specific nagare.application.myapp.ui namespace:

from nagare import log

@presentation.render_for(Root)
def render(self, h, *args):
    log.get_logger('.ui').debug('Rendering the Root component')
    # some rendering code...
    return h.root

and you can see, the message is now attached to the nagare.application.myapp.myapp.ui namespace:

2012-06-14 10:22:38,379 - nagare.application.myapp.ui - INFO - Rendering the root component

Being under the nagare.application.myapp namespace, this logger propagates the messages to the default logger. Also this logger inherits the default logger configuration, which can be overridden:

[[logging]]
[loggers]
keys=ui   # Declare the new logger

[[logger_ui]]
qualname=.ui    # Relative namespace
level=CRITICAL
# No specific handler: propagate the messages to the default application logger
handlers=

Here the log level of the logger is set to CRITICAL.

Or a more complex configuration can be created, for example to log the messages to a specific log file:

[logging]
[[loggers]]
keys=ui   # Declare the new logger

[[handlers]]
keys=logfile   # Declare the new handler

[[formatters]]
keys=simple   # Declare the new formatter

[[logger_ui]]
qualname=.ui    # Relative namespace
handlers=logfile
propagate=0   # Don't propagate the messages to the default application logger

[[handler_logfile]]
class=handlers.RotatingFileHandler
args="('/tmp/myapp_ui.log', 'a', 10485760, 10, 'UTF-8')"  # Specific log file
formatter=simple

[[formatter_simple]]
format=%(levelname)s: %(message)s

Creating other loggers

Any other general loggers can be created the same way, by just giving a full absolute namespace.

For example the SQLAlchemy documentation states the name of the SQL query logger is sqlalchemy.engine. So this logging configuration will display all the generated SQL queries:

[logging]
[[loggers]]
keys=sa

[[handlers]]
keys=stderr

[[formatters]]
keys=multilines

[[logger_sa]]
qualname=sqlalchemy.engine   # The absolute SQLAlchemy logger namespace
level=INFO
handlers=stderr

[[handler_stderr]]
class=StreamHandler
args=(sys.stderr,)
formatter=multilines

[[formatter_multilines]]
format={ %(message)s }