Example
Description
Section titled “Description”Logger Type Example This example demonstrates the Logger protocol/interface for consistent logging across the AlgoKit Utils SDK. Topics covered:
- Logger protocol definition with all log levels
- Console-based Logger implementation
- No-op (silent) logger for production
- Custom formatting logger
- Compatibility with common logging patterns
Prerequisites
Section titled “Prerequisites”No LocalNet required - pure type/interface example
Run This Example
Section titled “Run This Example”From the repository’s examples directory:
cd examplesuv run python common/06_logger.py# ruff: noqa: N999, C901, PLR0912, PLR0915, PLE1205"""Logger Type Example
This example demonstrates the Logger protocol/interface for consistent loggingacross the AlgoKit Utils SDK.
Topics covered:- Logger protocol definition with all log levels- Console-based Logger implementation- No-op (silent) logger for production- Custom formatting logger- Compatibility with common logging patterns
No LocalNet required - pure type/interface example"""
import loggingfrom datetime import datetime, timezonefrom typing import Any, Protocol
from shared import ( print_header, print_info, print_step, print_success,)
# ============================================================================# Logger Protocol Definition# ============================================================================
class Logger(Protocol): """ Protocol defining the standard logging interface.
This protocol matches the Logger type from the TypeScript SDK, providing a consistent interface across both SDKs. """
def error(self, message: str, *args: Any, **kwargs: Any) -> None: """Log an error message.""" ...
def warn(self, message: str, *args: Any, **kwargs: Any) -> None: """Log a warning message.""" ...
def info(self, message: str, *args: Any, **kwargs: Any) -> None: """Log an informational message.""" ...
def verbose(self, message: str, *args: Any, **kwargs: Any) -> None: """Log a verbose/trace message.""" ...
def debug(self, message: str, *args: Any, **kwargs: Any) -> None: """Log a debug message.""" ...
# ============================================================================# Logger Implementations# ============================================================================
class ConsoleLogger: """Console-based Logger implementation using print statements."""
def error(self, message: str, *args: Any, **kwargs: Any) -> None: """Log an error message.""" self._log("ERROR", message, args, kwargs)
def warn(self, message: str, *args: Any, **kwargs: Any) -> None: """Log a warning message.""" self._log("WARN ", message, args, kwargs)
def info(self, message: str, *args: Any, **kwargs: Any) -> None: """Log an informational message.""" self._log("INFO ", message, args, kwargs)
def verbose(self, message: str, *args: Any, **kwargs: Any) -> None: """Log a verbose/trace message.""" self._log("VERB ", message, args, kwargs)
def debug(self, message: str, *args: Any, **kwargs: Any) -> None: """Log a debug message.""" self._log("DEBUG", message, args, kwargs)
def _log(self, level: str, message: str, args: tuple, kwargs: dict) -> None: """Internal logging helper.""" extra = "" if args: extra = f" {args}" if kwargs: extra += f" {kwargs}" print(f"[{level}] {message}{extra}") # noqa: T201
class NullLogger: """No-op (silent) logger that discards all messages."""
def error(self, message: str, *args: Any, **kwargs: Any) -> None: """Discard error message."""
def warn(self, message: str, *args: Any, **kwargs: Any) -> None: """Discard warning message."""
def info(self, message: str, *args: Any, **kwargs: Any) -> None: """Discard informational message."""
def verbose(self, message: str, *args: Any, **kwargs: Any) -> None: """Discard verbose message."""
def debug(self, message: str, *args: Any, **kwargs: Any) -> None: """Discard debug message."""
class TimestampLogger: """Logger with custom formatting including timestamp and prefix."""
def __init__(self, prefix: str) -> None: """Initialize with a prefix for all log messages.""" self.prefix = prefix
def _format_message(self, level: str, message: str) -> str: """Format a message with timestamp and prefix.""" timestamp = datetime.now(timezone.utc).isoformat() return f"{timestamp} [{self.prefix}] {level:<7} {message}"
def error(self, message: str, *args: Any, **kwargs: Any) -> None: """Log an error message.""" print(self._format_message("ERROR", message), args if args else "", kwargs if kwargs else "") # noqa: T201
def warn(self, message: str, *args: Any, **kwargs: Any) -> None: """Log a warning message.""" print(self._format_message("WARN", message), args if args else "", kwargs if kwargs else "") # noqa: T201
def info(self, message: str, *args: Any, **kwargs: Any) -> None: """Log an informational message.""" print(self._format_message("INFO", message), args if args else "", kwargs if kwargs else "") # noqa: T201
def verbose(self, message: str, *args: Any, **kwargs: Any) -> None: """Log a verbose message.""" print(self._format_message("VERBOSE", message), args if args else "", kwargs if kwargs else "") # noqa: T201
def debug(self, message: str, *args: Any, **kwargs: Any) -> None: """Log a debug message.""" print(self._format_message("DEBUG", message), args if args else "", kwargs if kwargs else "") # noqa: T201
# Log level priority for filteringLOG_LEVEL_PRIORITY = { "error": 0, "warn": 1, "info": 2, "verbose": 3, "debug": 4,}
class FilteredLogger: """Logger that filters messages based on minimum log level."""
def __init__(self, min_level: str) -> None: """Initialize with minimum log level to display.""" self.min_priority = LOG_LEVEL_PRIORITY.get(min_level, 4)
def _should_log(self, level: str) -> bool: """Check if a message at this level should be logged.""" return LOG_LEVEL_PRIORITY.get(level, 4) <= self.min_priority
def error(self, message: str, *args: Any, **kwargs: Any) -> None: """Log an error message if level permits.""" if self._should_log("error"): print(f"[ERROR] {message}", args if args else "", kwargs if kwargs else "") # noqa: T201
def warn(self, message: str, *args: Any, **kwargs: Any) -> None: """Log a warning message if level permits.""" if self._should_log("warn"): print(f"[WARN] {message}", args if args else "", kwargs if kwargs else "") # noqa: T201
def info(self, message: str, *args: Any, **kwargs: Any) -> None: """Log an informational message if level permits.""" if self._should_log("info"): print(f"[INFO] {message}", args if args else "", kwargs if kwargs else "") # noqa: T201
def verbose(self, message: str, *args: Any, **kwargs: Any) -> None: """Log a verbose message if level permits.""" if self._should_log("verbose"): print(f"[VERB] {message}", args if args else "", kwargs if kwargs else "") # noqa: T201
def debug(self, message: str, *args: Any, **kwargs: Any) -> None: """Log a debug message if level permits.""" if self._should_log("debug"): print(f"[DEBUG] {message}", args if args else "", kwargs if kwargs else "") # noqa: T201
class PythonLoggingAdapter: """Adapter that wraps Python's standard logging module."""
def __init__(self, name: str = "algokit") -> None: """Initialize with a logger name.""" self.logger = logging.getLogger(name)
def error(self, message: str, *args: Any, **kwargs: Any) -> None: """Log an error message.""" self.logger.error(message, *args, **kwargs)
def warn(self, message: str, *args: Any, **kwargs: Any) -> None: """Log a warning message.""" self.logger.warning(message, *args, **kwargs)
def info(self, message: str, *args: Any, **kwargs: Any) -> None: """Log an informational message.""" self.logger.info(message, *args, **kwargs)
def verbose(self, message: str, *args: Any, **kwargs: Any) -> None: """Log a verbose message (maps to DEBUG in standard logging).""" self.logger.debug(message, *args, **kwargs)
def debug(self, message: str, *args: Any, **kwargs: Any) -> None: """Log a debug message.""" self.logger.debug(message, *args, **kwargs)
# ============================================================================# Main Example# ============================================================================
def main() -> None: print_header("Logger Type Example")
# ============================================================================ # Step 1: Logger Protocol Definition # ============================================================================ print_step(1, "Logger Protocol Definition")
print_info("The Logger protocol defines a standard logging interface:") print_info("") print_info(" class Logger(Protocol):") print_info(" def error(message: str, *args, **kwargs) -> None") print_info(" def warn(message: str, *args, **kwargs) -> None") print_info(" def info(message: str, *args, **kwargs) -> None") print_info(" def verbose(message: str, *args, **kwargs) -> None") print_info(" def debug(message: str, *args, **kwargs) -> None") print_info("") print_info("Log levels (from most to least severe):") print_info(" 1. error - Critical errors that need immediate attention") print_info(" 2. warn - Warning conditions that should be reviewed") print_info(" 3. info - Informational messages for normal operations") print_info(" 4. verbose - Detailed tracing for troubleshooting") print_info(" 5. debug - Developer debugging information") print_success("Logger protocol provides 5 standard log levels")
# ============================================================================ # Step 2: Console-based Logger Implementation # ============================================================================ print_step(2, "Console-based Logger Implementation")
console_logger = ConsoleLogger()
print_info("Created a console-based Logger implementation") print_info("Demonstrating each log level:") print_info("")
console_logger.error("Database connection failed", {"code": "ECONNREFUSED", "port": 5432}) console_logger.warn("API rate limit approaching", {"current": 95, "limit": 100}) console_logger.info("Transaction submitted", {"txId": "ABC123..."}) console_logger.verbose("Processing block", {"round": 12345, "txCount": 7}) console_logger.debug("Raw response payload", {"bytes": 1024})
print_info("") print_success("All 5 log levels demonstrated")
# ============================================================================ # Step 3: No-op (Silent) Logger # ============================================================================ print_step(3, "No-op (Silent) Logger")
null_logger = NullLogger()
print_info("Created a no-op (silent) logger") print_info("Silent loggers are useful for:") print_info(" - Production environments where logging is disabled") print_info(" - Unit tests where log output is not wanted") print_info(" - Default parameter values when no logger is provided") print_info("") print_info("Calling silent logger (no output expected):") null_logger.error("This error will not be logged") null_logger.warn("This warning will not be logged") null_logger.info("This info will not be logged") null_logger.verbose("This verbose message will not be logged") null_logger.debug("This debug message will not be logged") print_success("No-op logger created - produces no output")
# ============================================================================ # Step 4: Custom Formatting Logger # ============================================================================ print_step(4, "Custom Formatting Logger")
app_logger = TimestampLogger("MyApp")
print_info("Created a logger with custom formatting:") print_info(" - ISO timestamp prefix") print_info(" - Application name prefix") print_info(" - Padded log level for alignment") print_info("") print_info("Demonstrating custom formatted output:") print_info("")
app_logger.info("Application started") app_logger.debug("Configuration loaded", {"env": "development"}) app_logger.warn("Deprecated API endpoint called")
print_info("") print_success("Custom formatting logger created and demonstrated")
# ============================================================================ # Step 5: Level-filtered Logger # ============================================================================ print_step(5, "Level-filtered Logger")
print_info("Created a level-filtered logger factory") print_info("Filter levels control which messages are logged:") print_info(' - "error" -> only errors') print_info(' - "warn" -> errors + warnings') print_info(' - "info" -> errors + warnings + info') print_info(' - "verbose" -> all except debug') print_info(' - "debug" -> all messages') print_info("")
print_info('Testing with min_level="warn" (only error and warn):') warn_logger = FilteredLogger("warn") print_info("") warn_logger.error("This error IS logged") warn_logger.warn("This warning IS logged") warn_logger.info("This info is NOT logged") warn_logger.debug("This debug is NOT logged")
print_info("") print_success("Level-filtered logger demonstrated")
# ============================================================================ # Step 6: Logger Interface Compatibility # ============================================================================ print_step(6, "Logger Interface Compatibility")
print_info("The Logger protocol is compatible with common logging approaches:") print_info("") print_info(" 1. Python's logging module:") print_info(' adapter = PythonLoggingAdapter("myapp")') print_info(" # Maps verbose() to debug() level") print_info("") print_info(" 2. Custom implementations - as shown in this example") print_info(" ConsoleLogger, TimestampLogger, FilteredLogger") print_info("") print_info(" 3. Third-party loggers (structlog, loguru, etc.)") print_info(" Can be adapted to match the Logger protocol") print_info("")
# Demonstrate Python logging adapter print_info("Demonstrating PythonLoggingAdapter:") # Configure basic logging for demo logging.basicConfig(level=logging.DEBUG, format="%(levelname)s:%(name)s:%(message)s") python_logger = PythonLoggingAdapter("demo") print_info("") python_logger.info("Using Python standard logging") python_logger.debug("This maps to DEBUG level")
print_info("") print_success("Logger protocol compatible with Python logging ecosystem")
# ============================================================================ # Step 7: Using Logger in Functions # ============================================================================ print_step(7, "Using Logger in Functions")
def simulate_transaction(tx_id: str, logger: Logger | None = None) -> None: """Simulate a transaction with optional logging.""" # Use null logger if none provided log: Logger = logger if logger is not None else NullLogger()
log.info(f"Starting transaction: {tx_id}") log.debug("Validating transaction parameters") log.verbose("Serializing transaction data") log.info(f"Transaction {tx_id} completed successfully")
print_info("Created a function that accepts Logger as optional parameter") print_info("When no logger is provided, it defaults to NullLogger (silent)") print_info("")
print_info("Calling with no logger (silent):") simulate_transaction("TX-001") print_info(" (no output produced)") print_info("")
print_info("Calling with console_logger:") print_info("") simulate_transaction("TX-002", console_logger)
print_info("") print_success("Logger can be used as optional dependency injection")
# ============================================================================ # Summary # ============================================================================ print_step(8, "Summary")
print_info("Logger protocol provides:") print_info(" - 5 standard log levels (error, warn, info, verbose, debug)") print_info(" - Support for additional parameters (*args, **kwargs)") print_info(" - Compatibility with Python's logging module") print_info(" - Easy to implement custom formatters and filters") print_info(" - No-op logger for disabling output") print_info("") print_success("Logger Type Example completed!")
if __name__ == "__main__": main()Other examples in Common Utilities
Section titled “Other examples in Common Utilities”- Address Basics
- Address Encoding
- Array Utilities
- Constants Reference
- Crypto Hash (SHA-512/256)
- Example
- JSON BigInt Support
- MessagePack Encoding
- Primitive Serde (Serialization/Deserialization)
- Composite Serde (Serialization/Deserialization)
- Model Serde (Object Model Serialization)
- Sourcemap for TEAL Debugging