292 lines
8.6 KiB
Markdown
292 lines
8.6 KiB
Markdown
|
|
# Conditional Logging System
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
The TCP Dashboard project implements a sophisticated conditional logging system that provides fine-grained control over logging behavior across all components. This system supports hierarchical logging, conditional logging, and error-only logging modes.
|
||
|
|
|
||
|
|
## Key Features
|
||
|
|
|
||
|
|
### 1. Conditional Logging
|
||
|
|
- **No Logger**: If no logger instance is passed to a component's constructor, that component performs no logging operations
|
||
|
|
- **Logger Provided**: If a logger instance is passed, the component uses it for logging
|
||
|
|
- **Error-Only Mode**: If `log_errors_only=True` is set, only error and critical level messages are logged
|
||
|
|
|
||
|
|
### 2. Logger Inheritance
|
||
|
|
- Components that receive a logger pass the same logger instance down to child components
|
||
|
|
- This creates a hierarchical logging structure that follows the component hierarchy
|
||
|
|
|
||
|
|
### 3. Hierarchical File Organization
|
||
|
|
- Log files are organized based on component hierarchy
|
||
|
|
- Each major component gets its own log directory
|
||
|
|
- Child components log to their parent's log file
|
||
|
|
|
||
|
|
## Component Hierarchy
|
||
|
|
|
||
|
|
```
|
||
|
|
Top-level Application (individual logger)
|
||
|
|
├── ProductionManager (individual logger)
|
||
|
|
│ ├── DataSaver (receives logger from ProductionManager)
|
||
|
|
│ ├── DataValidator (receives logger from ProductionManager)
|
||
|
|
│ ├── DatabaseConnection (receives logger from ProductionManager)
|
||
|
|
│ └── CollectorManager (individual logger)
|
||
|
|
│ ├── OKX collector BTC-USD (individual logger)
|
||
|
|
│ │ ├── DataAggregator (receives logger from OKX collector)
|
||
|
|
│ │ ├── DataTransformer (receives logger from OKX collector)
|
||
|
|
│ │ └── DataProcessor (receives logger from OKX collector)
|
||
|
|
│ └── Another collector...
|
||
|
|
```
|
||
|
|
|
||
|
|
## Usage Examples
|
||
|
|
|
||
|
|
### Basic Usage
|
||
|
|
|
||
|
|
```python
|
||
|
|
from utils.logger import get_logger
|
||
|
|
from data.exchanges.okx.collector import OKXCollector
|
||
|
|
|
||
|
|
# Create a logger for the collector
|
||
|
|
collector_logger = get_logger('okx_collector_btc_usdt', verbose=True)
|
||
|
|
|
||
|
|
# Create collector with logger - all child components will use this logger
|
||
|
|
collector = OKXCollector(
|
||
|
|
symbol='BTC-USDT',
|
||
|
|
logger=collector_logger
|
||
|
|
)
|
||
|
|
|
||
|
|
# Child components (data processor, validator, transformer) will automatically
|
||
|
|
# receive and use the same logger instance
|
||
|
|
```
|
||
|
|
|
||
|
|
### No Logging Mode
|
||
|
|
|
||
|
|
```python
|
||
|
|
# Create collector without logger - no logging will be performed
|
||
|
|
collector = OKXCollector(
|
||
|
|
symbol='BTC-USDT',
|
||
|
|
logger=None # or simply omit the parameter
|
||
|
|
)
|
||
|
|
|
||
|
|
# No log files will be created, no console output
|
||
|
|
```
|
||
|
|
|
||
|
|
### Error-Only Logging Mode
|
||
|
|
|
||
|
|
```python
|
||
|
|
from utils.logger import get_logger
|
||
|
|
from data.collector_manager import CollectorManager
|
||
|
|
|
||
|
|
# Create logger for manager
|
||
|
|
manager_logger = get_logger('collector_manager', verbose=True)
|
||
|
|
|
||
|
|
# Create manager with error-only logging
|
||
|
|
manager = CollectorManager(
|
||
|
|
manager_name="production_manager",
|
||
|
|
logger=manager_logger,
|
||
|
|
log_errors_only=True # Only errors and critical messages will be logged
|
||
|
|
)
|
||
|
|
|
||
|
|
# Manager will only log errors, but child collectors can have their own loggers
|
||
|
|
```
|
||
|
|
|
||
|
|
### Hierarchical Logging Setup
|
||
|
|
|
||
|
|
```python
|
||
|
|
from utils.logger import get_logger
|
||
|
|
from data.collector_manager import CollectorManager
|
||
|
|
from data.exchanges.okx.collector import OKXCollector
|
||
|
|
|
||
|
|
# Create manager with its own logger
|
||
|
|
manager_logger = get_logger('collector_manager', verbose=True)
|
||
|
|
manager = CollectorManager(logger=manager_logger)
|
||
|
|
|
||
|
|
# Create individual collectors with their own loggers
|
||
|
|
btc_logger = get_logger('okx_collector_btc_usdt', verbose=True)
|
||
|
|
eth_logger = get_logger('okx_collector_eth_usdt', verbose=True)
|
||
|
|
|
||
|
|
btc_collector = OKXCollector('BTC-USDT', logger=btc_logger)
|
||
|
|
eth_collector = OKXCollector('ETH-USDT', logger=eth_logger)
|
||
|
|
|
||
|
|
# Add collectors to manager
|
||
|
|
manager.add_collector(btc_collector)
|
||
|
|
manager.add_collector(eth_collector)
|
||
|
|
|
||
|
|
# Result:
|
||
|
|
# - Manager logs to: logs/collector_manager/YYYY-MM-DD.txt
|
||
|
|
# - BTC collector logs to: logs/okx_collector_btc_usdt/YYYY-MM-DD.txt
|
||
|
|
# - ETH collector logs to: logs/okx_collector_eth_usdt/YYYY-MM-DD.txt
|
||
|
|
# - All child components of each collector log to their parent's file
|
||
|
|
```
|
||
|
|
|
||
|
|
## Implementation Details
|
||
|
|
|
||
|
|
### Base Classes
|
||
|
|
|
||
|
|
All base classes support conditional logging:
|
||
|
|
|
||
|
|
```python
|
||
|
|
class BaseDataCollector:
|
||
|
|
def __init__(self, ..., logger=None, log_errors_only=False):
|
||
|
|
self.logger = logger
|
||
|
|
self.log_errors_only = log_errors_only
|
||
|
|
|
||
|
|
def _log_debug(self, message: str) -> None:
|
||
|
|
if self.logger and not self.log_errors_only:
|
||
|
|
self.logger.debug(message)
|
||
|
|
|
||
|
|
def _log_error(self, message: str, exc_info: bool = False) -> None:
|
||
|
|
if self.logger:
|
||
|
|
self.logger.error(message, exc_info=exc_info)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Child Component Pattern
|
||
|
|
|
||
|
|
Child components receive logger from parent:
|
||
|
|
|
||
|
|
```python
|
||
|
|
class OKXCollector(BaseDataCollector):
|
||
|
|
def __init__(self, symbol: str, logger=None):
|
||
|
|
super().__init__(..., logger=logger)
|
||
|
|
|
||
|
|
# Pass logger to child components
|
||
|
|
self._data_processor = OKXDataProcessor(
|
||
|
|
symbol,
|
||
|
|
logger=self.logger # Pass parent's logger
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Conditional Logging Helpers
|
||
|
|
|
||
|
|
All components use helper methods for conditional logging:
|
||
|
|
|
||
|
|
```python
|
||
|
|
def _log_debug(self, message: str) -> None:
|
||
|
|
"""Log debug message if logger is available and not in errors-only mode."""
|
||
|
|
if self.logger and not self.log_errors_only:
|
||
|
|
self.logger.debug(message)
|
||
|
|
|
||
|
|
def _log_info(self, message: str) -> None:
|
||
|
|
"""Log info message if logger is available and not in errors-only mode."""
|
||
|
|
if self.logger and not self.log_errors_only:
|
||
|
|
self.logger.info(message)
|
||
|
|
|
||
|
|
def _log_warning(self, message: str) -> None:
|
||
|
|
"""Log warning message if logger is available and not in errors-only mode."""
|
||
|
|
if self.logger and not self.log_errors_only:
|
||
|
|
self.logger.warning(message)
|
||
|
|
|
||
|
|
def _log_error(self, message: str, exc_info: bool = False) -> None:
|
||
|
|
"""Log error message if logger is available (always logs errors)."""
|
||
|
|
if self.logger:
|
||
|
|
self.logger.error(message, exc_info=exc_info)
|
||
|
|
|
||
|
|
def _log_critical(self, message: str, exc_info: bool = False) -> None:
|
||
|
|
"""Log critical message if logger is available (always logs critical)."""
|
||
|
|
if self.logger:
|
||
|
|
self.logger.critical(message, exc_info=exc_info)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Log File Structure
|
||
|
|
|
||
|
|
```
|
||
|
|
logs/
|
||
|
|
├── collector_manager/
|
||
|
|
│ └── 2024-01-15.txt
|
||
|
|
├── okx_collector_btc_usdt/
|
||
|
|
│ └── 2024-01-15.txt
|
||
|
|
├── okx_collector_eth_usdt/
|
||
|
|
│ └── 2024-01-15.txt
|
||
|
|
└── production_manager/
|
||
|
|
└── 2024-01-15.txt
|
||
|
|
```
|
||
|
|
|
||
|
|
## Configuration Options
|
||
|
|
|
||
|
|
### Logger Parameters
|
||
|
|
|
||
|
|
- `logger`: Logger instance or None
|
||
|
|
- `log_errors_only`: Boolean flag for error-only mode
|
||
|
|
- `verbose`: Console output (when creating new loggers)
|
||
|
|
- `clean_old_logs`: Automatic cleanup of old log files
|
||
|
|
- `max_log_files`: Maximum number of log files to keep
|
||
|
|
|
||
|
|
### Environment Variables
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Enable verbose console logging
|
||
|
|
VERBOSE_LOGGING=true
|
||
|
|
|
||
|
|
# Enable console output
|
||
|
|
LOG_TO_CONSOLE=true
|
||
|
|
```
|
||
|
|
|
||
|
|
## Best Practices
|
||
|
|
|
||
|
|
### 1. Component Design
|
||
|
|
- Always accept `logger=None` parameter in constructors
|
||
|
|
- Pass logger to all child components
|
||
|
|
- Use conditional logging helper methods
|
||
|
|
- Never assume logger is available
|
||
|
|
|
||
|
|
### 2. Error Handling
|
||
|
|
- Always log errors regardless of `log_errors_only` setting
|
||
|
|
- Use appropriate log levels
|
||
|
|
- Include context in error messages
|
||
|
|
|
||
|
|
### 3. Performance
|
||
|
|
- Conditional logging has minimal performance impact
|
||
|
|
- Logger checks are fast boolean operations
|
||
|
|
- No string formatting when logging is disabled
|
||
|
|
|
||
|
|
### 4. Testing
|
||
|
|
- Test components with and without loggers
|
||
|
|
- Verify error-only mode works correctly
|
||
|
|
- Check that child components receive loggers properly
|
||
|
|
|
||
|
|
## Migration Guide
|
||
|
|
|
||
|
|
### Updating Existing Components
|
||
|
|
|
||
|
|
1. **Add logger parameter to constructor**:
|
||
|
|
```python
|
||
|
|
def __init__(self, ..., logger=None, log_errors_only=False):
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Add conditional logging helpers**:
|
||
|
|
```python
|
||
|
|
def _log_debug(self, message: str) -> None:
|
||
|
|
if self.logger and not self.log_errors_only:
|
||
|
|
self.logger.debug(message)
|
||
|
|
```
|
||
|
|
|
||
|
|
3. **Update all logging calls**:
|
||
|
|
```python
|
||
|
|
# Before
|
||
|
|
self.logger.info("Message")
|
||
|
|
|
||
|
|
# After
|
||
|
|
self._log_info("Message")
|
||
|
|
```
|
||
|
|
|
||
|
|
4. **Pass logger to child components**:
|
||
|
|
```python
|
||
|
|
child = ChildComponent(logger=self.logger)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Testing Changes
|
||
|
|
|
||
|
|
```python
|
||
|
|
# Test without logger
|
||
|
|
component = MyComponent(logger=None)
|
||
|
|
# Should work without errors, no logging
|
||
|
|
|
||
|
|
# Test with logger
|
||
|
|
logger = get_logger('test_component')
|
||
|
|
component = MyComponent(logger=logger)
|
||
|
|
# Should log normally
|
||
|
|
|
||
|
|
# Test error-only mode
|
||
|
|
component = MyComponent(logger=logger, log_errors_only=True)
|
||
|
|
# Should only log errors
|
||
|
|
```
|
||
|
|
|
||
|
|
This conditional logging system provides maximum flexibility while maintaining clean, maintainable code that works in all scenarios.
|