7

I'm currently working on 1.0.0 release of pyftpdlib module. This new release will introduce some backward incompatible changes in that certain APIs will no longer accept bytes but unicode. While I'm at it, as part of this breackage, I was contemplating the possibility to get rid of my logging functions, which currently use the print statement, and use the logging module instead.

As of right now pyftpdlib delegates the logging to 3 functions:

def log(s):
   """Log messages intended for the end user."""
   print s

def logline(s):
   """Log commands and responses passing through the command channel."""
   print s

def logerror(s):
   """Log traceback outputs occurring in case of errors."""
   print >> sys.stderr, s

The user willing to customize logs (e.g. write them to a file) is supposed to just overwrite these 3 functions as in:

>>> from pyftpdlib import ftpserver
>>>
>>> def log2file(s):
...        open('ftpd.log', 'a').write(s)
...
>>> ftpserver.log = ftpserver.logline = ftpserver.logerror = log2file

Now I'm wondering: what benefits would imply to get rid of this approach and use logging module instead? From a module vendor perspective, how exactly am I supposed to expose logging functionalities in my module? Am I supposed to do this:

import logging
logger = logging.getLogger("pyftpdlib")

...and state in my doc that "logger" is the object which is supposed to be used in case the user wants to customize how logs behave? Is it legitimate to deliberately set a pre-defined format output as in:

FORMAT = '[%(asctime)] %(message)s'
logging.basicConfig(format=FORMAT)
logger = logging.getLogger('pyftpdlib')

...?

Can you think of a third-party module I can take cues from where the logging functionality is exposed and consolidated as part of the public API?

Thanks in advance.

Giampaolo Rodolà
  • 12,488
  • 6
  • 68
  • 60

4 Answers4

4

libraries (ftp server or client library) should never initialize the logging system. So it's ok to instantiate a logger object and to point at logging.basicConfig in the documentation (or provide a function along the lines of basicConfig with fancier output and let the user choose among his logging configuration strategy, plain basicConfig or library provided configuration)

frameworks (e.g. django) or servers (ftp server daemon) should initialize the logging system to a reasonable default and allow for customization of logging system configuration.

paolo_losi
  • 283
  • 1
  • 7
2

Typically libraries should just create a NullHandler handler, which is simply a do nothing handler. The end user or application developer who uses your library can then configure the logging system. See the section Configuring Logging for a Library in the logging documentation for more information. In particular, see the note which begins

It is strongly advised that you do not add any handlers other than NullHandler to your library's loggers.

In your case I would simply create a logging handler, as per the logging documentation,

import logging
logging.getLogger('pyftpdlib').addHandler(logging.NullHandler())

Edit The logging implementation sketched out in the question seems perfectly reasonable. In your documentation just mention logger and discuss or point users to the logging.setLevel and logging.setFormatter methods for customising the output from your library. Rather than using logging.basicConfig(format=FORMAT) you could consider using logging.config.fileConfig to manage the settings for your output and document the configuration file somewhere in your documentation, again pointing the user to the logging module documentation for the format expected in this file.

Chris
  • 44,602
  • 16
  • 137
  • 156
  • Does that mean no output will be produced by default? In that case I'm not happy with it. – Giampaolo Rodolà May 21 '12 at 12:53
  • This won't produce any output by default. Also, `logging.NullHandler` is only available in Python 2.7 (and > 3.2 I think), which may be a problem. If you just use `logger = logging.getLogger('pyftpdlib')` you will get messages by default and you can just point your users to the `logging.setLevel` and `logging.setFormatter` documentation to demonstrate how to configure the output messages for your library. Also, you can use the file configuration [`logging.config.fileConfig`](http://docs.python.org/dev/library/logging.config.html#logging.config.fileConfig) to allow users to configure the logger – Chris May 21 '12 at 13:36
  • Also, to answer one of your original questions, the benefits of using logging rather than print statements are briefly mentioned in [PEP 282](http://www.python.org/dev/peps/pep-0282/): *If a single logging mechanism is enshrined in the standard library, 1) logging is more likely to be done 'well', and 2) multiple libraries will be able to be integrated into larger applications which can be logged reasonably coherently.* – Chris May 21 '12 at 13:40
  • 1
    Last update about my concern regarding the fact that no output is produced by default. That is based on the assumption that the *user* is supposed to call logging.basicConfig() in its own code and if he/she forgets to do so no logging is provided. I solved that by adding this "if not logging.getLogger().handlers: logging.basicConfig()" in the module's "start()" method. That way, even if the user has not configured logging, the modules decides to do it anyway in order to produce logs. – Giampaolo Rodolà Oct 15 '12 at 08:38
0

Here is a resource I used to make a customizable logger. I didn't change much, I just added an if statement, and pass in whether or not I want to log to a file or just the console.

Check this Colorer out. It's really nice for colorizing the output so DEBUG looks different than WARN which looks different than INFO.

The Logging module bundles a heck of a lot of nice functionality, like SMTP logging, file rotation logging (so you can save a couple old log files, but not make 100s of them every time something goes wrong).

If you ever want to migrate to Python 3, using the logging module will remove the need to change your print statements.

Logging is awesome depending on what you're doing, I've only lightly used it before to see where I am in a program (if you're running this function, color this way), but it has significantly more power than a regular print statement.

Community
  • 1
  • 1
MercuryRising
  • 892
  • 1
  • 7
  • 15
0

You can look at Django (just create a sample project) and see how it initialize logger subsystem.

There is also a contextual logger helper that I've written some time ago - this logger automatically takes name of module/class/function is was initialized from. This is very useful for debug messages where you can see right-through that module spits the messages and how the call flow goes.

Community
  • 1
  • 1
Zaar Hai
  • 9,152
  • 8
  • 37
  • 45