diff --git a/src/pages/LogsPage.module.scss b/src/pages/LogsPage.module.scss index ddfa990..f36c122 100644 --- a/src/pages/LogsPage.module.scss +++ b/src/pages/LogsPage.module.scss @@ -187,15 +187,8 @@ .rowMain { display: flex; - flex-direction: column; - gap: 4px; - min-width: 0; -} - -.rowMeta { - display: flex; - align-items: center; flex-wrap: wrap; + align-items: baseline; gap: 6px; min-width: 0; } @@ -236,6 +229,15 @@ } } +.requestIdBadge { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', + 'Courier New', monospace; + font-size: 11px; + color: #0891b2; + background: rgba(8, 145, 178, 0.1); + border-color: rgba(8, 145, 178, 0.25); +} + .statusBadge { font-variant-numeric: tabular-nums; } @@ -308,6 +310,5 @@ .message { color: var(--text-secondary); - white-space: pre-wrap; - word-break: break-word; + white-space: nowrap; } diff --git a/src/pages/LogsPage.tsx b/src/pages/LogsPage.tsx index 2d28cc0..3b272fe 100644 --- a/src/pages/LogsPage.tsx +++ b/src/pages/LogsPage.tsx @@ -44,11 +44,12 @@ type HttpMethod = (typeof HTTP_METHODS)[number]; const HTTP_METHOD_REGEX = new RegExp(`\\b(${HTTP_METHODS.join('|')})\\b`); const LOG_TIMESTAMP_REGEX = /^\[?(\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)\]?/; -const LOG_LEVEL_REGEX = /^\[?(trace|debug|info|warn|warning|error|fatal)\]?(?=\s|\[|$)\s*/i; +const LOG_LEVEL_REGEX = /^\[?(trace|debug|info|warn|warning|error|fatal)\s*\]?(?=\s|\[|$)\s*/i; const LOG_SOURCE_REGEX = /^\[([^\]]+)\]/; const LOG_LATENCY_REGEX = /\b(\d+(?:\.\d+)?)(?:\s*)(µs|us|ms|s)\b/i; const LOG_IPV4_REGEX = /\b(?:\d{1,3}\.){3}\d{1,3}\b/; const LOG_IPV6_REGEX = /\b(?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}\b/i; +const LOG_REQUEST_ID_REGEX = /^([a-f0-9]{8}|--------|---------)$/i; const LOG_TIME_OF_DAY_REGEX = /^\d{1,2}:\d{2}:\d{2}(?:\.\d{1,3})?$/; const GIN_TIMESTAMP_SEGMENT_REGEX = /^\[GIN\]\s+(\d{4})\/(\d{2})\/(\d{2})\s*-\s*(\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s*$/; @@ -102,6 +103,7 @@ type ParsedLogLine = { timestamp?: string; level?: LogLevel; source?: string; + requestId?: string; statusCode?: number; latency?: string; ip?: string; @@ -173,6 +175,7 @@ const parseLogLine = (raw: string): ParsedLogLine => { let ip: string | undefined; let method: HttpMethod | undefined; let path: string | undefined; + let requestId: string | undefined; let message = remaining; if (remaining.includes('|')) { @@ -199,6 +202,19 @@ const parseLogLine = (raw: string): ParsedLogLine => { } } + // request id (8-char hex or dashes) + const requestIdIndex = segments.findIndex((segment) => LOG_REQUEST_ID_REGEX.test(segment)); + if (requestIdIndex >= 0) { + const match = segments[requestIdIndex].match(LOG_REQUEST_ID_REGEX); + if (match) { + const id = match[1]; + if (!/^-+$/.test(id)) { + requestId = id; + } + consumed.add(requestIdIndex); + } + } + // status code const statusIndex = segments.findIndex((segment) => /^\d{3}\b/.test(segment)); if (statusIndex >= 0) { @@ -244,6 +260,16 @@ const parseLogLine = (raw: string): ParsedLogLine => { consumed.add(methodIndex); } + // source (e.g. [gin_logger.go:94]) + const sourceIndex = segments.findIndex((segment) => LOG_SOURCE_REGEX.test(segment)); + if (sourceIndex >= 0) { + const match = segments[sourceIndex].match(LOG_SOURCE_REGEX); + if (match) { + source = match[1]; + consumed.add(sourceIndex); + } + } + message = segments.filter((_, index) => !consumed.has(index)).join(' | '); } else { statusCode = detectHttpStatusCode(remaining); @@ -276,6 +302,7 @@ const parseLogLine = (raw: string): ParsedLogLine => { timestamp, level, source, + requestId, statusCode, latency, ip, @@ -735,65 +762,74 @@ export function LogsPage() { >