From your question, it’s clear that you have no problem creating the log name, log file name and entering the file, so I will keep this part simplified so that my code is short.
First: for me, your solution seems right, since logging.StreamHandler send the output to sys.stderr by default. You may have some code (not in your question) that sys.stderr , or you work your OSX code in such a way that the output in stderr is not displayed (but there really is a way out).
Logging Solution
Paste the following code in with_logging.py :
import logging import sys logformat = "%(asctime)s %(levelname)s %(module)s - %(funcName)s: %(message)s" datefmt = "%m-%d %H:%M" logging.basicConfig(filename="app.log", level=logging.INFO, filemode="w", format=logformat, datefmt=datefmt) stream_handler = logging.StreamHandler(sys.stderr) stream_handler.setFormatter(logging.Formatter(fmt=logformat, datefmt=datefmt)) logger = logging.getLogger("app") logger.addHandler(stream_handler) logger.info("information") logger.warning("warning") def fun(): logger.info(" fun inf") logger.warning("fun warn") if __name__ == "__main__": fun()
Run it: $ python with_logging.py and you will see the expected log entries (correctly formatted) in the log file app.log and on stderr too.
Note that if you do not see it on stderr , something hides your stderr output. To see something, change the stream in sys.stdout to sys.stdout .
Solution with logbook
logbook is a python package that provides alternative logging tools. I show it here to show the main difference from stdlib logging : when using a logbook using and configuring seems easier to me.
Previous solution rewritten to with_logbook.py
import logbook import sys logformat = ("{record.time:%m-%d %H:%M} {record.level_name} {record.module} - " "{record.func_name}: {record.message}") kwargs = {"level": logbook.INFO, "format_string": logformat, "bubble": True} logbook.StreamHandler(sys.stderr, **kwargs).push_application() logbook.FileHandler("app.log", **kwargs).push_application() logger = logbook.Logger("app") logger.info("information") logger.warning("warning") def fun(): logger.info(" fun inf") logger.warning("fun warn") if __name__ == "__main__": fun()
The main difference is that you do not need to attach handlers to the registrars, your registrars just emit some log entries.
These records will be processed by handlers if they are inserted into place. One method is to create a handler and call push_application() on it. This will put the handler on the stack of used handlers.
As before, we need two handlers: one for the file, the other for stderr.
If you run this script $ python with_logbook.py , you will see exactly the same results as with logging.
Separate registration setup from module code: short_logbook.py
With stdlib logging I don’t like opening dances to make the logger work. I want to just release some log entries and make it as simple as possible.
The next example is a modification of the previous one. Instead of setting up the record at the very beginning of the module, I do this right before the code runs - inside if __name__ == "__main__" (you can do the same in any other place).
For practical reasons, I split the module and call code into two files:
File funmodule.py
from logbook import Logger log = Logger(__name__) log.info("information") log.warning("warning") def fun(): log.info(" fun inf") log.warning("fun warn")
You may notice that in fact there are only two lines of code related to access to the log.
Then create the calling code placed in short_logbook.py :
import sys import logbook if __name__ == "__main__": logformat = ("{record.time:%m-%d %H:%M} {record.level_name} {record.module}" "- {record.func_name}: {record.message}") kwargs = {"level": logbook.INFO, "format_string": logformat, "bubble": True} logbook.StreamHandler(sys.stderr, **kwargs).push_application() logbook.FileHandler("app.log", **kwargs).push_application() from funmodule import fun fun()
Running the code, you will see that it works the same as before, only the registrar name will be funmodule .
Note that I did from funmodule import fun after setting up the log. If I did this on top, if the file is short_logbook.py , the first two log entries from funmodule.py would not be visible as this happens during module import.
EDIT: Added another logging solution to have fair comparison
Another stdlib logging solution
Trying to have a fair comparison of logbook and logging I rewrote the final example of logbook logging .
funmodule_logging.py looks like this:
import logging log = logging.getLogger(__name__) log.info("information") log.warning("warning") def fun(): log.info(" fun inf") log.warning("fun warn") log.error("no fun at all")
and short_logging.py looks like this:
import sys import logging if __name__ == "__main__": logformat = "%(asctime)s %(levelname)s %(module)s - %(funcName)s: %(message)s" datefmt = "%m-%d %H:%M" logging.basicConfig(filename="app.log", level=logging.INFO, filemode="w", format=logformat, datefmt=datefmt) stream_handler = logging.StreamHandler(sys.stderr) stream_handler.setFormatter(logging.Formatter(fmt=logformat, datefmt=datefmt)) logger = logging.getLogger("funmodule_logging") logger.addHandler(stream_handler) from funmodule_logging import fun fun()
The functionality is very similar.
I'm still afraid to keep a journal. stdlib logging not easy to understand, but it is located in stdlib and offers some nice things, such as logging.config.dictConfig , which allow you to configure dictionary logging. logbook was much easier to start, but at the moment it is a bit slower and lacking dictConfig. In any case, these differences are not relevant to your question.