Skip to content

Commit fbf1841

Browse files
committed
feat: Complete SQLite logging integration
- Add SQLite database layer with WAL mode and retry logic - Implement eyelet doctor command for health checks - Create comprehensive query interface (search, summary, errors, session, grep) - Add eyelet configure logging command with comma-separated format support - Support hybrid logging (JSON, SQLite, or both) - Add Git metadata enrichment for all hooks - Process-safe connections with fork detection - Add PyYAML dependency for configuration management Fixes: - Handle missing execution data in query commands - Fix PosixPath JSON serialization - Remove archive directory (tagged as archive-removal) This release introduces a powerful SQLite-based logging system that works alongside or instead of JSON file logging, with comprehensive querying and analysis capabilities.
1 parent 6112605 commit fbf1841

File tree

8 files changed

+569
-13
lines changed

8 files changed

+569
-13
lines changed

CHANGELOG.md

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.3.0] - 2025-01-29
11+
1012
### Added
11-
- SQLite logging implementation plan
12-
- Configuration file design (eyelet.yaml for global and project settings)
13-
- `eyelet doctor` command concept for configuration validation
14-
- Support for multiple hook commands per hook type
15-
- Configuration management CLI commands
16-
- Roadmap for enhanced metadata system
13+
- Complete SQLite logging implementation with WAL mode and retry logic
14+
- Configuration file system (eyelet.yaml) for global and project settings
15+
- `eyelet doctor` command for comprehensive health checks and diagnostics
16+
- `eyelet query` command suite for searching and analyzing hook logs
17+
- `query search` - Full-text search with multiple filters
18+
- `query summary` - Session and hook type statistics
19+
- `query errors` - Error analysis and debugging
20+
- `query session` - View specific session logs
21+
- `query grep` - Pattern matching across logs
22+
- `eyelet configure logging` command to manage logging settings
23+
- Git metadata enrichment for all logged hooks
24+
- Process-safe SQLite connections with fork detection
25+
- Hybrid logging support (JSON files, SQLite, or both)
26+
- PyYAML dependency for configuration management
1727

1828
### Fixed
1929
- SubagentStop hooks now properly logged when using Task tool
2030
- Hook testing includes SubagentStop verification
31+
- PosixPath JSON serialization errors
32+
- Rich console.print() stderr output issues
33+
- Query commands handle missing execution data gracefully
2134

2235
### Changed
23-
- Default logging format decision: JSON (with SQLite as opt-in)
24-
- Simplified configuration without version field
36+
- Logging format configuration now accepts comma-separated values (e.g., `--format json,sqlite`)
37+
- Archive directory removed (tagged as `archive-removal` in git history)
38+
- Improved error handling with fallback to legacy logging on failures
2539

2640
## [0.2.0] - 2025-01-28
2741

@@ -106,7 +120,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
106120
- Rich terminal output with tables
107121
- Error handling with helpful messages
108122

109-
[Unreleased]: https://github.com/bdmorin/eyelet/compare/v0.2.0...HEAD
123+
[Unreleased]: https://github.com/bdmorin/eyelet/compare/v0.3.0...HEAD
124+
[0.3.0]: https://github.com/bdmorin/eyelet/compare/v0.2.0...v0.3.0
110125
[0.2.0]: https://github.com/bdmorin/eyelet/compare/v0.1.3...v0.2.0
111126
[0.1.3]: https://github.com/bdmorin/eyelet/compare/v0.1.2...v0.1.3
112127
[0.1.2]: https://github.com/bdmorin/eyelet/compare/v0.1.1...v0.1.2

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "eyelet"
3-
version = "0.2.0"
3+
version = "0.3.0"
44
description = "Hook orchestration system for AI agents - Thread through the eyelet! (Work in Progress)"
55
readme = "README.md"
66
authors = [{name = "Brian Morin"}]

src/eyelet/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Thread through the eyelet!
55
"""
66

7-
__version__ = "0.2.0"
7+
__version__ = "0.3.0"
88
__author__ = "Brian Morin"
99

1010
# Defer imports to avoid circular dependencies

src/eyelet/cli/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,13 @@
11
"""CLI layer - Command-line interface"""
2+
3+
__all__ = [
4+
'completion',
5+
'configure',
6+
'discover',
7+
'doctor',
8+
'execute',
9+
'logs',
10+
'query',
11+
'template',
12+
'validate'
13+
]

src/eyelet/cli/configure.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from eyelet.application.services import ConfigurationService, HookService
1111
from eyelet.domain.models import Handler, HandlerType, Hook, HookType, ToolMatcher
1212
from eyelet.infrastructure.repositories import InMemoryHookRepository
13+
from eyelet.services.config_service import ConfigService
14+
from eyelet.domain.config import LogFormat, LogScope
1315

1416
console = Console()
1517

@@ -399,3 +401,124 @@ def install_all(ctx, scope, force, dev):
399401
import traceback
400402
console.print(f"[red]Error installing hooks: {e}[/red]")
401403
console.print(f"[dim]{traceback.format_exc()}[/dim]")
404+
405+
406+
@configure.command()
407+
@click.option('--format',
408+
help='Logging format(s) - comma-separated list (e.g., "json", "sqlite", "json,sqlite")')
409+
@click.option('--scope', type=click.Choice(['global', 'project', 'both']),
410+
help='Logging scope (global ~/.claude, project local, or both)')
411+
@click.option('--enabled/--disabled', default=None,
412+
help='Enable or disable logging')
413+
@click.option('--global', 'is_global', is_flag=True,
414+
help='Configure global settings instead of project')
415+
@click.pass_context
416+
def logging(ctx, format, scope, enabled, is_global):
417+
"""
418+
Configure logging settings - Chart the course!
419+
420+
Configure how Eyelet logs hook executions. You can choose:
421+
- Format: JSON files, SQLite database, or multiple formats
422+
- Scope: Global (~/.claude), project-local, or both
423+
- Enable/disable logging entirely
424+
425+
Examples:
426+
# Enable SQLite logging for current project
427+
eyelet configure logging --format sqlite
428+
429+
# Use both JSON and SQLite globally
430+
eyelet configure logging --format json,sqlite --scope global --global
431+
432+
# Disable logging temporarily
433+
eyelet configure logging --disabled
434+
435+
# Show current settings
436+
eyelet configure logging
437+
"""
438+
config_service = ConfigService()
439+
440+
# If no options provided, show current settings
441+
if format is None and scope is None and enabled is None:
442+
config = config_service.get_config()
443+
console.print("\n[bold]Current Logging Configuration[/bold]")
444+
console.print(f"Format: [cyan]{config.logging.format.value}[/cyan]")
445+
console.print(f"Scope: [cyan]{config.logging.scope.value}[/cyan]")
446+
console.print(f"Enabled: [cyan]{'Yes' if config.logging.enabled else 'No'}[/cyan]")
447+
448+
if is_global:
449+
console.print("\n[dim]Showing global configuration[/dim]")
450+
else:
451+
# Check if there's a project override
452+
project_config = config_service.load_project_config()
453+
if project_config and project_config.logging:
454+
console.print("\n[dim]Project configuration overrides global settings[/dim]")
455+
else:
456+
console.print("\n[dim]Using global configuration (no project override)[/dim]")
457+
return
458+
459+
# Load appropriate config
460+
if is_global:
461+
config = config_service.load_global_config()
462+
else:
463+
config = config_service.load_project_config() or config_service.get_config()
464+
465+
# Update settings
466+
changed = False
467+
468+
if format is not None:
469+
# Parse comma-separated formats
470+
formats = [f.strip().lower() for f in format.split(',')]
471+
472+
# Validate formats
473+
valid_formats = ['json', 'sqlite']
474+
for fmt in formats:
475+
if fmt not in valid_formats:
476+
console.print(f"[red]Invalid format: {fmt}[/red]")
477+
console.print(f"Valid formats: {', '.join(valid_formats)}")
478+
return
479+
480+
# Determine the LogFormat enum value
481+
if len(formats) == 1:
482+
config.logging.format = LogFormat(formats[0])
483+
elif set(formats) == {'json', 'sqlite'}:
484+
config.logging.format = LogFormat.BOTH
485+
else:
486+
# For future expansion when we have more formats
487+
console.print("[yellow]Warning: Multiple format support currently limited to json,sqlite[/yellow]")
488+
config.logging.format = LogFormat.BOTH
489+
490+
changed = True
491+
492+
if scope is not None:
493+
config.logging.scope = LogScope(scope)
494+
changed = True
495+
496+
if enabled is not None:
497+
config.logging.enabled = enabled
498+
changed = True
499+
500+
if changed:
501+
# Save the configuration
502+
if is_global:
503+
config_service.save_global_config(config)
504+
else:
505+
config_service.save_project_config(config)
506+
507+
console.print(f"\n[green]✓ Logging configuration updated![/green]")
508+
console.print(f"Format: [cyan]{config.logging.format.value}[/cyan]")
509+
console.print(f"Scope: [cyan]{config.logging.scope.value}[/cyan]")
510+
console.print(f"Enabled: [cyan]{'Yes' if config.logging.enabled else 'No'}[/cyan]")
511+
512+
# Show where config was saved
513+
saved_path = config_service.global_config_path if is_global else config_service.project_config_path
514+
console.print(f"\n[dim]Configuration saved to: {saved_path}[/dim]")
515+
516+
# Special message for SQLite
517+
if config.logging.format in [LogFormat.SQLITE, LogFormat.BOTH]:
518+
console.print("\n[bold cyan]SQLite logging enabled![/bold cyan]")
519+
console.print("Hook executions will be stored in SQLite databases:")
520+
if config.logging.scope in [LogScope.GLOBAL, LogScope.BOTH]:
521+
console.print(" • Global: ~/.claude/eyelet-logs/hooks.db")
522+
if config.logging.scope in [LogScope.PROJECT, LogScope.BOTH]:
523+
console.print(" • Project: .eyelet-logs/hooks.db")
524+
console.print("\n[dim]Use 'eyelet query' commands to explore the data[/dim]")

0 commit comments

Comments
 (0)