[P1] Log search experience #104

Closed
opened 2026-04-01 22:52:12 +02:00 by claude · 2 comments
Owner

Parent Epic

#100

Problem

Logs are the #1 debugging tool for developers. If they have to keep Grafana/Loki open alongside Cameleer for log search, they'll stay there. Currently, logs exist only as a tab on the exchange detail panel and in the Runtime instance view — there's no dedicated, searchable log experience.

Current State

  • Exchange detail panel has a "Log" tab showing processor-level logs for that exchange
  • Runtime instance page has log viewer with level filtering
  • No cross-application log search
  • No log correlation with exchanges (time-based or ID-based)
  • No structured field extraction or filtering
  • ClickHouse already stores logs (10,150 rows visible in admin)

Proposed Solution

1. Dedicated Logs Page

Add a fourth content tab: Exchanges | Dashboard | Runtime | Logs

┌──────────────────────────────────────────────────────────┐
│  Exchanges  Dashboard  Runtime  [Logs]                   │
│──────────────────────────────────────────────────────────│
│                                                          │
│  ┌─ Search ──────────────────────────────────────────┐   │
│  │ 🔍 level:ERROR AND "NullPointerException"         │   │
│  └───────────────────────────────────────────────────┘   │
│                                                          │
│  Filters:  [All Levels ▾]  [All Apps ▾]  [All Agents ▾]  │
│                                                          │
│  23,481 logs matching  •  Auto-tail: ON                  │
│                                                          │
│  22:31:41.504  ERROR  backend-app  route3                │
│  ├ java.lang.NullPointerException: Cannot invoke...      │
│  ├   at com.example.OrderProcessor.process(Order...      │
│  └   at org.apache.camel.processor.Pipeline.proc...      │
│       ↗ View Exchange  73A3A974C735725-B3                │
│                                                          │
│  22:31:41.100  WARN   sample-app   complex-fulfillment   │
│  ├ Retry attempt 3/5 for endpoint http://api.ext...      │
│  └       ↗ View Exchange  374A056A54937BB-78D            │
│                                                          │
│  22:31:40.892  INFO   caller-app   product-caller        │
│  ├ Successfully called backend service, response...      │
│                                                          │
│  22:31:40.500  DEBUG  backend-app  route2                │
│  ├ Processing order #4521 with priority STANDARD         │
│                                                          │
│  ──────────── Load more (scroll or click) ───────────    │
│                                                          │
└──────────────────────────────────────────────────────────┘

2. Key Features

Search syntax:

  • Full-text search across log messages
  • Field filters: level:ERROR, app:backend-app, route:route3, agent:pod-name
  • Combined: level:ERROR app:backend-app "timeout"
  • Autocomplete for field names and values

Exchange correlation:

  • Each log line that belongs to an exchange shows a "View Exchange" link
  • Clicking it opens the exchange detail with the log tab focused
  • From exchange detail, "View surrounding logs" opens the Logs tab filtered to that time window ± 5 seconds

Live tail:

  • Auto-scroll mode that streams new logs in real-time
  • Pause button to freeze the view for reading
  • Visual indicator showing new logs arriving above/below viewport

Level filtering:

  • Toggle buttons: TRACE | DEBUG | INFO | WARN | ERROR
  • Multi-select (show WARN + ERROR together)
  • Count badge per level

3. Integration with Sidebar Scope

  • Sidebar app selection filters logs to that application
  • Sidebar route selection filters to that route's logs
  • Breadcrumb reflects: All Applications > backend-app > route3 (logs)

Architecture Notes

  • ClickHouse logs table already exists with 10K+ rows
  • Backend needs a /api/v1/logs/search endpoint with pagination, filtering, and text search
  • Use ClickHouse full-text indexing for log message search
  • Tail mode can use SSE or polling (30s intervals)
  • Consider log volume — may need server-side aggregation for high-volume apps

Acceptance Criteria

  • Logs tab added to main navigation
  • Full-text search across log messages
  • Filter by level, application, route, agent
  • Exchange correlation links on log entries
  • Bidirectional: exchange → surrounding logs, log → parent exchange
  • Live tail mode with pause/resume
  • Sidebar scope filtering applies to logs
  • Pagination or virtual scrolling for large result sets

Competitive Reference

  • Datadog Log Explorer: faceted search, saved views, log patterns, live tail
  • Grafana Loki: LogQL query language, label filtering, log-to-trace correlation
  • Kibana: KQL search, field filters, surrounding documents, log streaming
## Parent Epic #100 ## Problem Logs are the #1 debugging tool for developers. If they have to keep Grafana/Loki open alongside Cameleer for log search, they'll stay there. Currently, logs exist only as a tab on the exchange detail panel and in the Runtime instance view — there's no dedicated, searchable log experience. ## Current State - Exchange detail panel has a "Log" tab showing processor-level logs for that exchange - Runtime instance page has log viewer with level filtering - No cross-application log search - No log correlation with exchanges (time-based or ID-based) - No structured field extraction or filtering - ClickHouse already stores logs (10,150 rows visible in admin) ## Proposed Solution ### 1. Dedicated Logs Page Add a fourth content tab: **Exchanges | Dashboard | Runtime | Logs** ``` ┌──────────────────────────────────────────────────────────┐ │ Exchanges Dashboard Runtime [Logs] │ │──────────────────────────────────────────────────────────│ │ │ │ ┌─ Search ──────────────────────────────────────────┐ │ │ │ 🔍 level:ERROR AND "NullPointerException" │ │ │ └───────────────────────────────────────────────────┘ │ │ │ │ Filters: [All Levels ▾] [All Apps ▾] [All Agents ▾] │ │ │ │ 23,481 logs matching • Auto-tail: ON │ │ │ │ 22:31:41.504 ERROR backend-app route3 │ │ ├ java.lang.NullPointerException: Cannot invoke... │ │ ├ at com.example.OrderProcessor.process(Order... │ │ └ at org.apache.camel.processor.Pipeline.proc... │ │ ↗ View Exchange 73A3A974C735725-B3 │ │ │ │ 22:31:41.100 WARN sample-app complex-fulfillment │ │ ├ Retry attempt 3/5 for endpoint http://api.ext... │ │ └ ↗ View Exchange 374A056A54937BB-78D │ │ │ │ 22:31:40.892 INFO caller-app product-caller │ │ ├ Successfully called backend service, response... │ │ │ │ 22:31:40.500 DEBUG backend-app route2 │ │ ├ Processing order #4521 with priority STANDARD │ │ │ │ ──────────── Load more (scroll or click) ─────────── │ │ │ └──────────────────────────────────────────────────────────┘ ``` ### 2. Key Features **Search syntax:** - Full-text search across log messages - Field filters: `level:ERROR`, `app:backend-app`, `route:route3`, `agent:pod-name` - Combined: `level:ERROR app:backend-app "timeout"` - Autocomplete for field names and values **Exchange correlation:** - Each log line that belongs to an exchange shows a "View Exchange" link - Clicking it opens the exchange detail with the log tab focused - From exchange detail, "View surrounding logs" opens the Logs tab filtered to that time window ± 5 seconds **Live tail:** - Auto-scroll mode that streams new logs in real-time - Pause button to freeze the view for reading - Visual indicator showing new logs arriving above/below viewport **Level filtering:** - Toggle buttons: TRACE | DEBUG | INFO | WARN | ERROR - Multi-select (show WARN + ERROR together) - Count badge per level ### 3. Integration with Sidebar Scope - Sidebar app selection filters logs to that application - Sidebar route selection filters to that route's logs - Breadcrumb reflects: All Applications > backend-app > route3 (logs) ## Architecture Notes - ClickHouse `logs` table already exists with 10K+ rows - Backend needs a `/api/v1/logs/search` endpoint with pagination, filtering, and text search - Use ClickHouse full-text indexing for log message search - Tail mode can use SSE or polling (30s intervals) - Consider log volume — may need server-side aggregation for high-volume apps ## Acceptance Criteria - [ ] Logs tab added to main navigation - [ ] Full-text search across log messages - [ ] Filter by level, application, route, agent - [ ] Exchange correlation links on log entries - [ ] Bidirectional: exchange → surrounding logs, log → parent exchange - [ ] Live tail mode with pause/resume - [ ] Sidebar scope filtering applies to logs - [ ] Pagination or virtual scrolling for large result sets ## Competitive Reference - **Datadog Log Explorer:** faceted search, saved views, log patterns, live tail - **Grafana Loki:** LogQL query language, label filtering, log-to-trace correlation - **Kibana:** KQL search, field filters, surrounding documents, log streaming
claude added the featureuxpmf labels 2026-04-01 22:52:12 +02:00
Author
Owner

Design Specification

ClickHouse Infrastructure

Existing logs table has ngram bloom filter indexes on message and stack_trace. Key columns: timestamp (DateTime64(3)), application, instance_id, level, logger_name, message, stack_trace, exchange_id, mdc (Map(String,String)).

New API: GET /api/v1/logs/search

Param Type Required Description
q string no Free-text case-insensitive substring of message
level csv no Level filter: WARN,ERROR
application string no App filter (optional — omit for cross-app search)
agentId string no Agent instance filter
exchangeId string no Exchange ID filter
logger string no Logger name substring
from/to ISO yes Time range
cursor string no Keyset pagination cursor
limit int no Page size (default 100, max 500)
sort string no asc or desc (default desc)

Response:

{
  "data": [{ "timestamp", "level", "loggerName", "message", "threadName", "stackTrace", "exchangeId", "instanceId", "application", "mdc" }],
  "nextCursor": "...",
  "hasMore": true,
  "levelCounts": { "ERROR": 42, "WARN": 130, "INFO": 8420, "DEBUG": 2841, "TRACE": 0 }
}

Frontend: Logs Tab (4th tab)

[Exchanges] [Dashboard] [Runtime] [*Logs*]

+--search-bar---------------------------------------------+
| [Q] Search logs...                    [Live tail: OFF]  |
+---------------------------------------------------------+
| [TRACE 0] [DEBUG 2.8K] [INFO 8.4K] [*WARN 342*] [*ERR 87*] |
| Logger: [____]  Thread: [____]  [x] Include stacks     |
+---------------------------------------------------------+
| 14:30:22.456  ERR  order-svc  c.e.OrderProcessor       |
| Failed to process order #12345: timeout...  [Exchange]  |
+---------------------------------------------------------+
| 14:30:22.100  WRN  order-svc  c.e.PaymentClient        |
| Connection pool exhausted, waiting...                   |
+---------------------------------------------------------+
| ... (virtual scroll)                                    |
+---------------------------------------------------------+
| 342 of 9,690 results | Query: 45ms | Page 1            |

Search Syntax

Free text = substring match on message. Field prefixes: level:ERROR, app:order-service, logger:OrderProcessor, exchange:ABC-123, mdc.traceId:abc. Multiple terms AND-ed. Autocomplete for field names and app/level values.

Log Entry Component

Collapsed: timestamp (HH:mm:ss.SSS) + level badge + app badge + abbreviated logger + thread + message (truncated 200 chars) + [Stack]/[Exchange] chips.

Expanded (click): Full logger, thread, instance, exchange link, stack trace in CodeBlock, MDC map, action buttons [View Exchange] [View Surrounding Logs] [Copy Message].

Live Tail

Polling every 2s (adaptive 1-5s based on volume). Append mode (newest at bottom, auto-scroll). Pause on scroll-up with "47 new entries" indicator. Buffer limit 5K entries. Auto-pause after 5min inactivity. Requires at least one filter active.

Cross-Navigation

  • Log entry → Exchange: [Exchange] chip navigates to /exchanges/{app}/{route}/{exchangeId}. Route resolved from mdc['camel.routeId'] or executions table lookup.
  • Exchange → Logs: [View Logs] button on ExchangeHeader navigates to /logs?exchangeId={id}&from={start-5s}&to={end+5s}.
  • LogTab footer → Logs tab: "Open in Logs tab" link.

Sidebar Integration

/logs/:appId?/:routeId? routes. Sidebar app selection → application filter. Route selection → MDC-based filtering (mdc['camel.routeId']).

Performance

Virtual scrolling via @tanstack/react-virtual. 300ms debounce on text inputs. 10s ClickHouse query timeout. TanStack Query cache: 30s staleTime, 5min gcTime.

Implementation Phases

  1. Backend API (LogSearchController, ClickHouseLogSearchStore)
  2. Frontend Logs tab (page, entry component, search, filters)
  3. Live tail (polling, auto-scroll, pause/resume)
  4. Cross-navigation (exchange links, surrounding logs)
## Design Specification ### ClickHouse Infrastructure Existing `logs` table has ngram bloom filter indexes on `message` and `stack_trace`. Key columns: `timestamp` (DateTime64(3)), `application`, `instance_id`, `level`, `logger_name`, `message`, `stack_trace`, `exchange_id`, `mdc` (Map(String,String)). ### New API: `GET /api/v1/logs/search` | Param | Type | Required | Description | |-------|------|----------|-------------| | `q` | string | no | Free-text case-insensitive substring of message | | `level` | csv | no | Level filter: `WARN,ERROR` | | `application` | string | no | App filter (optional — omit for cross-app search) | | `agentId` | string | no | Agent instance filter | | `exchangeId` | string | no | Exchange ID filter | | `logger` | string | no | Logger name substring | | `from`/`to` | ISO | yes | Time range | | `cursor` | string | no | Keyset pagination cursor | | `limit` | int | no | Page size (default 100, max 500) | | `sort` | string | no | `asc` or `desc` (default `desc`) | **Response:** ```json { "data": [{ "timestamp", "level", "loggerName", "message", "threadName", "stackTrace", "exchangeId", "instanceId", "application", "mdc" }], "nextCursor": "...", "hasMore": true, "levelCounts": { "ERROR": 42, "WARN": 130, "INFO": 8420, "DEBUG": 2841, "TRACE": 0 } } ``` ### Frontend: Logs Tab (4th tab) ``` [Exchanges] [Dashboard] [Runtime] [*Logs*] +--search-bar---------------------------------------------+ | [Q] Search logs... [Live tail: OFF] | +---------------------------------------------------------+ | [TRACE 0] [DEBUG 2.8K] [INFO 8.4K] [*WARN 342*] [*ERR 87*] | | Logger: [____] Thread: [____] [x] Include stacks | +---------------------------------------------------------+ | 14:30:22.456 ERR order-svc c.e.OrderProcessor | | Failed to process order #12345: timeout... [Exchange] | +---------------------------------------------------------+ | 14:30:22.100 WRN order-svc c.e.PaymentClient | | Connection pool exhausted, waiting... | +---------------------------------------------------------+ | ... (virtual scroll) | +---------------------------------------------------------+ | 342 of 9,690 results | Query: 45ms | Page 1 | ``` ### Search Syntax Free text = substring match on `message`. Field prefixes: `level:ERROR`, `app:order-service`, `logger:OrderProcessor`, `exchange:ABC-123`, `mdc.traceId:abc`. Multiple terms AND-ed. Autocomplete for field names and app/level values. ### Log Entry Component **Collapsed**: timestamp (HH:mm:ss.SSS) + level badge + app badge + abbreviated logger + thread + message (truncated 200 chars) + [Stack]/[Exchange] chips. **Expanded** (click): Full logger, thread, instance, exchange link, stack trace in CodeBlock, MDC map, action buttons [View Exchange] [View Surrounding Logs] [Copy Message]. ### Live Tail Polling every 2s (adaptive 1-5s based on volume). Append mode (newest at bottom, auto-scroll). Pause on scroll-up with "47 new entries" indicator. Buffer limit 5K entries. Auto-pause after 5min inactivity. Requires at least one filter active. ### Cross-Navigation - Log entry → Exchange: `[Exchange]` chip navigates to `/exchanges/{app}/{route}/{exchangeId}`. Route resolved from `mdc['camel.routeId']` or executions table lookup. - Exchange → Logs: `[View Logs]` button on ExchangeHeader navigates to `/logs?exchangeId={id}&from={start-5s}&to={end+5s}`. - LogTab footer → Logs tab: "Open in Logs tab" link. ### Sidebar Integration `/logs/:appId?/:routeId?` routes. Sidebar app selection → application filter. Route selection → MDC-based filtering (`mdc['camel.routeId']`). ### Performance Virtual scrolling via `@tanstack/react-virtual`. 300ms debounce on text inputs. 10s ClickHouse query timeout. TanStack Query cache: 30s staleTime, 5min gcTime. ### Implementation Phases 1. Backend API (LogSearchController, ClickHouseLogSearchStore) 2. Frontend Logs tab (page, entry component, search, filters) 3. Live tail (polling, auto-scroll, pause/resume) 4. Cross-navigation (exchange links, surrounding logs)
Author
Owner

Implemented

Commits: b73f5e6, 4d66d6a, 8c7c991, 5629770

What was delivered

Backend:

  • Extended GET /api/v1/logs with cursor pagination, multi-level filtering, optional application scoping, level count aggregation
  • Added exchangeId, instanceId, application, mdc fields to log responses
  • Refactored ClickHouseLogStore with keyset pagination (N+1 pattern), ILIKE for case-insensitive search across message + stack_trace + logger_name
  • New LogSearchRequest/LogSearchResponse core domain records
  • Updated integration tests with cursor, multi-level, cross-app, logger, sort tests

Frontend:

  • Logs tab (4th content tab) with full search experience
  • Debounced text search with case-insensitive match highlighting (amber)
  • Level filter bar with count badges (unaffected by active level filter)
  • Expandable log entries: collapsed row → full detail with stack trace, MDC, exchange links
  • Cursor pagination with "Load more"
  • Live tail mode (2s polling, auto-scroll, pause on scroll-up)
  • App-scoped view via /logs/:appId
  • Cross-navigation: exchange header → logs, LogTab footer → logs tab
  • Deterministic badge colors for app names (shared attributeBadgeColor hash)

Not included (future)

  • Faceted search (app/logger/route breakdown)
  • Virtual scrolling for very large result sets
  • Search syntax with field:value parsing and autocomplete
## Implemented Commits: b73f5e6, 4d66d6a, 8c7c991, 5629770 ### What was delivered **Backend:** - Extended `GET /api/v1/logs` with cursor pagination, multi-level filtering, optional application scoping, level count aggregation - Added `exchangeId`, `instanceId`, `application`, `mdc` fields to log responses - Refactored `ClickHouseLogStore` with keyset pagination (N+1 pattern), `ILIKE` for case-insensitive search across message + stack_trace + logger_name - New `LogSearchRequest`/`LogSearchResponse` core domain records - Updated integration tests with cursor, multi-level, cross-app, logger, sort tests **Frontend:** - Logs tab (4th content tab) with full search experience - Debounced text search with case-insensitive match highlighting (amber) - Level filter bar with count badges (unaffected by active level filter) - Expandable log entries: collapsed row → full detail with stack trace, MDC, exchange links - Cursor pagination with "Load more" - Live tail mode (2s polling, auto-scroll, pause on scroll-up) - App-scoped view via `/logs/:appId` - Cross-navigation: exchange header → logs, LogTab footer → logs tab - Deterministic badge colors for app names (shared `attributeBadgeColor` hash) ### Not included (future) - Faceted search (app/logger/route breakdown) - Virtual scrolling for very large result sets - Search syntax with `field:value` parsing and autocomplete
Sign in to join this conversation.