Advanced guide.¶
In this guide, we will cover more advanced uses of ObjLog, such as custom LogMessage types, Logging Python Exceptions, and catching logged errors.
Custom LogMessage types.¶
You can create custom LogMessage types by subclassing the LogMessage class.
they have two attributes that must be defined for them to work properly:
level: The level of the message. This is a string, and can be any value you want.color: The color of the message. This is prefixed before the message, and is supposed to be an ansi color code.
Here is an example of a custom LogMessage type:
from objlog import LogMessage
class CustomLogMessage(LogMessage):
level = "custom"
color = "\033[35m"
it’s exactly the same as the built-in LogMessage types but with a different level and color.
# extends the code from above.
from objlog import LogNode
log = LogNode("my logger")
log.log(CustomLogMessage("Hello, world!"))
Interacting with the LogNode¶
You can interact with the LogNode in a few ways.
Getting logged messages¶
You can get the messages that have been logged to the LogNode by using the get method.
from objlog import LogNode
from objlog.LogMessages import Info
log = LogNode("my logger")
log.log(Info("Hello, world!"))
print(log.get()) # prints: [Info("Hello, world!")]
you can also filter what types of messages you want to get by passing the specified types to the get method.
log.log(Info("Hello, world!"))
log.log(Debug("Hello, world!"))
log.log(Warn("Hello, world!"))
print(log.get(Info, Debug)) # prints: [Info("Hello, world!"), Debug("Hello, world!")]
Clearing logged messages¶
You can clear the messages that have been logged to the LogNode by using the wipe_messages method.
log.log(Info("Hello, world!"))
print(log.get()) # prints: [Info("Hello, world!")]
log.wipe_messages()
print(log.get()) # prints: []
keep in mind this will not clear any log files that are being logged to, to do that you can either set the parameter wipe_logfiles to True when calling the wipe_messages method, or you can call the clear_log method if you do not want to wipe the memory.
log.log(Info("Hello, world!"))
print(log.get()) # prints: [Info("Hello, world!")]
log.wipe_messages(wipe_logfiles=True)
print(log.get()) # prints: []
# or
log.log(Info("Hello, world!"))
print(log.get()) # prints: [Info("Hello, world!")]
log.clear_log()
print(log.get()) # prints: [Info("Hello, world!")] as it did not wipe memory.
it also works with retrieving python exceptions of certain types (more on that later).
log.log(ImportError("Hello, world!"))
print(log.get(ImportError)) # prints: [PythonExceptionMessage("Hello, world!")]
checking for types of messages¶
You can check if a certain type of message has been logged to the LogNode by using the has method.
log.log(Info("Hello, world!"))
print(log.has(Info)) # prints: True
print(log.has(Debug)) # prints: False
if you want to find if you have a specific kind of python exception, you can pass the exception type to the has
method.
log.log(ImportError("Hello, world!"))
print(log.has(ImportError)) # prints: True
print(log.has(ValueError)) # prints: False
it even works with both combined.
log.log(Info("Hello, world!"))
log.log(ImportError("Hello, world!"))
print(log.has(Info, ImportError)) # prints: True
filtering messages (in place)¶
You can filter the messages that have been logged to the LogNode by using the filter method.
log.log(Info("Hello, world!"))
log.log(Debug("Hello, world!"))
log.log(Warn("Hello, world!"))
log.filter(Info, Debug)
print(log.get()) # prints: [Info("Hello, world!"), Debug("Hello, world!")]
optionally, you can filter logfiles as well by setting the filter_logfiles parameter to True.
log.log(Info("Hello, world!"))
log.log(Debug("Hello, world!"))
log.log(Warn("Hello, world!"))
log.filter(Info, Debug, filter_logfiles=True)
print(log.get()) # prints: [Info("Hello, world!"), Debug("Hello, world!")]
Logging Python Exceptions¶
You can log Python exceptions by using the log method with an exception instead of a LogMessage.
however, when getting the exception from the LogNode, it will be wrapped in a PythonExceptionMessage object. which is a subclass of LogMessage.
to get the original exception, you can use the .exception attribute of the PythonExceptionMessage object.
log.log(ImportError("Hello, world!"))
log.get() # returns: [PythonExceptionMessage("Hello, world!")]
log.get()[0].exception # returns: ImportError("Hello, world!")
Catching Real python exceptions¶
logging python exceptions is great, but what if you want to catch them when they happen?
you can do it in two ways, try/except, or by using the @monitor decorator.
from objlog import LogNode
from objlog.utils import monitor
log = LogNode("my logger")
try:
1 / 0
except ZeroDivisionError as e:
log.log(e) # logs the exception
@monitor(log)
def my_function():
1 / 0
my_function() # logs the exception to LogNode 'log' when it occurs
@monitor decorator¶
The @monitor decorator is a decorator in the utils subpackage that logs any exceptions that occur in the function it is decorating.
it has a few parameters:
log: The LogNode to log the exceptions to. This is required.raise_exceptions: Whether to raise the exception after logging it. This is optional, and defaults to False.exit_on_exception: Whether to exit the program after logging the exception. This is optional, and defaults to False. It also completely ignores theraise_exceptionsparameter, regardless of its value.
exit_on_exception¶
exit on exception is useful for when you want to log an exception and then exit the program in user-facing code.
however, it is not recommended to use it in library code, as it makes debugging harder.
@monitor(log, exit_on_exception=True)
def my_function():
1 / 0
my_function() # logs the exception to LogNode 'log' when it occurs, and then exits the program.
exit on exception acts differently depending on where the lognode outputs to.
if the lognode outputs to a file and doesn’t print, it will log the exception and location to where the exception occurred, and then exit the program printing a message like “An exception occurred: (exception message) please check the log file for more information.”
however, if the lognode outputs to the console, it will not print any extra info, and you will see the exception message printed to the console (assuming it’s in the print list).
if the LogNode does not output to a file, it will print the whole traceback to the console.
raise_exceptions¶
raise exceptions is useful for when you want to log an exception and then raise it.
@monitor(log, raise_exceptions=True)
def my_function():
1 / 0
my_function() # logs the exception to LogNode 'log' when it occurs, and then raises a ZeroDivisionError.
raise exceptions does not act differently depending on where the lognode outputs to.
it will always raise the exception after logging it.
it won’t do anything extra, it will just raise the exception.
Saving LogNodes for later use¶
You may have noticed that after a LogNode is closed, the rich versions of all the messages are lost,
the only things that remains are the strings within the log files.
If you want preserve the lognode so that you can use the rich versions of the messages later, you can use the save method to save the lognode to a .lgnd file.
log = LogNode("my logger")
log.log(Info("Hello, world!"))
del log # message is lost
log = LogNode("my logger")
log.log(Info("Hello, world!"))
log.save("my_lognode") # messages and lognode saved to the file 'my_lognode.lgnd'
del log # lognode no longer exists in program, but is saved in file.
you can then load the lognode back into the program by using the load function from the objlog package.
from objlog import load
log = load("my_lognode.lgnd") # loads the lognode back into the program.
log.get() # returns: [Info("Hello, world!")]
using this method, you can save lognodes for later use, and even share the rich log messages with others.
Disabling logging temporarily¶
You can disable logging temporarily by using the enable() and disable() methods of the LogNode
or by changing the enabled attribute directly.
This will change the behavior of the log() method to do nothing if enabled is False.
To re-enable logging, simply set enabled back to True.
you can use this to disable logging when a verbose mode in your program is not enabled.
log = LogNode("my logger")
log.disable() # disable logging
# or: log.enabled = False
log.log(Info("This will not be logged")) # does nothing and returns None.
log.log(Debug("This will also not be logged"), verbose=True) # does nothing and returns None, even though verbose is True.
log.enable() # enable logging
# or: log.enabled = True
log.log(Info("This will be logged")) # logs the message and returns None (unless in verbose mode).
Asynchronous logging¶
You can enable asynchronous logging by setting the asynchronous parameter to True when creating the LogNode.
In asynchronous mode, calls to log() and other write operations are offloaded to a background thread, making them non-blocking.
from objlog import LogNode
from objlog.LogMessages import Info
log = LogNode("my logger", asynchronous=True)
log.log(Info("this message is queued and logged in the background"))
# functions that read from the LogNode (like get() or has()) will automatically wait for pending
# operations to finish before returning.
# you can also wait explicitly using await_finish():
log.await_finish()
# IMPORTANT: always call await_finish() before the program exits, or some queued messages may be lost.
You can also switch a LogNode between synchronous and asynchronous mode at runtime by setting the asynchronous property:
log = LogNode("my logger")
log.asynchronous = True # start the background thread and switch to async mode
log.asynchronous = False # wait for the queue to drain, then stop the background thread
Conclusion¶
That’s it for the advanced guide. You should now have a good understanding of how to use ObjLog in more advanced ways.
for the complete API reference, see the API reference.