Skip to content

Commit a874049

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

File tree

3 files changed

+114
-16
lines changed

3 files changed

+114
-16
lines changed

Makefile

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# DialogChain - Flexible Dialog Processing Framework
22
# Makefile for development and deployment
33

4+
# Logging configuration
5+
LOG_LEVEL ?= INFO
6+
LOG_FILE ?= logs/dialogchain.log
7+
DB_LOG_FILE ?= logs/dialogchain.db
8+
LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
9+
410
.PHONY: help install dev test clean build docker run-example lint docs \
511
test-unit test-integration test-e2e coverage typecheck format check-codestyle \
612
check-all pre-commit-install setup-dev-env docs-serve docs-clean \
@@ -26,6 +32,10 @@ help:
2632
@echo " logs - View recent logs (use: make logs LINES=50)"
2733
@echo " view-logs - View logs for running example"
2834
@echo " stop-example - Stop a running example"
35+
@echo " log-level - Set log level (use: make log-level LEVEL=DEBUG)"
36+
@echo " log-db - View database logs (use: make log-db LIMIT=50)"
37+
@echo " log-tail - Follow log file in real-time"
38+
@echo " log-clear - Clear log files"
2939
@echo " docs - Generate documentation"
3040
@echo " setup-env - Create example .env file"
3141
@echo ""
@@ -69,10 +79,18 @@ install-deps:
6979
# Development
7080
test: venv test-unit test-integration test-e2e
7181

72-
# Run unit tests
82+
# Run unit tests with logging
7383
test-unit:
74-
@pytest tests/unit/ -v --cov=src/dialogchain --cov-report=term-missing
75-
@echo "✅ Unit tests completed"
84+
@mkdir -p logs
85+
@echo "🔍 Running unit tests with log level: $(LOG_LEVEL)"
86+
@PYTHONPATH=./src pytest tests/unit/ -v \
87+
--log-cli-level=$(LOG_LEVEL) \
88+
--log-file=$(LOG_FILE) \
89+
--log-file-level=$(LOG_LEVEL) \
90+
--log-file-format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" \
91+
--cov=src/dialogchain \
92+
--cov-report=term-missing
93+
@echo "✅ Unit tests completed - Logs saved to $(LOG_FILE)"
7694

7795
# Run integration tests
7896
test-integration:
@@ -97,6 +115,32 @@ typecheck:
97115
@mypy src/dialogchain/
98116
@echo "✅ Type checking completed"
99117

118+
# Logging commands
119+
log-level:
120+
@if [ -z "$(LEVEL)" ]; then \
121+
echo "Current log level: $(LOG_LEVEL)"; \
122+
echo "Usage: make log-level LEVEL=DEBUG|INFO|WARNING|ERROR|CRITICAL"; \
123+
else \
124+
sed -i.bak 's/^LOG_LEVEL = .*/LOG_LEVEL = $(LEVEL)/' Makefile && \
125+
rm -f Makefile.bak && \
126+
echo "✅ Log level set to $(LEVEL)"; \
127+
fi
128+
129+
log-db:
130+
@mkdir -p logs
131+
@echo "📋 Displaying last $(or $(LIMIT), 50) database log entries:"
132+
@python -c "from dialogchain.utils.logger import display_recent_logs; display_recent_logs(limit=$(or $(LIMIT), 50))"
133+
134+
log-tail:
135+
@echo "📜 Tailing log file: $(LOG_FILE)"
136+
@tail -f $(LOG_FILE)
137+
138+
log-clear:
139+
@rm -f $(LOG_FILE) $(DB_LOG_FILE)
140+
@mkdir -p logs
141+
@touch $(LOG_FILE) $(DB_LOG_FILE)
142+
@echo "🧹 Log files cleared"
143+
100144
# Run all linters
101145
lint:
102146
@echo "🔍 Running flake8..."

src/dialogchain/utils/logger.py

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,76 @@
11
"""
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)
316
"""
417
import logging
518
import sqlite3
619
import json
720
import os
21+
import sys
22+
import threading
823
from pathlib import Path
924
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()
1137

1238
# Create a module-level logger that doesn't trigger setup
1339
_logger = logging.getLogger(__name__)
14-
_logger.setLevel(logging.INFO)
40+
_logger.setLevel(DEFAULT_LOG_LEVEL)
1541

16-
# Add console handler if no handlers are configured
42+
# Prevent duplicate handlers
1743
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'
2153
)
54+
console_handler.setFormatter(console_formatter)
2255
_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)
2374

2475

2576
class DatabaseLogHandler(logging.Handler):

tests/unit/test_connectors_destinations.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,20 @@ async def test_send_http_request(self, mock_session_class, http_dest, capsys):
8585
# Setup mock response
8686
mock_response = AsyncMock()
8787
mock_response.status = 200
88-
mock_response.text.return_value = "OK"
88+
mock_response.text = AsyncMock(return_value="OK")
8989

90-
# Setup mock post method
90+
# Setup mock post method with async context manager
9191
mock_post = AsyncMock()
9292
mock_post.__aenter__.return_value = mock_response
9393

94-
# Setup mock session
94+
# Setup mock session with async context manager
9595
mock_session = AsyncMock()
9696
mock_session.post.return_value = mock_post
97-
mock_session.__aenter__.return_value = mock_session
98-
mock_session_class.return_value = mock_session
97+
98+
# Setup the session context manager
99+
mock_session_ctx = AsyncMock()
100+
mock_session_ctx.__aenter__.return_value = mock_session
101+
mock_session_class.return_value = mock_session_ctx
99102

100103
# Test with dict message
101104
await http_dest.send({"key": "value"})
@@ -124,7 +127,7 @@ async def test_send_http_request(self, mock_session_class, http_dest, capsys):
124127
# Reset mocks for error test
125128
mock_session.post.reset_mock()
126129
mock_response.status = 400
127-
mock_response.text.return_value = "Bad Request"
130+
mock_response.text = AsyncMock(return_value="Bad Request")
128131

129132
# Test error case
130133
await http_dest.send({"key": "value"})

0 commit comments

Comments
 (0)