Package: alchemy-logging
Alchemy Logging (alog) - PythonThealogframework provides tunable logging with easy-to-use defaults and power-user capabilities. The mantra ofalogis"Log Early And Often". To accomplish this goal,alogmakes it easy to enable verbose logging at develop/debug time and trim the verbosity at production run time.SetupTo use thealogmodule, simply install it withpip:pipinstallalchemy-loggingChannels and LevelsThe primary components of the framework arechannelsandlevelswhich allow for each log statement to be enabled or disabled when appropriate.Levels: Each logging statement is made at a specific level. Levels provide sequential granularity, allowing detailed debugging statements to be placed in the code without clogging up the logs at runtime. The sequence of levels and their general usage is as follows:off: Disable the given channel completelyfatal: A fatal error has occurred. Any behavior after this statement should be regarded as undefined.error: An unrecoverable error has occurred. Any behavior after this statement should be regarded as undefined unless the error is explicitly handled.warning: A recoverable error condition has come up that the service maintainer should be aware of.info: High-level information that is valuable at runtime under moderate load.trace: Used to log begin/end of functions for debugging code paths.debug: High-level debugging statements such as function parameters.debug1: High-level debugging statements.debug2: Mid-level debugging statements such as computed values.debug3: Low-level debugging statements such as computed values inside loops.debug4: Ultra-low-level debugging statements such as data dumps and/or statements inside multiple nested loops.Channels: Each logging statement is made to a specific channel. Channels are independent of one another and allow for logical grouping of log messages by functionality. A channel can be any string. A channel may have a specificlevelassigned to it, or it may use the configured default level if it is not given a specific level filter.Using this combination ofChannelsandLevels, you can fine-tune what log statements are enabled when you run your application under different circumstances.UsageConfigurationimportalogif__name__=="__main__":alog.configure(default_level="info",filters="FOO:debug,BAR:off")In this example, the channel"FOO"is set to thedebuglevel, the channel"BAR"is fully disabled, and all other channels are set to use theINFOlevel.In addition to the above, theconfigurefunction also supports the following arguments:formatter: May be"pretty","json", or any class derived fromAlogFormatterBasethread_id: Bool indicating whether or not to include a unique thread ID with the logging header (pretty) or structure (json).handler_generator: This allows users to provide their own output handlers and replace the standard handler that sends log messages tostderr. Seetheloggingdocumentationfor details.Logging FunctionsFor each log level, there are two functions you can use to create log lines: The standardloggingpackage function, or the correspondingalog.use_channel(...).<level>function. The former will always log to therootchannel while the later requires that
a channel string be specified viause_channel().importalogimportloggingdeffoo(age):alog.use_channel("FOO").debug3("Debug3 line on the FOO channel with an int value%d!",age)logging.debug("debug line on the MAIN channel")Channel LogIn a given portion of code, it often makes sense to have a common channel that is used by many logging statements. Re-typing the channel name can be cumbersome and error-prone, so the concept of theChannel Loghelps to eliminate this issue. To create a Channel Log, call theuse_channelfunction. This gives you a handle to a channel log which has all of the same standard log functions as the top-levelalog, but without the requirement to specify a channel. For example:importaloglog=alog.use_channel("FOO")deffoo(age):log.info("Hello Logging World! I am%dyears old",age)NOTE: In this (python) implementation, this is simply a wrapper aroundlogging.getLogger()Extra Log InformationThere are several other types of information thatalogsupports adding to log records:Log CodesThis is an optional argument to all logging functions which adds a specified code to the record. It can be useful for particularly high-profile messages (such as per-request error summaries in a server) that you want to be able to track in a programmatic way. The only requirement for alog_codeis that it begin with<and end with>. The log code always comes before themessage. For example:ch=alog.use_channel("FOO")ch.debug("<FOO80349757I>","Logging is fun!")Dict DataSometimes, it's useful to log structured key/value pairs in a record, rather than a plain-text message, even when using theprettyoutput formatter. To do this, simply use adictin place of astrin themessageargument to the logging function. For example:ch=alog.use_channel("FOO")ch.debug({"foo":"bar"})When adictis logged with thejsonformatter enabled, all key/value pairs are added as key/value pairs under the top-levelmessagekey.Log ContextsOne of the most common uses for logging is to note events when a certain block of code executes. To facilitate this,aloghas the concept of log contexts. The two primary contexts thatalogsupports are:ContextLog: Thiscontextmanagerlogs aSTART:message when the context starts and anEND:message when the context ends. All messages produced within the same thread inside of the context will have an incremented level of indentation.importalogalog.configure("debug2")log=alog.use_channel("DEMO")withalog.ContextLog(log.info,"Doing some work"):log.debug("Deep in the muck!")2021-07-29T19:09:03.819422 [DEMO :INFO] BEGIN: Doing some work
2021-07-29T19:09:03.820079 [DEMO :DBUG]   Deep in the muck!
2021-07-29T19:09:03.820178 [DEMO :INFO] END: Doing some workContextTimer: Thiscontextmanagerstarts a timer when the context begins and logs a message with the duration when the context ends.importalogimporttimealog.configure("debug2")log=alog.use_channel("DEMO")withalog.ContextTimer(log.info,"Slow work finished in: "):log.debug("Starting the slow work")time.sleep(1)2021-07-29T19:12:00.887949 [DEMO :DBUG] Starting the slow work
2021-07-29T19:12:01.890839 [DEMO :INFO] Slow work finished in: 0:00:01.002793Function DecoratorsIn addition to arbitrary blocks of code that you may wish to scope or time, a very common use case for logging contexts is to provide function tracing. To this end,alogprovides two useful function decorators:@logged_function: Thisdecoratorwraps theContextLogand provides aSTART/ENDscope where the message is prepopulated with the name of the function.importalogalog.configure("debug")log=alog.use_channel("DEMO")@alog.logged_function(log.trace)deffoo():log.debug("This is a test")foo()2021-07-29T19:16:40.036119 [DEMO :TRCE] BEGIN: foo()
2021-07-29T19:16:40.036807 [DEMO :DBUG]   This is a test
2021-07-29T19:16:40.036915 [DEMO :TRCE] END: foo()@timed_function: Thisdecoratorwraps theContextTimerand performs a scoped timer on the entire function.importalogimporttimealog.configure("debug")log=alog.use_channel("DEMO")@alog.timed_function(log.trace)deffoo():log.debug("This is a test")time.sleep(1)foo()2021-07-29T19:19:47.468428 [DEMO :DBUG] This is a test
2021-07-29T19:19:48.471788 [DEMO :TRCE] 0:00:01.003284TipVisual Studio Code (VSCode) users can take advantage ofalchemy-logging extensionthat provides automatic log code generation and insertion.