|
1 | 1 | """ |
2 | | -Standardized logging with SQLite storage support. |
| 2 | +Standardized logging with SQLite storage support for DialogChain. |
| 3 | +
|
| 4 | +This module provides a robust logging solution with the following features: |
| 5 | +- SQLite-based log storage for persistence and querying |
| 6 | +- Configurable log levels and formats |
| 7 | +- Thread-safe logging |
| 8 | +- Structured log data with timestamps and metadata |
| 9 | +- Support for both file and database logging |
| 10 | +
|
| 11 | +Example usage: |
| 12 | + >>> from dialogchain.utils.logger import setup_logger, get_logs |
| 13 | + >>> logger = setup_logger(__name__, log_level='DEBUG') |
| 14 | + >>> logger.info('This is an info message', extra={'key': 'value'}) |
| 15 | + >>> logs = get_logs(limit=10) |
3 | 16 | """ |
4 | 17 | import logging |
5 | 18 | import sqlite3 |
6 | 19 | import json |
7 | 20 | import os |
| 21 | +import sys |
| 22 | +import threading |
8 | 23 | from pathlib import Path |
9 | 24 | from datetime import datetime |
10 | | -from typing import Dict, Any, Optional, List |
| 25 | +from typing import Dict, Any, Optional, List, Union |
| 26 | +from logging.handlers import RotatingFileHandler |
| 27 | + |
| 28 | +# Default configuration |
| 29 | +DEFAULT_LOG_LEVEL = logging.INFO |
| 30 | +DEFAULT_DB_PATH = 'logs/dialogchain.db' |
| 31 | +DEFAULT_LOG_FILE = 'logs/dialogchain.log' |
| 32 | +MAX_LOG_SIZE = 10 * 1024 * 1024 # 10MB |
| 33 | +BACKUP_COUNT = 5 |
| 34 | + |
| 35 | +# Thread lock for SQLite operations |
| 36 | +db_lock = threading.Lock() |
11 | 37 |
|
12 | 38 | # Create a module-level logger that doesn't trigger setup |
13 | 39 | _logger = logging.getLogger(__name__) |
14 | | -_logger.setLevel(logging.INFO) |
| 40 | +_logger.setLevel(DEFAULT_LOG_LEVEL) |
15 | 41 |
|
16 | | -# Add console handler if no handlers are configured |
| 42 | +# Prevent duplicate handlers |
17 | 43 | if not _logger.handlers: |
18 | | - console_handler = logging.StreamHandler() |
19 | | - console_handler.setFormatter( |
20 | | - logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') |
| 44 | + # Create logs directory if it doesn't exist |
| 45 | + os.makedirs(os.path.dirname(DEFAULT_DB_PATH) or '.', exist_ok=True) |
| 46 | + os.makedirs(os.path.dirname(DEFAULT_LOG_FILE) or '.', exist_ok=True) |
| 47 | + |
| 48 | + # Console handler |
| 49 | + console_handler = logging.StreamHandler(sys.stdout) |
| 50 | + console_formatter = logging.Formatter( |
| 51 | + '%(asctime)s - %(name)s - %(levelname)s - %(message)s', |
| 52 | + datefmt='%Y-%m-%d %H:%M:%S' |
21 | 53 | ) |
| 54 | + console_handler.setFormatter(console_formatter) |
22 | 55 | _logger.addHandler(console_handler) |
| 56 | + |
| 57 | + # File handler with rotation |
| 58 | + try: |
| 59 | + file_handler = RotatingFileHandler( |
| 60 | + DEFAULT_LOG_FILE, |
| 61 | + maxBytes=MAX_LOG_SIZE, |
| 62 | + backupCount=BACKUP_COUNT, |
| 63 | + encoding='utf-8' |
| 64 | + ) |
| 65 | + file_formatter = logging.Formatter( |
| 66 | + '{"timestamp": "%(asctime)s", "level": "%(levelname)s", ' |
| 67 | + '"module": "%(name)s", "message": "%(message)s", "data": %(extra)s}', |
| 68 | + datefmt='%Y-%m-%dT%H:%M:%S%z' |
| 69 | + ) |
| 70 | + file_handler.setFormatter(file_formatter) |
| 71 | + _logger.addHandler(file_handler) |
| 72 | + except Exception as e: |
| 73 | + _logger.error(f"Failed to initialize file handler: {e}", exc_info=True) |
23 | 74 |
|
24 | 75 |
|
25 | 76 | class DatabaseLogHandler(logging.Handler): |
|
0 commit comments