mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-19 03:00:49 +08:00
feat(logs): add request id parsing and refactor row layout
This commit is contained in:
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,65 +762,74 @@ 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={[
|
styles.badge,
|
||||||
styles.badge,
|
line.level === 'info' ? styles.levelInfo : '',
|
||||||
line.level === 'info' ? styles.levelInfo : '',
|
line.level === 'warn' ? styles.levelWarn : '',
|
||||||
line.level === 'warn' ? styles.levelWarn : '',
|
line.level === 'error' || line.level === 'fatal'
|
||||||
line.level === 'error' || line.level === 'fatal'
|
? styles.levelError
|
||||||
? styles.levelError
|
: '',
|
||||||
: '',
|
line.level === 'debug' ? styles.levelDebug : '',
|
||||||
line.level === 'debug' ? styles.levelDebug : '',
|
line.level === 'trace' ? styles.levelTrace : '',
|
||||||
line.level === 'trace' ? styles.levelTrace : '',
|
]
|
||||||
]
|
.filter(Boolean)
|
||||||
.filter(Boolean)
|
.join(' ')}
|
||||||
.join(' ')}
|
>
|
||||||
>
|
{line.level.toUpperCase()}
|
||||||
{line.level.toUpperCase()}
|
</span>
|
||||||
</span>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
{line.source && (
|
{line.source && (
|
||||||
<span className={styles.source} title={line.source}>
|
<span className={styles.source} title={line.source}>
|
||||||
{line.source}
|
{line.source}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{typeof line.statusCode === 'number' && (
|
{typeof line.statusCode === 'number' && (
|
||||||
<span
|
<span
|
||||||
className={[
|
className={[
|
||||||
styles.badge,
|
styles.badge,
|
||||||
styles.statusBadge,
|
styles.statusBadge,
|
||||||
line.statusCode >= 200 && line.statusCode < 300
|
line.statusCode >= 200 && line.statusCode < 300
|
||||||
? styles.statusSuccess
|
? styles.statusSuccess
|
||||||
: line.statusCode >= 300 && line.statusCode < 400
|
: line.statusCode >= 300 && line.statusCode < 400
|
||||||
? styles.statusInfo
|
? styles.statusInfo
|
||||||
: line.statusCode >= 400 && line.statusCode < 500
|
: line.statusCode >= 400 && line.statusCode < 500
|
||||||
? styles.statusWarn
|
? styles.statusWarn
|
||||||
: styles.statusError,
|
: styles.statusError,
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
>
|
>
|
||||||
{line.statusCode}
|
{line.statusCode}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{line.latency && <span className={styles.pill}>{line.latency}</span>}
|
{line.latency && <span className={styles.pill}>{line.latency}</span>}
|
||||||
{line.ip && <span className={styles.pill}>{line.ip}</span>}
|
{line.ip && <span className={styles.pill}>{line.ip}</span>}
|
||||||
|
|
||||||
{line.method && (
|
{line.method && (
|
||||||
<span className={[styles.badge, styles.methodBadge].join(' ')}>
|
<span className={[styles.badge, styles.methodBadge].join(' ')}>
|
||||||
{line.method}
|
{line.method}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{line.path && (
|
|
||||||
<span className={styles.path} title={line.path}>
|
{line.requestId && (
|
||||||
{line.path}
|
<span
|
||||||
</span>
|
className={[styles.badge, styles.requestIdBadge].join(' ')}
|
||||||
)}
|
title={line.requestId}
|
||||||
</div>
|
>
|
||||||
{line.message && <div className={styles.message}>{line.message}</div>}
|
{line.requestId}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{line.path && (
|
||||||
|
<span className={styles.path} title={line.path}>
|
||||||
|
{line.path}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{line.message && <span className={styles.message}>{line.message}</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user