Skip to content

Commit daf3b44

Browse files
committed
feat: add first basis version of framework
1 parent 2bf4b5f commit daf3b44

15 files changed

Lines changed: 2520 additions & 2 deletions

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.11

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
# logstructor
2-
Logstructor – Bringing structure to your logs with simple, Pythonic structured logging
1+
# structlogger
2+
Cloud-native structured logging for Python: JSON logs, context support, AWS-ready, zero dependencies.

logstructor/__init__.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""
2+
structlogger - Cloud-native structured logging for Python
3+
4+
A drop-in replacement for Python's logging module that adds structured logging
5+
capabilities while maintaining 100% compatibility with the standard logging API.
6+
"""
7+
8+
from .config import basic_config, configure, get_logger, reset_configuration
9+
from .context import bind_context, clear_context, get_context, update_context
10+
from .exceptions import (
11+
ConfigurationError,
12+
ContextError,
13+
EnvironmentVariableError,
14+
FormatterError,
15+
StructLoggerError,
16+
)
17+
from .formatter import StructFormatter
18+
from .logger import StructLogger
19+
20+
__version__ = "0.1.0"
21+
__all__ = [
22+
# Core classes
23+
"StructLogger",
24+
"StructFormatter",
25+
# Main functions
26+
"get_logger",
27+
"configure",
28+
"basic_config",
29+
# Context management
30+
"bind_context",
31+
"clear_context",
32+
"get_context",
33+
"update_context",
34+
# Exceptions
35+
"StructLoggerError",
36+
"ConfigurationError",
37+
"EnvironmentVariableError",
38+
"FormatterError",
39+
"ContextError",
40+
# Utilities
41+
"reset_configuration",
42+
# Legacy alias
43+
"getLogger",
44+
]
45+
46+
47+
# Legacy alias for backward compatibility
48+
getLogger = get_logger # noqa: N816 # noqa: N816

logstructor/config.py

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
"""
2+
Configuration utilities for structured logging.
3+
4+
This module provides convenient functions to configure structured logging
5+
with sensible defaults, making it easy to get started with minimal setup.
6+
"""
7+
8+
import logging
9+
import os
10+
from typing import Any, Dict, Optional, Union
11+
12+
from .exceptions import ConfigurationError, EnvironmentVariableError
13+
from .formatter import StructFormatter
14+
from .logger import StructLogger
15+
16+
# Track if we've already configured
17+
_configured = False
18+
19+
20+
def _collect_env_fields(
21+
env_fields: Optional[Dict[str, str]] = None, env_prefix: Optional[str] = None
22+
) -> Dict[str, str]:
23+
"""
24+
Collect environment variables for inclusion in log context.
25+
26+
Args:
27+
env_fields: Dictionary mapping environment variable names to context field names
28+
env_prefix: Prefix for environment variables to auto-include
29+
30+
Returns:
31+
Dictionary of context field names and their values
32+
"""
33+
env_context = {}
34+
35+
# Add specific environment fields
36+
if env_fields:
37+
for env_var, context_key in env_fields.items():
38+
value = os.getenv(env_var)
39+
if value is not None:
40+
env_context[context_key] = value
41+
else:
42+
raise EnvironmentVariableError(f"Required environment variable '{env_var}' is not set")
43+
44+
# Add environment variables with prefix
45+
if env_prefix:
46+
for key, value in os.environ.items():
47+
if key.startswith(env_prefix):
48+
# Remove prefix and convert to lowercase
49+
context_key = key[len(env_prefix) :].lower()
50+
env_context[context_key] = value
51+
52+
return env_context
53+
54+
55+
def _ensure_configured() -> None:
56+
"""
57+
Ensure structlogger is configured with sensible defaults.
58+
59+
This function is called automatically by get_logger() to set up
60+
structured logging with default settings if configure() hasn't
61+
been called yet.
62+
"""
63+
global _configured
64+
65+
if not _configured:
66+
# Auto-configure with sensible defaults
67+
configure()
68+
_configured = True
69+
70+
71+
def configure(
72+
level: Union[int, str] = logging.INFO,
73+
format_type: str = "json",
74+
timestamp_format: str = "iso",
75+
extra_fields: Optional[Dict[str, Any]] = None,
76+
env_fields: Optional[Dict[str, str]] = None,
77+
env_prefix: Optional[str] = None,
78+
handler_type: str = "console",
79+
) -> None:
80+
"""
81+
Configure structured logging with sensible defaults.
82+
83+
This function sets up structured logging for the entire application
84+
by configuring the root logger with a StructFormatter and setting
85+
StructLogger as the default logger class.
86+
87+
Args:
88+
level: Logging level (INFO, DEBUG, etc. or logging constants)
89+
format_type: Output format ("json" is currently the only option)
90+
timestamp_format: Timestamp format ("iso" or "epoch")
91+
extra_fields: Static fields to include in every log entry
92+
env_fields: Dictionary mapping environment variable names to context field names
93+
env_prefix: Prefix for environment variables to auto-include (e.g. "APP_")
94+
handler_type: Handler type ("console" is currently the only option)
95+
96+
Examples:
97+
Basic setup:
98+
>>> configure()
99+
>>> logger = structlogger.getLogger(__name__)
100+
>>> logger.info("Hello world")
101+
102+
Custom level and fields:
103+
>>> configure(
104+
... level="DEBUG",
105+
... extra_fields={"service": "my-app", "version": "1.0.0"}
106+
... )
107+
108+
Production setup:
109+
>>> configure(
110+
... level=logging.WARNING,
111+
... extra_fields={"environment": "production", "service": "api"}
112+
... )
113+
114+
With environment variables:
115+
>>> configure(env_fields={"SERVICE_NAME": "service", "VERSION": "version", "ENVIRONMENT": "env"})
116+
>>> # Maps SERVICE_NAME -> service, VERSION -> version, ENVIRONMENT -> env in context
117+
118+
With environment prefix:
119+
>>> configure(env_prefix="APP_")
120+
>>> # Includes all APP_* environment variables in context
121+
122+
Combined approach:
123+
>>> configure(
124+
... env_prefix="APP_",
125+
... env_fields={"SERVICE_NAME": "service"},
126+
... extra_fields={"component": "api"}
127+
... )
128+
"""
129+
global _configured
130+
131+
# Set StructLogger as the default logger class
132+
logging.setLoggerClass(StructLogger)
133+
134+
# Get root logger and clear existing handlers
135+
root_logger = logging.getLogger()
136+
root_logger.handlers.clear()
137+
138+
# Set logging level
139+
if isinstance(level, str):
140+
level = getattr(logging, level.upper())
141+
root_logger.setLevel(level)
142+
143+
# Create handler
144+
if handler_type == "console":
145+
handler = logging.StreamHandler()
146+
else:
147+
raise ConfigurationError(f"Unsupported handler type: {handler_type}")
148+
149+
# Collect environment variables
150+
env_context = _collect_env_fields(env_fields, env_prefix)
151+
152+
# Merge extra_fields with environment context
153+
combined_extra_fields = {}
154+
if extra_fields:
155+
combined_extra_fields.update(extra_fields)
156+
combined_extra_fields.update(env_context)
157+
158+
# Create formatter
159+
if format_type == "json":
160+
formatter = StructFormatter(
161+
timestamp_format=timestamp_format,
162+
extra_fields=combined_extra_fields if combined_extra_fields else None,
163+
)
164+
else:
165+
raise ConfigurationError(f"Unsupported format type: {format_type}")
166+
167+
# Set up handler and add to root logger
168+
handler.setFormatter(formatter)
169+
root_logger.addHandler(handler)
170+
171+
# Mark as configured
172+
_configured = True
173+
174+
175+
def basic_config(**kwargs) -> None:
176+
"""
177+
Basic configuration for structured logging.
178+
179+
This is an alias for configure() that mimics the standard logging.basicConfig()
180+
function name for familiarity.
181+
182+
Args:
183+
**kwargs: Same arguments as configure()
184+
185+
Examples:
186+
>>> basic_config(level="DEBUG")
187+
>>> logger = structlogger.getLogger(__name__)
188+
>>> logger.info("Configured with basic_config")
189+
"""
190+
configure(**kwargs)
191+
192+
193+
def get_logger(name: Optional[str] = None) -> StructLogger:
194+
"""
195+
Get a structured logger instance.
196+
197+
This function returns a StructLogger instance, which provides structured
198+
logging capabilities while maintaining full compatibility with the
199+
standard logging API.
200+
201+
Args:
202+
name: Logger name (typically __name__ or None for root logger)
203+
204+
Returns:
205+
StructLogger instance
206+
207+
Examples:
208+
Module logger:
209+
>>> logger = get_logger(__name__)
210+
>>> logger.info("Module-specific logger")
211+
212+
Root logger:
213+
>>> logger = get_logger()
214+
>>> logger.info("Root logger")
215+
216+
Named logger:
217+
>>> logger = get_logger("my_component")
218+
>>> logger.info("Component logger")
219+
"""
220+
# Auto-configure with defaults if not already configured
221+
_ensure_configured()
222+
223+
return logging.getLogger(name) # type: ignore[return-value]
224+
225+
226+
def reset_configuration() -> None:
227+
"""
228+
Reset logging configuration to defaults.
229+
230+
This function clears all handlers from the root logger and resets
231+
the logger class to the standard logging.Logger. Useful for testing
232+
or when you need to reconfigure logging completely.
233+
234+
Examples:
235+
>>> configure(level="DEBUG")
236+
>>> # ... use structured logging ...
237+
>>> reset_configuration()
238+
>>> # Back to standard logging
239+
"""
240+
# Reset logger class to standard
241+
logging.setLoggerClass(logging.Logger)
242+
243+
# Clear root logger handlers
244+
root_logger = logging.getLogger()
245+
root_logger.handlers.clear()
246+
247+
# Reset level to default
248+
root_logger.setLevel(logging.WARNING)
249+
250+
# Mark as not configured
251+
global _configured
252+
_configured = False

0 commit comments

Comments
 (0)