Functional structured logging with composable effects
Project description
effect-log
🪵 Functional structured logging with composable effects for Python
Part of the effect-py ecosystem - bringing functional programming patterns to Python.
Features
- 🔄 Composable Effects: Chain logging operations with pipes
- 📊 Structured Logging: JSON output with rich context
- 🎯 Type Safe: Full type hints and mypy support
- 🔧 Multiple Writers: Console, file, buffered, and custom output targets
- 📈 Observability: Built-in tracing and span support
- 🧪 Immutable: Functional approach with immutable loggers
- 🌐 HTTP Middleware: Framework-agnostic HTTP request/response logging
- ⚡ Performance: Buffered writers and efficient processing
- 🔍 Filtering: Conditional logging with custom predicates
Installation
pip install effect-log
For development:
pip install effect-log[dev]
Quick Start
from effect_log import Logger, with_context, with_span
# Create logger
logger = Logger()
# Basic logging
logger.info("Application started", service="api", version="1.0.0")
logger.error("Database connection failed", error="timeout")
# Functional composition
request_logger = logger.pipe(
with_context(request_id="req-123", user_id="user-456"),
with_span("handle_request", "trace-789")
)
request_logger.info("Processing user request")
request_logger.warn("Rate limit approaching", current=95, limit=100)
Advanced Usage
Structured Production Logging
from effect_log import Logger, LogLevel
from effect_log.writers import JSONConsoleWriter, FileWriter, MultiWriter
# Production logger with JSON output
logger = Logger(
writer=MultiWriter(
JSONConsoleWriter(),
FileWriter("app.log")
),
min_level=LogLevel.INFO
).with_context(service="user-service")
logger.info("Server started", port=8080, environment="production")
Context Composition
# Build context incrementally
base_logger = Logger().with_context(service="payment-api")
request_logger = base_logger.with_context(request_id="req-123")
user_logger = request_logger.with_context(user_id="user-456")
user_logger.info("Payment processed", amount=99.99, currency="USD")
# Output: {"level": "INFO", "message": "Payment processed", "context": {"service": "payment-api", "request_id": "req-123", "user_id": "user-456", "amount": 99.99, "currency": "USD"}}
HTTP Middleware Integration
from effect_log import Logger
from effect_log.middleware import HttpLoggerMiddleware
logger = Logger().with_context(service="web-api")
middleware = HttpLoggerMiddleware(logger, include_headers=True)
# Flask example
from flask import Flask
app = Flask(__name__)
@app.before_request
def log_request():
from flask import request, g
result = middleware(request)
g.logger = result["logger"]
g.request_id = result["request_id"]
@app.route("/users", methods=["POST"])
def create_user():
from flask import g
g.logger.info("Creating user", action="user_create")
return {"status": "created"}
API Reference
Logger
The main Logger class provides immutable logging with functional composition.
Methods
trace(message, **context)- Log trace level messagedebug(message, **context)- Log debug level messageinfo(message, **context)- Log info level messagewarn(message, **context)- Log warning level messageerror(message, **context)- Log error level messagefatal(message, **context)- Log fatal level messagewith_context(**context)- Create logger with additional contextwith_span(span_id, trace_id=None)- Create logger with tracing spanwith_writer(writer)- Create logger with different writerwith_min_level(level)- Create logger with minimum log levelpipe(*operations)- Apply operations in functional pipeline
Writers
ConsoleWriter(use_colors=True, min_level=LogLevel.INFO)- Write to console with optional colorsJSONConsoleWriter(min_level=LogLevel.INFO)- Write JSON to consoleFileWriter(file_path, min_level=LogLevel.INFO, append=True)- Write to fileMultiWriter(*writers)- Write to multiple destinationsFilterWriter(writer, predicate)- Conditional writingBufferedWriter(writer, buffer_size=100)- Buffered writing for performance
Functional Composition
with_context(**context)- Curried context additionwith_span(span_id, trace_id=None)- Curried span additionwith_writer(writer)- Curried writer settingwith_min_level(level)- Curried minimum level settingfork_logger(logger)- Create independent copymerge_loggers(logger1, logger2)- Merge two loggers
Log Levels
Available log levels in ascending order of severity:
LogLevel.TRACE- Detailed diagnostic informationLogLevel.DEBUG- Debug informationLogLevel.INFO- General informationLogLevel.WARN- Warning messagesLogLevel.ERROR- Error messagesLogLevel.FATAL- Fatal error messages
HTTP Middleware
The HttpLoggerMiddleware class provides framework-agnostic HTTP request/response logging:
from effect_log.middleware import HttpLoggerMiddleware, FlaskMiddleware
# Generic middleware
middleware = HttpLoggerMiddleware(
logger,
include_headers=True,
include_body=True,
max_body_size=1024,
exclude_paths=["/health", "/metrics"]
)
# Framework-specific helpers
flask_middleware = FlaskMiddleware(middleware)
Performance Features
from effect_log.writers import BufferedWriter, FilterWriter
# Buffered writing for high-throughput scenarios
buffered = BufferedWriter(FileWriter("app.log"), buffer_size=1000)
# Conditional logging to reduce overhead
error_only = FilterWriter(
FileWriter("errors.log"),
predicate=lambda entry: entry.level >= LogLevel.ERROR
)
logger = Logger(writer=MultiWriter(buffered, error_only))
Development
Setup
git clone https://github.com/effect-py/log.git
cd log
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
Testing
pytest
Code Quality
black .
ruff check .
mypy .
Framework Integration
Flask Integration
from flask import Flask, request, g
from effect_log import Logger
from effect_log.middleware import HttpLoggerMiddleware
app = Flask(__name__)
logger = Logger()
middleware = HttpLoggerMiddleware(logger)
@app.before_request
def before_request():
result = middleware(request)
g.logger = result["logger"]
g.request_id = result["request_id"]
@app.route("/users")
def get_users():
g.logger.info("Fetching users")
return {"users": []}
FastAPI Integration
from fastapi import FastAPI, Request
from effect_log import Logger
from effect_log.middleware import FastAPIMiddleware
app = FastAPI()
logger = Logger()
middleware = FastAPIMiddleware(HttpLoggerMiddleware(logger))
app.add_middleware(middleware)
@app.get("/users")
async def get_users(request: Request):
request.state.logger.info("Fetching users")
return {"users": []}
Django Integration
# In settings.py
MIDDLEWARE = [
'effect_log.middleware.DjangoMiddleware',
# ... other middleware
]
# In views.py
def user_view(request):
request.logger.info("User view accessed")
return JsonResponse({"status": "success"})
Best Practices
1. Use Structured Logging
Always include relevant context with your log messages:
# Good
logger.info("User login",
user_id="123",
ip_address="192.168.1.1",
success=True,
duration_ms=45
)
# Avoid
logger.info("User 123 logged in from 192.168.1.1 successfully in 45ms")
2. Create Specialized Loggers
Create loggers for different parts of your application:
# Base logger for the service
base_logger = Logger().with_context(service="user-service", version="1.0.0")
# Specialized loggers
db_logger = base_logger.with_context(component="database")
cache_logger = base_logger.with_context(component="cache")
auth_logger = base_logger.with_context(component="auth")
3. Use Appropriate Log Levels
# TRACE: Very detailed information
logger.trace("Entering function", function="calculate_score", args=args)
# DEBUG: Diagnostic information
logger.debug("Query executed", query=sql, duration_ms=23)
# INFO: General information
logger.info("User registered", user_id="123", email="user@example.com")
# WARN: Something unexpected happened
logger.warn("Rate limit approaching", current=95, limit=100)
# ERROR: Error occurred but application continues
logger.error("Payment failed", user_id="123", error="Card declined")
# FATAL: Critical error, application may stop
logger.fatal("Database unavailable", error="Connection refused")
4. Performance Considerations
# Use buffered writers for high-volume logging
buffered_writer = BufferedWriter(FileWriter("app.log"), buffer_size=500)
# Use minimum log levels appropriately
production_logger = Logger(min_level=LogLevel.INFO)
debug_logger = Logger(min_level=LogLevel.DEBUG)
# Use filters for selective logging
error_writer = FilterWriter(
FileWriter("errors.log"),
predicate=lambda entry: entry.level >= LogLevel.ERROR
)
Documentation
For comprehensive documentation, see the docs directory:
- API Reference - Complete API documentation
- Framework Integration - Detailed integration guides
- Best Practices - Production-ready recommendations
- Migration Guide - Migrating from other logging libraries
- Troubleshooting - Common issues and solutions
Contributing
Contributions are welcome! Please read our Contributing Guide.
License
MIT License - see LICENSE file.
Related Projects
- effect-py/http-client - Functional HTTP client
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 effect_log-0.0.1.tar.gz.
File metadata
- Download URL: effect_log-0.0.1.tar.gz
- Upload date:
- Size: 13.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e784d5c59cb2a2e6433861619392425f2a44d57abc1f49774e5be7cca2593d4f
|
|
| MD5 |
e23998e3a4cde728edaaf381f4296fbf
|
|
| BLAKE2b-256 |
e4e8a381d2983a459de1a5b80fc47ad75f8b13594f416b705c6fe7e41a16cbf2
|
File details
Details for the file effect_log-0.0.1-py3-none-any.whl.
File metadata
- Download URL: effect_log-0.0.1-py3-none-any.whl
- Upload date:
- Size: 12.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
30c7be29c8057c92bf418568df82f4c4ed488d05501f3297818abb90ce7c056d
|
|
| MD5 |
f4fa1421a747cbf44c08ce9018ac382a
|
|
| BLAKE2b-256 |
8ea3e7e5bde16a45ed1d03de2a3b2fce27e3cc7b56794727667237d0c5db25ec
|