Package: sidetrack
SidetrackSidetrackprovides a simple interface for writing log messages in Python programs.  Calls to the log functions can be left in your code to let users produce debug logs in the field; if performance matters, using certain coding idioms and turning on Python optimization will cause log statements to be compiled out.Table of contentsIntroductionInstallationUsageGetting helpContributingLicenseAuthors and historyAcknowledgmentsIntroductionIDEs are great for debugging and tracing execution of your code, but they can't be used in all situations.  For example, if your code is executing on multiple remote computers, or you have released a program to general users and you would like them to send you a debug log/trace of execution, using an IDE at run time may be impractical or impossible.  Logging packages such asloggingare made for these situations; you can insert logging statements in your code and use the output to understand what is happening as well as for software telemetry and other purposes.  However, setting up Pythonloggingor most similar packages is (IMHO) complicated and verbose if you don't need all its features.Sidetrack(Simpledebugtracing package) offers a simple API that lets you turn on logging, set the output destination (which can be stdout), and sprinklelog(f'my message and my {variable} value')throughout your code.  Moreover, it is carefully written so that you can cause thelogcalls to beoptimized out completelyif your run Python with the-Ooption and you prefix yourlogcalls withif __debug__.  This leads to the following style of using Sidetrack:...foriteminitem_list:if__debug__:log(f'getting data for{item}')...When running with-O, thelogstatement in the loop will not simply be a no-op function call: Python willcompletely discard the conditional block, as if the code did not exist.  This is as optimal as possible, and means that you do not have to worry about the performance costs of usinglogor evaluating its arguments.InstallationThe instructions below assume you have a Python interpreter installed on your computer; if that's not the case, please firstinstall Python version 3and familiarize yourself with running Python programs on your system.OnLinux,macOS, andWindowsoperating systems, you should be able to installsidetrackwithpip.  To installsidetrackfrom thePython package repository (PyPI), run the following command:python3 -m pip install sidetrackAs an alternative to getting it fromPyPI, you can usepipto installsidetrackdirectly from GitHub, like this:python3-mpipinstallgit+https://github.com/caltechlibrary/sidetrack.gitUsageThere are just four functions in thesidetrackpackage:set_debug: turn logging on/off, set the output destination, and configure optionslog: logs a message as-is.loglist: takes a list of messages and logs them individually, as if applyinglogto each one in turn.logf: logs a message with optional arguments; the message string can contain embeddedformatdirectives.How to import SidetrackTo take advantage of Python's optimization behavior, make sure to conditionalize all references to Sidetrack functions on the Python built-in symbol__debug__.  This includes the import statement for Sidetrack:if__debug__:fromsidetrackimportset_debug,log,logf,loglistThe fragment above illustrates another tip: to make calls to thelogfunctions as short as possible in your code, importset_debug,log,logf, andloglistdirectly using thefrom sidetrack ...approach instead of doing a plainimport sidetrack, so that you can writelog(...)instead ofsidetrack.log(...).  Believe me, your fingers and eyes will thank you!How to turn on debug loggingTo turn on logging, callset_debug(...)at least once in your code.  Often, this will most convenient if combined with a command-line argument to your program, so that debug tracing can be enabled or disabled at run-time.  The following example shows the basic usage.if__debug__:if...someconditionofyourchoosing...:set_debug(True)else:print('Python -O is in effect, so debug logging is not available.')The above will turn on debug logging and send output to the default destination, which is the standard error stream (sys.stderr).  To send the output to a different destination, use the optional second argumentdebug_output, which can be either a file name, a stream, or the dash symbol (-); the latter indicates the destination should be the default (i.e.,sys.stderr).  Here is an example:if__debug__:set_debug(True,dest='/tmp/debug.txt')The functionset_debugalso accepts another optional argument,show_package, that causes eachlog,loglist, andlogfmessage to be prefixed with the name of the Python package containing the source file where the call to the log function is used.  This is very helpful when Sidetrack is used in multiple packages.if__debug__:set_debug(True,show_package=True)Finally, the functionset_debug(...)also accepts one more optional argument,extra, that lets you prefix every output line with extra text of your choosing.  Theextratext string can containPython logging system % formatting strings.  For example, the process ID can be inserted by passing'%(process)d'as in the following example:if__debug__:set_debug(True,debug_output,extra='%(process)d')If your program uses threads, you may find the use ofextra = "%(threadName)s"helpful.How to calllog,loglist, andlogfThe functionlogis the most basic function in Sidetrack. Call it with a single argument, the message to be printed:if__debug__:log("I'm right here!")If you want to print multiple strings, it's certainly possible to calllogmultiple times consecutively, but in some situations, it may be more convenient to callloglist– especially if the strings are generated dynamically as in the following example:if__debug__:loglist(f'{var}={value}'forvar,valueinsettings())Finally, there are situations where f-strings cannot be used due to how they are evaluated at run time or due tocertain inherent limitations. For those situations, Sidetrack provides thelogffunction. It accepts one argument, a string, and any number of optional arguments.  Here's an example:if__debug__:logf('exception (failure #{}):{}',failures,str(ex))Internally,logfappliesformatto the string and passes any remaining arguments as the arguments toformat.  In other words, it is essentially the following pseudocode:deflogf(msg,*other_args):final_text=msg.format(*other_args)write_log(final_text)When usinglogf, beware of including references to variables thatmightexpand at run time to contain characters that have special meaning to Python'sformatcommand, such as the{character.Understanding the outputIn all cases, each line of the output has the following form:<package>extrafilename:linenofunction()--messagewherepackageandextraare optional and controlled by the argumentsshow_packageandextra, respectively, toset_debug(...), and the remaining values are always printed: the file name, line number and function where the call to thelog,loglist, orlogfwas made, and the message.  Examples are shown in the next section.Tips for using SidetrackThroughout the rest of your code, in places where it's useful, add calls to the log functions.  Here's a simple contrived example, taken from thedemonstration programsupplied with Sidetrack:if__debug__:log('=== demo program starting ===')print('Looping my loopy loop:')foriinrange(0,3):if__debug__:log(f'loop value{i}')print('Another go-around the loop')print('Done looping.')if__debug__:log('=== demo program stopping ===')With the code above, if debugging isnotturned on,orthe program is running withPython optimization turned on, the output will be:Looping my loopy loop:
Another go-around the loop
Another go-around the loop
Another go-around the loop
Done looping.With debugging turned on and the destination set to-, the output becomes:demo_debug.py:32 main() -- === demo program starting ===
Looping my loopy loop:
demo_debug.py:36 main() -- loop value 0
Another go-around the loop
demo_debug.py:36 main() -- loop value 1
Another go-around the loop
demo_debug.py:36 main() -- loop value 2
Another go-around the loop
Done looping.
demo_debug.py:40 main() -- === demo program stopping ===Being able to send the debug output to a file becomes useful when dealing with longer and more complicated programs – it makes it possible to store a detailed trace without cluttering the output as it is in the sample above.  File output can also be useful for deployed code: you can leave the debug functionality in your code and instruct your users to turn on debugging with output directed to a file, then send you the file so you can debug problems more easily.How to run the demo programIn thetestssubdirectory, there is a simple demonstration program illustrating the use of Sidetrack.  To run it, on Linux and macOS systems, you can start a terminal shell and run the following commands:python3tests/demo_debug.py-hTo run it with debug logging enabled, use the-dcommand-line option (where the output in this example is given as-, which means to send the output to the terminal):python3tests/demo_debug.py-d-To see the difference when Python optimization is active, add the-Ooption to the Python interpreter:python3-Otests/demo_debug.py-d-Getting helpIf you find an issue, please submit it inthe GitHub issue trackerfor this repository.ContributingWe would be happy to receive your help and participation with enhancingsidetrack!  Please visit theguidelines for contributingfor some tips on getting started.LicenseSoftware produced by the Caltech Library is Copyright (C) 2020, Caltech.  This software is freely distributed under a BSD/MIT type license.  Please see theLICENSEfile for more information.Authors and historyI developed the first version of this code while implementingSpiral.  I started using the code in essentially every Python software package I have written since then, first by copy-pasting the code (which was initially very short) and eventually creating a single-file module (nameddebug.py).  This was obviously a suboptimal approach.  Finally, in 2020, I decided it was time to break it out into a proper self-contained Python package.AcknowledgmentsThis work was funded by the California Institute of Technology Library.Thevector artworkof a document with a line break, used as the icon for this repository, was created byiconixarfrom the Noun Project.  It is licensed under the Creative CommonsCC-BY 3.0license.