Fenixflow structured logging package with scoped, instance-based loggers
Project description
ff-logger
A scoped, instance-based logging package for Fenixflow applications. Unlike traditional Python logging which uses a global configuration, ff-logger provides self-contained logger instances that can be passed around as objects, with support for context binding and multiple output formats.
Created by Ben Moag at Fenixflow
Quick Start
Installation
From PyPI (when published)
pip install ff-logger
From GitLab (current)
pip install git+https://gitlab.com/fenixflow/fenix-packages.git#subdirectory=ff-logger
Basic Usage
from ff_logger import ConsoleLogger
import logging
# Create a logger instance with permanent context
logger = ConsoleLogger(
name="my_app",
level="INFO", # Can use strings now! (or logging.INFO)
context={"service": "api", "environment": "production"}
)
# Log messages with the permanent context
logger.info("Application started")
# Output: [2025-08-20 10:00:00] INFO [my_app] Application started | service="api" environment="production"
# Add runtime context with kwargs
logger.info("Request processed", request_id="req-123", duration=45)
# Output includes both permanent and runtime context
Context Binding
Add permanent context fields to your logger instance:
# Add context that will appear in all subsequent logs
logger.bind(
request_id="req-456",
user_id=789,
ip="192.168.1.1"
)
# All messages now include the bound context
logger.info("Processing payment")
logger.error("Payment failed", error_code="CARD_DECLINED")
# bind() returns self for chaining
logger.bind(session_id="xyz").info("Session started")
Note: As of v0.3.0, bind() modifies the logger instance in place rather than creating a new one. This is cleaner and more intuitive. The method validates that fields are not reserved and values are JSON-serializable.
Logger Types
ConsoleLogger
Outputs colored, human-readable logs to console:
from ff_logger import ConsoleLogger
logger = ConsoleLogger(
name="app",
level="INFO", # String or int (logging.INFO)
colors=True, # Enable colored output
show_hostname=False # Optional hostname in logs
)
JSONLogger
Outputs structured JSON lines, perfect for log aggregation:
from ff_logger import JSONLogger
logger = JSONLogger(
name="app",
level="WARNING", # String or int levels supported
show_hostname=True,
include_timestamp=True
)
logger.info("Event occurred", event_type="user_login", user_id=123)
# Output: {"level":"INFO","logger":"app","message":"Event occurred","timestamp":"2025-08-20T10:00:00Z","event_type":"user_login","user_id":123,...}
FileLogger
Writes to files with rotation support:
from ff_logger import FileLogger
logger = FileLogger(
name="app",
filename="/var/log/app.log",
rotation_type="size", # "size", "time", or "none"
max_bytes=10*1024*1024, # 10MB
backup_count=5
)
NullLogger
Zero-cost logger for testing or when logging is disabled:
from ff_logger import NullLogger
# Preferred: Use directly as a class (no instantiation needed)
NullLogger.info("This does nothing") # No-op
NullLogger.debug("Debug message") # No-op
# As a default parameter (perfect for dependency injection)
def process_data(data, logger=NullLogger):
logger.info("Processing data: %s", data)
return data * 2
# Call without providing a logger
result = process_data([1, 2, 3])
# Backward compatibility: Can still instantiate if needed
logger = NullLogger() # All parameters are optional
logger.info("This also does nothing")
DatabaseLogger
Writes logs to a database table (requires ff-storage):
from ff_logger import DatabaseLogger
from ff_storage.db.postgres import PostgresPool
db = PostgresPool(...)
logger = DatabaseLogger(
name="app",
db_connection=db,
table_name="logs",
schema="public"
)
Key Features
v0.4.0 Features
Temporary Context Manager
Use the temp_context() context manager to add temporary fields that are automatically removed:
logger = ConsoleLogger("app")
with logger.temp_context(request_id="123", user_id=456):
logger.info("Processing request") # Includes request_id and user_id
logger.info("Request complete") # Still includes the fields
# Fields automatically removed after context
logger.info("Next request") # request_id and user_id no longer present
Lazy Evaluation for Performance
Pass callables as kwargs to defer expensive computations until needed:
logger = ConsoleLogger("app", level="ERROR") # Only ERROR and above
# This callable is NEVER executed (DEBUG is disabled)
logger.debug("Debug info", expensive_data=lambda: compute_expensive_data())
# This callable IS executed (ERROR is enabled)
logger.error("Error occurred", context=lambda: gather_error_context())
Robust JSON Serialization
JSON logger now handles complex Python types without crashing:
from datetime import datetime
from decimal import Decimal
from uuid import uuid4
from pathlib import Path
logger = JSONLogger("app")
# All of these work automatically
logger.info("Event",
timestamp=datetime.now(), # → ISO format string
user_id=uuid4(), # → string representation
price=Decimal("19.99"), # → float
file_path=Path("/tmp/file"), # → string
status=Status.ACTIVE # → enum value
)
Thread-Safe Context Updates
All context operations are now thread-safe:
logger = ConsoleLogger("app")
# Safe to call from multiple threads
def worker(worker_id):
logger.bind(worker_id=worker_id)
logger.info("Worker started")
threads = [Thread(target=worker, args=(i,)) for i in range(10)]
v0.3.0 Features
Flexible Log Levels
Accepts both string and integer log levels for better developer experience:
- Strings:
"DEBUG","INFO","WARNING","ERROR","CRITICAL" - Case-insensitive:
"info"works the same as"INFO" - Integers: Traditional
logging.DEBUG,logging.INFO, etc. - Numeric values:
10,20,30,40,50
Instance-Based
Each logger is a self-contained instance with its own configuration:
def process_data(logger):
"""Accept any logger instance."""
logger.info("Processing started")
# ... do work ...
logger.info("Processing complete")
# Use with different loggers
console = ConsoleLogger("console")
json_log = JSONLogger("json")
process_data(console) # Outputs to console
process_data(json_log) # Outputs as JSON
Context Preservation
Permanent context fields appear in every log message:
logger = ConsoleLogger(
name="worker",
context={
"worker_id": "w-1",
"datacenter": "us-east-1"
}
)
# Every log includes worker_id and datacenter
logger.info("Task started")
logger.error("Task failed")
Zero Dependencies
Built entirely on Python's standard logging module - no external dependencies required for core functionality.
Migration from Traditional Logging
# Traditional Python logging (global)
import logging
logging.info("Message")
# ff-logger (instance-based)
from ff_logger import ConsoleLogger
logger = ConsoleLogger("app")
logger.info("Message")
Advanced Usage
Flexible Log Levels
# All of these work now (v0.3.0+):
logger1 = ConsoleLogger("app", level="DEBUG") # String
logger2 = ConsoleLogger("app", level="info") # Case-insensitive
logger3 = ConsoleLogger("app", level=logging.INFO) # Traditional int
logger4 = ConsoleLogger("app", level=20) # Numeric value
logger5 = ConsoleLogger("app") # Default: "DEBUG"
# Supported string levels:
# "DEBUG", "INFO", "WARNING"/"WARN", "ERROR", "CRITICAL"
Exception Logging
try:
risky_operation()
except Exception:
logger.exception("Operation failed")
# Automatically includes full traceback
Reserved Fields
Some field names are reserved by Python's logging module. If you use them, they'll be automatically prefixed with x_:
# "module" is reserved, becomes "x_module"
logger.info("Message", module="auth")
Testing
Use NullLogger in tests for zero overhead:
def test_my_function():
# Option 1: Pass the class directly
result = my_function(logger=NullLogger) # No logging output
assert result == expected
# Option 2: Functions with NullLogger as default
def my_function(data, logger=NullLogger):
logger.info("Processing: %s", data)
return process(data)
# In tests, just call without logger parameter
result = my_function(test_data) # Silent by default
Contributing
Contributions are welcome! Please feel free to submit a Pull Request to the GitLab repository.
License
MIT License - see LICENSE file for details.
Copyright (c) 2024 Ben Moag / Fenixflow
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ff_logger-0.4.0.tar.gz.
File metadata
- Download URL: ff_logger-0.4.0.tar.gz
- Upload date:
- Size: 27.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.2.0 CPython/3.12.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ff7c6adc35ef88550b521c566422b2346628050336ed33e865a52cd5929f33cb
|
|
| MD5 |
d16925d06b90128840e8053562e02a6e
|
|
| BLAKE2b-256 |
7c0c34a707ccf7e4f84c40eb5dd4f96479832fa617cce35ec9290fb11ff48668
|
File details
Details for the file ff_logger-0.4.0-py3-none-any.whl.
File metadata
- Download URL: ff_logger-0.4.0-py3-none-any.whl
- Upload date:
- Size: 19.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.2.0 CPython/3.12.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
056fa6556e505b65879dccecdb0b93da96196e3955028e3eeab0c3d7e9830e76
|
|
| MD5 |
df5be391513bc69bd38582b9b05ea6db
|
|
| BLAKE2b-256 |
274edd1ef4f857298f72d0c1546836f07bdd98dbc70a2e70ca26e8aedfb7688c
|