当前位置: 首页 > java >正文

日志搜索系统前端页面(暂无后端功能)

日志搜索系统 · 快速定位,精准排查

欢迎使用 日志搜索系统 —— 专为开发者与运维人员打造的高效日志查询平台。本系统通过统一采集、结构化解析与高性能检索,帮助您在海量日志数据中秒级定位问题根源,提升系统可观测性与故障响应效率。

🔍 核心功能
  • 全文检索:支持关键词、时间范围、服务名、日志级别多维度组合查询,快速定位目标日志。
  • 高亮展示:匹配内容自动高亮,便于快速识别关键信息。
  • 上下文查看:点击某条日志,可查看其前后 N 条日志,还原完整执行流程。
  • 多环境支持:覆盖开发、测试、预发、生产等多环境日志,一键切换。
  • TraceID 关联:支持与分布式追踪系统联动,通过 traceId 跨服务串联调用链路。

说明:选择是纯前端的伪造的测试数据,后面需要后端实现日志数据采集分组,使用全文检索ES进行快速查询,前端需要实现调用后端接口返回日志数据。通过 traceId 跨服务串联调用链路这个也需要后端结合logback和MDC实现跨线程、跨进程(跨服务)还原整个方法调用链路。由于时间有限现在就是把页面画出来而已,你可以直接复制代码,随便起个名字如   logsearch.html,然后使用浏览器直接打开,就能体验交互效果。有兴趣可以试试。

目录结构:logsreach-web: 日志搜索系统前端页面

logsearch.html:

<!DOCTYPE html>
<html lang="zh"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>日志浏览器 - 测试版</title><!-- Edge 浏览器没有问题,但是谷歌浏览不行,所以使用Edge分别访问下面3个链接,将内容拷贝下来,js是压缩的,看不懂的-->
<!--        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">-->
<!--        <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>-->
<!--        <script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/zh.js"></script>--><!-- 不要联网下载,有时谷歌浏览大概率访问不了,使用本地的--><link rel="stylesheet" href="./lib/flatpickr/flatpickr.min.css"><script src="./lib/flatpickr/flatpickr.min.js"></script><script src="./lib/zh/zh.js"></script></head><body><div class="form"><label for="group">分组:</label><div class="select-group"><select id="group"><option value="logs-app-*">应用服务</option><option value="logs-auth-*" selected>认证服务</option><option value="logs-*">全部日志</option></select></div><input type="text" id="keyword" placeholder="输入关键字搜索,如:ERROR, NullPointerException" class="input-keyword" /><label for="timeRange">时间:</label><div class="select-time"><!-- 修改1:只保留指定选项 --><select id="timeRange"><option value="5m">最近 5 分钟</option><option value="10m">最近 10 分钟</option><option value="1h">最近 1 小时</option><option value="2h">最近 2 小时</option><option value="12h">最近 12 小时</option><option value="24h">最近 24 小时</option><option value="custom">自定义时间</option></select></div><!-- 自定义时间输入框(默认隐藏) --><div id="customTimeContainer" style="display:none; margin-left:10px; display:flex; align-items:center; gap:8px;"><input type="text" id="startTime" placeholder="开始时间" class="time-input" /><span style="white-space:nowrap;">至</span><input type="text" id="endTime" placeholder="结束时间" class="time-input" /></div><button type="button" onclick="searchLogs()" class="btn-query">🔍 查询</button></div><div id="logResults" class="log-list-container"><div style="padding:20px;color:#090;">【测试模式】:输入关键字开始搜索</div></div><script>// === 生成模拟日志 ===function generateMockLogs() {const logs = [];const now = new Date();const start = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 24小时前let current = new Date(start);// 定义关键事件(时间偏移 + 多行日志)const events = [{timeOffset: -3 * 60, // 3分钟前logs: ["[5m] ERROR User login failed due to null authentication service","java.lang.NullPointerException: Cannot invoke \"AuthService.authenticate(User)\" because 'authService' is null","  at com.example.controller.LoginController.handleLogin(LoginController.java:67)","  at com.example.servlet.LoginServlet.doPost(LoginServlet.java:45)","  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)","  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)","  at com.example.filter.SecurityFilter.doFilter(SecurityFilter.java:39)","  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)"]},{timeOffset: -8 * 60, // 8分钟前logs: ["[10m] WARN  Session timeout for user ID: 7721, last active: 2025-08-23 16:52:10","Session expired after 30 minutes of inactivity. Triggering cleanup task."]},{timeOffset: -30 * 60, // 30分钟前logs: ["[1h] INFO  Starting nightly batch job: UserStatusSyncJob","Job parameters: batchSize=500, retryCount=3, priority=HIGH","Connecting to legacy system via SOAP endpoint: https://legacy-auth.internal/v1/sync"]},{timeOffset: -90 * 60, // 1.5小时前logs: ["[2h] DEBUG Cache cleared for region: user-profile-cache, nodes=3, entriesRemoved=1247","Triggered by config reload from ZooKeeper. New version: v2.3.1-rc.2"]},{timeOffset: -6 * 60 * 60, // 6小时前logs: ["[12h] ERROR Failed to connect to database: jdbc:postgresql://db-prod-01:5432/auth","com.zaxxer.hikari.pool.HikariPool$PoolInitializationException: Failed to initialize pool: Connection timed out","  at com.zaxxer.hikari.pool.HikariPool.throwPoolInitializationException(HikariPool.java:597)","  at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:583)","  at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:100)","  at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:112)"]},{timeOffset: -18 * 60 * 60, // 18小时前logs: ["[24h] CRITICAL System overload detected! CPU=99%, memory=95%, disk=/ 89% full","Alert sent to on-call engineer (p0-alerts@company.com).","Auto-scaling group 'auth-cluster' failed to launch new instance: QuotaExceeded","Stack trace of main thread blocking the event loop:","  at java.base@17/java.lang.Thread.sleep(Native Method)","  at com.example.monitor.SystemMonitor.run(SystemMonitor.java:124)","  at java.base@17/java.lang.Thread.run(Thread.java:833)"]}];// 计算事件发生时间(绝对时间)const eventMap = {};events.forEach(event => {const eventTime = new Date(now.getTime() + event.timeOffset * 1000);// 修复:使用本地时间格式 YYYY-MM-DD HH:mm:ssconst key = eventTime.getFullYear() + '-' +String(eventTime.getMonth() + 1).padStart(2, '0') + '-' +String(eventTime.getDate()).padStart(2, '0') + ' ' +String(eventTime.getHours()).padStart(2, '0') + ':' +String(eventTime.getMinutes()).padStart(2, '0') + ':' +String(eventTime.getSeconds()).padStart(2, '0');eventMap[key] = event.logs;});// 按秒生成日志while (current <= now) {// 修复:使用本地时间格式const dateStr = current.getFullYear() + '-' +String(current.getMonth() + 1).padStart(2, '0') + '-' +String(current.getDate()).padStart(2, '0') + ' ' +String(current.getHours()).padStart(2, '0') + ':' +String(current.getMinutes()).padStart(2, '0') + ':' +String(current.getSeconds()).padStart(2, '0');// 检查是否有事件发生if (eventMap[dateStr]) {// 插入多行事件eventMap[dateStr].forEach(line => {logs.push(`${dateStr} ${line}`);});} else {// 普通日志:随机生成请求const level = Math.random() > 0.85 ? 'DEBUG' : 'INFO';const userId = Math.floor(Math.random() * 1000);const duration = Math.floor(2 + Math.random() * 50);const endpoint = ['/api/login', '/api/profile', '/api/settings', '/api/logout'][Math.floor(Math.random() * 4)];logs.push(`${dateStr} ${level} HTTP ${level === 'DEBUG' ? 'GET' : 'POST'} ${endpoint}/${userId} - 200 ${duration}ms`);}// 时间前进1秒current = new Date(current.getTime() + 1000);}return logs;}const ALL_LOGS = generateMockLogs();// === 解析日志时间 ===function parseLogTime(logLine) {const match = logLine.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/);if (!match) return null;return new Date(match[0]);}// 获取时间范围function getStartTime() {const now = new Date();const range = document.getElementById('timeRange').value;if (range === 'custom') {const startDates = fpStart.selectedDates;return startDates && startDates.length > 0 ? startDates[0] : now;}// 修改1:只保留你指定的时间范围const minutes = {'5m': 5,'10m': 10,'1h': 60,'2h': 120,'12h': 12 * 60,'24h': 24 * 60}[range];return new Date(now.getTime() - minutes * 60 * 1000);}function getEndTime() {const range = document.getElementById('timeRange').value;if (range === 'custom') {const endDates = fpEnd.selectedDates;return endDates && endDates.length > 0 ? endDates[0] : new Date();}return new Date();}// === 工具函数 ===function escapeHtml(text) {const div = document.createElement('div');div.textContent = text;return div.innerHTML;}function highlightText(text, keyword) {if (!keyword) return escapeHtml(text);const escapedText = escapeHtml(text);const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');const regex = new RegExp(`(${escapedKeyword})`, 'gi');return escapedText.replace(regex, '<span class="highlight">$1</span>');}// === 搜索主函数 ===function searchLogs() {const keyword = document.getElementById('keyword').value.trim();const container = document.getElementById('logResults');container.innerHTML = '';if (!keyword) {container.innerHTML = '<div style="padding:20px;color:#666;">请输入关键字</div>';return;}const startTime = getStartTime();const endTime = getEndTime();if (!startTime || !endTime || startTime > endTime) {container.innerHTML = '<div style="padding:20px;color:#c00;">请检查时间范围是否正确</div>';return;}const matches = [];ALL_LOGS.forEach((line, index) => {const logTime = parseLogTime(line);if (!logTime) return;if (line.toLowerCase().includes(keyword.toLowerCase()) &&logTime >= startTime &&logTime <= endTime) {matches.push({ line, index });}});if (matches.length === 0) {container.innerHTML = '<div style="padding:20px;color:#999;">未找到匹配日志</div>';return;}matches.forEach(({ line, index }) => {const item = document.createElement('div');item.className = 'log-item';item.innerHTML = `<div class="log-item-summary">${highlightText(line, keyword)}<span class="toggle"><span class="arrow right">❯</span> 展开上下文</span></div><div class="log-item-context" style="display:none;"></div>`;item.onclick = function(e) {// 只有点击 .toggle 或其内部(比如箭头)才响应const toggleBtn = item.querySelector('.toggle');if (!toggleBtn.contains(e.target)) return;const contextDiv = item.querySelector('.log-item-context');const isHidden = contextDiv.style.display === 'none';if (isHidden) {// 展开:加载上下文const start = Math.max(0, index - 100);const end = Math.min(ALL_LOGS.length, index + 100);const contextLines = ALL_LOGS.slice(start, end).map((l, i) => {const lineNum = start + i;const isTarget = (lineNum === index);const prefix = isTarget ? '❱ ' : '  ';const lineNumber = i + 1; // 上下文内从1开始编号// 行号和内容拼成一个字符串,不要换行return ('<span style="display:inline-block;width:30px;text-align:right;color:#999;font-family:monospace;margin-right:8px;user-select:none;">' +lineNumber +'</span>' +prefix +highlightText(l, keyword));});contextDiv.innerHTML = contextLines.join('<br>');contextDiv.style.display = 'block';// 更新 toggleBtn 内容:箭头向下 + “收起上下文”toggleBtn.innerHTML = '<span class="arrow down">❯</span> 收起上下文';} else {// 收起:隐藏上下文contextDiv.style.display = 'none';// 箭头向右 + “展开上下文”toggleBtn.innerHTML = '<span class="arrow right">❯</span> 展开上下文';}// 加这一行!点击后立刻失去焦点toggleBtn.blur();};container.appendChild(item);});}// 回车触发搜索document.getElementById('keyword').addEventListener('keyup', e => {if (e.key === 'Enter') searchLogs();});// === 初始化 flatpickr 和事件 ===let fpStart, fpEnd;document.addEventListener('DOMContentLoaded', () => {const timeRange = document.getElementById('timeRange');const customContainer = document.getElementById('customTimeContainer');const now = new Date();// 修改2:minuteIncrement 设置为 1,实现“按1分钟加减”fpStart = flatpickr("#startTime", {enableTime: true,dateFormat: "Y-m-d H:i:S",time_24hr: true,defaultDate: now,locale: 'zh',minuteIncrement: 1,  // ← 关键:分钟调节步长为1onOpen: function() {setTimeout(() => {const calendar = this.calendarContainer;const calendarWidth = calendar.offsetWidth;calendar.style.left = `calc(50% + ${calendarWidth}px)`;calendar.style.transform = 'translateX(-50%)';calendar.style.right = 'auto';}, 10);}});fpEnd = flatpickr("#endTime", {enableTime: true,dateFormat: "Y-m-d H:i:S",time_24hr: true,defaultDate: now,locale: 'zh',minuteIncrement: 1,onOpen: function() {setTimeout(() => {const calendar = this.calendarContainer;const calendarWidth = calendar.offsetWidth;calendar.style.left = `calc(50% + ${calendarWidth}px)`;calendar.style.transform = 'translateX(-50%)';calendar.style.right = 'auto';}, 10);}});function updateCustomTimeVisibility() {if (timeRange.value === 'custom') {customContainer.style.display = 'flex';} else {customContainer.style.display = 'none';}}timeRange.addEventListener('change', updateCustomTimeVisibility);updateCustomTimeVisibility();});window.onload = () => {document.getElementById('logResults').innerHTML ='<div style="padding:20px;color:#090;"> 【测试模式】:输入关键字(如 ERROR)开始搜索,点击结果查看上下文</div>';};</script><style>body {font-family: "Courier New", monospace;padding: 20px;background: #f0f0f0; /* 灰白色背景 */color: #333;max-width: 1200px;margin: 0 auto;}.form {background: #ffffff;padding: 12px;border-radius: 8px;box-shadow: 0 2px 10px rgba(0,0,0,0.1);margin-bottom: 20px;display: flex;align-items: center;gap: 8px;flex-wrap: wrap;}.form label {font-size: 14px;font-weight: bold;white-space: nowrap;color: #333;}.select-group, .select-time {width: 130px;}.input-keyword {flex: 1;min-width: 200px;}.btn-query {width: 90px;font-weight: bold;}.form select,.form input[type="text"],button {height: 40px;padding: 0 10px;border: 1px solid #ccc;border-radius: 4px;font-size: 14px;box-sizing: border-box;}.form select { width: 100%; }.form input[type="text"] { width: 100%; }button {background: #0066cc;color: white;border: none;cursor: pointer;}button:hover { background: #0055aa; }/* === 时间输入框 === */#startTime, #endTime {width: 200px;min-width: 200px;padding: 0 10px;text-align: center;font-family: "Courier New", monospace;box-sizing: border-box;}/* === 日历位置:居中 + 右移一个身位 === */.flatpickr-calendar {left: calc(50% + 250px) !important;right: auto !important;}/* === 日志列表容器:更高,减少内部滚动 === */.log-list-container {background: #ffffff; /* 白色背景 */border-radius: 8px;box-shadow: 0 2px 10px rgba(0,0,0,0.1);padding: 10px;font-family: "Courier New", monospace;font-size: 13px;line-height: 1.6;height: calc(100vh - 250px); /* 填充整个视口高度减去顶部和底部间距 */overflow-y: auto;            /* 容器可滚动 */width: 100%;                 /* 确保宽度填满父级容器 */box-sizing: border-box;}.log-item-summary {cursor: pointer;padding: 6px;border-bottom: 1px solid #eee;}.log-item-summary:hover {background: #f0f0f0;}.toggle {float: right;color: #0066cc;font-size: 12px;cursor: pointer;-webkit-user-select: none;-moz-user-select: none;user-select: none;outline: none;}/* === 上下文样式:更明显! === */.log-item-context {background: #f0f0f0;         /* 更浅的灰色 */border: 1px solid #ccc;      /* 加边框 */border-radius: 4px;          /* 圆角 */padding: 12px;margin: 0;                   /* 移除上下外边距 */font-size: 12px;line-height: 1.5;overflow: auto;max-height: none;           /* 移除最大高度限制 */white-space: pre-wrap;       /* 保持换行 */word-wrap: break-word;box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); /* 内阴影,更有层次 */width: 100%;                 /* 确保宽度100% */box-sizing: border-box;      /* 确保宽度计算包含padding和border */}.highlight {background: #ffdd00;         /* 更亮的黄色 */font-weight: bold;padding: 0 2px;border-radius: 2px;}.arrow {display: inline-block;transition: transform 0.3s ease;transform-origin: center; /* 可选:让旋转更自然 */}.arrow.right {transform: rotate(0deg); /* 向右 */}.arrow.down {transform: rotate(90deg); /* 向下 */}</style></body>
</html>

http://www.xdnf.cn/news/18564.html

相关文章:

  • webrtc弱网-SendSideBandwidthEstimation类源码分析与算法原理
  • 手机横屏适配方案
  • 20250823给荣品RD-RK3588开发板刷Rockchip原厂的Buildroot【linux-5.10】时调通AP6275P的WIFI【源码部分】
  • ArkTS 语言全方位解析:鸿蒙生态开发新选择
  • 【AI基础:神经网络】17、神经网络基石:从MP神经元到感知器全解析 - 原理、代码、异或困境与突破
  • 线程间Bug检测工具Canary
  • uniapp 页面跳转及字符串转义
  • Redis学习笔记 ----- 缓存
  • rust语言 (1.88) egui (0.32.1) 学习笔记(逐行注释)(八)按键事件
  • 大语言模型应用开发——利用OpenAI函数与LangChain结合从文本构建知识图谱搭建RAG应用全流程
  • 【KO】前端面试七
  • 20250823给荣品RD-RK3588开发板刷Rockchip原厂的Android14【EVB7的V10】时调通AP6275P的WIFI
  • react相关知识
  • GitLab CI:Auto DevOps 全解析,告别繁琐配置,拥抱自动化未来
  • 运行npm run命令报错“error:0308010C:digital envelope routines::unsupported”
  • 二叉树的经典算法与应用
  • 【网安干货】--操作系统基础(上)
  • USRP采集的WiFi信号绘制星座图为方形
  • 新手向:异步编程入门asyncio最佳实践
  • K8s 实战:Pod 版本更新回滚 + 生命周期管控
  • 嵌入式学习日记(33)TCP
  • 【UnityAS】Unity Android Studio 联合开发快速入门:环境配置、AAR 集成与双向调用教程
  • CMake link_directories()详细介绍与使用指南
  • STM32F1 GPIO介绍及应用
  • C/C++三方库移植到HarmonyOS平台详细教程(补充版so库和头文件形式)
  • 凌霄飞控开发日志兼新手教程——基础篇:认识基本的文件内容和相关函数作用(25电赛备赛版)
  • 【序列晋升】12 Spring Boot 约定优于配置
  • Spring发布订阅模式详解
  • Python 调用 sora_image模型 API 实现图片生成与垫图
  • 【论文】Zotero文献管理