Header Image (C) Tai Kedzierski
Goto Snippet
This post is opinionated.
Python's default log setup is unhelpful; it works against the "batteries included" approach we have come to expect.
From a useful log message, I want to know when, what level, and what information. I may want it on console, I may want it in a file.
This should be simple - but in Python I end up every time having to look up how to create a full logging utility with custom file handling and string formatting.
It should be as simple as logger = getLogger(), but the default behaviour for some unknown reason is to provide a completely useless formatting, and no shorthand for a sensible default.
That or I need to download some pip package of unknown provenance, trust that it hasn't been name-hijacked, or doing some obfuscated exfilration. The leftpad incident from 2016 comes to mind, as well as the Revival hijack attack from 2024 which was essentially the same problem in a different repo system.
In fact, any user-repo without namespacing is vulnerable to this: Node's npm, Python's pip, Arch's AUR, Canonical's snap ... to name a handful who just let users upload whatever. Even namespacing isn't a guarantee of trust - I've come across projects that distribute their software through these channels not through the project's name, but via some arbitrary dev's monicker, raising doubt as to the authenticity of the package. I gave my thought process on how to decide on whether to trust a source in a previous post on using syncthing in a work environment.
External dependencies in user-controlled repos are the devil, and should only be considered when the solution to a problem is complex. And in general, simple solutions should just exist directly in the code base - ideally self-written, but sometimes the problem just strafes into the "cumbersome enough" space to make a dependency feel both reasonable and icky.
The answer: write it once, stash it away in a Github gist or in a "useful snippets" repo of your own. Copy and paste.
Copy Paste? Ew!
"Copy and paste" of code probably sends alarm bells ringing for any seasoned coder. "Don't repeat yourself," "use a package manager," "write once, update everywhere." These are good instincts to have, but case-by-case, it is also good to know when copy-paste is preferable.
In this case, the requirement is to avoid unnecessary external dependencies for a simple solution to a simple need . In leftpad as with this mini-logger, the required code snippet is short and easy to understand ; it is no loss to reimplement if needed. It is also appropriately licensed (yes, it may be just a snippet; it remains however recommendable to ensure that what you are copying is indeed allowable. Be wary of copying random blobs of code.)
I include below a code snippet for a mini-logger utility which allows for a single call with minimal configuration:
from minilog import SimpleLogger LOG = SimpleLogger(name="mylog", level=SimpleLogger.INFO) LOG.info("this is useful")
Which prints to console:
2024-11-20 10:43:44,567 | INFO | mylog : this is useful
Copy this into a minilogger.py file in your project. Tada - no external dependency needed. Left untouched, it will remain the same forever. No name hijacking. No supply-chain injection.
# For completeness: # (C) Tai Kedzierski - Provided under MIT license. Go wild. import logging class SimpleLogger(logging.Logger): FORMAT_STRING = '%(asctime)s | %(levelname)s | %(name)s : %(message)s' ERROR = logging.ERROR WARN = logging.WARN INFO = logging.INFO DEBUG = logging.DEBUG def __init__(self, name="main", fmt_string=FORMAT_STRING, level=logging.WARNING, console=True, files=None): logging.Logger.__init__(self, name, level) formatter_obj = logging.Formatter(fmt_string) if files is None: files = [] elif isinstance(files, str): files = [files] def _add_stream(handler:logging.Handler, **kwargs): handler = handler(**kwargs) handler.setLevel(level) handler.setFormatter(formatter_obj) self.addHandler(handler) if console is True: _add_stream(logging.StreamHandler, stream=sys.stdout) for filepath in files: _add_stream(logging.FileHandler, filename=filepath)
The MIT license essentially allows you to "do whatever you want with this." No strings attached.
There we are. A simple log ?
The above is the detailed content of Simple Python Logging - and a digression on dependencies, trust, and Copy/pasting code. For more information, please follow other related articles on the PHP Chinese website!