LogTide

Changelog & Roadmap

What we've shipped and what's coming next.

40
Releases
v0.9.7
Latest
2
Planned
Released
In Progress
Planned
Roadmap
v1.0
Planned
Feature
H2 2026

Beta Release Target

Scheduled digest reports, webhook event receivers, full documentation coverage, migration guides from ELK/Datadog/Loki, and production-ready Helm chart.

  • Scheduled digest reports via email
  • Webhook event receivers
  • Full documentation coverage
  • Migration guides for ELK, Datadog, Loki
  • Production-ready Helm chart

Planned for v1.0 Beta

  • Scheduled Digest Reports — Email-based summaries on a configurable schedule
  • Webhook Event Receivers — Accept events from external services and integrations
  • Full Documentation — Complete documentation coverage for all features
  • Migration Guides — Step-by-step guides for migrating from ELK, Datadog, and Grafana Loki
  • Helm Chart Stable — Production-ready Kubernetes deployment via Helm
v1.x
Planned
Feature
H2 2026

Stable Release & Enterprise Features

Performance hardening, enterprise SSO/SAML, advanced RBAC, managed cloud GA, and tiered storage with hot/warm/cold data lifecycle.

  • Performance hardening for production scale
  • Enterprise SSO/SAML
  • Advanced RBAC
  • Managed cloud GA
  • Tiered storage (hot/warm/cold)

Planned

  • Performance Hardening — Production-scale optimization and stress testing
  • Enterprise SSO/SAML — SAML 2.0 integration for enterprise identity providers
  • Advanced RBAC — Fine-grained role-based access control
  • Managed Cloud GA — General availability of the hosted LogTide cloud service
  • Tiered Storage — Hot/warm/cold data lifecycle management for cost optimization
2026
v0.9.7
Released
Fix

SSRF Fix in Alert/Sigma Webhook Delivery

Closes a sibling-gap left by the 0.9.6 SSRF hardening: the alert/Sigma webhook delivery path now routes through the centralized safeFetch guard instead of the bypassable inline filter (GHSA-7v53-pw6r-99vj, CWE-918).

  • SSRF fix: alert/Sigma webhook delivery now uses the centralized safeFetch guard (GHSA-7v53-pw6r-99vj, CWE-918)
  • DNS resolution + per-redirect-hop revalidation + full IPv4/IPv6 private/reserved range coverage on webhook delivery
  • Read-back oracle closed: blocked targets are rejected before the response body is read
  • MONITOR_ALLOW_PRIVATE_TARGETS still lets self-hosted deployments target internal endpoints

Security

  • SSRF in alert/Sigma webhook delivery via the legacy delivery path (authenticated) (GHSA-7v53-pw6r-99vj, CWE-918): the 0.9.6 SSRF hardening added the centralized utils/ssrf-guard.ts guard and wired it into the HTTP/TCP monitors and the WebhookProvider, but the actual alert/Sigma webhook delivery path was left on the old inline filter. sendWebhookNotification in queue/jobs/alert-notification.ts (reached by the alert-notifications BullMQ worker for threshold, rate-of-change and Sigma-rule alerts) still ran a bare fetch(webhook_url, …) guarded only by a string-based isPrivateIP(). That check was bypassable: any non-dotted-quad hostname returned false (no DNS resolution), so a domain whose A record points at 127.0.0.1 / 169.254.169.254 / an internal host passed the filter and the resolved internal address was then connected; the bare fetch used the default redirect: 'follow', so a public host that 302s to an internal URL was followed straight there; and CGNAT (100.64.0.0/10), IPv6, IPv4-mapped IPv6, 0.0.0.0/8 and 198.18.0.0/15 were not covered. An authenticated org owner/admin who can create a webhook notification channel could therefore use the backend as a blind SSRF probe against internal services and cloud metadata, with a partial read-back oracle since a non-2xx internal response body was spliced into the alert-history error message. The guarded “Test” button already blocked the same URLs, confirming this as an incomplete-fix sibling-gap. Fix: sendWebhookNotification now routes delivery through safeFetch(url, init, { allowPrivate: config.MONITOR_ALLOW_PRIVATE_TARGETS }), exactly as WebhookProvider does (DNS resolution + per-redirect-hop revalidation + full IPv4/IPv6 private/reserved range coverage), mapping SsrfBlockedError to the existing “private/internal addresses are not allowed” error. The inline isPrivateIP/BLOCKED_HOSTS filter and the bare fetch are removed, and blocked targets are now rejected before the response body is read (closing the read-back oracle). The MONITOR_ALLOW_PRIVATE_TARGETS opt-in still lets self-hosted deployments target internal endpoints. Reported by tonghuaroot
v0.9.6
Released
Fix

SPA Frontend, SSRF & Cross-Tenant Hardening, OIDC Issuer Fix

Frontend now runs as a full SPA on adapter-node (kills hydration bugs), SSRF guard on monitors and webhooks, cross-tenant projectId validation on four routes, OIDC iss-parameter fix for Authelia, and an infinite-skeleton fix on search/traces/metrics.

  • Frontend converted to a full SPA (ssr=false root cascade), removing 22 per-route ssr=false workarounds and a whole class of hydration bugs
  • Security: SSRF guard on uptime monitors and webhooks, denied by default (MONITOR_ALLOW_PRIVATE_TARGETS to opt in)
  • Security: cross-tenant projectId validation enforced on alerts/preview, alerts, monitors and sourcemaps routes
  • OIDC login fixed for issuer-identifying providers (RFC 9207 iss param) like Authelia (#233, #234)
  • Fixed infinite skeleton spinner on search/traces/metrics when no project had a data-availability flag

Changed

  • Frontend now runs as a full SPA on adapter-node instead of doing SSR with client hydration. A single export const ssr = false in the new root +layout.ts cascades to every route, so the server only ships the empty app shell and the client renders from scratch. Eliminates the entire class of hydration mismatch bugs that had been accumulating across login, register, invite, public status page, the /auth/callback page and the various dashboard subpages, each previously patched with its own per-route ssr = false. Server load functions and the Node runtime are untouched (the adapter, API proxying, env vars, BullMQ etc. keep working exactly as before); there are no +page.server.ts / +layout.server.ts files in the repo today so nothing had to change semantically on the server side. UX tradeoff: the public status page at /status/[orgSlug]/[projectSlug] now flashes the empty shell for one paint before the JS hydrates, which is acceptable for an authenticated-by-default product but should be revisited if SEO on the status page becomes a goal (a single-line override export const ssr = true in that page’s +page.ts puts it back on SSR without affecting anything else)
  • Removed 22 redundant route-level ssr = false declarations (dashboard/+layout.ts, dashboard/admin/+layout.ts and 20 +page.ts files across landing, login, register, onboarding, invite, status, auth callback and the dashboard search / metrics / alerts / monitoring / projects / sessions / settings subtrees) that had been added one by one as each page hit a hydration bug. The new root-level cascade makes them all dead config; deleting them removes the temptation to copy the pattern into new routes

Fixed

  • Infinite skeleton spinner on /dashboard/search, /dashboard/traces and /dashboard/metrics when no project in the org had its data-availability flag set, typically right after a user deleted the only projects that had been ingesting. The filter logic at search/+page.svelte:441-444 (and the identical pattern at traces/+page.svelte:142-145 and metrics/+page.svelte:90-93) read const logsProjectIds = availability?.logs and then branched on logsProjectIds ? filter : fallback. When getProjectDataAvailability legitimately returned { logs: [] } the empty array took the truthy branch ([] is truthy in JS) and [].includes(p.id) excluded every project, so the displayed project list was empty, loadLogs() never fired, and hasLoadedOnce stayed false so the SkeletonTable rendered forever. Fix: guard the truthy branch with logsProjectIds && logsProjectIds.length > 0 so an empty availability response falls back to “show all projects” exactly like the API-failure path (.catch(() => null)) already does. The 0.9.4 backend optimization that introduced the cached availability flags is unaffected; this is purely a frontend null-vs-empty conflation. Logs of already-hard-deleted projects on the TimescaleDB engine remain unrecoverable due to ON DELETE CASCADE on logs.project_id (tracked separately under the soft-delete projects epic)
  • OIDC login failed against issuer-identifying providers (e.g. Authelia) because the iss callback parameter was dropped (#233, #234): the OIDC callback handler extracted only code and state from the provider redirect and rebuilt the callback URL from just those two, discarding everything else. Providers implementing RFC 9207 (OAuth 2.0 Authorization Server Issuer Identification) append iss to the redirect and openid-client’s authorizationCodeGrant() validates it, so the token exchange failed with “issuer parameter missing” and login never completed. The Fastify callback route now forwards the full request.query through handleOidcCallback into the provider, which replays every parameter onto the callback URL handed to the token exchange (so iss, session_state, etc. survive); the required code/state are always re-asserted from the validated values. Duplicated/array-valued query params are collapsed to a single value with searchParams.set instead of being appended, since OIDC authorization-response params are single-valued per RFC 6749 / RFC 9207, avoiding a malformed URL with duplicate iss/code reaching authorizationCodeGrant. Covered by new tests across the route, service and provider layers, including the array-collapse and undefined-param branches

Security

  • Cross-tenant project access via unvalidated projectId (authenticated): several routes accepted both an organizationId and a projectId, verified the caller was a member of the organization, but never verified that the supplied project actually belonged to that organization. Since project UUIDs are normal identifiers that appear in dashboard URLs and client API calls, a user could pair their own organizationId (membership check passes) with a victim’s known projectId and reach another tenant’s data. Confirmed on four handlers: POST /api/v1/alerts/preview returned sampleLogs (time, service, level, message, trace ID) from the foreign project; POST /api/v1/alerts and POST /api/v1/monitors let a rule/monitor be scoped to a foreign project (the monitor case also surfaces on the victim’s public status page, which renders by project_id); and GET/DELETE /api/v1/sourcemaps let a member list or delete another tenant’s source maps. Fix: a shared projectsService.projectBelongsToOrg(projectId, organizationId) helper (single projects lookup filtered on both id and organization_id) is now enforced right after the existing membership check on each of those routes, returning 403 when the project is foreign. The alert/monitor update paths don’t accept a projectId and the custom-dashboards panel pipeline already had an equivalent ensureProjectInOrg guard at its choke point, so no change was needed there. Regression tests cover each handler
  • Server-side request forgery (SSRF) and internal port scanning via monitors and webhooks (authenticated): HTTP/TCP uptime monitors and webhook delivery executed user-supplied targets from the backend’s network with no meaningful destination validation. Monitor creation only checked that an HTTP target started with http(s):// and that a TCP target contained :; checker.ts then called fetch(target, { redirect: 'follow' }) and createConnection({ host, port }), so a registered user could point a monitor at http://169.254.169.254/…, http://127.0.0.1, 10.0.0.0/8, etc. and use the sanitized up/down result and timing to probe internal services. The webhook provider had only literal-string private-IP filtering (no DNS resolution, incomplete IPv6, followed redirects), leaving DNS- and redirect-based bypasses open. Fix: a centralized outbound guard (utils/ssrf-guard.ts) resolves hostnames and rejects loopback, private, link-local (incl. 169.254.169.254 cloud metadata), CGNAT (100.64.0.0/10), multicast and reserved IPv4/IPv6 ranges (including IPv4-mapped IPv6 and ULA/fc00::/7, link-local fe80::/10). TCP checks resolve-then-pin the socket to the validated address (closing DNS-rebinding between validation and connect); HTTP checks and webhook delivery follow redirects manually and revalidate every hop instead of redirect: 'follow'. The guard runs both at monitor create/update time (immediate 400 feedback) and at execution time (authoritative, returns a blocked result). Private/internal targets are denied by default; self-hosted deployments that legitimately monitor internal services can opt back in with MONITOR_ALLOW_PRIVATE_TARGETS=true, which also governs webhook delivery. Note: HTTPS does not yet pin the connected address against a custom dispatcher, so a narrow DNS-rebinding window remains for HTTP monitors (tracked for a follow-up); the reported direct-target and redirect-to-internal vectors are closed
v0.9.5
Released
Fix

Multi-Engine Metadata Filters, Error Notification Throttle & Dependency Security

Metadata filters now work on ClickHouse and MongoDB, error notifications are throttled per group to stop email storms, the notification-channels defaults endpoint accepts monitoring, plus 19 Dependabot advisories resolved.

  • Metadata filters now translated on ClickHouse and MongoDB, not just TimescaleDB (#226, #224)
  • Error notifications throttled per error group via a race-safe cooldown (ERROR_NOTIFICATION_COOLDOWN_MINUTES, default 15)
  • notification-channels defaults endpoint now accepts the monitoring event type
  • Migration 043 adds last_notified_at to error_groups
  • Security: 19 Dependabot advisories resolved (8 high, 11 moderate)

Fixed

  • Metadata filters were silently ignored on ClickHouse and MongoDB (#226, issue #224): the metadata filter operators (equals, not_equals, in, not_in, exists, not_exists, contains) were only translated by the TimescaleDB query builder. The ClickHouse and MongoDB query translators never read params.metadataFilters, so any log search or alert rule that relied on a metadata.* filter came back unfiltered on those engines (the filter appeared to do nothing). ClickHouse now translates each filter to a predicate over the JSON metadata column using JSONExtractString paired with JSONHas to distinguish a missing key from an empty string (not_in/not_equals split on whether the key is present, contains uses positionCaseInsensitive). MongoDB builds one clause per filter keyed on metadata.<key>, all wrapped in $and so repeated filters on the same key don’t overwrite each other, with include_missing controlling whether not_equals/not_in also match documents where the field is absent ($exists). Covered by new per-engine translator tests
  • Error notifications spammed one email per occurrence: processErrorNotification sent an in-app notification, email and webhook for every exception occurrence, suppressed only when the error group’s status was ignored. A high-frequency client error (e.g. a Svelte effect_update_depth_exceeded loop firing thousands of times) produced one exception row per occurrence and therefore thousands of identical alert emails. The job now throttles per error group: it atomically claims a notification slot via UPDATE error_groups SET last_notified_at = now() WHERE status != 'ignored' AND (last_notified_at IS NULL OR last_notified_at <= cutoff) RETURNING id, so only the first occurrence inside the cooldown window notifies and the rest are skipped. The conditional UPDATE is race-safe (concurrent jobs serialize on the row lock and re-evaluate the predicate against the freshly written timestamp), so even a burst of thousands collapses to a single notification per window. Cooldown is configurable via ERROR_NOTIFICATION_COOLDOWN_MINUTES (default 15, set 0 to notify on every occurrence). Occurrence counts on the error group and in-app dashboards are unaffected
  • monitoring rejected by the notification-channels defaults endpoint: PUT/GET /api/v1/notification-channels/defaults/:eventType validated :eventType against a local Zod enum of ['alert', 'sigma', 'incident', 'error'] that was missing monitoring, even though the shared NotificationEventType type, the DB organization_default_channels constraint (migration 037) and the service all support it. Setting a default monitoring channel returned 400 Validation error (“received ‘monitoring’”). Added monitoring to the route enum so the five event types are consistent across the stack

Added

  • Migration 043 error_notification_throttle: adds a nullable last_notified_at TIMESTAMPTZ column to error_groups, used as the per-group notification cooldown anchor (see the error-notification throttle fix above)

Security

  • Resolved 19 Dependabot advisories (8 high, 11 moderate) by bumping direct dependencies and pinning patched versions through the root pnpm overrides. protobufjs7.6.1 (kept on the 7.x line via >=7.5.8 <8; covers code injection, prototype-pollution gadget, unbounded-recursion DoS, unsafe option paths, crafted-field DoS, overlong UTF-8 in @protobufjs/utf8 >=1.1.1). kysely0.28.17 (bounded >=0.28.17 <0.29; JSON-path traversal injection in JSONPathBuilder.key()/.at()). svelte5.55.9 (>=5.55.7; SSR XSS via spread attributes and promise serialization, DOM-clobbering XSS, <svelte:element> ReDoS). @sveltejs/kit2.61.1 (>=2.60.1; query.batch cross-talk). fast-uri3.1.2 (path traversal + host confusion via percent-encoded segments). qs6.15.2 (DoS in qs.stringify), devalue5.8.1 (sparse-array DoS), brace-expansion5.0.6 (numeric-range DoS), ws8.21.0 (uninitialized memory disclosure). protobufjs and kysely were deliberately held on their current major/minor lines (their “latest” is a breaking jump) while still landing on the patched release
v0.9.4
Released
Fix

Cached Data-Availability, Activity Overview Multi-Engine Fix & Page Titles

Data-availability endpoint now reads cached flags on projects (6s+ → <50ms on ClickHouse), activity_overview panel works across all engines, 14 dashboard pages get proper titles, plus uuid 14 and fast-xml-parser 5.7.3 security bumps.

  • GET /api/v1/projects/data-availability rewritten to read cached has_X_at flags — 6s+ → <50ms on production ClickHouse
  • Boot-time one-shot backfill for the new project flags, throttled and gated by system_settings
  • activity_overview panel logs/log_errors series now work on ClickHouse and MongoDB via reservoir.aggregate
  • 14 dashboard pages now set proper <title> instead of falling back to generic 'LogTide'
  • Security: uuid 14.0.0 (CVE-2026-41907) and fast-xml-parser 5.7.3 (CVE-2026-41650) bumps

Changed

  • GET /api/v1/projects/data-availability rewritten to read from cached flags on projects instead of fanning out N*3 existence queries to the reservoir per call. In production (ClickHouse, ~70M rows) the old path scanned count(*) and LIMIT 1 probes across logs/traces/metrics for every project in the org, taking 6s+ on non-trivial orgs. The new path is a single SELECT id, has_logs_at, has_traces_at, has_metrics_at FROM projects WHERE organization_id = $1 plus an in-memory staleness filter against organizations.retention_days, returning in <50ms regardless of ingest volume. The response shape is unchanged ({ logs: string[], traces: string[], metrics: string[] })
  • Ingest paths mark data-availability flags fire-and-forget: after a successful batch in logs / traces / metrics ingest, projectsService.markHasData(projectId, kind) updates projects.has_X_at. A module-scoped in-memory debounce (5 min per (projectId, kind)) prevents UPDATE spam on hot projects: at most one UPDATE per 5 min per pod per project per kind. Failures are logged and swallowed so ingest is never impacted; the boot-time backfill is the safety net

Added

  • Migration 042 project_data_availability: adds nullable has_logs_at, has_traces_at, has_metrics_at TIMESTAMPTZ columns to projects. No backfill in SQL (handled at runtime, see below)
  • One-shot backfill at server boot: runDataAvailabilityBackfill() runs fire-and-forget after app.listen, guarded by a data_availability.backfilled row in system_settings so subsequent boots are a no-op. It selects every project with all three flags still NULL and, for each, runs three LIMIT 1 probes (logs / traces / metrics) via the reservoir in parallel. Throttled in batches of 10 with a 50ms pause to avoid hammering ClickHouse/Timescale/Mongo at boot. On a deployment with 1000 projects this takes ~6s on ClickHouse or MongoDB, up to ~30s on TimescaleDB when most projects are empty (chunk scan confirming zero rows). Force a re-backfill by deleting the safety row
  • Multi-engine staleness rule: a project is reported as “has data” only if has_X_at >= now() - organizations.retention_days. Handles the “data aged out by retention” case without needing a background worker. The same logic works identically on TimescaleDB, ClickHouse and MongoDB deployments because the flag lives in Postgres and the reservoir only backs the ingest and backfill paths

Fixed

  • activity_overview dashboard panel showed empty logs / log_errors series on ClickHouse and MongoDB: the fetcher in panel-data-service.ts gated both the logs and the spans queries behind reservoir.getEngineType() === 'timescale', so on non-Timescale engines those branches were skipped entirely and the panel rendered with all four log/span counters flat at zero (only detections and alerts, which read from Postgres operational tables, kept working). The logs path now branches: TimescaleDB still uses the logs_hourly_stats / logs_daily_stats continuous aggregate with the raw logs fallback, while ClickHouse and MongoDB go through reservoir.aggregate({ interval: '1h' | '1d' }) (same dual pattern already used by baseline-calculator). Bucket boundaries from toStartOfHour / $dateTrunc align with the panel’s UTC buildBucketTimes, so the keys collide cleanly. logs sums total per bucket, log_errors sums byLevel.error + byLevel.critical. Spans / span_errors stay TimescaleDB-only because IReservoir has no aggregateSpans() primitive, matching the existing convention in trace_latency and trace_volume
  • Missing <title> on 14 dashboard pages: every tab opened on /dashboard, /dashboard/admin/{organizations,projects,users,settings} (list and detail), /dashboard/monitoring (list and detail) and /dashboard/projects/[id]/{alerts,overview,performance,settings} showed the generic fallback “LogTide” from app.html because no <svelte:head><title>...</title></svelte:head> was set on the page. They now follow the existing Title - LogTide convention used by the rest of the app, with dynamic titles where the entity is already in scope: {$activeDashboard.name} for the custom dashboards root, {org.name} / {project.name} / {user.name ?? user.email} / {monitor.name} for the four [id] pages (each with a sensible fallback while the loader resolves). The two pure-redirect pages (/dashboard/projects/[id] and /dashboard/settings, which goto their default sub-route on mount) intentionally keep no title since the destination sets one immediately

Security

  • Bump uuid to 14.0.0 (GHSA-w5hq-g745-h8pq, CVE-2026-41907, MODERATE): v3, v5 and v6 accepted a caller-provided buf + offset but did not validate bounds, so a small buffer or an out-of-range offset produced silent partial writes instead of throwing RangeError like v1/v4/v7 do. uuid is pulled in transitively by bullmq and ldapts (production) and bson/ioredis (dev only); both production consumers resolve to their ESM entry points from our "type": "module" backend, so the major bump from ^11.1.0 is safe (uuid 12+ dropped CommonJS, but we never load the CJS path). Added "uuid": ">=14.0.0" to the root pnpm overrides; resolves to 14.0.0. Engines requirement of Node 20+ is already satisfied by engines.node: ">=20.0.0"
  • Bump fast-xml-parser to 5.7.3 (GHSA-gh4j-gqv2-49f6, CVE-2026-41650, MODERATE): XMLBuilder did not escape the --> sequence in comment bodies or the ]]> sequence in CDATA sections, so user-controlled data flowing into either could break out and inject sibling XML nodes (XSS in browser-rendered XML, SOAP injection, RSS/SVG payloads, etc.). The fix lands in 5.7.0; we use it transitively only via @aws-sdk/xml-builder pulled in by @types/nodemailer (dev), but bumping the override from >=5.5.7 to >=5.7.0 clears the lockfile advisory. Resolves to 5.7.3
v0.9.3
Released
Feature

Traces Live Tail, Dashboard Panels & Async Ingestion Buffer

Traces gain live tail (SSE), row expand, keyboard shortcuts and export; monitoring reorganized into tabs; user settings promoted to a full page; new trace_volume and activity_overview dashboard panels; opt-in ReservoirBuffered async ingestion with in-memory and Redis Streams transports.

  • Traces live tail via SSE, inline span expand, j/k/enter/r keyboard shortcuts and JSON/CSV export
  • Monitoring page split into Monitors / Incidents / Maintenance / Status page tabs with summary cards
  • User settings promoted from dialog to full page at /dashboard/account
  • New trace_volume and activity_overview dashboard panels using continuous aggregates with raw fallback
  • Opt-in ReservoirBuffered async ingestion: in-memory (signal-wakeup) and Redis Streams transports with DLQ, circuit breaker and Prometheus metrics

Added

  • Traces live tail (SSE): new GET /api/v1/traces/stream Server-Sent Events endpoint polls the traces table once a second and emits new trace_ids as they appear, filtered by projectId, service (CSV) and error. On the frontend the traces page now has a “Live tail” switch + rows-limit selector (50/100/200/500/1000, persisted in localStorage) in the filter bar; incoming traces are prepended and the list is capped at the chosen limit. The tracesEventSource is torn down on onDestroy and on every filter-key change
  • Traces row click-to-expand with inline span list: clicking a trace row now toggles an inline panel below it that fetches GET /api/v1/traces/:traceId/spans once (cached per trace_id for the session) and shows a compact table with Service / Operation / Kind / Duration / Status for each span. The “View” action still opens the full trace detail page
  • Traces keyboard shortcuts: / focuses the Trace ID input, r refreshes, j/k move the selection down/up with scroll-into-view, enter expands/collapses the selected trace. Registered via shortcutsStore under scope traces and unregistered on destroy
  • Traces export (JSON / CSV): new Export popover in the filter bar produces a client-side download of the current list (disabled during live tail or when the list is empty). CSV covers start_time / service / operation / duration_ms / span_count / error / trace_id
  • Monitoring entry in the command palette: cmd+k now lists Monitoring under Navigation (icon + g o shortcut hint), matching the sidebar’s Detect group so the page is reachable without leaving the keyboard
  • trace_volume dashboard panel: new panel type that plots span count over time (optional errors line), configurable time range (1h/6h/24h/7d) and optional service filter. Mirrors the shape of the existing logs “Log Volume” panel but for OTLP traces. Reads the spans_hourly_stats / spans_daily_stats continuous aggregate first, falling back to the raw spans hypertable with date_trunc only when the cagg is empty for the window (covers the end_offset=1h refresh lag on freshly-ingested data). TimescaleDB-only
  • activity_overview dashboard panel: unified multi-series timeline combining logs, log errors, spans, span errors, detection events and alert triggers on a common bucket grid (hourly for ≤24h, daily for 7d/30d). Each series is individually toggleable in the config form. Per-source queries run in parallel and prefer the logs_*_stats, spans_*_stats, detection_events_*_stats continuous aggregates for cost; if a cagg returns zero rows for the window the fetcher falls back to the raw hypertable (logs, spans, detection_events) for that source only, so the panel stays correct on freshly-ingested data without running a raw scan in the hot path. Alerts always come from alert_history + alert_rules (no cagg exists for it)

Changed

  • Monitoring page reorganized into tabs: what was a single 1000-line scroll with three unrelated sections (Monitors, Incidents, Maintenance) plus a status-page config block wedged at the top is now a Tabs.Root with four tabs — Monitors, Incidents, Maintenance, Status page — each with its own header, its own primary CTA, and its own empty state. The Monitors tab gains a summary card grid (Total / Up / Down / Paused, each card clickable to filter the list by that status), a search input that matches name / target / type, and a “Clear filters” shortcut. Every row now has a one-click Pause/Resume button (previously buried in the edit form). Status-page visibility, password, slug and embed-badge config moved into the Status page tab. No changes to underlying API calls or permissions
  • Admin dashboard sidebar collapses on mobile: the 256px admin sidebar was previously always visible, taking up a huge chunk of the viewport on phones. It now hides below lg: and opens as a fixed drawer triggered by a hamburger in a new mobile-only sticky header that also shows the current section name (Dashboard / Users / Organizations / etc). The drawer has a backdrop, is dismissed on nav-click or backdrop-click, and locks body scroll while open. Desktop behaviour unchanged
  • Dashboard footer wraps properly on mobile: was a single justify-between row that squeezed “LogTide / Alpha v0.9.2” against ”© 2026 LogTide · Documentation · GitHub” on narrow screens. Now stacks the brand block over the links block below sm: (each block keeps its own horizontal flow with flex-wrap gap-x-4 gap-y-1), and “Documentation” shortens to “Docs” under sm: to fit on a single line. Container padding also drops from px-6 py-4 to px-4 py-3 on mobile
  • User settings promoted from a dialog to a full page at /dashboard/account. The cramped scrollable UserSettingsDialog modal (profile + password + tutorial restart + danger zone stacked inside a 32rem-wide overlay) is now a proper route using a max-w-3xl container with each concern split into its own rounded-lg border bg-card section (Profile / Change password / Onboarding / Danger zone). The dropdown menu item still says “User Settings” but now navigates instead of opening an overlay. UserSettingsDialog.svelte is removed. The delete-account confirmation remains a nested AlertDialog since that one is genuinely destructive and modal-appropriate
  • Public status page polished for mobile and clarity: outer container shrinks from px-4 py-10 to px-3 py-6 on phones (only sm: and up gets the roomy desktop padding). The 45-day uptime bars drop their min-w-[6px] floor to 4px on mobile so the whole row fits comfortably on 320px viewports. The footer (“Last updated …” and “Powered by LogTide”) grows from text-[10px] to text-xs with a taller top margin, and “LogTide” is now styled as text-primary font-medium hover:underline so the link affordance is obvious (previously only a hover recolor). Incident update timestamps/status labels also bumped from text-[10px] to text-xs. The monitor-type badge (HTTP/TCP/etc.) hides below sm to free up the row for the name + uptime%. The password input switches from a fixed w-64 to w-full max-w-xs so it never overflows narrow phones
  • Monitoring forms moved into modal dialogs: the previous inline create/edit monitor form, the “New incident” form, the “Post incident update” form and the “Schedule maintenance” form were expanding/collapsing sections that shifted the whole page layout when opened. They now open as centered Dialog modals with max-h-[90vh] overflow-y-auto so tall forms (monitor create) scroll independently of the page. The per-row “Post update” Dialog is a single instance driven by a showUpdateForm id + an updatingIncident derived lookup, replacing the one-per-row inline expand. Form state, validations and submit handlers are untouched
  • Traces filter UI reworked into the same two-row pill bar as the search page: row 1 keeps Time Range (TimeRangePicker in a popover), the Live tail switch, the List/Map view toggle and the Export menu. Row 2 exposes every filter as its own always-visible pill — Project (single-select), Services (multi-select), Status (tri-state: all / errors / ok), Duration range (min/max ms inputs), Trace ID (direct-navigate to the trace detail page). Pills switch to the secondary variant when a non-default value is set; “Clear all” appears when any filter is active. Mobile popovers use w-[Xpx] max-w-[90vw] so they never overflow the viewport. handleTimeRangeChange syncs picker state back into timeRangeType / customFromTime / customToTime so the trigger label survives popover close/remount
  • Traces backend accepts multi-value projectId and service (CSV on the query string) and two new minDurationMs / maxDurationMs bounds on GET /api/v1/traces. The cross-project access check loops over every project in the list. tracesService.listTraces widens its input types to string | string[] and forwards to reservoir.queryTraces, which already supported these filters
  • Search page paginator now uses the same ellipsis/windowed control as the traces page: shows up to four numbered buttons plus sentinel on each side when totalPages > 7. Falls back to the previous “Page N” label when the backend doesn’t return a total (storage engines without total in the query response). “Previous” / “Next” labels hide on <sm screens so only the chevron shows on phones
  • Empty states wait for the first load to complete before rendering on the search, traces, errors and security-incidents pages. Each page now tracks a hasLoadedOnce flag flipped in the finally branch of its loader; until the first fetch resolves (success or failure) the page shows its skeleton instead of the “No Logs Yet / Start sending logs from your applications” onboarding card. Prevents the flash where an org that already has data showed the onboarding CTA during the initial render between mount and the first query. When a fetch legitimately returns zero rows, the page also distinguishes “nothing matches these filters” (with a Clear-filters shortcut) from “project has no data at all” (the full onboarding)
  • Search page filter UI reworked into a two-row pill bar: row 1 keeps Search + mode, a Time Range popover trigger, Live Tail and Export inline. Row 2 exposes every filter as its own always-visible pill (Projects, Services, Hostnames, Levels, Trace ID, Session ID, Metadata) — each pill displays the current value (e.g. “Service: api-gateway”, “Levels: 2”) and opens its own popover for editing. Pills switch to the secondary variant when a non-default value is set so active filters are visible at a glance. A “Clear all” link appears at the end of the row when any filter is active. Popover widths use w-[min(Xpx,calc(100vw-1rem))] so they never overflow the viewport on phones. No semantic change to the underlying filter behavior (state, query params, live-tail flow, metadata apply/clear) - purely a layout/affordance pass replacing the previous six-column “Filters” Card
  • Auto-created Default dashboard now uses the new activity_overview panel instead of the logs-only time_series “Log Volume” chart. Existing organizations keep their current dashboard untouched; the swap only affects orgs that don’t yet have a default (i.e. new signups or orgs where the default was deleted and re-seeded via ensureDefaultExists)
  • Opt-in async buffer for reservoir writes: new ReservoirBuffered decorator sits between the ingestion API and the storage engine. When enabled via RESERVOIR_BUFFER_ENABLED=true, POST /api/v1/ingest returns as soon as records are accepted into a shard-partitioned queue; a flush consumer pool drains to storage asynchronously. Two transports ship in the box: an optimized in-memory queue with signal-based wakeup (single-instance, not crash-safe) and a Redis Streams transport with consumer groups, XAUTOCLAIM-based stale reclaim, atomic MULTI/EXEC nack, and a DLQ side stream (multi-instance, durable). Circuit breaker bypasses to sync ingestion when the buffer fills beyond a configurable pending threshold or after repeated flush failures in a rolling window. Off by default; see docs/async-buffer/ for when to enable and the per-engine benchmark table
  • IReservoir shared interface: public type that Reservoir and ReservoirBuffered both implement, so downstream code (backend monitoring, etc.) can type against IReservoir without caring which implementation is live
  • Prometheus-style buffer metrics: reservoir_buffer_enqueued_total, reservoir_buffer_bypass_total, reservoir_buffer_flush_success_total, reservoir_buffer_flush_failure_total, reservoir_buffer_flush_duration_ms (histogram), reservoir_buffer_dlq_total, reservoir_buffer_breaker_state (0 closed / 1 open / 2 half-open). All labelled by record kind and shard where applicable
  • Warning at startup when buffer is enabled on non-Timescale engines: benchmarks show the buffer regresses p95 under saturation on ClickHouse and MongoDB (the bottleneck is the flush side, which the buffer cannot hide). The backend now logs a clear warning with a link to the docs when RESERVOIR_BUFFER_ENABLED=true is set together with STORAGE_ENGINE=clickhouse or STORAGE_ENGINE=mongodb
  • k6 load-test script load-tests/buffered-vs-sync.js (pnpm --filter @logtide/backend load:buffered-vs-sync): constant-arrival-rate 100 req/s for 3 min, batch size 10, reports p50/p95/p99 + error rate. The file lives in the gitignored load-tests/ directory; only the npm script is checked in
  • Graceful shutdown now drains the reservoir buffer in parallel with Fastify app.close(): previously serialized, so a slow app.close() (BullMQ workers, websocket connections) could exhaust the container stop timeout before shutdownReservoir() ran. The new order calls them via Promise.all, giving the buffer its full RESERVOIR_BUFFER_GRACEFUL_SHUTDOWN_MS window to drain regardless of how long app.close() takes
  • Redis client in the backend’s buffer path is now explicitly quit()-ed on shutdown: RedisStreamTransport documents that it does not own the client, so the backend owns the lifecycle. Without this, the Redis connection was torn down by the process exit instead of closing cleanly
  • Buffer start-up failure is fail-fast: when RESERVOIR_BUFFER_ENABLED=true and the consumer pool cannot start (e.g. Redis down at boot), the backend logs a CRITICAL message and process.exit(1) instead of silently continuing with a non-flushing buffer
  • RedisStreamTransport hardening: XPENDING now filters by consumer name in claimStale, so delivery-count mapping is accurate even on busy streams with claims from other consumers; getStats.oldestPendingAgeMs now tracks the oldest not-yet-delivered entry (via XRANGE with exclusive start past the PEL tail) to match the documented contract; nack uses MULTI/EXEC so the DLQ write and the original ACK either both land or neither does; enqueueMany inspects pipeline exec() results and throws on the first error instead of swallowing them; default consumer name changed from consumer-${pid} to ${hostname}-${pid}-${randomHex} to avoid collisions between containers with the same PID
  • FlushConsumer isolates partial failures by kind: the three ingest calls (ingest, ingestSpans, ingestMetrics) now run via Promise.allSettled; the breaker records success only when all three succeed; metrics reflect per-kind reality; DLQ only counts the failed kinds on retry exhaustion. An optional FlushLogger can be injected; default falls back to console
  • FlushConsumerPool relaxes drain check to pendingRecords == 0: previously also required inflightRecords == 0, which never converged when another backend instance held claims on the same Redis consumer group. Consumer-task crashes are now surfaced via logger.error instead of being swallowed by Promise.allSettled. Drain poll bumped from 50 ms to 200 ms to reduce chatter
  • InMemoryTransport uses signal-based wakeup: dequeue no longer polls every 50 ms; enqueues wake waiters directly via a per-shard resolver list, and stop() unblocks waiters promptly. enqueueMany now batches records into a single Array.push(...entries) per shard plus one wake, eliminating the N-await bottleneck that made the in-memory buffer slower than sync on fast TimescaleDB workloads (p95 dropped from 4224 ms to 24 ms in the 3-min 100 req/s test)
  • Reservoir exposes getEngine() on the concrete Reservoir class (not on IReservoir): replaces the (inner as unknown as { engine }).engine cast inside ReservoirBuffered’s constructor. Kept out of the public interface so backend consumers typed against IReservoir cannot reach the engine and bypass the decorator
  • ReservoirBuffered.ingest/ingestSpans/ingestMetrics return the correct IngestResult shape: { ingested, failed, durationMs } instead of the previous { inserted } masked by as unknown as casts. durationMs measures the enqueue time, so the value reflects the real cost imposed on the caller
v0.9.2
Released
Feature

Metadata Filters, Admin User Creation & Org-Scoped Status URLs

Generic metadata filters across log search and alert rules, admin-driven user provisioning, editable org/project slugs, org-scoped public status page URLs (BREAKING), plus SIEM heatmap crash fix and three fastify/SvelteKit security bumps.

  • Generic metadata filter builder with 7 operators, backed by GIN-indexed JSONB queries and shared with alert rules
  • Configurable metadata.* columns in the log search table, persisted per project
  • Admins can create users from the dashboard (#198) without enabling public signup
  • BREAKING: public status page URLs moved to /status/:orgSlug/:projectSlug
  • Security: fastify 5.8.5 and SvelteKit 2.57.1 bumps (3 CVEs, 2 HIGH / 1 MEDIUM)

Added

  • Generic metadata filters in log search and alert rules: the search page and alert rule dialogs now expose a “Metadata filters” section backed by a new MetadataFilterBuilder component. Supported operators: equals, not_equals, in, not_in, exists, not_exists, contains. Filters are applied server-side via a GIN-indexed JSONB query builder in reservoir; alert evaluation also runs the same matcher in-process so rules can fire only when a specific metadata field matches
  • Configurable metadata columns in log table view: users can add arbitrary metadata.* keys as extra columns in the search results table via a “Columns” picker. The selected column set is persisted per project in localStorage so it survives page reloads
  • Create user from admin panel (#198): admins can now provision new accounts directly from Admin → User Management via a “Create User” button, without having to temporarily re-enable public signup. Opens a dialog to set email, name, password and optional admin role. Backed by a new POST /api/v1/admin/users endpoint that bypasses the auth.signup_enabled gate and logs a create_user entry to the audit log
  • Set a custom dashboard as default from the UI: the dashboard switcher now shows a clickable star next to each org-wide, non-personal dashboard; clicking it promotes that dashboard to be the org’s default. Backed by a new POST /api/v1/custom-dashboards/:id/set-default endpoint that atomically unsets the previous default and sets the new one in a single transaction, respecting the existing partial unique index. Personal and project-scoped dashboards are rejected with a 400
  • Editable project slug from monitoring page: the status-page settings card in /dashboard/monitoring now exposes a “Public URL slug” input that lets the user rename a project’s slug, with inline validation against a shared format check (lowercase alphanumeric + hyphens, 2-50 chars), reserved-word list (api, admin, dashboard, status, auth, login, signup, logout, _app, health), and per-org uniqueness. Conflicts surface as 409 with a friendly inline error; race conditions are caught at the DB layer via the new composite unique index
  • Editable organization slug from settings: /dashboard/settings/general slug field is no longer read-only; owners can rename the org slug with the same validation rules and global uniqueness, with a warning that any existing status-page links and embed badges will break

Changed

  • Public status page URL is now scoped under the organization (BREAKING): page and badge URLs changed from /status/:projectSlug to /status/:orgSlug/:projectSlug. Affects the public web page, /api/v1/status/:orgSlug/:projectSlug/badge.svg, and /api/v1/status/:orgSlug/:projectSlug/badge.json. No redirect from the old URLs. Anyone embedding the badge SVG/JSON or sharing a status-page link must update the URL to include the org slug. Migration 040 simultaneously moves project-slug uniqueness from global back to per-org, so two different organizations can now both have a project named frontend without auto-suffixing

Fixed

  • Security dashboard crashed with “Cannot read properties of null (reading ‘toLowerCase’)” (#200): root cause was two parallel unnest(mitre_techniques) / unnest(mitre_tactics) calls in the same SELECT list of SiemDashboardService.getMitreHeatmap. PostgreSQL evaluates sibling set-returning functions in lockstep and NULL-pads the shorter array, so any detection event whose tactic and technique arrays had different lengths produced heatmap rows with a null tactic, which then crashed MitreHeatmap.abbreviateTactic on the frontend. The heatmap query now unnests techniques only and resolves each one to its canonical tactic via the shared MITRE_TECHNIQUES map (with sub-technique to parent fallback), eliminating the malformed pairs at the source. Frontend MitreHeatmap also filters cells with null tactic/technique and DetectionEventsList.getLogLevelClass defensively handles a null level as belt-and-suspenders

Security

  • Bump fastify to 5.8.5 (GHSA-247c-9743-5963, CVE-2026-33806, HIGH): body schema validation could be bypassed by prepending a single space to the Content-Type header. Parser and validator disagreed on how to trim the header, so the body was still parsed but the schema lookup returned no validator and validation was skipped entirely. Upgraded from ^5.8.3 to ^5.8.5
  • Bump @sveltejs/kit to 2.57.1 (GHSA-2crg-3p73-43xp, CVE-2026-40073, HIGH): BODY_SIZE_LIMIT could be bypassed under certain conditions in adapter-node. Tightened the pnpm override from >=2.53.3 to >=2.57.1
  • Bump @sveltejs/kit to 2.57.1 (GHSA-3f6h-2hrp-w5wx, CVE-2026-40074, MEDIUM): calling redirect inside the handle hook with a location containing characters invalid for an HTTP header threw an unhandled TypeError, enabling DoS if the location included unsanitized user input
v0.9.1
Released
Fix

Security Hardening, Input Validation & Bug Fixes

SQL injection fix in querySpans, ReDoS protection for monitor assertions, 25+ bug fixes across auth, pipelines, SIEM, monitoring, and SSR safety, plus comprehensive input validation on all route params.

  • SQL injection fix in querySpans sortBy/sortOrder for both TimescaleDB and ClickHouse
  • ReDoS protection on HTTP monitor body assertion via safe-regex2
  • UUID validation, positive-int clamping, and max-length guards across all route params
  • PII salt race condition, SSE duplicate sends, and Sigma sync FK corruption fixed
  • localStorage SSR crash guards and URL-encoding fixes in frontend navigation

Fixed

  • Identifier pattern update/delete failed with “Organization ID is required” (#193): PUT and DELETE routes required organizationId as an explicit query param or API key context, but session-based auth never sets that field. Now falls back to the user’s first organization, consistent with GET and POST
  • Project rename failed with “Expected string, received null” (#195): updateProjectSchema used z.string().optional() which rejects null, but projects without a description send null from the DB. Changed to z.string().nullable().optional() and updated UpdateProjectInput accordingly
  • Pipeline create/preview/import returned generic “Validation error” (#193, #194): POST routes expected organizationId in the request body but the frontend sends it as a query param. Routes now merge the query param into the body before Zod validation. Frontend error messages now surface the first validation detail instead of the generic label
  • Invitation accept race condition: wrapping the membership check + insert in a transaction caused the accepted_at update to roll back when throwing “already a member”. Split the early-exit path out of the transaction and added 23505 unique-constraint handling for true concurrent accepts
  • SSE live tail duplicate sends: latestLog picked the oldest entry from a DESC-sorted array instead of the newest, causing every poll to re-fetch and re-send all logs since the oldest timestamp. Replaced with a defensive max-time computation
  • Sigma sync corrupted alert_rule_id FK: fallback alertRuleId || existing.id wrote the sigma rule’s own PK into the alert_rules FK column when no alert was auto-created. Now omits the column entirely unless a new alert rule was just created
  • Exception log viewer returned empty results for org-wide error groups: getLogsForErrorGroup passed an empty string as projectId to reservoir when no project filter was set. Now groups log IDs by their exception’s project_id and issues one getByIds call per project
  • ReDoS in HTTP monitor body assertion: user-supplied regex pattern was compiled with only a 256-char length limit, no catastrophic-backtracking check. Added safe-regex2 validation and a compile-error catch
  • OTLP int64 precision loss: parseInt() silently truncated int64 attribute values exceeding Number.MAX_SAFE_INTEGER. Unsafe values are now kept as strings in metadata
  • PII salt race condition fallthrough: if two workers raced on the first hash for an org, the loser could return an unpersisted local salt when the retry read failed, permanently desynchronizing PII hashes. Now scopes the catch to 23505 and throws on unexpected errors
  • Monitor status fallthrough on missing row: processCheckResult silently skipped the entire state machine (no status update, no notifications) when monitor_status was undefined. Now re-reads from DB or creates a default row before proceeding
  • Test isolation: auth mode pollution across test files: system_settings was never reset between tests, so any test setting auth.mode='none' caused 12 unrelated “401 without auth” assertions to return 503. Added cleanup + cache invalidation to global beforeEach
  • Sigma detection tests used sigma_id after code migrated to id: fixture data still passed rule.sigma_id as sigmaRuleId, which would not match the new .where('id', '=', ...) queries
  • translateDelete dropped level filter in reservoir: pushFilter return value was discarded for the level condition, corrupting $N parameter slots when both service and level filters were present
  • SQL injection in querySpans via sortBy/sortOrder: user-controlled strings were interpolated directly into raw SQL in both TimescaleDB and ClickHouse engines. Added explicit column/direction allowlists
  • ingestSpans hardcoded ::uuid[] for project_id: ignored the projectIdType engine option, breaking text-based project IDs with a Postgres cast error
  • localStorage SSR crash in organization store: 7 direct localStorage calls without a browser guard would throw ReferenceError during server-side rendering. Added browser check on all accesses
  • Invite token not URL-encoded in redirect: goto(/login?redirect=/invite/${token}) corrupted the redirect path for tokens containing +, =, or /. Now wraps in encodeURIComponent
  • ruleId not URL-encoded in security dashboard navigation: inconsistent with serviceName and technique which already used encodeURIComponent
  • Missing UUID validation on monitoring route params: all /:id handlers passed request.params.id directly to DB queries without format validation. Added Zod .uuid() parsing on every route
  • Negative limit/days in monitoring routes: Number("-1") || 50 evaluates to -1 (truthy), passing a negative value to LIMIT. Replaced with a parsePositiveInt guard that clamps to [1, max]
  • Missing UUID validation on status-incident route params: same issue as monitoring - :id params were used unvalidated in DB queries
  • SIEM comment body has no max length: z.string().min(1) with no upper bound allowed arbitrarily large comment payloads. Added .max(10000)
  • Notifications and alerts limit/offset NaN passthrough: parseInt("abc") returned NaN, which was passed to Kysely .limit(NaN). Added safe integer parsing with fallback and max cap
  • Correlation referenceTime accepted invalid date strings: schema validated only {type: 'string'}, so new Date("garbage") flowed into Kysely WHERE clauses as Invalid Date. Added format: 'date-time' and a defensive 400 response
  • Log pipeline comment contradicted jsonb merge direction: code comment said “do NOT overwrite existing” but the in-progress fix had flipped the jsonb || operand order so pipeline fields now win. Updated comment to match actual behavior
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 httpConfig with 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 hardcoded high
  • BullMQ-style polling: worker checks all due monitors every 30s, batched in groups of 20 concurrent checks via Promise.allSettled
  • TimescaleDB storage: monitor_results hypertable with 7-day compression, 30-day retention, and monitor_uptime_daily continuous 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 via monitor_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/heartbeat accepts 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, and steps fields; upserts (replace existing pipeline for the same scope)
  • In-memory cache: getForProject caches 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 ($effect instead of onMount)
  • 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-action with 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_SCHEMA to block JS-tag prototype pollution
  • Versioned JSON schema (schema_version: 1) with a migration framework in @logtide/shared: each version writes a MigrationFn indexed by target version, migrateDashboard walks the chain on every read; clamps out-of-range versions defensively
  • Cross-org isolation guard: every panel data fetch verifies that config.projectId belongs 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 via Promise.allSettled, individual panel errors do not fail the dashboard
  • Organization scoping: dashboards are org-scoped with optional is_personal flag (only visible to creator) and created_by tracking; partial unique indexes prevent multiple defaults per (org, project) scope
  • Migration 039_custom_dashboards.sql: JSONB panels column with GIN index for future panel-type filtering, partial unique indexes for default scope guarantees

Fixed

  • Status page slug collision: getPublicStatus now filters by status_page_public flag instead of returning the first project matching the slug, preventing cross-org data leaks
  • createMonitor not transactional: monitor and monitor_status inserts are now wrapped in db.transaction() to prevent orphaned monitors
  • mapMonitor typed: replaced any parameter with proper MonitorWithStatusRow interface for compile-time safety
  • Org membership check optimized: monitoring routes now use a single SELECT WHERE user_id AND organization_id query instead of fetching all user orgs and scanning in JS
  • Redundant DB read eliminated: processCheckResult now 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.by fix: monitor detail page uptime calculation now uses $derived.by() instead of $derived(() => ...) for correct Svelte 5 reactivity
  • @const placement: replaced invalid {@const} inside <div> elements with {#if}/{:else} blocks for Svelte 5 compatibility
  • uptimePct type coercion: Postgres ROUND() returns numeric as string - status page now coerces to number before calling .toFixed()
  • Default failureThreshold aligned: frontend form default changed from 3 to 2 to match backend default
  • Test setup cleanup: added monitor_results, monitor_status, monitors, incident_alerts to global beforeEach cleanup
v0.8.7
Released
Fix

Ingestion Schema, Sigma Log Noise & Admin Bootstrap Fixes

Quick Start cURL example now validates without a manual timestamp, Sigma worker no longer floods logs on empty detection batches, and fresh instances without INITIAL_ADMIN_* env vars auto-promote the first registered user to admin.

  • logSchema now defaults time to current ISO string so minimal payloads and copy-paste examples validate
  • Sigma worker [SigmaDetection] No matches found log gated behind DEBUG_SIGMA=true
  • First registered user is auto-promoted to admin when no INITIAL_ADMIN_* is set and no admin exists

Fixed

  • Quick Start cURL example failed validation: the empty-state code snippet sent {logs: [{level, service, message}]} without a time field, but logSchema required it for the standard ingestion path (only the array-format path ran normalizeLogData). The schema now defaults time to the current ISO string when missing, so copy-paste examples and minimal payloads validate without requiring users to inject a timestamp.
  • Noisy Sigma worker logs: [SigmaDetection] No matches found was emitted at info level on every batch with no detections, flooding worker output in normal operation. The line is now gated behind DEBUG_SIGMA=true so it only appears when explicitly opted in.
  • *No admin user when INITIAL_ADMIN_ not set (#188)**: on a fresh instance without INITIAL_ADMIN_EMAIL/INITIAL_ADMIN_PASSWORD, no usable admin existed and admin settings were unreachable. The bootstrap no longer creates a system fallback user; instead, the first user to register (via /register or external auth provider) is automatically promoted to admin if no admin exists yet.
v0.8.6
Released
Fix

ClickHouse Data-Availability & Session Validation Fixes

Fixed ClickHouse traces/metrics data-availability queries failing due to raw epoch date parameters, and resolved stale dashboard sessions after volume resets.

  • ClickHouse traces/metrics data-availability queries now use toDateTime64() clamp instead of raw epoch dates
  • Dashboard validates session token against backend on load and auto-logs out on invalid session

Fixed

  • ClickHouse traces/metrics data-availability always empty: queryTraces and queryMetrics passed raw 0 for epoch dates as DateTime64(3) parameter, which ClickHouse can’t parse; now uses the same toDateTime64() clamp used by log queries
  • Stale session after volume reset: dashboard only checked localStorage for a token without validating it against the backend; now calls /auth/me on load and auto-logs out if the session is invalid
v0.8.5
Released
Fix

Security Hardening, SSE Fixes & Memory Leak Cleanup

Five cross-org and auth security fixes, corrected SSE real-time streams, resolved 20+ Svelte memory leaks on navigation, and multiple bug fixes across SIEM, incidents, webhooks, and retention jobs.

  • Cross-org isolation and auth bypass fixes in SIEM and pattern routes
  • SSRF protection for legacy webhook path and disabled-user login blocked
  • SSE real-time events and log stream duplicate emission fixed
  • 20+ Svelte memory leaks from unsubscribed auth stores resolved
  • Docker config sync and dependency security bumps (picomatch, brace-expansion, fast-xml-parser)

Security

  • Cross-org isolation fix in SIEM: linkDetectionEventsToIncident now scopes detection events to the requesting organization, preventing cross-tenant data corruption via crafted API calls
  • Cross-org auth bypass in pattern routes: PUT and DELETE handlers for correlation patterns now verify organization membership before mutating data (same check GET/POST already had)
  • SSRF protection for legacy webhook path: the alert-notification job’s direct fetch() call now validates URLs against private/internal IP ranges, matching the WebhookProvider safeguard
  • Disabled user login blocked: POST /login now checks the disabled flag before creating a session, preventing disabled accounts from obtaining tokens
  • Expired invitation info leak: getInvitationByToken now filters on expires_at > NOW(), preventing enumeration of expired invitation details

Fixed

  • SIEM dashboard timeline crash: time_bucket() call was missing ::interval cast on the parameterized bucket width, causing a PostgreSQL type error that broke the timeline widget for all users
  • SSE real-time events broken: SIEM store and incident detail page read auth token from localStorage('session_token') (wrong key), so the SSE connection never authenticated; now uses getAuthToken() from the shared auth utility
  • SSE log stream duplicate emission: when multiple logs shared the same timestamp, the inclusive from bound caused them to be re-sent on every poll tick; stream now tracks sent log IDs to deduplicate
  • Incident severity auto-grouping wrong: MAX(severity) used PostgreSQL alphabetical ordering (medium > critical), producing incorrect severity on auto-grouped incidents; now uses ordinal ranking
  • Sigma notification failures silent: notification job payload was missing organization_id and project_id, and markAsNotified was called with null historyId; both now handled correctly
  • Incidents pagination total always zero: loadIncidents in the SIEM store never wrote response.total to incidentsTotal
  • Memory leaks on navigation: 20+ Svelte components called authStore.subscribe() without cleanup; all now store the unsubscribe function and call it in onDestroy
  • offset=0 silently dropped: API client functions used if (filters.offset) which is falsy for zero, so page-1 requests never sent the offset parameter; changed to if (filters.offset != null)
  • Search debounce timer leak: searchDebounceTimer was not cleared in onDestroy, causing post-unmount API calls when navigating away mid-search
  • verifyProjectAccess double call: when projectId is an array, the first element was verified twice (once before the loop, once inside it); consolidated into a single loop
  • updateIncident silent field skip: title, severity, and status used truthy checks (&&) instead of !== undefined, inconsistent with description and assigneeId
  • Webhook error messages empty: response.statusText is empty for HTTP/2; error now reads the response body for useful detail
  • Retention job crash on empty orgs: Math.max(...[]) returns -Infinity, cascading to an Invalid Date in the drop_chunks call; early return added when no organizations exist
  • escapeHtml DOM leak: PDF export’s escapeHtml created orphaned DOM nodes in the parent document; replaced with pure string replacement
  • Webhook headers validation missing: CreateChannelDialog silently swallowed invalid JSON in the custom headers field; now validates on submit
  • getIncidentDetections no org scope: query now accepts optional organizationId for defense-in-depth filtering
  • Stale shared package types: dist contained outdated Project and Incident interfaces with phantom fields (slug, statusPageVisibility, source, monitorId); rebuilt from source

Changed

  • Docker config sync: docker-compose.build.yml now matches docker-compose.yml with all environment variables (MongoDB, TRUST_PROXY, FRONTEND_URL, INTERNAL_DSN, DOCKER_CONTAINER), MongoDB service, and fluent-bit-metrics service
  • NODE_ENV for backend: production docker-compose.yml now sets NODE_ENV: production on the backend service (worker and frontend already had it)
  • docker/.env.example: added STORAGE_ENGINE, ClickHouse, and MongoDB configuration sections

Dependencies

  • picomatch 4.0.3 → 4.0.4 (fix ReDoS via extglob quantifiers + POSIX character class method injection)
  • brace-expansion 5.0.2 → 5.0.5 (fix zero-step sequence DoS)
  • fast-xml-parser 5.5.6 → 5.5.9 (fix entity expansion limits bypass)
  • fastify bumped via dependabot
  • kysely bumped via dependabot
v0.8.4
Released
Feature

Skeleton Loaders, Helm Auto-Release & Performance Fixes

All dashboard pages now show content-shaped skeleton loaders, automated Helm chart releases on every stable Docker image, and multiple performance and correctness fixes for API endpoints and admin pages.

  • Skeleton loaders and loading overlays on all dashboard pages
  • Automated Helm chart releases triggered on every stable Docker image release
  • API 400 responses now include field-level validation errors
  • Fixed admin pages returning 502 on direct load/reload
  • POST /api/v1/logs/identifiers/batch performance: bypasses storage engine, one PostgreSQL query
  • GET /api/v1/logs/hostnames 8+ second queries fixed with engine-specific indexes and a 6h window cap

Added

  • Skeleton loaders and loading overlays: all dashboard pages now show content-shaped loading states instead of blank spinners
    • New Skeleton, SkeletonTable, and TableLoadingOverlay components (src/lib/components/ui/skeleton/)
    • Directional shimmer animation via @keyframes shimmer using design tokens — works in light and dark mode, disabled for prefers-reduced-motion
    • Initial load (no data yet): animated skeleton rows mirroring the page layout — stat cards on /dashboard, project cards on /dashboard/projects, table rows on search, traces, errors, admin tables, incidents, alerts history, and members
    • Re-fetch (filter change, pagination): existing content dims with a translucent overlay and centered spinner, preventing layout shift and context loss
    • Pages updated: /dashboard, /dashboard/search, /dashboard/projects, /dashboard/alerts, /dashboard/errors, /dashboard/traces, /dashboard/security, /dashboard/security/incidents, /dashboard/admin/organizations, /dashboard/admin/users, /dashboard/admin/projects, /dashboard/settings/members
  • Automated Helm chart releases: every stable Docker image release now triggers a repository_dispatch to logtide-dev/logtide-helm-chart, which auto-bumps appVersion and chart version (patch), commits, and publishes a new chart release to the Helm repo on GitHub Pages

Fixed

  • API 400 responses now include a details array with field-level validation errors instead of just a generic message. Covers both Fastify/AJV schema validation and Zod validation errors (including uncaught ZodError that previously returned 500)
  • Admin pages returned 502 Bad Gateway on direct load/reload: the admin layout ([email protected]) breaks out of the dashboard layout chain, so ssr = false was not inherited; added a dedicated +layout.ts to the admin section
  • /dashboard/admin/projects/[id] crashed with “Something went wrong” due to formatDate being called but not defined (function was named formatTimestamp)
  • POST /api/v1/logs/identifiers/batch slow: the route was calling reservoir.getByIds (hitting ClickHouse/TimescaleDB/MongoDB) only to verify project access, then querying log_identifiers (PostgreSQL) separately. Since log_identifiers already stores log_id → project_id + identifier data, the storage engine call is now bypassed entirely — one PostgreSQL query replaces the N×storage-engine-roundtrips loop. Added bloom filter skip index on id in ClickHouse and a standalone id index in TimescaleDB (migration 032) for getByIds used by findCorrelatedLogs
  • GET /api/v1/logs/hostnames taking 8+ seconds: the 6h window cap was only applied when from was absent — explicit from params (e.g. 24h range from the search page) bypassed it and triggered a full-range metadata scan; cap now clamps any window to 6h max. Added limit: 500 to the distinct call. Per-engine optimizations: ClickHouse adds a hostname materialized column (computed at ingest, eliminates JSONExtractString at query time) and uses it directly in distinct queries; TimescaleDB adds a composite expression index (project_id, (metadata->>'hostname'), time) (migration 032); MongoDB adds a sparse compound index on metadata.hostname. All three engines also now extract the metadata field in a subquery (once per row vs 3×)
v0.8.3
Released
Feature

Comprehensive Audit Logging & OIDC Brand Icons

Major expansion of the audit trail system covering all critical platform actions for GDPR/SOC2 compliance, OIDC provider brand icons, and fixes for date formatting localization and data availability routing.

  • Audit logging for log access, auth events, identity management, provider config, settings, and sessions
  • OIDC login page shows brand icons for well-known providers (Google, Microsoft, GitHub, GitLab, Okta, Auth0, Keycloak, Authentik)
  • Backend auto-detects OIDC provider icon from issuer URL
  • Date/number formatting now respects user's browser/system locale
  • Fixed data-availability endpoint for ClickHouse and MongoDB storage engines

Added

  • Comprehensive Audit Logging: major expansion of the audit trail system to cover all critical platform actions for improved compliance (GDPR/SOC2) and security monitoring.
    • Log Access Auditing: every log search, trace view, context lookup, single log detail view, and live stream connection is now recorded with user identity, IP address, and query parameters.
    • External Authentication Auditing: successful logins via OIDC and LDAP providers are now tracked, including new user registration events.
    • Identity Management Auditing: linking and unlinking of external identities (Google, GitHub, LDAP, etc.) to user accounts is now recorded.
    • Authentication Provider Auditing: all administrative actions on auth providers (create, update, delete, reorder) are now fully audited with configuration change summaries.
    • System Settings Auditing: any changes to global platform settings (auth mode, signup status, default users) are now tracked with before/after metadata.
    • Session Auditing: viewing of active session lists and individual session event timelines is now recorded.
    • Audit metadata now includes detailed context like search queries (q), filter parameters, and updated keys for configuration changes.
  • OIDC login page now shows brand icons for well-known providers (Google, Microsoft/Azure, GitHub, GitLab, Okta, Auth0, Keycloak, Authentik); unknown providers fall back to the generic icon
  • Backend auto-detects the provider icon from the issuer URL when creating or updating an OIDC provider, with name/slug matching as fallback for self-hosted setups

Fixed

  • Date and number formatting localization: removed hardcoded locales (it-IT, en-US) from the frontend (SIEM, Search, Admin, etc.) to ensure the application automatically respects the user’s browser/system language settings.
  • GET /api/v1/projects/data-availability returned logs: [] (and incorrect traces/metrics) when STORAGE_ENGINE=clickhouse or mongodb; the endpoint now routes all three checks through the reservoir so they hit the correct backend
v0.8.2
Released
Fix

SigmaHQ Auto-Sync, Trace Links & Security Fixes

Daily auto-sync for SigmaHQ community rules, trace navigation from log detail panel, audit logs for alert rules, and security/robustness fixes for ingestion and admin endpoints.

  • SigmaHQ rules auto-sync daily at 2:30 AM
  • View Trace link in log detail panel when trace_id is present
  • Audit log entries for alert rule create/update/delete
  • Admin pagination capped at 200 to prevent oversized allocations
  • NDJSON lines exceeding 1MB rejected with HTTP 400
  • api_key_id removed from log metadata (information disclosure fix)

Added

  • SigmaHQ auto-sync: SigmaHQ community rules now auto-sync daily at 2:30 AM for organizations that have existing community rules enabled.
  • Trace link in log detail panel: The log detail panel now shows a “View Trace →” link that navigates directly to the trace timeline when a trace_id is present in the log.
  • Audit logs for alert rules: Create, update, and delete operations on alert rules now generate audit log entries.

Fixed

  • Admin pagination cap: The limit parameter on admin list endpoints is now capped at 200, preventing oversized result set allocation.
  • NDJSON ingestion size limit: Individual lines exceeding 1 MB in NDJSON payloads are now rejected with HTTP 400 instead of being silently processed.
  • Log metadata information disclosure: api_key_id is no longer stored in log metadata. It was previously injected in v0.8.1 for exception log display but exposed sensitive data; the display now resolves the key name at read time instead.
v0.8.1
Released
Breaking

Project Visibility in Errors & Storage Engine Fix

Project name display in error pages, API Key visibility in exception logs, and a fix for data-availability routing when using ClickHouse or MongoDB.

  • All existing API Keys have been invalidated — regenerate your keys
  • Project name shown in Exceptions list and error group detail pages
  • API Key name visible in recent exception logs
  • Data-availability endpoint now respects storage engine selection
  • Search page project filter fixed for empty logs array

Breaking Changes

  • All API Keys have been invalidated: All existing API Keys were deleted as part of this release. You must regenerate your API Keys from the dashboard and update your SDKs and ingestion clients accordingly.

Added

  • Project visibility in Exceptions: The /dashboard/errors list and the individual error group detail pages now explicitly display the name of the project that generated the error.
  • API Key visibility in Exception logs: The recent logs tab within an error group detail page now displays the specific API Key name used to ingest the log. Ingestion now injects the api_key_id into log metadata.

Fixed

  • Project data-availability ignoring storage engine: GET /api/v1/projects/data-availability was always querying the PostgreSQL logs table via Kysely, returning logs: [] when STORAGE_ENGINE was set to clickhouse or mongodb. The logs check now uses reservoir.distinct() which routes to the correct storage backend.
  • Search page showing no projects when logs is empty array: the project filter guard logsProjectIds ? was truthy for [], filtering out all projects. Changed to logsProjectIds?.length so an empty array correctly falls back to showing all projects.
v0.8.0
Released
Feature

MongoDB Adapter, Browser SDKs & Metrics Dashboards

MongoDB storage adapter for @logtide/reservoir, browser and frontend SDKs with source maps and Core Web Vitals, and OTLP metrics dashboards with cross-signal correlation.

  • MongoDB storage adapter for @logtide/reservoir
  • Browser/Frontend SDKs with session tracking & source maps
  • Core Web Vitals and network breadcrumbs
  • OTLP Metrics rollups & Golden Signals (P50/P95/P99)
  • Correlation across logs, traces, and metrics

Added

  • Browser & Frontend SDK Enhancements — Sentry-level browser observability across all frontend framework SDKs.
    • @logtide/browser: Dedicated browser SDK with session tracking, Web Vitals, and offline resilience.
    • Source Maps: @logtide/cli for source map uploads and backend un-minification service.
    • Frameworks: Deep integrations for Next.js, Nuxt, SvelteKit, and Angular.
  • Metrics Dashboard & Rollups — Redesigned metrics page with pre-aggregated rollups for TimescaleDB and ClickHouse.
  • MongoDB Storage Adapter — Full MongoDB backend for the @logtide/reservoir storage layer.
  • Golden Signals with Percentiles — P50/P95/P99 percentile aggregation across all storage engines.
  • Smart Project Selectors — Dropdowns now only show projects that have data in the relevant category.

Optimized

  • TimescaleDB Skip-Scan — Recursive CTEs for instant distinct queries on high-cardinality fields.
  • Intelligent Volume EstimationcountEstimate support for instant dashboard loads on high-volume projects.
  • Batch Ingestion — Ordered-false inserts and tuned connection pools for maximum throughput.
  • ClickHouse Projections — Faster query execution for common search patterns.

Fixed

  • Internal Logging Plugin — Fixed DSN passing to @logtide/fastify for self-monitoring.
  • Backend Self-Monitoring — Improved DSN construction and verbose startup logging.
  • Docker Compose — Fixed missing environment variables and worker health check race conditions.
  • Admin Dashboard — Fixed missing metrics series in platform activity chart.
v0.7.0
Released
Feature

OTLP Metrics, Service Graph & Audit Log

OpenTelemetry metrics ingestion, service dependency graph visualization, audit logging, and major UX restructuring.

  • OTLP Metrics with protobuf/JSON support
  • Service Dependency Graph with force-directed layout
  • Audit Log for compliance tracking
  • UX sidebar restructured into Observe/Detect/Manage
  • 46 TypeScript/Svelte warnings eliminated

Added

  • OTLP Metrics Ingestion — Full OpenTelemetry metrics support with protobuf/JSON, all 5 metric types, exemplar support, TimescaleDB hypertables, ClickHouse support, query API with 7 aggregation intervals and 6 functions, group-by label support, 118+ tests
  • Service Dependency Graph — Force-directed graph visualization with health color-coding, click-to-inspect panels, and PNG export
  • Audit Log — Tracks 4 event categories (login, config changes, user management, data modifications) with high-performance buffer and CSV export

Changed

  • Batch ingestion endpoint accepts flexible payload formats (standard, direct array, wrapped array) with auto-normalization
  • UX Restructuring — Sidebar grouped into Observe/Detect/Manage sections, Service Map merged into Traces, Sigma Rules moved to Security, Settings restructured, Command palette updated

Fixed

  • OTLP Traces typo using resource_logs instead of resource_spans
  • OTLP Authentication for /v1/otlp routes
  • JavaScript SDKs updated to v0.6.1 for OTLP compatibility
  • Frontend environment loading via $env/dynamic/public
  • SDK code examples across dashboard
  • Pagination total count with fast approximate estimates
  • Admin dashboard timeline gaps (ClickHouse bucket key format)
  • Chart locale now respects system language
  • Silent API errors now show toasts
  • Empty states for services/errors
  • Docker auto-database creation
v0.6.3
Released
Fix

Fix: Unauthenticated SMTP Support

SMTP no longer requires user/password credentials. Only the host is needed, and the from address uses the SMTP_FROM parameter.

  • SMTP works without USER/PASS credentials
  • From address uses SMTP_FROM parameter

Fixed SMTP configuration to support unauthenticated mail servers. Only SMTP_HOST is now required — SMTP_USER and SMTP_PASS are optional. The sender address is configured via SMTP_FROM.

v0.6.2
Released
Breaking

Write-Only API Keys & Domain Allowlists

New write-only API key type safe for browsers and mobile, plus domain/IP allowlists with wildcard subdomain support for origin validation.

  • Write-only API keys safe for client-side use
  • Domain/IP allowlist with wildcard subdomains
  • Dogfooding SDK migration to official plugins
  • fast-xml-parser security fix

Added

  • Write-Only API Keys — New type field (write/full), safe for browsers and mobile, defaults to write
  • Domain/IP Allowlist — Up to 50 allowed origins per key with wildcard subdomain support and Origin header validation

Security

  • fast-xml-parser bumped to >=5.3.6 for entity expansion DoS fix
  • Read endpoints reject write-only API keys with 403
  • Origin allowlist validation with wildcard subdomain parsing

Breaking Changes

  • API key default type changed to write — existing keys migrated automatically, server-side queries need full type
  • Database migration 024_api_key_scopes.sql required
v0.6.1
Released
Feature

ClickHouse Storage Engine

ClickHouse as a full alternative to TimescaleDB via the @logtide/reservoir abstraction layer, with factory pattern engine selection and full query migration.

  • ClickHouse via @logtide/reservoir abstraction
  • Factory pattern engine selection
  • 26 integration tests against both engines
  • Full log query migration to Reservoir

Added

  • ClickHouse Storage Engine — Full support as TimescaleDB alternative via @logtide/reservoir abstraction layer with PREWHERE, async_insert, and ngrambf_v1 indexes
  • Full Log Query Migration — All query operations (alerts, dashboard, admin, retention, ingestion, correlation) migrated to the Reservoir abstraction

Performance

  • Removed COUNT(*) full scans in admin queries, switched to continuous aggregates
  • ClickHouse DateTime64(3) millisecond precision with hasToken() fallback
  • TimescaleDB removed redundant indexes, added UNNEST batch inserts
v0.6.0
Released
Feature

Security Packs, PII Masking & Keyboard Shortcuts

Host security detection packs with MITRE ATT&CK mapping, PII masking at ingestion, rate-of-change alerts, keyboard shortcuts, and admin dashboard revision.

  • 3 security packs with 15 MITRE-mapped rules
  • PII masking with mask/redact/hash strategies
  • Rate-of-change alerts with baseline detection
  • Command palette and keyboard shortcuts
  • Revised admin dashboard with health monitoring

Added

  • Host Security Detection Packs — 3 packs (Antivirus & Malware, Rootkit Detection, File Integrity Monitoring) with 15 rules total, MITRE ATT&CK mapped
  • PII Masking at Ingestion — Content patterns (email, credit card, phone, SSN, IPv4, API keys), field name masking, custom rules, three strategies (mask, redact, hash with per-org salt), Settings UI with live test panel
  • Keyboard Shortcuts — Command Palette (Ctrl/Cmd+K), Help Modal (?), sequence navigation (G+D/S/A/P/T/E/R/X), search shortcuts, first-time toast
  • Admin Dashboard Revision — Health status cards, 24h activity timeline, 8 stat cards, System Health page with database/pool/Redis diagnostics
  • Rate-of-Change Alerts — 4 baseline methods (same_time_yesterday, same_day_last_week, rolling_7d_avg, percentile_p95), anti-spam, frontend baseline picker
  • Timeline Event Markers — Alerts and security detections shown on log timeline chart
  • Version Update Notifications — GitHub release checking with 6-hour cache and release channel setting

Fixed

  • Sigma API missing tags and MITRE fields
  • Badge components stretching in containers
  • Client errors (4xx) returning 500 instead of correct status
  • Log Context metadata expanding dialog infinitely
  • Email logo not rendering in Outlook/Gmail
  • Continuous Aggregates showing “Refresh: unknown”
  • Charts not resizing on sidebar toggle (switched to ResizeObserver)
v0.5.4
Released
Fix

Detection Pack Routing & Exception Fixes

Detection pack category routing directs results to correct UI sections. Multiple fixes for exception handling and onboarding.

  • Detection results routed to correct UI sections
  • Exception detection for metadata.error structure
  • Onboarding race condition fixed

Added

  • Detection Pack Category Routing directing results to correct UI sections

Fixed

  • Exception Detection for metadata.error structure
  • Exception Details Dialog showing [object Object]
  • Onboarding race condition with concurrent requests
  • Internal org missing members assignment
  • Unwanted email/webhook notifications dispatch
  • Email logo not rendering with hosted SVG URLs
  • Ingestion JSON parse errors returning proper 400 status
v0.5.5
Released
Fix

Detection Filter Fix & Admin Performance

Fixed detection category filter validation and optimized admin dashboard from 31s to ~1s with continuous aggregates.

  • Detection category filter validation fix
  • Admin stats optimized from 31s to ~1s
  • Error Group Logs timeout fixed

Fixed

  • Detection Category Filter Validation Error with schema correction
  • Admin Dashboard timeout fixed with continuous aggregates

Performance

  • Admin stats endpoints optimized from 31s to ~1s
  • Error Group Logs timeout fixed with time bounds
v0.5.3
Released
Improvement

Hostname Filter & Hypertable Optimization

Hostname filter for syslog sources, log retention fixes for compressed chunks, and major performance improvements with TimescaleDB hypertables.

  • Hostname filter for syslog sources
  • log_identifiers as TimescaleDB hypertable
  • Continuous aggregates for spans and detections
  • Hybrid query architecture for historical data

Added

  • Hostname Filter for Syslog Sources with automatic extraction and filtering

Fixed

  • Log Retention on Compressed Chunks with proper decompression handling
  • Fluent Bit Kubernetes metadata extraction improvements

Performance

  • log_identifiers table optimized as TimescaleDB hypertable
  • Continuous aggregates for spans and detection events
  • Hybrid query architecture using aggregates for historical data
  • Admin monitoring endpoints for compression and aggregate stats
v0.5.2
Released
Security

Security Fixes & Batch Splitting

Fastify security vulnerabilities patched, automatic batch request splitting, and Unicode escape sanitization.

  • Fastify upgraded to 5.7.3+ for security fixes
  • Automatic batch request splitting
  • Unicode escape sequence sanitization

Security

  • Fastify security vulnerabilities fixed in upgrade to 5.7.3+

Fixed

  • API batch request limit with automatic batch splitting
  • Unicode escape sequences sanitization
  • POST requests without body compatibility
  • Log retention cleanup for compressed chunks
  • Fluent Bit Kubernetes metadata extraction
v0.5.1
Released
Feature

Notification Channels

Configurable email and webhook notification destinations with channel testing before saving and UI space optimization.

  • Email and webhook notification channels
  • Test channel before saving
  • UI space optimization

Added

  • Notification Channels — Configurable email and webhook destinations with channel testing before saving

Changed

  • UI space optimization reducing margins and padding across the dashboard

Fixed

  • Invitation email resend functionality
  • Unwanted notifications when channels unconfigured
v0.5.0
Released
Feature

Terminal View, Detection Packs & Event Correlation

Terminal log view with ANSI color coding, pre-configured detection packs, event correlation by identifier, alert preview testing, and optional Redis dependency.

  • Terminal Log View with ANSI color coding
  • Pre-configured Sigma detection packs
  • Event correlation by request/trace/user ID
  • Alert preview with 'Would Have Fired' simulation
  • Redis dependency now optional

Added

  • Terminal Log View — ANSI-style color coding for a familiar terminal experience
  • Detection Packs — Pre-configured Sigma rule bundles for common scenarios (startup, auth, database health)
  • Event Correlation — Click any request ID, trace ID, or user ID to see all related logs
  • Alert Preview — “Would Have Fired” simulation to test rules before enabling
  • Optional Redis — PostgreSQL-based alternatives with adapter pattern queue system

Fixed

  • Log Context modal reopening after close
  • Exception details from metadata display
  • WebSocket memory leak in live tail handler
  • SQL injection prevention in notification publisher
v0.4.2
Released
Fix

Clipboard Utility & Config Validation

Centralized clipboard copy function, config validation test coverage, and documentation corrections.

  • Centralized clipboard utility
  • Config validation test coverage

Added

  • Clipboard utility with centralized copy function
  • Config validation test coverage

Fixed

  • Documentation corrections for API configuration
  • Docker Compose configuration information
v0.4.1
Released
Improvement

Multi-Language Exception Parsers

Stack trace parsing support for multiple programming languages and dependency updates including @sveltejs/kit.

  • Multi-language stack trace parsing
  • SvelteKit and dependency updates

Added

  • Exception parsers for multi-language stack trace parsing

Changed

  • Dependencies updated including @sveltejs/kit

Fixed

  • OTLP endpoint URLs in documentation
v0.4.0
Released
Breaking

LogWard to LogTide Rebrand & Search

Project rebranding from LogWard to LogTide, substring search with trigram indexes, clickable dashboard elements, exception visualization, and customizable retention.

  • Project rebranded from LogWard to LogTide
  • Substring search with PostgreSQL trigram index
  • Clickable dashboard elements
  • Enhanced exception & stack trace visualization
  • Customizable log retention policy

Added

  • Substring Search — Full-text search with PostgreSQL trigram index
  • Clickable Dashboard Elements — Interactive navigation from charts and widgets
  • Enhanced Exception Visualization — Improved stack trace display
  • Customizable Log Retention — Configurable retention policies per project
  • Full-Page Export — Export all matching logs across pages
  • Custom Time Range Picker — Improved date/time selection UI

Breaking Changes

  • Environment variables renamed (LOGWARDLOGTIDE)
  • Fluent Bit configuration variables renamed
  • Database defaults changed
  • Docker container and service names changed
  • SMTP default sender changed

Fixed

  • Mobile navigation menu hamburger functionality
  • Services dropdown showing all services
  • Journald log format detection
  • Syslog level mapping improvements
  • OTLP protobuf parsing with proper binary support
2025
v0.3.2
Released
Fix

SvelteKit 2 Compatibility Fixes

Fixed SvelteKit 2 compatibility with new store patterns, traces page navigation 404s, and registration error handling.

  • SvelteKit 2 store pattern compatibility
  • Traces page 404 navigation fix

Fixed

  • SvelteKit 2 compatibility with new store patterns
  • Traces page navigation fixing 404 links
  • Registration error network handling
v0.3.3
Released
Feature

LDAP, OIDC & Auth-Free Mode

Enterprise authentication with LDAP and OpenID Connect, auth-free mode for home labs, initial admin via environment variables, and ARM64 Docker builds.

  • LDAP authentication for enterprise directories
  • OpenID Connect (OIDC) SSO
  • Auth-free mode for home labs
  • ARM64 / Raspberry Pi Docker builds
  • Disable sign-ups capability

Added

  • LDAP Authentication — Enterprise directory integration
  • OpenID Connect (OIDC) — SSO with any OIDC-compliant provider
  • Initial Admin via Environment Variables — Bootstrap admin user at first startup
  • Disable Sign-ups — Control user registration for private instances
  • Auth-free Mode — No authentication required, ideal for home labs and local development
  • ARM64 / Raspberry Pi Support — Docker images for ARM architecture

Changed

  • Fluent Bit default version pinned to 4.2.2
v0.3.1
Released
Fix

Security Policy Update

Updated security policy with current supported versions for responsible disclosure.

  • Supported versions updated for security policy

Updated security policy with current supported versions.

v0.3.0
Released
Feature

SIEM Dashboard, C# SDK & GeoIP Enrichment

Real-time SIEM dashboard with security widgets, C# / .NET SDK, IP reputation and GeoIP enrichment, and organization invitations.

  • Real-time SIEM dashboard with security widgets
  • C# / .NET SDK
  • IP reputation & GeoIP enrichment
  • Organization invitations
  • Horizontal scaling docs with Traefik

Added

  • SIEM Dashboard — Real-time security dashboard with dedicated widgets
  • C# / .NET SDK — Full .NET application support
  • IP Reputation & GeoIP Enrichment — Automatic IP context enrichment for security analysis
  • Organization Invitations — Invite users to join your organization
  • Horizontal Scaling Documentation — Traefik-based setup guide

Fixed

  • PDF export functionality in incident detail
v0.2.4
Released
Improvement

Syslog Integration & Go SDK Docs

Syslog integration documentation with device-specific guides, Go SDK documentation, and documentation restructure with new Integrations section.

  • Syslog integration with device guides
  • Go SDK documentation
  • Documentation restructure
  • Runtime PUBLIC_API_URL configuration

Added

  • Syslog integration documentation with device-specific guides
  • Go SDK documentation at /docs/sdks/go
  • Documentation restructure with new Integrations section

Changed

  • Docker Compose improved container orchestration
  • Onboarding flow skip behavior refinement
  • Runtime configuration for PUBLIC_API_URL

Fixed

  • Sign Up Free link pointing to correct route
  • Skip tutorial redirect loop
  • API URL in code examples
v0.2.3
Released
Improvement

Docker Image Publishing & Self-Hosting Docs

Automated Docker image publishing via GitHub Actions CI/CD and comprehensive self-hosting deployment documentation.

  • Docker images via GitHub Actions CI/CD
  • Self-hosting deployment documentation
  • Pre-built images in docker-compose.yml

Added

  • Docker image publishing with GitHub Actions CI/CD pipeline
  • Self-hosting documentation with deployment guides

Changed

  • docker-compose.yml now uses pre-built images by default
v0.2.2
Released
Feature

Onboarding Tutorial & Empty States

Multi-step onboarding wizard with progress tracking, empty state components for guidance, and expanded testing infrastructure.

  • Multi-step onboarding wizard
  • User onboarding checklist with progress
  • Empty state guidance components
  • Expanded testing infrastructure

Added

  • Onboarding Tutorial — Multi-step wizard guiding new users through setup
  • Empty State Components — Helpful guidance when dashboards have no data yet
  • User Onboarding Checklist — Progress tracking for initial setup steps
  • UI enhancements with help components

Fixed

  • Organization context handling improvements
  • Error states and loading indicators
v0.2.1
Released
Improvement

Redis Caching & 50x Performance Boost

Type-safe Redis caching layer with session validation 30x faster, API key verification 20x faster, query results 10x faster, and aggregations 50x faster.

  • Session validation 30x faster
  • API key verification 20x faster
  • Query results 10x faster
  • Aggregations 50x faster

Added

  • Redis Caching Layer — Type-safe cache keys with automatic invalidation
  • Landing page public index

Changed

  • Database optimization with composite indexes

Performance

  • Session validation 30x faster with caching
  • API key verification 20x faster
  • Query results 10x faster
  • Aggregations 50x faster

Fixed

  • Admin panel double sidebar issue
  • Admin routes navigation path corrections
v0.2.0
Released
Feature

OpenTelemetry & Distributed Tracing

Full OpenTelemetry support with OTLP endpoints, distributed tracing with complete CRUD operations, and 563+ tests.

  • OpenTelemetry OTLP endpoints
  • Distributed tracing with full CRUD
  • 563+ tests
  • Keyboard navigation for span selection

Added

  • OpenTelemetry Support — OTLP ingestion endpoints for traces and logs
  • Distributed Tracing — Full CRUD operations with waterfall visualization
  • Testing Infrastructure — 563+ tests covering all major features

Changed

  • OTLP ingestion performance optimization
  • Span selection UX with keyboard navigation

Fixed

  • Frontend UX issues in OTLP data display
  • trace_id handling flexibility for various formats
v0.1.0
Released
Feature

Initial Release

First public release of LogTide with multi-organization architecture, batch log ingestion, real-time streaming, TimescaleDB storage, Sigma detection engine, and official SDKs.

  • Multi-organization architecture
  • High-performance batch log ingestion
  • Real-time log streaming via SSE
  • Sigma detection engine
  • Node.js, Python, PHP & Kotlin SDKs
  • Docker Compose deployment

Added

  • Multi-organization architecture — Isolated workspaces for teams and projects
  • High-performance batch log ingestion — Optimized for high throughput
  • Real-time log streaming — Server-Sent Events for live tail
  • Advanced search and filtering — Query logs with complex filters
  • TimescaleDB storage — Automatic compression and retention policies
  • Dashboard with statistics — Overview of log volume, errors, and trends
  • Alert system — Rules with email and webhook notifications
  • Sigma detection engine — Industry-standard threat detection rules
  • Official SDKs — Node.js, Python, PHP, and Kotlin
  • Docker Compose deployment — Single-command setup

Have a feature request?

Open a discussion on GitHub or vote on existing proposals to help shape the roadmap.

Open a Discussion