Skip to content

Commit c7b438e

Browse files
author
Tom Softreck
committed
update
1 parent a874049 commit c7b438e

File tree

6 files changed

+129
-25
lines changed

6 files changed

+129
-25
lines changed

README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,79 @@ curl -X POST http://localhost:8000/process -H "Content-Type: application/json" -
8585

8686
For detailed documentation, please visit our [documentation site](https://dialogchain.github.io/python/).
8787

88+
## 📝 Logging
89+
90+
DialogChain includes a robust logging system with the following features:
91+
92+
### Features
93+
94+
- **Multiple Handlers**: Console and file logging out of the box
95+
- **Structured Logs**: JSON-formatted logs for easy parsing
96+
- **SQLite Storage**: Logs are stored in a searchable database
97+
- **Log Rotation**: Automatic log rotation to prevent disk space issues
98+
- **Thread-Safe**: Safe for use in multi-threaded applications
99+
100+
### Basic Usage
101+
102+
```python
103+
from dialogchain.utils.logger import setup_logger, get_logs
104+
105+
# Get a logger instance
106+
logger = setup_logger(__name__, log_level='DEBUG')
107+
108+
# Log messages with different levels
109+
logger.debug('Debug message')
110+
logger.info('Information message')
111+
logger.warning('Warning message')
112+
logger.error('Error message', extra={'error_code': 500})
113+
114+
# Get recent logs from database
115+
recent_logs = get_logs(limit=10)
116+
```
117+
118+
### Logging Commands
119+
120+
DialogChain provides several make commands for log management:
121+
122+
```bash
123+
# View recent logs (default: 50 lines)
124+
make logs
125+
126+
# View specific number of log lines
127+
make logs LINES=100
128+
129+
# Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
130+
make log-level LEVEL=DEBUG
131+
132+
# View database logs
133+
make log-db LIMIT=50
134+
135+
# Follow log file in real-time
136+
make log-tail
137+
138+
# Clear log files
139+
make log-clear
140+
```
141+
142+
### Configuration
143+
144+
Logging can be configured via environment variables:
145+
146+
```bash
147+
# Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
148+
LOG_LEVEL=DEBUG
149+
150+
# Log file path
151+
LOG_FILE=logs/dialogchain.log
152+
153+
# Database log file path
154+
DB_LOG_FILE=logs/dialogchain.db
155+
```
156+
157+
### Log Rotation
158+
159+
Log files are automatically rotated when they reach 10MB, keeping up to 5 backup files.
160+
88161
## 📦 Project Structure
89162

90163
```

pytest.ini

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
[pytest]
2+
# Test discovery
23
testpaths = tests
34
python_files = test_*.py
45
python_classes = Test*
56
python_functions = test_*
6-
asyncio_mode = auto
77

8-
# Test execution options
8+
# Test execution
99
addopts = -v --strict-markers --disable-warnings --durations=10 -p no:warnings
10+
asyncio_mode = auto
1011

11-
# Markers for test categorization
12+
# Test markers
1213
markers =
1314
slow: marks tests as slow (deselect with '-m "not slow"')
1415
integration: marks integration tests (run with '-m integration')
1516
unit: marks unit tests (run with '-m unit')
1617
network: tests that require network access
18+
asyncio: marks tests that use asyncio
19+
anyio: marks tests that use anyio
1720

18-
# Configure logging
21+
# Logging configuration
1922
log_cli = true
2023
log_cli_level = INFO
2124
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
2225
log_cli_date_format = %Y-%m-%d %H:%M:%S
2326

24-
# Test configuration
25-
[tool:pytest]
26-
testpaths = tests
27-
python_files = test_*.py
28-
python_classes = Test*
29-
python_functions = test_*
27+
# Disable warning about asyncio default fixture loop scope
28+
filterwarnings =
29+
ignore::pytest.PytestDeprecationWarning:asyncio

src/dialogchain/utils/logger.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,34 +27,52 @@
2727

2828
# Default configuration
2929
DEFAULT_LOG_LEVEL = logging.INFO
30-
DEFAULT_DB_PATH = 'logs/dialogchain.db'
31-
DEFAULT_LOG_FILE = 'logs/dialogchain.log'
30+
DEFAULT_LOG_DIR = 'logs'
31+
DEFAULT_DB_PATH = f'{DEFAULT_LOG_DIR}/dialogchain.db'
32+
DEFAULT_LOG_FILE = f'{DEFAULT_LOG_DIR}/dialogchain.log'
3233
MAX_LOG_SIZE = 10 * 1024 * 1024 # 10MB
3334
BACKUP_COUNT = 5
3435

3536
# Thread lock for SQLite operations
3637
db_lock = threading.Lock()
3738

39+
def _ensure_log_dir():
40+
"""Ensure log directory exists."""
41+
try:
42+
# Skip if already exists
43+
if os.path.exists(DEFAULT_LOG_DIR):
44+
# If it's a file, remove it
45+
if not os.path.isdir(DEFAULT_LOG_DIR):
46+
os.remove(DEFAULT_LOG_DIR)
47+
os.makedirs(DEFAULT_LOG_DIR, exist_ok=True)
48+
return True
49+
50+
# Create directory if it doesn't exist
51+
os.makedirs(DEFAULT_LOG_DIR, exist_ok=True)
52+
return True
53+
except Exception as e:
54+
print(f"Warning: Could not create log directory: {e}", file=sys.stderr)
55+
return False
56+
3857
# Create a module-level logger that doesn't trigger setup
3958
_logger = logging.getLogger(__name__)
4059
_logger.setLevel(DEFAULT_LOG_LEVEL)
4160

42-
# Prevent duplicate handlers
61+
# Ensure log directory exists
62+
_log_dir_ready = _ensure_log_dir()
63+
64+
# Add console handler by default
4365
if not _logger.handlers:
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)
66+
console_handler = logging.StreamHandler(sys.stderr)
5067
console_formatter = logging.Formatter(
5168
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
5269
datefmt='%Y-%m-%d %H:%M:%S'
5370
)
5471
console_handler.setFormatter(console_formatter)
5572
_logger.addHandler(console_handler)
56-
57-
# File handler with rotation
73+
74+
# Add file handler if directory is ready
75+
if _log_dir_ready and not any(isinstance(h, logging.FileHandler) for h in _logger.handlers):
5876
try:
5977
file_handler = RotatingFileHandler(
6078
DEFAULT_LOG_FILE,
@@ -64,11 +82,11 @@
6482
)
6583
file_formatter = logging.Formatter(
6684
'{"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'
85+
'"module": "%(name)s", "message": "%(message)s"}'
6986
)
7087
file_handler.setFormatter(file_formatter)
7188
_logger.addHandler(file_handler)
89+
_logger.info(f"File logging initialized at: {os.path.abspath(DEFAULT_LOG_FILE)}")
7290
except Exception as e:
7391
_logger.error(f"Failed to initialize file handler: {e}", exc_info=True)
7492

tests/unit/test_basic.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""Basic test without any fixtures or async code."""
2+
3+
def test_addition():
4+
"""Test basic addition."""
5+
assert 1 + 1 == 2
6+
7+
def test_string_concatenation():
8+
"""Test string concatenation."""
9+
assert "hello" + " " + "world" == "hello world"

tests/unit/test_minimal.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Minimal test to verify test setup."""
2+
3+
def test_minimal():
4+
"""A simple test that should always pass."""
5+
assert True

tests/unit/test_utils.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@
77

88
from dialogchain import utils
99

10-
# Re-export the event_loop fixture from conftest
11-
pytest_plugins = ['tests.conftest']
1210

11+
# Re-export the event_loop fixture from conftest
1312

1413

1514
def test_import_string():

0 commit comments

Comments
 (0)