diff --git a/README.md b/README.md index 7779d4d..44e3058 100644 --- a/README.md +++ b/README.md @@ -27,18 +27,21 @@ Reports are written under the `output/` directory. Each command updates the corr ### Configuring Reports Report queries are defined in `reports.yml`. Each entry specifies the `name`, -`interval`, optional `label` and `chart` type, and a SQL `query` that must return -`bucket` and `value` columns. When `generate_reports.py` runs, every matching -definition creates `output//.json` and an interval dashboard. +optional `label` and `chart` type, and a SQL `query` that must return `bucket` +and `value` columns. The special token `{bucket}` is replaced with the +appropriate SQLite `strftime` expression for each interval (hourly, daily, +weekly or monthly) so that a single definition works across all durations. +When `generate_reports.py` runs, every definition is executed for the requested +interval and creates `output//.json` along with an HTML +dashboard. Example snippet: ```yaml - name: hits - interval: hourly chart: bar query: | - SELECT strftime('%Y-%m-%d %H:00:00', datetime(time)) AS bucket, + SELECT {bucket} AS bucket, COUNT(*) AS value FROM logs GROUP BY bucket diff --git a/reports.yml b/reports.yml index f08dc34..86e3129 100644 --- a/reports.yml +++ b/reports.yml @@ -1,20 +1,18 @@ - name: hits - interval: hourly label: Hits chart: bar query: | - SELECT strftime('%Y-%m-%d %H:00:00', datetime(time)) AS bucket, + SELECT {bucket} AS bucket, COUNT(*) AS value FROM logs GROUP BY bucket ORDER BY bucket - name: error_rate - interval: hourly label: Error Rate (%) chart: line query: | - SELECT strftime('%Y-%m-%d %H:00:00', datetime(time)) AS bucket, + SELECT {bucket} AS bucket, SUM(CASE WHEN status >= 500 THEN 1 ELSE 0 END) * 100.0 / COUNT(*) AS value FROM logs GROUP BY bucket diff --git a/scripts/generate_reports.py b/scripts/generate_reports.py index 8dacf68..890c947 100644 --- a/scripts/generate_reports.py +++ b/scripts/generate_reports.py @@ -13,6 +13,17 @@ OUTPUT_DIR = Path("output") TEMPLATE_DIR = Path("templates") REPORT_CONFIG = Path("reports.yml") +# Mapping of interval names to SQLite strftime formats. These strings are +# substituted into report queries whenever the special ``{bucket}`` token is +# present so that a single report definition can be reused for multiple +# intervals. +INTERVAL_FORMATS = { + "hourly": "%Y-%m-%d %H:00:00", + "daily": "%Y-%m-%d", + "weekly": "%Y-%W", + "monthly": "%Y-%m", +} + app = typer.Typer(help="Generate aggregated log reports") def _load_config() -> List[Dict]: @@ -35,13 +46,23 @@ def _render_html(interval: str, reports: List[Dict], out_path: Path) -> None: template = env.get_template("report.html") out_path.write_text(template.render(interval=interval, reports=reports)) + +def _bucket_expr(interval: str) -> str: + """Return the SQLite strftime expression for the given interval.""" + fmt = INTERVAL_FORMATS.get(interval) + if not fmt: + typer.echo(f"Unsupported interval: {interval}") + raise typer.Exit(1) + return f"strftime('{fmt}', datetime(time))" + def _generate_interval(interval: str) -> None: cfg = _load_config() - defs = [d for d in cfg if d.get("interval") == interval] - if not defs: - typer.echo(f"No reports defined for {interval}") + if not cfg: + typer.echo("No report definitions found") return + bucket = _bucket_expr(interval) + conn = sqlite3.connect(DB_PATH) cur = conn.cursor() @@ -49,9 +70,9 @@ def _generate_interval(interval: str) -> None: out_dir.mkdir(parents=True, exist_ok=True) report_list = [] - for definition in defs: + for definition in cfg: name = definition["name"] - query = definition["query"] + query = definition["query"].replace("{bucket}", bucket) cur.execute(query) rows = cur.fetchall() headers = [c[0] for c in cur.description] diff --git a/tests/test_reports.py b/tests/test_reports.py index 0bf7483..8319828 100644 --- a/tests/test_reports.py +++ b/tests/test_reports.py @@ -48,16 +48,14 @@ def sample_reports(tmp_path): cfg.write_text( """ - name: hits - interval: hourly query: | - SELECT strftime('%Y-%m-%d %H:00:00', datetime(time)) AS bucket, COUNT(*) AS value + SELECT {bucket} AS bucket, COUNT(*) AS value FROM logs GROUP BY bucket ORDER BY bucket - name: error_rate - interval: hourly query: | - SELECT strftime('%Y-%m-%d %H:00:00', datetime(time)) AS bucket, + SELECT {bucket} AS bucket, SUM(CASE WHEN status >= 400 THEN 1 ELSE 0 END) * 100.0 / COUNT(*) AS value FROM logs GROUP BY bucket