log2fast-fastapi 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ from .__version__ import __version__
2
+ from .base import FastLogger, get_logger
3
+ from .middleware import RequestLoggingMiddleware, get_request_id
4
+ from .settings import (
5
+ LogEnvironment,
6
+ LogFileSettings,
7
+ LogFormat,
8
+ LogLevel,
9
+ LogSettings,
10
+ RotationStrategy,
11
+ settings,
12
+ )
13
+
14
+ __all__ = [
15
+ "__version__",
16
+ "FastLogger",
17
+ "get_logger",
18
+ "LogSettings",
19
+ "LogLevel",
20
+ "LogFormat",
21
+ "LogEnvironment",
22
+ "LogFileSettings",
23
+ "RotationStrategy",
24
+ "settings",
25
+ "RequestLoggingMiddleware",
26
+ "get_request_id",
27
+ ]
@@ -0,0 +1,5 @@
1
+ """Version information for log2fast-fastapi."""
2
+
3
+ __version__ = "0.1.0"
4
+ __author__ = "Angel Daniel Sanchez Castillo"
5
+ __license__ = "MIT"
@@ -0,0 +1,342 @@
1
+ import logging
2
+ import os
3
+ from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
4
+ from typing import Any
5
+
6
+ from .formatters import (
7
+ ColoredFormatter,
8
+ JSONFormatter,
9
+ SimpleFormatter,
10
+ StructuredFormatter,
11
+ )
12
+ from .settings import LogFormat, LogSettings, RotationStrategy, settings
13
+
14
+
15
+ class FastLogger:
16
+ """
17
+ Professional logger wrapper for FastAPI applications.
18
+
19
+ Provides environment-aware logging with support for:
20
+ - Multiple output formats (JSON, colored, structured, simple)
21
+ - File rotation
22
+ - Module-based loggers
23
+ - Context injection (request_id, user_id, etc.)
24
+ """
25
+
26
+ _instances: dict[str, logging.Logger] = {}
27
+ _configured: bool = False
28
+
29
+ def __init__(
30
+ self,
31
+ name: str,
32
+ config: LogSettings | None = None,
33
+ ):
34
+ """
35
+ Initialize a FastLogger instance.
36
+
37
+ Args:
38
+ name: Logger name (typically module name)
39
+ config: Optional custom configuration (uses global settings if not provided)
40
+ """
41
+ self.name = name
42
+ self.config = config or settings
43
+ self.logger = self._get_or_create_logger(name)
44
+
45
+ @classmethod
46
+ def _get_or_create_logger(cls, name: str) -> logging.Logger:
47
+ """Get or create a logger instance."""
48
+ if name not in cls._instances:
49
+ logger = logging.getLogger(name)
50
+
51
+ # Configure logger if not already configured
52
+ if not cls._configured:
53
+ cls._configure_root_logger()
54
+
55
+ cls._instances[name] = logger
56
+
57
+ return cls._instances[name]
58
+
59
+ @classmethod
60
+ def _configure_root_logger(cls) -> None:
61
+ """Configure the root logger with handlers and formatters."""
62
+ if cls._configured:
63
+ return
64
+
65
+ root_logger = logging.getLogger()
66
+ root_logger.setLevel(settings.get_effective_level())
67
+
68
+ # Remove existing handlers
69
+ root_logger.handlers.clear()
70
+
71
+ # Add console handler if enabled
72
+ if settings.console_enabled:
73
+ console_handler = logging.StreamHandler()
74
+ console_handler.setLevel(settings.get_effective_level())
75
+ console_handler.setFormatter(
76
+ cls._get_formatter(settings.get_effective_format())
77
+ )
78
+ root_logger.addHandler(console_handler)
79
+
80
+ # Add file handler if enabled
81
+ if settings.file_settings.enabled:
82
+ file_handler = cls._create_file_handler()
83
+ if file_handler:
84
+ file_handler.setLevel(settings.get_effective_level())
85
+ # Always use JSON format for file output in production
86
+ if settings.get_effective_format() == LogFormat.JSON.value:
87
+ file_handler.setFormatter(cls._get_formatter(LogFormat.JSON.value))
88
+ else:
89
+ file_handler.setFormatter(
90
+ cls._get_formatter(LogFormat.STRUCTURED.value)
91
+ )
92
+ root_logger.addHandler(file_handler)
93
+
94
+ cls._configured = True
95
+
96
+ @classmethod
97
+ def _get_formatter(cls, format_type: str) -> logging.Formatter:
98
+ """Get the appropriate formatter based on format type."""
99
+ formatters = {
100
+ LogFormat.JSON.value: JSONFormatter(),
101
+ LogFormat.COLORED.value: ColoredFormatter(
102
+ fmt="[%(asctime)s] %(levelname)s | %(name)s | %(message)s"
103
+ ),
104
+ LogFormat.STRUCTURED.value: StructuredFormatter(),
105
+ LogFormat.SIMPLE.value: SimpleFormatter(),
106
+ }
107
+ return formatters.get(format_type, SimpleFormatter())
108
+
109
+ @classmethod
110
+ def _create_file_handler(
111
+ cls, logger_name: str | None = None
112
+ ) -> RotatingFileHandler | TimedRotatingFileHandler | None:
113
+ """
114
+ Create a rotating file handler (time-based or size-based).
115
+
116
+ Args:
117
+ logger_name: Optional logger name for per-module files
118
+ """
119
+ try:
120
+ # Create logs directory if it doesn't exist
121
+ log_dir = settings.file_settings.directory
122
+ if not os.path.isabs(log_dir):
123
+ # Make it relative to current working directory (where the app runs)
124
+ log_dir = os.path.join(os.getcwd(), log_dir)
125
+
126
+ os.makedirs(log_dir, exist_ok=True)
127
+
128
+ # Generate filename
129
+ filename_vars = {
130
+ "module": settings.module_name or "app",
131
+ "environment": settings.log_environment.value,
132
+ "logger": logger_name or "app",
133
+ }
134
+
135
+ # Use logger name if per_module_files is enabled
136
+ if settings.file_settings.per_module_files and logger_name:
137
+ # Sanitize logger name for filename (replace dots with underscores)
138
+ safe_logger_name = logger_name.replace(".", "_")
139
+ filename_vars["logger"] = safe_logger_name
140
+ # Override pattern to include logger name
141
+ filename = f"{safe_logger_name}_{settings.log_environment.value}.log"
142
+ else:
143
+ filename = settings.file_settings.filename_pattern.format(
144
+ **filename_vars
145
+ )
146
+
147
+ filepath = os.path.join(log_dir, filename)
148
+
149
+ # Create handler based on rotation strategy
150
+ if settings.file_settings.rotation_strategy == RotationStrategy.TIME:
151
+ # Time-based rotation (default: daily at midnight, keep 31 days)
152
+ handler = TimedRotatingFileHandler(
153
+ filepath,
154
+ when=settings.file_settings.when,
155
+ interval=settings.file_settings.interval,
156
+ backupCount=settings.file_settings.backup_count,
157
+ encoding="utf-8",
158
+ )
159
+ else:
160
+ # Size-based rotation
161
+ handler = RotatingFileHandler(
162
+ filepath,
163
+ maxBytes=settings.file_settings.max_bytes,
164
+ backupCount=settings.file_settings.backup_count,
165
+ encoding="utf-8",
166
+ )
167
+
168
+ return handler
169
+
170
+ except Exception as e:
171
+ print(f"⚠️ Failed to create file handler: {e}")
172
+ return None
173
+
174
+ @classmethod
175
+ def reconfigure(cls, new_settings: LogSettings) -> None:
176
+ """
177
+ Reconfigure all loggers with new settings.
178
+
179
+ Args:
180
+ new_settings: New logging configuration
181
+ """
182
+ global settings
183
+ settings = new_settings
184
+ cls._configured = False
185
+ cls._configure_root_logger()
186
+
187
+ def _log_with_context(
188
+ self,
189
+ level: int,
190
+ message: str,
191
+ extra_data: dict[str, Any] | None = None,
192
+ only_in: list[str] | None = None,
193
+ **kwargs: Any,
194
+ ) -> None:
195
+ """
196
+ Internal method to log with context.
197
+
198
+ Args:
199
+ level: Log level
200
+ message: Log message
201
+ extra_data: Extra context data
202
+ only_in: List of environments where this log should appear (e.g., ['development', 'debug'])
203
+ If None, logs in all environments
204
+ **kwargs: Additional arguments for logger
205
+ """
206
+ # Check if we should log in current environment
207
+ if only_in is not None:
208
+ current_env = self.config.log_environment.value
209
+ if current_env not in only_in:
210
+ # Skip logging in this environment
211
+ return
212
+
213
+ extra = kwargs.get("extra", {})
214
+
215
+ if extra_data:
216
+ extra["extra_data"] = extra_data
217
+
218
+ kwargs["extra"] = extra
219
+ self.logger.log(level, message, **kwargs)
220
+
221
+ # Public logging methods
222
+ def debug(
223
+ self,
224
+ message: str,
225
+ extra_data: dict[str, Any] | None = None,
226
+ only_in: list[str] | None = None,
227
+ **kwargs: Any,
228
+ ) -> None:
229
+ """
230
+ Log a debug message.
231
+
232
+ Args:
233
+ message: Log message
234
+ extra_data: Extra context data
235
+ only_in: List of environments to log in (e.g., ['development', 'debug'])
236
+ """
237
+ self._log_with_context(logging.DEBUG, message, extra_data, only_in, **kwargs)
238
+
239
+ def info(
240
+ self,
241
+ message: str,
242
+ extra_data: dict[str, Any] | None = None,
243
+ only_in: list[str] | None = None,
244
+ **kwargs: Any,
245
+ ) -> None:
246
+ """
247
+ Log an info message.
248
+
249
+ Args:
250
+ message: Log message
251
+ extra_data: Extra context data
252
+ only_in: List of environments to log in (e.g., ['development', 'production'])
253
+ """
254
+ self._log_with_context(logging.INFO, message, extra_data, only_in, **kwargs)
255
+
256
+ def warning(
257
+ self,
258
+ message: str,
259
+ extra_data: dict[str, Any] | None = None,
260
+ only_in: list[str] | None = None,
261
+ **kwargs: Any,
262
+ ) -> None:
263
+ """
264
+ Log a warning message.
265
+
266
+ Args:
267
+ message: Log message
268
+ extra_data: Extra context data
269
+ only_in: List of environments to log in
270
+ """
271
+ self._log_with_context(logging.WARNING, message, extra_data, only_in, **kwargs)
272
+
273
+ def error(
274
+ self,
275
+ message: str,
276
+ extra_data: dict[str, Any] | None = None,
277
+ only_in: list[str] | None = None,
278
+ **kwargs: Any,
279
+ ) -> None:
280
+ """
281
+ Log an error message.
282
+
283
+ Args:
284
+ message: Log message
285
+ extra_data: Extra context data
286
+ only_in: List of environments to log in
287
+ """
288
+ self._log_with_context(logging.ERROR, message, extra_data, only_in, **kwargs)
289
+
290
+ def critical(
291
+ self,
292
+ message: str,
293
+ extra_data: dict[str, Any] | None = None,
294
+ only_in: list[str] | None = None,
295
+ **kwargs: Any,
296
+ ) -> None:
297
+ """
298
+ Log a critical message.
299
+
300
+ Args:
301
+ message: Log message
302
+ extra_data: Extra context data
303
+ only_in: List of environments to log in
304
+ """
305
+ self._log_with_context(logging.CRITICAL, message, extra_data, only_in, **kwargs)
306
+
307
+ def exception(
308
+ self,
309
+ message: str,
310
+ extra_data: dict[str, Any] | None = None,
311
+ only_in: list[str] | None = None,
312
+ **kwargs: Any,
313
+ ) -> None:
314
+ """
315
+ Log an exception with traceback.
316
+
317
+ Args:
318
+ message: Log message
319
+ extra_data: Extra context data
320
+ only_in: List of environments to log in
321
+ """
322
+ kwargs["exc_info"] = True
323
+ self._log_with_context(logging.ERROR, message, extra_data, only_in, **kwargs)
324
+
325
+
326
+ def get_logger(name: str, config: LogSettings | None = None) -> FastLogger:
327
+ """
328
+ Factory function to get a module-specific logger.
329
+
330
+ Args:
331
+ name: Logger name (typically __name__ of the calling module)
332
+ config: Optional custom configuration
333
+
334
+ Returns:
335
+ FastLogger instance
336
+
337
+ Example:
338
+ >>> from log2fast_fastapi import get_logger
339
+ >>> logger = get_logger(__name__)
340
+ >>> logger.info("Application started")
341
+ """
342
+ return FastLogger(name, config)
@@ -0,0 +1,148 @@
1
+ import json
2
+ import logging
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+
7
+ class ColoredFormatter(logging.Formatter):
8
+ """Colored formatter for console output in development."""
9
+
10
+ # ANSI color codes
11
+ COLORS = {
12
+ "DEBUG": "\033[36m", # Cyan
13
+ "INFO": "\033[32m", # Green
14
+ "WARNING": "\033[33m", # Yellow
15
+ "ERROR": "\033[31m", # Red
16
+ "CRITICAL": "\033[35m", # Magenta
17
+ }
18
+ RESET = "\033[0m"
19
+ BOLD = "\033[1m"
20
+
21
+ def format(self, record: logging.LogRecord) -> str:
22
+ """Format log record with colors.
23
+
24
+ IMPORTANT: We create a shallow copy of the record to avoid modifying
25
+ the original, which would cause color codes to leak into file logs.
26
+ """
27
+ # Create a shallow copy to avoid modifying the original record
28
+ record_copy = logging.makeLogRecord(record.__dict__)
29
+
30
+ # Add color to level name
31
+ levelname = record_copy.levelname
32
+ if levelname in self.COLORS:
33
+ record_copy.levelname = (
34
+ f"{self.COLORS[levelname]}{self.BOLD}{levelname}{self.RESET}"
35
+ )
36
+
37
+ # Add color to module name
38
+ record_copy.name = f"\033[94m{record_copy.name}{self.RESET}" # Blue
39
+
40
+ # Format timestamp
41
+ record_copy.asctime = self.formatTime(record_copy, "%Y-%m-%d %H:%M:%S")
42
+
43
+ # Format the message using the copy
44
+ formatted = super().format(record_copy)
45
+
46
+ return formatted
47
+
48
+
49
+ class JSONFormatter(logging.Formatter):
50
+ """JSON formatter for structured logging in production."""
51
+
52
+ def format(self, record: logging.LogRecord) -> str:
53
+ """Format log record as JSON."""
54
+ log_data: dict[str, Any] = {
55
+ "timestamp": datetime.fromtimestamp(record.created).isoformat(),
56
+ "level": record.levelname,
57
+ "logger": record.name,
58
+ "message": record.getMessage(),
59
+ "module": record.module,
60
+ "function": record.funcName,
61
+ "line": record.lineno,
62
+ }
63
+
64
+ # Add exception info if present
65
+ if record.exc_info:
66
+ log_data["exception"] = self.formatException(record.exc_info)
67
+
68
+ # Add extra fields from record
69
+ if hasattr(record, "request_id"):
70
+ log_data["request_id"] = record.request_id
71
+
72
+ if hasattr(record, "user_id"):
73
+ log_data["user_id"] = record.user_id
74
+
75
+ if hasattr(record, "extra_data"):
76
+ log_data["extra"] = record.extra_data
77
+
78
+ # Add any custom attributes
79
+ for key, value in record.__dict__.items():
80
+ if key not in [
81
+ "name",
82
+ "msg",
83
+ "args",
84
+ "created",
85
+ "filename",
86
+ "funcName",
87
+ "levelname",
88
+ "levelno",
89
+ "lineno",
90
+ "module",
91
+ "msecs",
92
+ "message",
93
+ "pathname",
94
+ "process",
95
+ "processName",
96
+ "relativeCreated",
97
+ "thread",
98
+ "threadName",
99
+ "exc_info",
100
+ "exc_text",
101
+ "stack_info",
102
+ "request_id",
103
+ "user_id",
104
+ "extra_data",
105
+ ]:
106
+ if not key.startswith("_"):
107
+ log_data[key] = value
108
+
109
+ return json.dumps(log_data, ensure_ascii=False)
110
+
111
+
112
+ class StructuredFormatter(logging.Formatter):
113
+ """Structured formatter for human-readable logs with context."""
114
+
115
+ def format(self, record: logging.LogRecord) -> str:
116
+ """Format log record with structured information."""
117
+ # Base format
118
+ base = f"[{self.formatTime(record, '%Y-%m-%d %H:%M:%S')}] {record.levelname:8s} | {record.name:20s} | {record.getMessage()}"
119
+
120
+ # Add context if available
121
+ context_parts = []
122
+
123
+ if hasattr(record, "request_id"):
124
+ context_parts.append(f"request_id={record.request_id}")
125
+
126
+ if hasattr(record, "user_id"):
127
+ context_parts.append(f"user_id={record.user_id}")
128
+
129
+ if hasattr(record, "extra_data") and record.extra_data:
130
+ for key, value in record.extra_data.items():
131
+ context_parts.append(f"{key}={value}")
132
+
133
+ if context_parts:
134
+ base += f" [{', '.join(context_parts)}]"
135
+
136
+ # Add exception if present
137
+ if record.exc_info:
138
+ base += f"\n{self.formatException(record.exc_info)}"
139
+
140
+ return base
141
+
142
+
143
+ class SimpleFormatter(logging.Formatter):
144
+ """Simple formatter for testing and minimal output."""
145
+
146
+ def format(self, record: logging.LogRecord) -> str:
147
+ """Format log record simply."""
148
+ return f"[{record.levelname}] {record.name}: {record.getMessage()}"
@@ -0,0 +1,106 @@
1
+ import time
2
+ import uuid
3
+ from collections.abc import Callable
4
+ from contextvars import ContextVar
5
+
6
+ from fastapi import Request, Response
7
+ from starlette.middleware.base import BaseHTTPMiddleware
8
+
9
+ from .base import get_logger
10
+
11
+ # Context variable to store request ID across async calls
12
+ request_id_var: ContextVar[str] = ContextVar("request_id", default="")
13
+
14
+ logger = get_logger(__name__)
15
+
16
+
17
+ class RequestLoggingMiddleware(BaseHTTPMiddleware):
18
+ """
19
+ Middleware for logging HTTP requests and responses in FastAPI.
20
+
21
+ Features:
22
+ - Automatic request ID generation
23
+ - Request/response timing
24
+ - Configurable body logging
25
+ - Context injection for request ID
26
+ """
27
+
28
+ async def dispatch(
29
+ self, request: Request, call_next: Callable[[Request], Response]
30
+ ) -> Response:
31
+ """Process the request and log information."""
32
+ # Generate unique request ID
33
+ request_id = str(uuid.uuid4())
34
+ request_id_var.set(request_id)
35
+
36
+ # Start timing
37
+ start_time = time.time()
38
+
39
+ # Log request
40
+ logger.info(
41
+ f"Request started: {request.method} {request.url.path}",
42
+ extra_data={
43
+ "method": request.method,
44
+ "path": request.url.path,
45
+ "query_params": str(request.query_params),
46
+ "client_host": request.client.host if request.client else None,
47
+ },
48
+ extra={"request_id": request_id},
49
+ )
50
+
51
+ # Process request
52
+ try:
53
+ response = await call_next(request)
54
+
55
+ # Calculate duration
56
+ duration = time.time() - start_time
57
+
58
+ # Log response
59
+ logger.info(
60
+ f"Request completed: {request.method} {request.url.path} - {response.status_code}",
61
+ extra_data={
62
+ "method": request.method,
63
+ "path": request.url.path,
64
+ "status_code": response.status_code,
65
+ "duration_ms": round(duration * 1000, 2),
66
+ },
67
+ extra={"request_id": request_id},
68
+ )
69
+
70
+ # Add request ID to response headers
71
+ response.headers["X-Request-ID"] = request_id
72
+
73
+ return response
74
+
75
+ except Exception as e:
76
+ # Calculate duration
77
+ duration = time.time() - start_time
78
+
79
+ # Log error
80
+ logger.exception(
81
+ f"Request failed: {request.method} {request.url.path}",
82
+ extra_data={
83
+ "method": request.method,
84
+ "path": request.url.path,
85
+ "error": str(e),
86
+ "duration_ms": round(duration * 1000, 2),
87
+ },
88
+ extra={"request_id": request_id},
89
+ )
90
+
91
+ # Re-raise the exception
92
+ raise
93
+
94
+
95
+ def get_request_id() -> str:
96
+ """
97
+ Get the current request ID from context.
98
+
99
+ Returns:
100
+ Current request ID or empty string if not in request context
101
+
102
+ Example:
103
+ >>> from log2fast_fastapi.middleware import get_request_id
104
+ >>> request_id = get_request_id()
105
+ """
106
+ return request_id_var.get()
@@ -0,0 +1,187 @@
1
+ import os
2
+ from enum import Enum
3
+
4
+ from pydantic import BaseModel, Field
5
+ from pydantic_settings import BaseSettings, SettingsConfigDict
6
+
7
+ # Look for .env in the current working directory (where the app is running)
8
+ # This allows the package to work correctly when installed via pip
9
+ DOTENV_PATH = os.path.join(os.getcwd(), ".env")
10
+
11
+
12
+ class LogLevel(str, Enum):
13
+ """Log levels supported by the logger."""
14
+
15
+ DEBUG = "DEBUG"
16
+ INFO = "INFO"
17
+ WARNING = "WARNING"
18
+ ERROR = "ERROR"
19
+ CRITICAL = "CRITICAL"
20
+
21
+
22
+ class LogEnvironment(str, Enum):
23
+ """Supported logging environments."""
24
+
25
+ DEVELOPMENT = "development"
26
+ PRODUCTION = "production"
27
+ TESTING = "testing"
28
+ DEBUG = "debug"
29
+
30
+
31
+ class LogFormat(str, Enum):
32
+ """Supported log formats."""
33
+
34
+ JSON = "json"
35
+ COLORED = "colored"
36
+ STRUCTURED = "structured"
37
+ SIMPLE = "simple"
38
+
39
+
40
+ class RotationStrategy(str, Enum):
41
+ """Log file rotation strategies."""
42
+
43
+ TIME = "time" # Rotate by time (daily, hourly, etc.)
44
+ SIZE = "size" # Rotate by file size
45
+
46
+
47
+ class LogFileSettings(BaseModel):
48
+ """Configuration for file logging."""
49
+
50
+ enabled: bool = Field(default=True, description="Enable file logging")
51
+ directory: str = Field(default="logs", description="Directory for log files")
52
+
53
+ # Rotation strategy
54
+ rotation_strategy: RotationStrategy = Field(
55
+ default=RotationStrategy.TIME,
56
+ description="Rotation strategy: 'time' (daily) or 'size' (by file size)",
57
+ )
58
+
59
+ # Time-based rotation settings (when rotation_strategy='time')
60
+ when: str = Field(
61
+ default="midnight",
62
+ description="When to rotate: 'midnight', 'H' (hourly), 'D' (daily), 'W0'-'W6' (weekday)",
63
+ )
64
+ interval: int = Field(
65
+ default=1,
66
+ description="Interval for rotation (e.g., 1 for daily, 2 for every 2 days)",
67
+ )
68
+ backup_count: int = Field(
69
+ default=31, description="Number of backup files to keep (default 31 days)"
70
+ )
71
+
72
+ # Size-based rotation settings (when rotation_strategy='size')
73
+ max_bytes: int = Field(
74
+ default=10 * 1024 * 1024,
75
+ description="Max size per log file in bytes (10MB default, only for size-based rotation)",
76
+ )
77
+
78
+ # Common settings
79
+ filename_pattern: str = Field(
80
+ default="{module}_{environment}.log",
81
+ description="Pattern for log filenames. Available: {module}, {environment}, {logger}",
82
+ )
83
+ per_module_files: bool = Field(
84
+ default=False,
85
+ description="Create separate log files per module (uses logger name in filename)",
86
+ )
87
+
88
+
89
+ class LogSettings(BaseSettings):
90
+ """Main logging configuration."""
91
+
92
+ # Environment configuration
93
+ log_environment: LogEnvironment = Field(
94
+ default=LogEnvironment.DEVELOPMENT,
95
+ description="Current logging environment",
96
+ )
97
+
98
+ # Log level configuration (None = auto-configure based on environment)
99
+ log_level: LogLevel | None = Field(
100
+ default=None,
101
+ description="Minimum log level to capture (None = auto from environment)",
102
+ )
103
+
104
+ # Format configuration (None = auto-configure based on environment)
105
+ log_format: LogFormat | None = Field(
106
+ default=None, description="Log output format (None = auto from environment)"
107
+ )
108
+
109
+ # Console logging
110
+ console_enabled: bool = Field(default=True, description="Enable console logging")
111
+
112
+ # File logging
113
+ file_settings: LogFileSettings = Field(
114
+ default_factory=LogFileSettings, description="File logging configuration"
115
+ )
116
+
117
+ # Request logging (for FastAPI middleware)
118
+ log_requests: bool = Field(
119
+ default=True, description="Enable request/response logging"
120
+ )
121
+ log_request_body: bool = Field(
122
+ default=False, description="Include request body in logs (be careful with PII)"
123
+ )
124
+ log_response_body: bool = Field(
125
+ default=False,
126
+ description="Include response body in logs (be careful with PII)",
127
+ )
128
+
129
+ # Module-specific settings
130
+ module_name: str | None = Field(
131
+ default=None, description="Default module name for loggers"
132
+ )
133
+
134
+ model_config = SettingsConfigDict(
135
+ env_file=DOTENV_PATH,
136
+ env_file_encoding="utf-8",
137
+ env_prefix="LOG_",
138
+ env_nested_delimiter="__", # Enable reading nested vars like LOG_FILE_SETTINGS__DIRECTORY
139
+ extra="ignore",
140
+ )
141
+
142
+ def get_effective_level(self) -> str:
143
+ """Get the effective log level based on environment."""
144
+ # If explicitly set, use that value
145
+ if self.log_level is not None:
146
+ return self.log_level.value
147
+
148
+ # Auto-configure based on environment
149
+ environment_defaults = {
150
+ LogEnvironment.DEBUG: LogLevel.DEBUG,
151
+ LogEnvironment.DEVELOPMENT: LogLevel.INFO,
152
+ LogEnvironment.TESTING: LogLevel.INFO,
153
+ LogEnvironment.PRODUCTION: LogLevel.WARNING,
154
+ }
155
+
156
+ return environment_defaults.get(self.log_environment, LogLevel.INFO).value
157
+
158
+ def get_effective_format(self) -> str:
159
+ """Get the effective log format based on environment."""
160
+ # If explicitly set, use that value
161
+ if self.log_format is not None:
162
+ return self.log_format.value
163
+
164
+ # Auto-configure based on environment
165
+ environment_defaults = {
166
+ LogEnvironment.DEBUG: LogFormat.COLORED,
167
+ LogEnvironment.DEVELOPMENT: LogFormat.COLORED,
168
+ LogEnvironment.TESTING: LogFormat.SIMPLE,
169
+ LogEnvironment.PRODUCTION: LogFormat.JSON,
170
+ }
171
+
172
+ return environment_defaults.get(self.log_environment, LogFormat.COLORED).value
173
+
174
+
175
+ try:
176
+ settings = LogSettings()
177
+ except Exception as e:
178
+ import traceback
179
+
180
+ print("🚨 Error loading log configuration:")
181
+ print(e)
182
+ traceback.print_exc()
183
+ # Fallback to defaults (auto-configure based on environment)
184
+ settings = LogSettings(
185
+ log_environment=LogEnvironment.DEVELOPMENT,
186
+ )
187
+ print("⚠️ Using fallback log configuration (DEVELOPMENT mode)")
@@ -0,0 +1,287 @@
1
+ Metadata-Version: 2.4
2
+ Name: log2fast-fastapi
3
+ Version: 0.1.0
4
+ Summary: Advanced logging module for FastAPI - Designed for Solautyc Team internal use
5
+ Author-email: Angel Daniel Sanchez Castillo <angeldaniel.sanchezcastillo@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Angel Daniel Sanchez Castillo
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/AngelDanielSanchezCastillo/log2fast-fastapi
29
+ Project-URL: Documentation, https://github.com/AngelDanielSanchezCastillo/log2fast-fastapi/tree/main/docs
30
+ Project-URL: Repository, https://github.com/AngelDanielSanchezCastillo/log2fast-fastapi
31
+ Project-URL: Issues, https://github.com/AngelDanielSanchezCastillo/log2fast-fastapi/issues
32
+ Keywords: fastapi,logging,rotation,colored-logs,structured-logging,middleware
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Framework :: FastAPI
41
+ Classifier: Topic :: System :: Logging
42
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
43
+ Requires-Python: >=3.10
44
+ Description-Content-Type: text/markdown
45
+ License-File: LICENSE
46
+ Requires-Dist: fastapi>=0.100.0
47
+ Requires-Dist: pydantic>=2.0.0
48
+ Requires-Dist: pydantic-settings>=2.0.0
49
+ Provides-Extra: dev
50
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
51
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
52
+ Requires-Dist: httpx>=0.24.0; extra == "dev"
53
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
54
+ Dynamic: license-file
55
+
56
+ # log2fast-fastapi
57
+
58
+ 🚀 Advanced logging module for FastAPI with file rotation, colored output, and environment-based auto-configuration
59
+
60
+ > [!WARNING]
61
+ > **Internal Use Notice**
62
+ >
63
+ > This package is designed and maintained by the **Solautyc Team** for internal use. While it is publicly available, it may not work as expected in all environments or use cases outside of our specific infrastructure. We do not provide support or guarantees for external usage, and we are not responsible for any issues that may arise from using this package in other contexts.
64
+ >
65
+ > Use at your own risk. Contributions and feedback are welcome, but compatibility with external environments is not guaranteed.
66
+
67
+ ## Features
68
+
69
+ - 🎨 **Multiple Output Formats**: JSON (production), Colored (development), Structured (debugging), Simple (testing)
70
+ - 🌍 **Environment-Based Configuration**: Automatic setup for dev, test, prod, and debug
71
+ - 📦 **Module-Based Loggers**: Each module gets its own logger instance
72
+ - 🔄 **File Rotation**: Automatic log file rotation with configurable size
73
+ - 🔄 **Environment-Specific Logging**: Control which logs appear in which environments (prevent sensitive data leaks)
74
+ - 🚀 **FastAPI Integration**: Middleware for automatic request/response logging with unique request IDs
75
+ - 📊 **Structured Logging**: Add context data to any log message
76
+ - 📊 **Context Injection**: Support for request_id, user_id, and custom context data
77
+ - 🎯 **Zero Configuration**: Works out of the box with sensible defaults
78
+
79
+ ## 📚 Documentation
80
+
81
+ - **[Usage Guide](docs/usage.md)** - Comprehensive usage guide with examples
82
+ - **[File Management](docs/file_management.md)** - Complete guide on log rotation and storage (English)
83
+ - **[Gestión de Archivos](docs/file_management_es.md)** - Guía completa de rotación y almacenamiento (Español)
84
+ - **[Logger Best Practices](docs/logger_best_practices.md)** - Best practices for creating and naming loggers
85
+
86
+
87
+ ## Installation
88
+
89
+ ### From PyPI (Recommended)
90
+
91
+ ```bash
92
+ pip install log2fast-fastapi
93
+ ```
94
+
95
+ ### From Source
96
+
97
+ ```bash
98
+ # Clone the repository
99
+ git clone https://github.com/AngelDanielSanchezCastillo/log2fast-fastapi.git
100
+ cd log2fast-fastapi
101
+
102
+ # Install in development mode
103
+ pip install -e .
104
+
105
+ # Or install with dev dependencies
106
+ pip install -e ".[dev]"
107
+ ```
108
+
109
+
110
+ ## Quick Start
111
+
112
+ ### Basic Usage
113
+
114
+ ```python
115
+ from log2fast_fastapi import get_logger
116
+
117
+ logger = get_logger(__name__)
118
+
119
+ logger.info("Application started")
120
+ logger.warning("This is a warning")
121
+ logger.error("An error occurred")
122
+ ```
123
+
124
+ ### FastAPI Integration
125
+
126
+ ```python
127
+ from fastapi import FastAPI
128
+ from log2fast_fastapi import RequestLoggingMiddleware, get_logger
129
+
130
+ app = FastAPI()
131
+ app.add_middleware(RequestLoggingMiddleware)
132
+
133
+ logger = get_logger(__name__)
134
+
135
+ @app.get("/")
136
+ async def root():
137
+ logger.info("Root endpoint accessed")
138
+ return {"message": "Hello World"}
139
+ ```
140
+
141
+ ### Logging with Context
142
+
143
+ ```python
144
+ from log2fast_fastapi import get_logger
145
+
146
+ logger = get_logger(__name__)
147
+
148
+ logger.info(
149
+ "User logged in",
150
+ extra_data={
151
+ "user_id": "12345",
152
+ "ip_address": "192.168.1.1"
153
+ }
154
+ )
155
+ ```
156
+
157
+ ### Environment-Specific Logging (Prevent Sensitive Data Leaks!)
158
+
159
+ ```python
160
+ # Logs ONLY in development/debug (NOT in production)
161
+ logger.debug(
162
+ "Sensitive debug info",
163
+ extra_data={"password_hash": "...", "token": "..."},
164
+ only_in=["development", "debug"]
165
+ )
166
+
167
+ # Logs ONLY in production
168
+ logger.info(
169
+ "Performance metrics",
170
+ extra_data={"response_time": 120},
171
+ only_in=["production"]
172
+ )
173
+ ```
174
+
175
+ ## Configuration
176
+
177
+ **Simple: Just set the environment in `.env`**
178
+
179
+ ```bash
180
+ # That's it! Format and level auto-configure
181
+ LOG_ENVIRONMENT=production
182
+ ```
183
+
184
+ Auto-configuration by environment:
185
+
186
+ | Environment | Auto Level | Auto Format |
187
+ |------------|-----------|-------------|
188
+ | `development` | INFO | colored |
189
+ | `production` | WARNING | json |
190
+ | `testing` | INFO | simple |
191
+ | `debug` | DEBUG | colored |
192
+
193
+ **Optional: Override defaults**
194
+
195
+
196
+ ```bash
197
+ # Optional: Override auto-configuration
198
+ LOG_LEVEL=DEBUG
199
+ LOG_FORMAT=json
200
+ LOG_FILE_SETTINGS__ENABLED=true
201
+ ```
202
+
203
+ ## Environment Presets
204
+
205
+ ### Development
206
+ - Format: Colored console output
207
+ - Level: INFO
208
+ - Perfect for local development
209
+
210
+ ### Production
211
+ - Format: JSON (structured)
212
+ - Level: WARNING
213
+ - Optimized for log aggregation tools
214
+
215
+ ### Testing
216
+ - Format: Simple
217
+ - Level: INFO
218
+ - Minimal output for tests
219
+
220
+ ### Debug
221
+ - Format: Colored
222
+ - Level: DEBUG
223
+ - Maximum verbosity
224
+
225
+ ## Documentation
226
+
227
+ See [docs/usage.md](docs/usage.md) for complete documentation including:
228
+ - Advanced configuration
229
+ - Custom formatters
230
+ - Best practices
231
+ - Integration examples
232
+
233
+ ## Example
234
+
235
+ Run the example application:
236
+
237
+ ```bash
238
+ python log2fast_fastapi/example.py
239
+ ```
240
+
241
+ Then visit:
242
+ - http://localhost:8000/ - Root endpoint
243
+ - http://localhost:8000/users/123 - User endpoint
244
+ - http://localhost:8000/docs - API documentation
245
+
246
+ ## Testing
247
+
248
+ Run the test suite:
249
+
250
+ ```bash
251
+ python log2fast_fastapi/tests/test_logging.py
252
+ ```
253
+
254
+ ## Module Structure
255
+
256
+ ```
257
+ log2fast-fastapi/
258
+ ├── pyproject.toml # Package configuration
259
+ ├── MANIFEST.in # Additional files to include
260
+ ├── README.md # This file
261
+ ├── LICENSE # License file
262
+ ├── src/
263
+ │ └── log2fast_fastapi/
264
+ │ ├── __init__.py # Main exports
265
+ │ ├── __version__.py # Version information
266
+ │ ├── base.py # Core FastLogger class
267
+ │ ├── settings.py # Configuration with Pydantic
268
+ │ ├── formatters.py # Custom log formatters
269
+ │ └── middleware.py # FastAPI middleware
270
+ ├── docs/
271
+ │ ├── usage.md # Complete documentation
272
+ │ ├── file_management.md # File rotation guide (EN)
273
+ │ ├── file_management_es.md # File rotation guide (ES)
274
+ │ ├── logger_best_practices.md # Best practices
275
+ │ └── publishing.md # PyPI publishing guide
276
+ ├── examples/
277
+ │ ├── example.py # Basic example
278
+ │ ├── demo_features.py # Feature demonstrations
279
+ │ └── demo_rotation.py # Rotation examples
280
+ └── tests/
281
+ ├── test_logging.py # Test suite
282
+ └── test_new_features.py # Feature tests
283
+ ```
284
+
285
+ ## License
286
+
287
+ Same as parent project.
@@ -0,0 +1,11 @@
1
+ log2fast_fastapi/__init__.py,sha256=MrLI4vM6FL8srk59V17kel__hiTWq-WIJ-MlS26RObM,553
2
+ log2fast_fastapi/__version__.py,sha256=kCz8xqPR71dMahmGZzxB8Rtha610gFPxiN-puxGgNSg,136
3
+ log2fast_fastapi/base.py,sha256=jd0dM3umwY-oGo161p-ianwZlspVBoien19bN8pIPVE,11113
4
+ log2fast_fastapi/formatters.py,sha256=OHjPiZp1aYF21_FK12ffdSkpZYBJVF8qAwybtAIVBcQ,4758
5
+ log2fast_fastapi/middleware.py,sha256=WslD7pQMWvsHYxRI1yLWZD6kffh1bTtmzJ7rZlInFKA,3162
6
+ log2fast_fastapi/settings.py,sha256=tRGoOKigjy1J_kmFjsSiyovjlXe3-ubZL8HUViRNkDs,5962
7
+ log2fast_fastapi-0.1.0.dist-info/licenses/LICENSE,sha256=CkISX1hNEwxxrPTOXet3IYMEH28Bn7SoUyKniRjg68I,1086
8
+ log2fast_fastapi-0.1.0.dist-info/METADATA,sha256=h2RXhjg5kl7C2PRkMeXprVVwJCotkn9QrjV_PguQRkA,9163
9
+ log2fast_fastapi-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
10
+ log2fast_fastapi-0.1.0.dist-info/top_level.txt,sha256=f7HIwpDpuUBk-2bhKzlkZ-WWqOS_eR_LO8fZ-XH3mqg,17
11
+ log2fast_fastapi-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Angel Daniel Sanchez Castillo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ log2fast_fastapi