feat(logs): add request id parsing and refactor row layout

This commit is contained in:
hkfires
2025-12-24 10:46:33 +08:00
parent 561e06503c
commit 02a01e5afc
2 changed files with 103 additions and 66 deletions

View File

@@ -187,15 +187,8 @@
.rowMain { .rowMain {
display: flex; display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.rowMeta {
display: flex;
align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
align-items: baseline;
gap: 6px; gap: 6px;
min-width: 0; 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 { .statusBadge {
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
@@ -308,6 +310,5 @@
.message { .message {
color: var(--text-secondary); color: var(--text-secondary);
white-space: pre-wrap; white-space: nowrap;
word-break: break-word;
} }

View File

@@ -44,11 +44,12 @@ type HttpMethod = (typeof HTTP_METHODS)[number];
const HTTP_METHOD_REGEX = new RegExp(`\\b(${HTTP_METHODS.join('|')})\\b`); 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_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_SOURCE_REGEX = /^\[([^\]]+)\]/;
const LOG_LATENCY_REGEX = /\b(\d+(?:\.\d+)?)(?:\s*)(µs|us|ms|s)\b/i; 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_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_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 LOG_TIME_OF_DAY_REGEX = /^\d{1,2}:\d{2}:\d{2}(?:\.\d{1,3})?$/;
const GIN_TIMESTAMP_SEGMENT_REGEX = 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*$/; /^\[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; timestamp?: string;
level?: LogLevel; level?: LogLevel;
source?: string; source?: string;
requestId?: string;
statusCode?: number; statusCode?: number;
latency?: string; latency?: string;
ip?: string; ip?: string;
@@ -173,6 +175,7 @@ const parseLogLine = (raw: string): ParsedLogLine => {
let ip: string | undefined; let ip: string | undefined;
let method: HttpMethod | undefined; let method: HttpMethod | undefined;
let path: string | undefined; let path: string | undefined;
let requestId: string | undefined;
let message = remaining; let message = remaining;
if (remaining.includes('|')) { 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 // status code
const statusIndex = segments.findIndex((segment) => /^\d{3}\b/.test(segment)); const statusIndex = segments.findIndex((segment) => /^\d{3}\b/.test(segment));
if (statusIndex >= 0) { if (statusIndex >= 0) {
@@ -244,6 +260,16 @@ const parseLogLine = (raw: string): ParsedLogLine => {
consumed.add(methodIndex); 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(' | '); message = segments.filter((_, index) => !consumed.has(index)).join(' | ');
} else { } else {
statusCode = detectHttpStatusCode(remaining); statusCode = detectHttpStatusCode(remaining);
@@ -276,6 +302,7 @@ const parseLogLine = (raw: string): ParsedLogLine => {
timestamp, timestamp,
level, level,
source, source,
requestId,
statusCode, statusCode,
latency, latency,
ip, ip,
@@ -735,7 +762,6 @@ export function LogsPage() {
> >
<div className={styles.timestamp}>{line.timestamp || ''}</div> <div className={styles.timestamp}>{line.timestamp || ''}</div>
<div className={styles.rowMain}> <div className={styles.rowMain}>
<div className={styles.rowMeta}>
{line.level && ( {line.level && (
<span <span
className={[ className={[
@@ -787,13 +813,23 @@ export function LogsPage() {
{line.method} {line.method}
</span> </span>
)} )}
{line.requestId && (
<span
className={[styles.badge, styles.requestIdBadge].join(' ')}
title={line.requestId}
>
{line.requestId}
</span>
)}
{line.path && ( {line.path && (
<span className={styles.path} title={line.path}> <span className={styles.path} title={line.path}>
{line.path} {line.path}
</span> </span>
)} )}
</div>
{line.message && <div className={styles.message}>{line.message}</div>} {line.message && <span className={styles.message}>{line.message}</span>}
</div> </div>
</div> </div>
); );