{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/python/no-hijack-root-logger/",
  "description": "Avoid configuring Python's root logger in libraries; use named loggers with NullHandler to let application code control logging behavior.",
  "path": "/python/no-hijack-root-logger/",
  "publishedAt": "2024-08-03T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "Python",
    "Logging"
  ],
  "textContent": "With the recent explosion of LLM tools, I often like to kill time fiddling with different\nLLM client libraries and SDKs in one-off scripts. Lately, I've noticed that some newer tools\nfrequently mess up the logger settings, meddling with my application logs. While it's less\ncommon in more seasoned libraries, I guess it's worth rehashing why hijacking the root\nlogger isn't a good idea when writing libraries or other forms of reusable code.\n\nIn Python, when I say [root logger], I mean the logger instance that logging.basicConfig\nacts on, or the one you get back when you don't specify a name in logging.getLogger(). The\nroot logger is for the application code to use and if you're a library author, you should\nprobably steer clear from it. If not, people using your code might get into situations as\nfollows.\n\nLet's say there's a single file library named lib.py that decides to configure the root\nlogger:\n\nNow, let's say the user of lib.py imports the frobnicate function and configures the\nroot logger in the following manner:\n\nSince the application code has set the log threshold to INFO, you might think that running\nthe code snippet would only print the log message from the application. But instead, you'll\nalso get the DEBUG message from the library:\n\nIt happens because before the application code had the chance to set the log level to\nINFO, the library code hijacked the root logger and configured it during the import time\nof frobnicate. You can test it by placing the from lib import frobnicate statement after\nthe logging.basicConfig(...) line in the main.py file. By doing so, the log\nconfiguration in the application code gets to run before the library has the chance to\nmeddle with it.\n\nThis makes things confusing for the library user, and the [Logging how-to] doc advises\nagainst doing so:\n\n> It is strongly advised that you do not log to the root logger in your library. Instead,\n> use a logger with a unique and easily identifiable name, such as the name for your\n> library's top-level package or module. Logging to the root logger will make it difficult\n> or impossible for the application developer to configure the logging verbosity or handlers\n> of your library as they wish.\n\nSolving this is quite straightforward. Avoid using the root logger in your library code.\nInstead, instantiate your own logger instance and configure it with your heart's content.\nThis way, your users get to keep using the root logger as they like, and they can also tap\ninto the library's log messages whenever they need to.\n\nHere's how to achieve that in the library:\n\nNow the library logger no longer conflicts with the application log configuration. The\napplication code in the main.py from the previous section can remain the same and running\nthe snippet will only print out the INFO message this time:\n\nThis setup also lets the application code access and adjust the library's logger to suit its\nneeds. Here's how it can be done in the main.py file:\n\nAbove, the library user sets up the root logger as usual while also reconfiguring the\nlibrary's logger. It's the library author's job to properly name and initialize the logger\nin the library code. The logger name and the default behavior should be well-documented as\nwell.\n\nThis allows the application code to retrieve and customize the logger as needed. Note that\ncalling getLogger with the same name always retrieves the same logger instance.\n\nAlso, you should avoid adding any handlers to your library's logger. Doing so can complicate\nthings for users who may want to attach their own handlers. The logging how-to guide\nstrongly warns against this:\n\n> It is strongly advised that you do not add any handlers other than NullHandler to your\n> library's loggers. This is because the configuration of handlers is the prerogative of the\n> application developer who uses your library. The application developer knows their target\n> audience and what handlers are most appropriate for their application: if you add handlers\n> ‘under the hood', you might well interfere with their ability to carry out unit tests and\n> deliver logs which suit their requirements.\n\nIf you're looking for a real-life example of how to minimally configure your library's\nlogger, check out the [httpx codebase]. The logging behavior is well-documented there.\n\nYou can easily reconfigure the httpx logger in your application code while making an HTTP\nrequest like this:\n\nRunning the script will print the DEBUG messages as follows:\n\n\n\n\n[root logger]:\n    https://docs.python.org/3/library/logging.html#:~:text=Logged%20messages%20to%20the%20module%2Dlevel%20logger%20get%20forwarded%20to%20handlers%20of%20loggers%20in%20higher%2Dlevel%20modules%2C%20all%20the%20way%20up%20to%20the%20highest%2Dlevel%20logger%20known%20as%20the%20root%20logger%3B%20this%20approach%20is%20known%20as%20hierarchical%20logging\n\n[logging how-to]:\n    https://docs.python.org/3/howto/logging.html\n\n[httpx codebase]:\n    https://github.com/search?q=repo%3Aencode%2Fhttpx%20logging&type=code",
  "title": "Please don't hijack my Python root logger"
}