The Action

Actions are Lithoxyl’s primary interface for instrumenting your application. Actions are created with a Logger instance, and are used to wrap functions and code blocks.

At their most basic, Actions have a:

  • name - A string description of the behavior being wrapped.
  • level - An indicator of the importance of the action (debug, info, critical).
  • status - The state of the action (begin, success, failure, exception).
  • duration - The time between the begin and end events of a completed action, i.e., the time between entering and exiting a code block.

To track this information, Lithoxyl wraps important pieces of your application in microtransactions called Actions:

with log.info('user creation', username=name) as act:
    succeeded = _create_user(name)
    if not succeeded:
        act.failure()

This pattern is using an info-level Action as a context manager. The indented part of the code after the with statement is the code block managed by the Action. Here is how the basics of the Action are populated in our example:

  • name - “user creation”
  • level - INFO
  • status - failure if _create_user(name) returns a falsey value, exception if it raises an exception, otherwise defaults to success.
  • duration - Set automatically, duration is the time difference
    from before the execution of the first line of the code block to after the execution of the last line in the code block, or the r.failure() call, depending on the outcome of _create_user(name).

There’s quite a bit going on, but Lithoxyl has several tricks that let it flow with the semantics of applications. First, let’s learn a bit about these attributes, starting with the Action level.

Action level

Levels are a basic indicator of how important a block of application logic is. Lithoxyl has three built-in levels. In order of increasing importance:

  • debug - Of interest to developers. Supplementary info for when something goes wrong.
  • info - Informational. Can be helpful to know even when there are no problems.
  • critical - Core functionality. Essential details at all times.

When instrumenting with Lithoxyl, the developer is always asking, how significant is the success of this code block, how catastrophic is a failure in this function?

It’s only natural that instrumented code will start with more critical actions. The most important parts should be instrumented first. Eventually the instrumentation spreads to lower levels.

Note

As a general tendency, as code gets closer to the operating system, the corresponding Action also gets a lower level. High-level operations get higher levels of Actions. Start high and move lower as necessary.

Action status

The Lithoxyl Action has an eventful lifetime. Even the most basic usage sees the Action going from creation to beginning to one of the ending states: success, failure, or exception.

First, simply creating an Action does not “begin” it. An action begins when it is entered with a with statement, as we saw in the example above. Entering an action creates a timestamp and makes it the parent of future actions, until it is ended.

There are three end statuses:

  • success - The action described by the action completed without issue. This is the automatic default when no exception is raised.
  • failure - The action did not complete successfully, and the failure was expected and/or handled within the application.
  • exception - The action terminated unexpectedly, likely with a Python exception. This is the automatic default when an exception is raised within an action context manager.

The split between failure and exception should be familiar to users of standard testing frameworks like py.test. Test frameworks distinguish between a test that fails and a test that could not be fully run because the test code raised an unexpected exception. Lithoxyl brings these semantics into an application’s runtime instrumentation.

Note

If an action is manually set to complete with success() or failure(), and an unexpected exception occurs, the Action will end with the exception status.

Action API

Actions are usually constructed through Loggers, but it can help to know the underlying API and see the obvious parallels.

class lithoxyl.action.Action(logger, level, name, data=None, reraise=True, parent=None, frame=None)[source]

The Action type is one of the core Lithoxyl types, and the key to instrumenting application logic. Actions are usually instantiated through convenience methods on Logger instances, associated with their level (e.g., critical()).

Parameters:
  • logger – The Logger instance responsible for creating and publishing the Action.
  • level – Log level of the Action. Generally one of DEBUG, INFO, or CRITICAL. Defaults to None.
  • name (str) – A string description of some application action.
  • data (dict) – A mapping of non-builtin fields to user values. Defaults to an empty dict ({}) and can be populated after Action creation by accessing the Action like a dict.
  • reraise (bool) – Whether or not the Action should catch and reraise exceptions. Defaults to True. Setting to False will cause all exceptions to be caught and logged appropriately, but not reraised. This should be used to eliminate try/except verbosity.
  • frame – Frame of the callpoint creating the Action. Defaults to the caller’s frame.

Most of these parameters are managed by the Actions and respective Logger themselves. While they are provided here for advanced use cases, usually only the name and raw_message are provided.

Actions are dict-like, and can be accessed as mappings

and used to store additional structured data:

>>> action['my_data'] = 20.0
>>> action['my_lore'] = -action['my_data'] / 10.0
>>> from pprint import pprint
>>> pprint(action.data_map)
{'my_data': 20.0, 'my_lore': -2.0}
exception(message=None, *a, **kw)[source]

Mark this Action as having had an exception. Also sets the Action’s message template similar to Action.success() and Action.failure().

Unlike those two attributes, this method is rarely called explicitly by application code, because the context manager aspect of the Action catches and sets the appropriate exception fields. When called explicitly, this method should only be called in an except block.

failure(message=None, *a, **kw)[source]

Mark this Action failed. Also set the Action’s message template. Positional and keyword arguments will be used to generate the formatted message. Keyword arguments will also be added to the Action’s data_map attribute.

get_elapsed_time()[source]

Simply get the amount of time that has passed since begin was called on this action, or 0.0 if it has not begun. This method has no side effects.

success(message=None, *a, **kw)[source]

Mark this Action successful. Also set the Action’s message template. Positional and keyword arguments will be used to generate the formatted message. Keyword arguments will also be added to the Action’s data_map attribute.

Action concurrency

TODO