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.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
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
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.
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
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)
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
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
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.
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 Estimation — countEstimate 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
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
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
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
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
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 (LOGWARD → LOGTIDE)
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.
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