Skip to content

v1.1.0

Choose a tag to compare

@Polliog Polliog released this 10 Jul 14:08
acb8ca4

The first feature minor since the 1.0.x line. Headline additions are inbound webhook receivers (push events into LogTide from CI/CD, uptime monitors and arbitrary tools through tokenized per-receiver endpoints) and project soft-delete with a 30-day recoverable grace window. Alongside them, five community-reported dashboard and query fixes (#271 to #275) tighten how totals, filter dropdowns and the activity/top-services widgets behave. Database migrations 050, 051 and 052 run on upgrade.

Added

  • Inbound webhook receivers (#155): external systems (CI/CD, uptime monitors, arbitrary tools) can now push events into LogTide through per-receiver tokenized endpoints (POST /api/v1/receivers/:id/:token, lr_-prefixed tokens stored as SHA-256 hashes, timing-safe compare; the URL is the credential since GitHub/Uptime Robot cannot send custom headers). Three adapters normalize payloads into log entries: GitHub (workflow_run and deployment_status events with conclusion-to-level mapping; ping and unsupported events marked skipped), Uptime (shape-detects Uptime Robot alertType and Better Stack incident payloads), and Generic JSON (optional dot-path field mapping with levelMap and defaults, validated by a shared Zod schema; full payload always preserved in metadata). The endpoint answers 202 and processing is asynchronous via a new receiver-events queue job (queue abstraction, both backends), which runs the adapter, validates against logSchema and ingests through ingestionService.ingestLogs, so PII masking (fail-closed), usage quotas, Sigma detection, pipelines, metering and live tail all apply automatically; the outcome (processed/skipped/failed, normalized output, error) is recorded on a receiver_events row pruned to the last 100 per receiver. Management: project-scoped CRUD + recent-events API under /api/v1/projects/:projectId/receivers (session auth, audit-logged as receiver.created/receiver.deleted), a new receivers.max capability limit enforced with the canonical withLimitLock pattern (4-case + race tests), and a "Webhook Receivers" section in project settings (create dialog with adapter picker and generic field-mapping inputs, one-time URL reveal with copy, enable/disable switch, recent-events viewer with raw/normalized JSON). Migration 052 (receivers, receiver_events); public docs in docs/receivers.md
  • Soft-delete for projects with a 30-day grace window: deleting a project now moves it to a recoverable "trash" state (deleted_at) instead of removing it immediately. Its logs, traces and metrics stay queryable and the project stays viewable read-only during the window (the project detail layout loads with includeDeleted, shows a "deleted (read-only)" banner with the permanent-deletion date, and hides the Settings tab); a "Restore" action brings it back. A daily purge worker (3 AM) hard-deletes projects past the grace window, calling reservoir.purgeProject() to clear logs/spans/metrics from every storage engine before deleting the row. Name and slug uniqueness moved to partial unique indexes (active projects only) so a deleted project's name/slug can be reused; restoring into a name or slug a new active project has since taken is rejected with a 409 instead of a raw constraint error. Soft-deleted projects are excluded from listings, data-availability widgets and API-key verification (ingestion is refused immediately, with the key-verification cache invalidated on delete). New POST /api/v1/projects/:id/restore and GET /api/v1/projects?includeDeleted=true. Migrations 050 (soft-delete column + partial indexes) and 051 (drop ON DELETE CASCADE from logs/spans/metrics so an out-of-band project delete cannot bulk-wipe data; the purge worker clears reservoir data explicitly first)

Fixed

  • Log total no longer disagrees with the visible logs on TimescaleDB (#271): the displayed result count came from the Postgres query planner's row estimate, which is unreliable at small scale (stale ANALYZE stats, few rows per chunk) and could be wrong in both directions, so the header showed a total that did not match the listed logs. countEstimate now falls back to an exact COUNT below a 50k-row estimate and keeps the fast planner estimate above it, so small and moderate deployments see an accurate total while large datasets stay performant.
  • Dashboard editing over plain HTTP on a LAN IP (#272): adding a panel called crypto.randomUUID(), which only exists in a secure context (HTTPS or localhost), so it threw when LogTide was accessed over plain HTTP by IP. Panel IDs are now generated through a uuid() helper that falls back to crypto.getRandomValues() (available in non-secure contexts) when randomUUID is missing.
  • Hosts missing from the filter dropdown (#273): getDistinctHostnames clamped its lookup window to 6 hours regardless of the selected range, so hosts that had only logged 6-24h ago were dropped from the hosts dropdown even though their logs were still visible in the (24h) logs view. The window now honors the requested range (default 24h, matching the log query default) so the dropdown lists every host with logs in the same window; hostnames stay cached for 5 minutes to bound the JSONB distinct cost.
  • Activity Overview chart stuck 1-2h behind live data (#274): on TimescaleDB the panel read its logs/spans/detections series entirely from continuous aggregates, whose refresh policy (end_offset = 1 bucket, running once per bucket) never materializes the most recent 1-2 buckets, so the chart's latest edge showed empty while logs were still arriving (the raw-backed logs view stayed current, which read like a timezone offset). The panel now reads the cagg only up to a bucket-aligned cutoff two buckets back and backfills the recent tail live from the raw tables, keeping the fast aggregate for the bulk of the window while the latest buckets track real time. ClickHouse/MongoDB are unaffected (no continuous aggregates); alerts already read raw.
  • Top Services table window was unlabeled (#275): the "Top Services by Volume" widget covers a fixed 7-day window (and its total is a 7-day total), but it sat next to the 24h-framed activity chart with no window label, so its service list and total looked inconsistent with the logs page (which follows the selected range, 24h by default) and read like stale/stuck data. The widget title now states its window ("Last 7 Days") so the different counts are self-explanatory; the 7-day window itself is intentional (daily continuous aggregate for the historical span) and unchanged.

Full Changelog: v1.0.3...v1.1.0