fix(frontend): 补齐 analysis 构建依赖与共享组件搬迁
补 commit hooks/图表组件、路由与 i18n domains、logs metrics API; StatCard/TimeRangePicker/ingress-access-log 抽到共享路径,干净 checkout 可编过。
This commit is contained in:
1 parent
f993d5b420
commit
11d0fff764
22 files changed
+552
-313
No files matched your search
@@ -0,0 +1,100 @@
|
||||
/** ingress-nginx 默认 access log;采集器可能在前面附加 CRI 时间/stdout 包装。 */
|
||||
export interface IngressAccessLog {
|
||||
clientIp: string
|
||||
method: string
|
||||
path: string
|
||||
status: string
|
||||
bytesSent: string
|
||||
referer: string
|
||||
domain: string
|
||||
domainSource: 'host' | 'referer' | ''
|
||||
userAgent: string
|
||||
requestLength: string
|
||||
requestTime: string
|
||||
upstreamName: string
|
||||
upstreamAddress: string
|
||||
upstreamStatus: string
|
||||
upstreamResponseTime: string
|
||||
}
|
||||
|
||||
export function stripLogEnvelope(message: string): string {
|
||||
return message.replace(/^\d{4}-\d\d-\d\dT\S+\s+(?:stdout|stderr)\s+[FP]\s+/, '')
|
||||
}
|
||||
|
||||
const accessPattern = /^(\S+)\s+\S+\s+\S+\s+\[[^\]]+\]\s+"([A-Z]+)\s+(\S+)\s+[^"]+"\s+(\d{3})\s+(\S+)\s+"([^"]*)"\s+"([^"]*)"(?:\s+(.*))?$/
|
||||
const upstreamPattern = /^(\S+)\s+(\S+)\s+\[([^\]]*)\]\s+\[[^\]]*\]\s+(\S+)\s+\S+\s+(\S+)\s+(\S+)(?:\s+\S+)?$/
|
||||
|
||||
const asText = (value: unknown) => value === undefined || value === null ? '' : String(value)
|
||||
|
||||
function domainFromHost(value: string): string {
|
||||
const host = value.trim().toLowerCase()
|
||||
if (!host || host === '-') return ''
|
||||
try {
|
||||
return new URL(/^https?:\/\//.test(host) ? host : `http://${host}`).hostname
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function domainFromReferer(value: string): string {
|
||||
try {
|
||||
return new URL(value).hostname.toLowerCase()
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function accessDomain(host: string, path: string): Pick<IngressAccessLog, 'domain' | 'domainSource'> {
|
||||
const requestHost = domainFromHost(host) || (/^https?:\/\//i.test(path) ? domainFromReferer(path) : '')
|
||||
if (requestHost) return { domain: requestHost, domainSource: 'host' }
|
||||
return { domain: '', domainSource: '' }
|
||||
}
|
||||
|
||||
export function parseIngressAccessLog(message: string): IngressAccessLog | null {
|
||||
const content = stripLogEnvelope(message).trim()
|
||||
if (content.startsWith('{')) {
|
||||
try {
|
||||
const value: unknown = JSON.parse(content)
|
||||
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
const row = value as Record<string, unknown>
|
||||
const method = asText(row.method ?? row.request_method)
|
||||
const path = asText(row.path ?? row.uri ?? row.request_uri)
|
||||
const status = asText(row.status ?? row.status_code)
|
||||
const referer = asText(row.http_referer ?? row.referer)
|
||||
if (method && path && /^\d{3}$/.test(status)) return {
|
||||
clientIp: asText(row.remote_addr ?? row.client_ip), method, path, status,
|
||||
bytesSent: asText(row.bytes_sent ?? row.body_bytes_sent),
|
||||
referer,
|
||||
...accessDomain(asText(row.http_host || row.host || row.server_name), path),
|
||||
userAgent: asText(row.http_user_agent ?? row.user_agent),
|
||||
requestLength: asText(row.request_length), requestTime: asText(row.request_time),
|
||||
upstreamName: asText(row.proxy_upstream_name ?? row.upstream_name),
|
||||
upstreamAddress: asText(row.upstream_addr),
|
||||
upstreamStatus: asText(row.upstream_status),
|
||||
upstreamResponseTime: asText(row.upstream_response_time),
|
||||
}
|
||||
}
|
||||
} catch { /* Fall through to plain access log parsing. */ }
|
||||
}
|
||||
|
||||
const match = content.match(accessPattern)
|
||||
if (!match) return null
|
||||
const upstream = (match[8] ?? '').match(upstreamPattern)
|
||||
return {
|
||||
clientIp: match[1], method: match[2], path: match[3], status: match[4],
|
||||
bytesSent: match[5], referer: match[6], userAgent: match[7],
|
||||
...accessDomain('', match[3]),
|
||||
requestLength: upstream?.[1] ?? '', requestTime: upstream?.[2] ?? '',
|
||||
upstreamName: upstream?.[3] ?? '', upstreamAddress: upstream?.[4] ?? '',
|
||||
upstreamResponseTime: upstream?.[5] ?? '', upstreamStatus: upstream?.[6] ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
export function httpStatusClass(status: string): '2xx' | '3xx' | '4xx' | '5xx' | 'other' {
|
||||
const group = status[0]
|
||||
return group === '2' || group === '3' || group === '4' || group === '5' ? `${group}xx` : 'other'
|
||||
}
|
||||
|
||||
export function pathWithoutQuery(path: string): string {
|
||||
return path.split('?')[0] || '/'
|
||||
}
|
||||
Reference in new issue
Block a user