|
1 | 1 | """Logging configuration with Rich handler for beautiful colored output.""" |
2 | 2 |
|
3 | 3 | import logging |
| 4 | +import os |
| 5 | +from typing import Optional |
4 | 6 |
|
5 | 7 | from rich.logging import RichHandler |
6 | 8 |
|
7 | 9 |
|
| 10 | +def _get_log_level_from_env() -> Optional[int]: |
| 11 | + """Get log level from environment variables. |
| 12 | +
|
| 13 | + Checks for LOG_LEVEL and LOGGING_LEVEL environment variables. |
| 14 | + Supports both numeric values (10, 20, 30, 40, 50) and string values |
| 15 | + (DEBUG, INFO, WARNING, ERROR, CRITICAL). |
| 16 | +
|
| 17 | + Returns: |
| 18 | + Log level as integer, or None if not found/invalid |
| 19 | + """ |
| 20 | + for env_var in ["LOG_LEVEL", "LOGGING_LEVEL"]: |
| 21 | + level_str = os.getenv(env_var) |
| 22 | + if level_str: |
| 23 | + # Try to parse as integer first |
| 24 | + try: |
| 25 | + return int(level_str) |
| 26 | + except ValueError: |
| 27 | + pass |
| 28 | + |
| 29 | + # Try to parse as string level name |
| 30 | + level_str = level_str.upper() |
| 31 | + level_map = { |
| 32 | + "DEBUG": logging.DEBUG, |
| 33 | + "INFO": logging.INFO, |
| 34 | + "WARNING": logging.WARNING, |
| 35 | + "WARN": logging.WARNING, |
| 36 | + "ERROR": logging.ERROR, |
| 37 | + "CRITICAL": logging.CRITICAL, |
| 38 | + "FATAL": logging.CRITICAL, |
| 39 | + } |
| 40 | + if level_str in level_map: |
| 41 | + return level_map[level_str] |
| 42 | + |
| 43 | + return None |
| 44 | + |
| 45 | + |
8 | 46 | def setup_logging( |
9 | | - level: int = logging.INFO, |
| 47 | + level: Optional[int] = None, |
10 | 48 | show_path: bool = True, |
11 | 49 | third_party_level: int = logging.WARNING, |
| 50 | + log_file: Optional[str] = None, |
12 | 51 | ) -> None: |
13 | | - """Configure logging with Rich handler. |
| 52 | + """Configure logging with Rich handler for beautiful colored output. |
14 | 53 |
|
15 | 54 | Args: |
16 | | - level: Logging level (e.g., logging.INFO, logging.DEBUG) |
| 55 | + level: Logging level (e.g., logging.INFO, logging.DEBUG). |
| 56 | + If None, will check LOG_LEVEL or LOGGING_LEVEL environment variables. |
| 57 | + Defaults to logging.INFO if no environment variable is set. |
17 | 58 | show_path: Whether to show file path and line numbers in logs |
18 | 59 | third_party_level: Logging level for third-party libraries (botocore, boto3, etc.) |
| 60 | + log_file: Optional file path to also log to a file |
| 61 | + """ |
| 62 | + # Determine the actual log level to use |
| 63 | + if level is None: |
| 64 | + level = _get_log_level_from_env() |
| 65 | + if level is None: |
| 66 | + level = logging.INFO |
19 | 67 |
|
20 | | - Example: |
21 | | - >>> from redis_release.logging_config import setup_logging |
22 | | - >>> import logging |
23 | | - >>> setup_logging(level=logging.DEBUG) |
24 | | - >>> logger = logging.getLogger(__name__) |
25 | | - >>> logger.info("[blue]Hello[/blue] [green]World[/green]") |
| 68 | + handler = RichHandler( |
| 69 | + rich_tracebacks=True, |
| 70 | + show_time=True, |
| 71 | + show_level=True, |
| 72 | + show_path=show_path, |
| 73 | + markup=True, |
| 74 | + tracebacks_show_locals=True, |
| 75 | + omit_repeated_times=False, |
| 76 | + ) |
| 77 | + |
| 78 | + handlers = [handler] |
| 79 | + |
| 80 | + # Add file handler if log_file is specified |
| 81 | + if log_file: |
| 82 | + file_handler = logging.FileHandler(log_file) |
| 83 | + file_handler.setLevel(level) |
| 84 | + formatter = logging.Formatter( |
| 85 | + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" |
| 86 | + ) |
| 87 | + file_handler.setFormatter(formatter) |
| 88 | + handlers.append(file_handler) |
26 | 89 |
|
27 | | - # To see botocore debug logs: |
28 | | - >>> setup_logging(level=logging.DEBUG, third_party_level=logging.DEBUG) |
29 | | - """ |
30 | 90 | logging.basicConfig( |
31 | 91 | level=level, |
32 | | - format="[cyan1]%(name)s:[/cyan1] %(message)s", |
| 92 | + format="%(name)s: %(message)s", |
33 | 93 | datefmt="[%X]", |
34 | | - handlers=[ |
35 | | - RichHandler( |
36 | | - rich_tracebacks=True, |
37 | | - show_time=True, |
38 | | - show_level=True, |
39 | | - show_path=show_path, |
40 | | - markup=True, # Enable Rich markup in log messages |
41 | | - tracebacks_show_locals=True, # Show local variables in tracebacks |
42 | | - omit_repeated_times=False, # Force timestamp on every line |
43 | | - ) |
44 | | - ], |
| 94 | + handlers=handlers, |
| 95 | + force=True, |
45 | 96 | ) |
46 | 97 |
|
| 98 | + # Set root logger to the desired level |
| 99 | + logging.getLogger().setLevel(level) |
| 100 | + |
47 | 101 | # Optionally reduce noise from some verbose libraries |
48 | 102 | logging.getLogger("asyncio").setLevel(third_party_level) |
49 | 103 | logging.getLogger("aiohttp").setLevel(third_party_level) |
|
0 commit comments