Library Coordination

Application and Module with Custom Flavors

When the application installs its own truck configuration, any module configurations registered by libraries will be preserved. This allows:

  1. Applications to control global settings like trace levels.

  2. Libraries to maintain their own debug output formatting.

  3. Both to coexist without configuration conflicts.

(Example courtesy of Anthropic claude-3-7-sonnet.)

An example application which uses a library module for some analytics:

# ruff: noqa: F821

import os
import sys


# Ensure that our demonstration package is available.
sys.path.insert( 0, os.path.dirname( __file__ ) )


import ictruck

# Application installs truck with both trace levels and active flavors.
# While active flavors could be global, targeting a particular package
# generally makes more sense.
#
# Installation needs to happen before module registers itself.
ictruck.install(
    trace_levels = 2,
    active_flavors = { 'analytics': [ 'info', 'error', 'perf' ] } )

from analytics import calculate_metrics, detect_anomalies # noqa: E402


def main( ):
    ictr( 0 )( "Application running." )

    normal_data = [ 10, 12, 11, 13, 10, 14, 12, 11, 10, 13 ]
    ictr( 0 )( "Calculating metrics..." )
    metrics = calculate_metrics( normal_data )
    ictr( 1 )( metrics ) # Greater depth for more detail.

    anomaly_data = [ 10, 12, 11, 13, 10, 40, 12, 11, 10, 13 ]
    ictr( 0 )( "Detecting anomalies..." )
    anomalies = detect_anomalies( anomaly_data )

    if anomalies:
        summary = f"Found {len( anomalies )} anomalies."
        ictr( 1 )( summary )
        for position, value, z_score in anomalies:
            ictr( 2 )( position, value, z_score )

    ictr( 0 )( "Calculating metrics on empty dataset..." )
    calculate_metrics( [ ] )

    ictr( 0 )( "Application finished" )


if __name__ == "__main__": main( )

And the library module for analytics:

# ruff: noqa: F821


from typing_extensions import Any

from ictruck import register_module, FlavorConfiguration

# Register module with custom flavors.
register_module(
    flavors = {
        'info': FlavorConfiguration( prefix_emitter = "ANALYTICS INFO| " ),
        'error': FlavorConfiguration( prefix_emitter = "ANALYTICS ERROR| " ),
        'perf': FlavorConfiguration( prefix_emitter = "ANALYTICS PERF| " ) } )


def calculate_metrics( dataset: list[ int ] ) -> dict[ str, Any ]:
    ''' Calculates statistical metrics from dataset. '''
    ictr( 'info' )( "Calculating metrics..." )

    if not dataset:
        ictr( 'error' )( "Empty dataset provided." )
        return { }

    try:
        count = len( dataset )
        total = sum( dataset )
        average = total / count
        minimum = min( dataset )
        maximum = max( dataset )
        variance = sum( ( x - average ) ** 2 for x in dataset ) / count
        std_dev = variance ** 0.5
    except Exception as exc:
        ictr( 'error' )( exc )
        return { }

    metrics: dict[ str, Any ] = {
        'count': count,
        'total': total,
        'average': average,
        'minimum': minimum,
        'maximum': maximum,
        'variance': variance,
        'std_dev': std_dev,
    }

    ictr( 'info' )( "Metrics calculation complete." )
    ictr( 'perf' )( count )
    return metrics


def detect_anomalies(
    dataset: list[ int ], threshold = 2.0
) -> list[ tuple[ int, int, float ] ]:
    ''' Detects anomalies in dataset using standard deviation. '''
    ictr( 'info' )( f"Detecting anomalies with threshold {threshold}." )

    if not dataset:
        ictr( 'error' )( "Empty dataset provided." )
        return [ ]

    metrics = calculate_metrics( dataset )
    if not metrics: return [ ]

    anomalies: list[ tuple[ int, int, float ] ] = [ ]
    for i, value in enumerate( dataset ):
        z_score = abs( value - metrics[ 'average' ] ) / metrics[ 'std_dev' ]
        if z_score > threshold:
            anomalies.append( ( i, value, z_score ) )

    ictr( 'perf' )( len( anomalies ) )
    return anomalies

Running this will result in:

TRACE0| 'Application running.'
TRACE0| 'Calculating metrics...'
ANALYTICS INFO| 'Calculating metrics...'
ANALYTICS INFO| 'Metrics calculation complete.'
ANALYTICS PERF| count: 10
TRACE1| metrics: {'average': 11.6,
                  'count': 10,
                  'maximum': 14,
                  'minimum': 10,
                  'std_dev': 1.3564659966250536,
                  'total': 116,
                  'variance': 1.8399999999999999}
TRACE0| 'Detecting anomalies...'
ANALYTICS INFO| f"Detecting anomalies with threshold {threshold}.": 'Detecting anomalies with threshold 2.0.'
ANALYTICS INFO| 'Calculating metrics...'
ANALYTICS INFO| 'Metrics calculation complete.'
ANALYTICS PERF| count: 10
ANALYTICS PERF| len( anomalies ): 1
TRACE1| summary: 'Found 1 anomalies.'
TRACE2| position: 5, value: 40, z_score: 2.975954728492207
TRACE0| 'Calculating metrics on empty dataset...'
ANALYTICS INFO| 'Calculating metrics...'
ANALYTICS ERROR| 'Empty dataset provided.'
TRACE0| 'Application finished'