Eyelet's SQLite implementation provides high-performance, concurrent logging for multiple Claude Code instances with rich metadata and powerful query capabilities. The system supports flexible logging formats (JSON files, SQLite, or both) based on user configuration.
Process-Local Connections
- Each process maintains its own connection (fork-safe)
- Automatically recreates connection when process ID changes
- No shared connection pool between processes (avoids multi-process issues)
class ProcessLocalConnection:
def __init__(self, db_path: Path):
self._pid = None
self._conn = None
@property
def connection(self):
if self._pid != os.getpid():
# New process detected, create new connection
self._conn = self._create_connection()
self._pid = os.getpid()
return self._connOptimizations Applied
- WAL mode for concurrent reads/writes
- 64MB cache size
- Memory-mapped I/O (256MB)
- 60-second busy timeout
- Autocommit mode for logging
Exponential Backoff with Jitter
@sqlite_retry(max_attempts=10, base_delay=0.05)
def log_hook(self, hook_data: HookData) -> bool:
# Automatic retry on database lock- Base delay: 50ms
- Exponential growth: 0.05s → 0.1s → 0.2s → 0.4s → 0.8s...
- Random jitter prevents thundering herd
- Maximum 10 attempts before failure
Hybrid Design: Indexed fields + full JSON data
CREATE TABLE hooks (
-- Core indexed fields for fast queries
id INTEGER PRIMARY KEY,
timestamp REAL NOT NULL,
session_id TEXT NOT NULL,
hook_type TEXT NOT NULL,
tool_name TEXT,
status TEXT,
-- Metadata fields
hostname TEXT,
ip_address TEXT,
project_dir TEXT,
-- Full data as BLOB (JSONB optimization)
data BLOB NOT NULL CHECK(json_valid(data)),
-- Generated columns for JSON fields
error_code TEXT GENERATED ALWAYS AS
(json_extract(data, '$.execution.error_message')) STORED,
git_branch TEXT GENERATED ALWAYS AS
(json_extract(data, '$.metadata.git.branch')) STORED
);Indexes
- Single column indexes on core fields
- Composite index for time-based queries:
(hook_type, timestamp DESC) - Conditional indexes on generated columns
1. Hook Triggered → execute.py
2. ConfigService loads eyelet.yaml settings
3. HookLogger determines format (json/sqlite/both) and scope (project/global/both)
4. For SQLite:
- GitMetadata enriches log data
- SQLiteLogger writes with retry logic
- ProcessLocalConnection handles concurrency
5. For JSON:
- Central logging to ~/.eyelet/hooks/ or local ./eyelet-hooks/ based on scope
Flexible Filtering
- By hook type, tool name, session ID
- Time-based (since/until)
- By status, git branch, error presence
- Full-text search across JSON data
CLI Commands
# Search with filters
eyelet query search --hook-type PreToolUse --tool Bash --since 1h
# Summary statistics
eyelet query summary --since 24h
# Recent errors
eyelet query errors --limit 10
# Session timeline
eyelet query session <session-id>
# Search in data
eyelet query grep "git push"Global Configuration (~/.eyelet/eyelet.yaml)
logging:
format: json # json, sqlite, or both
enabled: true
scope: global # global, project, or both (default: global)
global_path: ~/.eyelet
project_path: .eyelet-hooks
add_to_gitignore: true
metadata:
include_hostname: true
include_ip: true
custom_fields:
team: engineeringProject Override (./eyelet.yaml)
logging:
format: sqlite # Override to use SQLite
scope: both # Log to both locations- Single insert: ~5-10ms with retry logic
- Batch insert: 100-200 inserts/second
- Concurrent writes: Handled via WAL mode + retry
- Lock contention: Mitigated by exponential backoff
- Indexed queries: <10ms for most operations
- Full-text search: Linear scan (optimizable with FTS5)
- Summary statistics: ~50ms for 10,000 records
- Overhead: ~1KB per log entry
- Compression: Via BLOB storage for JSON
- Growth rate: ~100MB per 100,000 hooks
Schema migrations use SQLite's PRAGMA user_version:
MIGRATIONS = [
(1, "Initial schema", "..."),
(2, "Add user_id column", "ALTER TABLE hooks ADD COLUMN user_id TEXT;")
]
def migrate_database(conn):
current_version = conn.execute("PRAGMA user_version").fetchone()[0]
for version, description, sql in MIGRATIONS:
if version > current_version:
conn.executescript(sql)
conn.execute(f"PRAGMA user_version = {version}")Database Health Checks
- Integrity verification
- WAL file size monitoring
- Table statistics and growth tracking
- Index usage analysis
Performance Monitoring
- Insert/query timing statistics
- Retry counts and lock wait times
- Success rates and error tracking
- Git Credentials: Sanitized from remote URLs
- Environment Variables: Only CLAUDE*/EYELET* vars logged
- File Permissions: Database created with user-only access
- SQL Injection: Parameterized queries throughout
- Full-Text Search: Add FTS5 for better search performance
- Compression: zlib compression for older entries
- Partitioning: Time-based partitioning for large databases
- Replication: Optional sync to remote database
- Analytics: Pre-computed statistics tables
- Retention: Automatic cleanup of old entries