v0.9.0
Released
Feature
Service Monitoring, Log Pipelines & Custom Dashboards
Three major features: uptime monitoring with public status pages (#152), log parsing and enrichment pipelines with grok and GeoIP, and fully customizable dashboards with 9 panel types (#151).
- Service health monitoring with HTTP/TCP/heartbeat checks and public Uptime Kuma-style status pages
- Log parsing pipelines with 5 built-in parsers, custom grok patterns, and GeoIP enrichment
- Custom dashboards with 9 panel types, drag-and-drop reorder, resize, and YAML import/export
- Scheduled maintenances and manual status incidents on public status pages
- Versioned dashboard schema with migration framework and cross-org isolation guards
Added
Service health monitoring and status pages (#152)
Proactive uptime monitoring with auto-generated public status pages.
- 3 monitor types: HTTP/HTTPS (configurable method, expected status, headers, body assertion), TCP ping, and heartbeat (alert when no ping received within grace window)
- HTTP config: per-monitor
httpConfigwith method, expectedStatus, custom headers, and body assertion (contains or regex) - stored as JSONB, validated via Zod - Per-monitor severity: incident severity is configurable per monitor (
critical,high,medium,low,informational) instead of hardcodedhigh - BullMQ-style polling: worker checks all due monitors every 30s, batched in groups of 20 concurrent checks via
Promise.allSettled - TimescaleDB storage:
monitor_resultshypertable with 7-day compression, 30-day retention, andmonitor_uptime_dailycontinuous aggregate refreshed hourly - State machine: consecutive failure tracking with configurable threshold, atomic incident dedup guard (
WHERE incident_id IS NULL), auto-resolve on recovery - Auto-incident creation: when failure threshold is crossed, a SIEM incident is created with
source: 'monitor'and linked viamonitor_id; notifications sent via existing email/webhook channels - Public status page (
/status/:projectSlug): Uptime Kuma-inspired design with 45-day heartbeat bar grid, per-monitor uptime badge, overall status banner, custom CSS tooltips, light/dark mode toggle - Status page access control: configurable visibility per project - disabled (default), public, password-protected, or org-members-only
- Scheduled maintenances: create maintenance windows with start/end times; active maintenances suppress monitor incident creation and display a banner on the status page
- Manual status incidents: create public incident communications (investigating → identified → monitoring → resolved) with update timeline, independent from SIEM incidents
- Heartbeat endpoint:
POST /api/v1/monitors/:id/heartbeataccepts both API key and session auth, rate-limited to 600/min - Project slugs: auto-generated from project name on creation, unique per org, backfilled for existing projects via migration
- Dashboard UI (
/dashboard/monitoring): monitor list with project selector, create/edit/delete forms with client-side validation, detail page with refresh button, uptime chart, recent checks, copy heartbeat URL - Monitoring navigation: added to sidebar under “Detect” section alongside Alerts and Security
Log parsing and enrichment pipelines
Define multi-step processing rules that automatically parse and enrich incoming log messages before they are stored.
- 5 built-in parsers: nginx (combined log format), apache (identical to nginx), syslog (RFC 3164 and RFC 5424), logfmt, and JSON message body
- Custom grok patterns:
%{PATTERN:field}and%{PATTERN:field:type}syntax with 22 built-in patterns (IPV4, WORD, NOTSPACE, NUMBER, POSINT, DATA, GREEDYDATA, QUOTEDSTRING, METHOD, URIPATH, HTTPDATE, etc.) and optional type coercion (:int,:float) - GeoIP enrichment: extract country, city, coordinates, timezone, and ISP data from any IP field using the embedded MaxMind GeoLite2 database
- Async processing via BullMQ: pipelines run as background jobs after ingestion - zero impact on ingestion latency
- Project-scoped vs org-wide: pipelines can target a specific project or apply to all projects in the organization; project-specific pipelines take priority over org-wide ones
- Pipeline preview: test any combination of steps against a sample log message and inspect per-step extracted fields and the final merged result before saving
- YAML import/export: import pipeline definitions from YAML with
name,description,enabled, andstepsfields; upserts (replace existing pipeline for the same scope) - In-memory cache:
getForProjectcaches the resolved pipeline per project for 5 minutes, automatically invalidated on create/update/delete - Settings UI (
/dashboard/settings/pipelines): list, enable/disable toggle, create, edit, and delete pipelines with live org-switch reactivity ($effectinstead ofonMount) - Step builder: interactive UI for adding, reordering, and configuring parser, grok, and geoip steps with per-type configuration forms
- Pipeline edit page redirects to the list when the active organization is switched, preventing stale-ID errors
Custom dashboards with configurable panels (#151)
User-built dashboards replace the previous fixed /dashboard page, with team-specific views across all observability domains.
- 9 panel types covering every data source: time series, single stat, top-N table, live log stream, alert status (logs/alerts), metric chart and metric stat (OTLP metrics with avg/sum/min/max/count/last/p50/p95/p99 aggregations), trace latency (p50/p95/p99 from spans), detection events (SIEM by severity), monitor status (uptime + response time)
- Panel registry architecture: adding a new panel type touches only 6 files (shared types, backend Zod schema, backend fetcher, frontend panel component, frontend config form, frontend registry entry); the renderer, container, store, and routes never need to change
- Drag-and-drop reorder via
svelte-dnd-actionwith optimistic local state and a single PUT save - Drag-to-resize with bottom-right pointer-event handle, snapping to grid units; constrained by per-type min width/height from the registry
- Responsive 12/6/1 column grid that collapses panels to 6 columns on tablet (640-1024px) and 1 column on mobile (<640px); stored widths are always in the canonical 12-col reference and scale proportionally
- Auto-created Default dashboard per organization, idempotent via Postgres unique-violation guard, replicating the previous fixed layout (4 stat cards + log volume + top services + top error messages) so existing users see no visual change
- Inline edit mode with toggle, no separate edit page; pending changes are kept in a snapshot and discarded on Cancel
- Per-panel configuration dialogs with type-specific forms (level toggles, intervals, aggregation pickers, percentile selectors)
- Dashboard switcher dropdown in the page header with personal/shared distinction, create, delete (default protected), import, export
- YAML import/export: dashboards round-trip through YAML for version-controlling alongside infrastructure code; import regenerates panel IDs and uses
JSON_SCHEMAto block JS-tag prototype pollution - Versioned JSON schema (
schema_version: 1) with a migration framework in@logtide/shared: each version writes aMigrationFnindexed by target version,migrateDashboardwalks the chain on every read; clamps out-of-range versions defensively - Cross-org isolation guard: every panel data fetch verifies that
config.projectIdbelongs to the requesting org, preventing data leaks via crafted YAML imports or stale references - Batch panel data endpoint (
POST /:id/panels/data): single round-trip fetches all panel data viaPromise.allSettled, individual panel errors do not fail the dashboard - Organization scoping: dashboards are org-scoped with optional
is_personalflag (only visible to creator) andcreated_bytracking; partial unique indexes prevent multiple defaults per (org, project) scope - Migration
039_custom_dashboards.sql: JSONBpanelscolumn with GIN index for future panel-type filtering, partial unique indexes for default scope guarantees
Fixed
- Status page slug collision:
getPublicStatusnow filters bystatus_page_publicflag instead of returning the first project matching the slug, preventing cross-org data leaks createMonitornot transactional: monitor andmonitor_statusinserts are now wrapped indb.transaction()to prevent orphaned monitorsmapMonitortyped: replacedanyparameter with properMonitorWithStatusRowinterface for compile-time safety- Org membership check optimized: monitoring routes now use a single
SELECT WHERE user_id AND organization_idquery instead of fetching all user orgs and scanning in JS - Redundant DB read eliminated:
processCheckResultnow receives status data from the already-fetched monitor object instead of issuing a second SELECT - Target validation on update: PUT endpoint now validates target format against monitor type (HTTP must start with
http:///https://, TCP must contain:) $derived.byfix: monitor detail page uptime calculation now uses$derived.by()instead of$derived(() => ...)for correct Svelte 5 reactivity@constplacement: replaced invalid{@const}inside<div>elements with{#if}/{:else}blocks for Svelte 5 compatibilityuptimePcttype coercion: PostgresROUND()returns numeric as string - status page now coerces to number before calling.toFixed()- Default
failureThresholdaligned: frontend form default changed from 3 to 2 to match backend default - Test setup cleanup: added
monitor_results,monitor_status,monitors,incident_alertsto globalbeforeEachcleanup