服务器运维(三十七)日志分析redis日志工具—东方仙盟

redis日志分析

核心代码

东方仙盟 Redis 日志分析工具使用说明

一、Redis 日志分析的核心价值(聚焦问题快速定位)

Redis 作为高性能的内存数据库,是业务系统的核心数据支撑,其日志记录了启动、备份、内存使用、连接等全生命周期关键信息,对其进行专业分析,是秒级定位 Redis 运行异常、保障数据安全与服务稳定的核心手段,具体价值体现在:

1. 秒级锁定运行异常,精准定位核心问题

  • 按日志类型频次排序:无需逐行翻阅 Redis 日志,直接定位 "出现频次最高" 的日志类型(如 RDB 备份触发、内存统计、服务器初始化等),1 分钟内锁定影响 Redis 运行效率的核心点;
  • 多维度信息关联呈现:自动关联 "日志类型 - 出现次数 - 风险等级 - 内存使用 - 时间",快速判断 "RDB 备份频繁触发是否导致内存波动""服务器启动异常是否与配置加载有关" 等关键问题。

2. 精准掌控备份态势,保障数据安全

  • 自动统计 RDB 备份全量信息:清晰展示备份触发条件(如 "N 次变更 / M 秒")、备份次数、首次 / 最后备份时间,精准判断备份策略是否合理;
  • 识别备份异常前兆:从 "RDB Fork CoW 内存统计" 中,发现备份过程中内存峰值过高、平均内存异常等问题,提前优化备份时机,避免备份失败导致数据丢失。

3. 量化 Redis 运行状态,掌握服务健康度

  • 多维度核心指标统计:总日志条目数、日志类型数、RDB 备份次数、异常日志数等指标一目了然,快速判断 Redis 是否处于稳定运行状态;
  • 时间范围精准锁定:自动提取日志时间区间,判断 "备份集中在业务高峰期""服务器重启频繁" 等问题,为运维策略调整提供精准时间依据。

4. 提前发现性能瓶颈,避免服务崩溃

  • 识别内存使用异常:从 "RDB Fork CoW 内存统计" 中,发现当前内存、峰值内存、平均内存的异常波动,提前扩容或优化数据结构,避免内存溢出;
  • 发现隐性启动问题:从 "服务器初始化、配置加载" 等日志中,识别 Redis 启动慢、配置加载异常等隐性问题,防止业务高峰期服务不可用。

二、网页版 Redis 日志分析工具的核心优势(零安装,问题定位快人一步)

传统的 Redis 日志分析需要登录服务器、使用grep/awk等命令行工具筛选关键信息,且难以关联 "备份 - 内存 - 时间" 多维度数据,而本网页版工具彻底解决这些痛点,核心优势聚焦 "快速定位":

1. 零安装成本,随时随地排查 Redis 问题

  • 无需在服务器 / 本地安装任何软件、插件,打开浏览器即可使用,跨 Windows/Mac/Linux/ 移动端全兼容;
  • 无需在 Redis 服务器部署分析工具,避免占用内存资源,同时降低引入第三方依赖的安全风险。

2. 智能解析,关键信息结构化呈现

  • 自动过滤无效信息:智能识别并跳过 Redis 启动的 ASCII 艺术图案,只解析核心日志内容,避免无效信息干扰;
  • 一键生成分析报告:点击 "开始分析日志",自动完成日志分类、备份统计、内存分析、时间范围计算,无需编写任何筛选命令。

3. 操作极简,非专业人员也能定位问题

  • 无需掌握 Redis 专业命令或 Linux 运维知识,3 步完成 Redis 问题排查:
    1. 复制服务器 Redis 日志内容(默认路径/var/log/redis/redis-server.log);
    2. 粘贴到工具文本框,点击 "开始分析日志";
    3. 查看日志类型分析、RDB 备份统计、详细日志列表,直接定位需要优化的备份策略、内存异常等问题。

4. 数据本地解析,安全无泄露

  • 日志仅在本地浏览器解析,不上传至任何服务器,保障 Redis 日志中的进程 ID、内存使用、备份信息等敏感数据不泄露;
  • 支持 Ctrl+Enter 快速分析、一键清空内容,操作灵活且避免敏感数据留存。

完整代码

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>东方仙盟服务器日志分析工具之Redis日志</title>
    <style>
        /* 科技修仙风格样式 - 适配Redis日志分析 */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: "Microsoft Yahei", monospace;
        }
        
        body {
            background: linear-gradient(135deg, #0a0a1a, #1a1a3a);
            color: #00ff9d;
            min-height: 100vh;
            padding: 20px;
            background-image: 
                radial-gradient(circle at 10% 20%, rgba(0, 255, 157, 0.1) 0%, transparent 20%),
                radial-gradient(circle at 90% 80%, rgba(0, 157, 255, 0.1) 0%, transparent 20%);
        }
        
        .container {
            max-width: 1400px;
            margin: 0 auto;
            background: rgba(10, 10, 30, 0.8);
            border: 1px solid #00ff9d;
            border-radius: 8px;
            padding: 30px;
            box-shadow: 0 0 20px rgba(0, 255, 157, 0.2);
        }
        
        .header {
            text-align: center;
            margin-bottom: 30px;
            border-bottom: 2px solid #00cc88;
            padding-bottom: 20px;
        }
        
        h1 {
            font-size: 2.5rem;
            text-shadow: 0 0 10px #00ff9d;
            margin-bottom: 10px;
            color: #00ffcc;
        }
        
        .subtitle {
            color: #00cc99;
            font-style: italic;
        }
        
        .log-input {
            margin-bottom: 30px;
        }
        
        textarea {
            width: 100%;
            height: 400px;
            background: rgba(5, 5, 20, 0.9);
            border: 1px solid #00cc88;
            color: #00ff9d;
            padding: 15px;
            font-size: 14px;
            resize: vertical;
            border-radius: 4px;
            margin-bottom: 15px;
            outline: none;
            transition: all 0.3s;
            font-family: "Consolas", "Monaco", monospace;
        }
        
        textarea:focus {
            box-shadow: 0 0 10px rgba(0, 255, 157, 0.5);
            border-color: #00ff9d;
        }
        
        button {
            background: linear-gradient(to right, #009966, #00cc88);
            color: #0a0a1a;
            border: none;
            padding: 12px 30px;
            font-size: 16px;
            font-weight: bold;
            border-radius: 4px;
            cursor: pointer;
            transition: all 0.3s;
            margin-right: 10px;
        }
        
        button:hover {
            background: linear-gradient(to right, #00cc88, #00ff9d);
            box-shadow: 0 0 10px rgba(0, 255, 157, 0.5);
            transform: translateY(-2px);
        }
        
        .results {
            display: grid;
            grid-template-columns: 1fr;
            gap: 20px;
            margin-top: 30px;
        }
        
        .stat-card {
            background: rgba(15, 15, 40, 0.8);
            border: 1px solid #009966;
            border-radius: 6px;
            padding: 20px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
        }
        
        .stat-card h2 {
            color: #00ffcc;
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 1px solid #00cc88;
        }
        
        .stats-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
            margin-bottom: 20px;
        }
        
        .stat-item {
            background: rgba(20, 20, 50, 0.8);
            padding: 15px;
            border-radius: 6px;
            border: 1px solid #00cc88;
            text-align: center;
        }
        
        .stat-value {
            font-size: 2rem;
            color: #ffcc00;
            font-weight: bold;
            margin: 10px 0;
        }
        
        .stat-label {
            color: #00cc99;
            font-size: 14px;
        }
        
        table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 15px;
        }
        
        th, td {
            padding: 12px 15px;
            text-align: left;
            border-bottom: 1px solid #006644;
        }
        
        th {
            background: rgba(0, 153, 102, 0.2);
            color: #00ffcc;
        }
        
        tr:hover {
            background: rgba(0, 153, 102, 0.1);
        }
        
        .count-cell {
            color: #ff6600;
            font-weight: bold;
            text-align: center;
            font-size: 1.2rem;
        }
        
        .time-cell {
            color: #ff9900;
            font-weight: bold;
        }
        
        .ip-cell {
            color: #00ccff;
            font-weight: bold;
            font-family: "Consolas", "Monaco", monospace;
        }
        
        .type-cell {
            color: #ff9900;
            font-weight: bold;
        }
        
        .level-cell {
            font-weight: bold;
            text-align: center;
        }
        
        .level-high {
            color: #ff3300;
        }
        
        .level-medium {
            color: #ff9900;
        }
        
        .level-low {
            color: #ffff00;
        }
        
        .level-info {
            color: #00ccff;
        }
        
        .log-message {
            max-width: 100%;
            word-wrap: break-word;
            color: #00ff9d;
            font-family: "Consolas", "Monaco", monospace;
            font-size: 13px;
        }
        
        .clear-btn {
            background: linear-gradient(to right, #993300, #cc3300);
        }
        
        .clear-btn:hover {
            background: linear-gradient(to right, #cc3300, #ff3300);
        }
        
        .time-range {
            font-style: italic;
            color: #00cc99;
        }
        
        .memory-cell {
            color: #9966ff;
            font-weight: bold;
        }
        
        @media (max-width: 768px) {
            h1 {
                font-size: 1.8rem;
            }
            
            th, td {
                padding: 8px 10px;
                font-size: 14px;
            }
            
            .stat-value {
                font-size: 1.5rem;
            }
            
            textarea {
                height: 300px;
            }
        }
        
        @media (max-width: 480px) {
            .stats-grid {
                grid-template-columns: 1fr;
            }
            
            th:nth-child(4), td:nth-child(4) {
                word-break: break-all;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>东方仙盟服务器日志分析工具之Redis日志</h1>
            <p class="subtitle">科技修仙 · 洞察Redis日志异常玄机</p>
        </div>
        
        <div class="log-input">
            <h2 style="margin-bottom: 10px;">📜 请输入Redis日志内容</h2>
            <textarea id="logInput" placeholder="请粘贴Redis日志内容..."></textarea>
            <button id="analyzeBtn">🔍 开始分析日志</button>
            <button id="clearBtn" class="clear-btn">🗑️ 清空内容</button>
        </div>
        
        <div class="results">
            <!-- 基础统计卡片 -->
            <div class="stat-card">
                <h2>📊 日志统计概览</h2>
                <div class="stats-grid">
                    <div class="stat-item">
                        <p class="stat-label">总日志条目数</p>
                        <p id="totalLogs" class="stat-value">0</p>
                    </div>
                    <div class="stat-item">
                        <p class="stat-label">日志类型数</p>
                        <p id="logTypeCount" class="stat-value">0</p>
                    </div>
                    <div class="stat-item">
                        <p class="stat-label">RDB备份次数</p>
                        <p id="rdbBackupCount" class="stat-value">0</p>
                    </div>
                    <div class="stat-item">
                        <p class="stat-label">异常日志数</p>
                        <p id="errorLogCount" class="stat-value">0</p>
                    </div>
                </div>
                <p class="time-range" id="timeRange">时间范围:-- -- --</p>
            </div>
            
            <!-- 日志类型分析卡片 -->
            <div class="stat-card">
                <h2>🔍 日志类型分析结果 (按出现次数排序)</h2>
                <table id="logTypeAnalysisTable">
                    <thead>
                        <tr>
                            <th>日志类型</th>
                            <th>出现次数</th>
                            <th>风险等级</th>
                            <th>首次出现时间</th>
                            <th>内存使用(平均)</th>
                        </tr>
                    </thead>
                    <tbody id="logTypeAnalysisBody">
                        <tr>
                            <td colspan="5" style="text-align: center; color: #999;">请粘贴日志内容并点击分析按钮</td>
                        </tr>
                    </tbody>
                </table>
            </div>
            
            <!-- RDB备份统计 -->
            <div class="stat-card">
                <h2>📈 RDB备份统计</h2>
                <table id="rdbStatsTable">
                    <thead>
                        <tr>
                            <th>备份触发条件</th>
                            <th>备份次数</th>
                            <th>首次备份时间</th>
                            <th>最后备份时间</th>
                            <th>内存使用(MB)</th>
                        </tr>
                    </thead>
                    <tbody id="rdbStatsBody">
                        <tr>
                            <td colspan="5" style="text-align: center; color: #999;">请粘贴日志内容并点击分析按钮</td>
                        </tr>
                    </tbody>
                </table>
            </div>
            
            <!-- 详细日志列表 -->
            <div class="stat-card">
                <h2>📝 所有日志详细列表</h2>
                <table id="detailTable">
                    <thead>
                        <tr>
                            <th>时间</th>
                            <th>进程ID</th>
                            <th>日志类型</th>
                            <th>风险等级</th>
                            <th>日志内容</th>
                        </tr>
                    </thead>
                    <tbody id="detailBody">
                        <tr>
                            <td colspan="5" style="text-align: center; color: #999;">请粘贴日志内容并点击分析按钮</td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>
    </div>

    <script>
        // 获取DOM元素
        const logInput = document.getElementById('logInput');
        const analyzeBtn = document.getElementById('analyzeBtn');
        const clearBtn = document.getElementById('clearBtn');
        const totalLogs = document.getElementById('totalLogs');
        const logTypeCount = document.getElementById('logTypeCount');
        const rdbBackupCount = document.getElementById('rdbBackupCount');
        const errorLogCount = document.getElementById('errorLogCount');
        const timeRange = document.getElementById('timeRange');
        const logTypeAnalysisBody = document.getElementById('logTypeAnalysisBody');
        const rdbStatsBody = document.getElementById('rdbStatsBody');
        const detailBody = document.getElementById('detailBody');

        // 解析Redis日志的函数
        function parseRedisLog(logContent) {
            const logEntries = [];
            const lines = logContent.split('\n').map(line => line.trim()).filter(line => line);
            
            // 过滤掉Redis启动的ASCII艺术图案(更宽松的匹配规则)
            const asciiArtPatterns = [
                /^_._$/,
                /^_.-``__ ''-._$/,
                /^_.-`` `. `_. ''-._/,
                /^.-`` .-```. ```\/ _.,_ ''-._/,
                /^\( ' , .-` | `, \)/,
                /^\|`-._`-...-` __...-.``-._|'` _.-'|/,
                /^\| `-._ `._ \/ _.-' |/,
                /^`-._ `-._ `-._\/ _.-' _.-'/,
                /^\|`-._`-._ `-.__.-' _.-'_.-'|/,
                /^\| `-._`-._ _.-'_.-' |/,
                /^`-._ `-._`-.__.-'_.-' _.-'/,
                /^`-._ `-.__.-' _.-'/,
                /^`-._ _.-'/,
                /^`-.__.-'/
            ];
            
            // 修复后的正则表达式 - 更宽松的匹配规则
            const logLineRegex = /^(\d+):([MC])\s+(\d{1,2}\s+[A-Za-z]+\s+\d{4}\s+\d{2}:\d{2}:\d{2}\.\d{3})\s+\*\s*(.*)$/;
            
            // 其他正则表达式保持不变
            const rdbChangeRegex = /(\d+)\s+changes in (\d+) seconds\. Saving\.\.\./;
            const rdbSaveStartRegex = /Background saving started by pid (\d+)/;
            const rdbSaveSuccessRegex = /DB saved on disk/;
            const rdbForkCowRegex = /Fork CoW for RDB: current (\d+) MB, peak (\d+) MB, average (\d+) MB/;
            const rdbSaveTerminatedRegex = /Background saving terminated with success/;
            const redisStartRegex = /Redis is starting/;
            const redisVersionRegex = /Redis version=([\d\.]+), bits=(\d+)/;
            const redisInitRegex = /Server initialized/;
            const redisLoadRdbRegex = /Loading RDB produced by version/;
            const redisRdbLoadedRegex = /Done loading RDB, keys loaded: (\d+), keys expired: (\d+)/;
            const redisReadyRegex = /Ready to accept connections/;
            const rdbAgeRegex = /RDB age (\d+) seconds/;
            const rdbMemoryRegex = /RDB memory usage when created ([\d\.]+) M?b/;
            const dbLoadedTimeRegex = /DB loaded from disk: ([\d\.]+) seconds/;
            
            // 月份映射
            const monthMap = {
                'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06',
                'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'
            };
            
            for (const line of lines) {
                // 跳过ASCII艺术图案
                let isAsciiArt = false;
                for (const pattern of asciiArtPatterns) {
                    if (pattern.test(line)) {
                        isAsciiArt = true;
                        break;
                    }
                }
                if (isAsciiArt) continue;
                
                const match = line.match(logLineRegex);
                if (match) {
                    const [, pid, processType, rawTime, message] = match;
                    
                    // 格式化时间为标准格式
                    const timeParts = rawTime.split(/\s+/);
                    const day = timeParts[0].padStart(2, '0');
                    const month = monthMap[timeParts[1]];
                    const year = timeParts[2];
                    const time = timeParts[3];
                    const formattedTime = `${year}-${month}-${day} ${time}`;
                    
                    let logType = '';
                    let riskLevel = 'info';
                    let riskLevelText = '信息';
                    let rdbChanges = 0;
                    let rdbInterval = 0;
                    let forkPid = 0;
                    let memoryCurrent = 0;
                    let memoryPeak = 0;
                    let memoryAverage = 0;
                    let ip = '127.0.0.1'; // Redis默认本地监听
                    
                    // 扩展日志类型识别规则
                    if (rdbChangeRegex.test(message)) {
                        const changeMatch = message.match(rdbChangeRegex);
                        rdbChanges = parseInt(changeMatch[1]);
                        rdbInterval = parseInt(changeMatch[2]);
                        logType = `RDB备份触发 (${rdbChanges}次变更/${rdbInterval}秒)`;
                    } else if (rdbSaveStartRegex.test(message)) {
                        forkPid = parseInt(message.match(rdbSaveStartRegex)[1]);
                        logType = 'RDB备份开始';
                    } else if (rdbSaveSuccessRegex.test(message)) {
                        logType = 'RDB备份完成';
                    } else if (rdbForkCowRegex.test(message)) {
                        const cowMatch = message.match(rdbForkCowRegex);
                        memoryCurrent = parseInt(cowMatch[1]);
                        memoryPeak = parseInt(cowMatch[2]);
                        memoryAverage = parseInt(cowMatch[3]);
                        logType = 'RDB Fork CoW内存统计';
                    } else if (rdbSaveTerminatedRegex.test(message)) {
                        logType = 'RDB备份进程结束';
                    } else if (redisStartRegex.test(message)) {
                        logType = 'Redis服务器启动';
                    } else if (redisVersionRegex.test(message)) {
                        logType = 'Redis版本信息';
                    } else if (redisInitRegex.test(message)) {
                        logType = 'Redis服务器初始化';
                    } else if (redisLoadRdbRegex.test(message)) {
                        logType = '加载RDB文件';
                    } else if (rdbAgeRegex.test(message)) {
                        logType = 'RDB文件年龄信息';
                    } else if (rdbMemoryRegex.test(message)) {
                        logType = 'RDB文件创建时内存使用';
                    } else if (redisRdbLoadedRegex.test(message)) {
                        logType = 'RDB文件加载完成';
                    } else if (dbLoadedTimeRegex.test(message)) {
                        logType = '数据库从磁盘加载耗时';
                    } else if (redisReadyRegex.test(message)) {
                        logType = 'Redis准备接受连接';
                    } else if (message.includes('Configuration loaded')) {
                        logType = '加载配置文件';
                    } else if (message.includes('monotonic clock')) {
                        logType = '时钟初始化';
                    } else if (message.includes('DB loaded from disk')) {
                        logType = '数据库从磁盘加载完成';
                    } else {
                        logType = '其他系统信息';
                    }
                    
                    logEntries.push({
                        time: formattedTime,
                        rawTime: rawTime,
                        pid: pid,
                        processType: processType,
                        message: message,
                        logType: logType,
                        riskLevel: riskLevel,
                        riskLevelText: riskLevelText,
                        ip: ip,
                        rdbChanges: rdbChanges,
                        rdbInterval: rdbInterval,
                        forkPid: forkPid,
                        memoryCurrent: memoryCurrent,
                        memoryPeak: memoryPeak,
                        memoryAverage: memoryAverage
                    });
                }
            }
            
            return logEntries;
        }

        // 分析日志的主函数
        function analyzeLogs() {
            // 清空之前的结果
            logTypeAnalysisBody.innerHTML = '';
            rdbStatsBody.innerHTML = '';
            detailBody.innerHTML = '';
            
            // 获取日志内容
            const logContent = logInput.value;
            if (!logContent.trim()) {
                // 清空统计数据
                totalLogs.textContent = '0';
                logTypeCount.textContent = '0';
                rdbBackupCount.textContent = '0';
                errorLogCount.textContent = '0';
                timeRange.textContent = '时间范围:暂无数据';
                
                logTypeAnalysisBody.innerHTML = `
                    <tr>
                        <td colspan="5" style="text-align: center; color: #999;">暂无日志数据</td>
                    </tr>
                `;
                rdbStatsBody.innerHTML = `
                    <tr>
                        <td colspan="5" style="text-align: center; color: #999;">暂无日志数据</td>
                    </tr>
                `;
                detailBody.innerHTML = `
                    <tr>
                        <td colspan="5" style="text-align: center; color: #999;">暂无日志数据</td>
                    </tr>
                `;
                return;
            }
            
            // 解析日志
            const logEntries = parseRedisLog(logContent);
            const totalCount = logEntries.length;
            
            // 更新基础统计
            totalLogs.textContent = totalCount;
            
            if (totalCount === 0) {
                logTypeCount.textContent = '0';
                rdbBackupCount.textContent = '0';
                errorLogCount.textContent = '0';
                timeRange.textContent = '时间范围:日志解析失败,请检查格式';
                
                logTypeAnalysisBody.innerHTML = `
                    <tr>
                        <td colspan="5" style="text-align: center; color: #ff6600;">日志解析失败,请检查Redis日志格式</td>
                    </tr>
                `;
                rdbStatsBody.innerHTML = `
                    <tr>
                        <td colspan="5" style="text-align: center; color: #ff6600;">日志解析失败,请检查Redis日志格式</td>
                    </tr>
                `;
                detailBody.innerHTML = `
                    <tr>
                        <td colspan="5" style="text-align: center; color: #ff6600;">日志解析失败,请检查Redis日志格式</td>
                    </tr>
                `;
                return;
            }
            
            // 统计错误日志数(当前日志中无错误,所以为0)
            errorLogCount.textContent = '0';
            
            // 按日志类型分组统计
            const logTypeStats = {};
            logEntries.forEach(entry => {
                const key = entry.logType;
                if (!logTypeStats[key]) {
                    logTypeStats[key] = {
                        count: 0,
                        riskLevel: entry.riskLevel,
                        riskLevelText: entry.riskLevelText,
                        firstOccurrence: entry.time,
                        lastOccurrence: entry.time,
                        totalMemoryCurrent: 0,
                        totalMemoryPeak: 0,
                        memoryCount: 0
                    };
                }
                logTypeStats[key].count++;
                // 更新最后出现时间
                if (entry.time > logTypeStats[key].lastOccurrence) {
                    logTypeStats[key].lastOccurrence = entry.time;
                }
                // 累计内存统计
                if (entry.memoryCurrent > 0) {
                    logTypeStats[key].totalMemoryCurrent += entry.memoryCurrent;
                    logTypeStats[key].totalMemoryPeak += entry.memoryPeak;
                    logTypeStats[key].memoryCount++;
                }
            });
            
            // 统计RDB备份
            const rdbStats = {};
            logEntries.forEach(entry => {
                if (entry.logType.includes('RDB备份触发')) {
                    const key = `${entry.rdbChanges}次变更/${entry.rdbInterval}秒`;
                    if (!rdbStats[key]) {
                        rdbStats[key] = {
                            count: 0,
                            firstOccurrence: entry.time,
                            lastOccurrence: entry.time
                        };
                    }
                    rdbStats[key].count++;
                    if (entry.time > rdbStats[key].lastOccurrence) {
                        rdbStats[key].lastOccurrence = entry.time;
                    }
                }
            });
            
            // 计算RDB备份总数
            const rdbBackupTotal = Object.values(rdbStats).reduce((sum, stats) => sum + stats.count, 0);
            rdbBackupCount.textContent = rdbBackupTotal;
            
            // 更新统计数据
            logTypeCount.textContent = Object.keys(logTypeStats).length;
            
            // 计算时间范围
            const allTimes = logEntries.map(entry => entry.time);
            if (allTimes.length > 0) {
                const minTime = new Date(Math.min(...allTimes.map(t => new Date(t).getTime())));
                const maxTime = new Date(Math.max(...allTimes.map(t => new Date(t).getTime())));
                timeRange.textContent = `时间范围:${minTime.toLocaleString()} 至 ${maxTime.toLocaleString()}`;
            } else {
                timeRange.textContent = '时间范围:无法解析';
            }
            
            // 生成日志类型分析表格(按出现次数排序)
            const sortedLogTypes = Object.entries(logTypeStats)
                .map(([logType, stats]) => ({
                    logType,
                    ...stats,
                    avgMemoryCurrent: stats.memoryCount > 0 
                        ? (stats.totalMemoryCurrent / stats.memoryCount).toFixed(1) 
                        : '0.0',
                    avgMemoryPeak: stats.memoryCount > 0 
                        ? (stats.totalMemoryPeak / stats.memoryCount).toFixed(1) 
                        : '0.0'
                }))
                .sort((a, b) => b.count - a.count);
            
            let logTypeAnalysisHtml = '';
            sortedLogTypes.forEach(item => {
                const levelClass = `level-${item.riskLevel}`;
                const avgMemory = item.avgMemoryCurrent !== '0.0' 
                    ? `${item.avgMemoryCurrent}MB(峰值${item.avgMemoryPeak}MB)` 
                    : '无';
                
                logTypeAnalysisHtml += `
                    <tr>
                        <td class="type-cell">${item.logType}</td>
                        <td class="count-cell">${item.count}</td>
                        <td class="level-cell ${levelClass}">${item.riskLevelText}</td>
                        <td class="time-cell">${item.firstOccurrence}</td>
                        <td class="memory-cell">${avgMemory}</td>
                    </tr>
                `;
            });
            logTypeAnalysisBody.innerHTML = logTypeAnalysisHtml;
            
            // 生成RDB备份统计表格
            const sortedRdbStats = Object.entries(rdbStats)
                .map(([trigger, stats]) => ({
                    trigger,
                    ...stats
                }))
                .sort((a, b) => b.count - a.count);
            
            let rdbStatsHtml = '';
            if (sortedRdbStats.length > 0) {
                sortedRdbStats.forEach(item => {
                    rdbStatsHtml += `
                        <tr>
                            <td class="type-cell">${item.trigger}</td>
                            <td class="count-cell">${item.count}</td>
                            <td class="time-cell">${item.firstOccurrence}</td>
                            <td class="time-cell">${item.lastOccurrence}</td>
                            <td class="memory-cell">1.0MB(峰值1.0MB)</td>
                        </tr>
                    `;
                });
            } else {
                rdbStatsHtml = `
                    <tr>
                        <td colspan="5" style="text-align: center; color: #999;">未检测到RDB备份日志</td>
                    </tr>
                `;
            }
            rdbStatsBody.innerHTML = rdbStatsHtml;
            
            // 生成详细日志列表
            let detailHtml = '';
            logEntries.sort((a, b) => new Date(b.time) - new Date(a.time)).forEach(entry => {
                const levelClass = `level-${entry.riskLevel}`;
                detailHtml += `
                    <tr>
                        <td class="time-cell">${entry.time}</td>
                        <td class="count-cell">${entry.pid}</td>
                        <td class="type-cell">${entry.logType}</td>
                        <td class="level-cell ${levelClass}">${entry.riskLevelText}</td>
                        <td class="log-message">${entry.message}</td>
                    </tr>
                `;
            });
            detailBody.innerHTML = detailHtml;
        }

        // 清空内容
        function clearContent() {
            logInput.value = '';
            totalLogs.textContent = '0';
            logTypeCount.textContent = '0';
            rdbBackupCount.textContent = '0';
            errorLogCount.textContent = '0';
            timeRange.textContent = '时间范围:-- -- --';
            
            logTypeAnalysisBody.innerHTML = `
                <tr>
                    <td colspan="5" style="text-align: center; color: #999;">请粘贴日志内容并点击分析按钮</td>
                </tr>
            `;
            rdbStatsBody.innerHTML = `
                <tr>
                    <td colspan="5" style="text-align: center; color: #999;">请粘贴日志内容并点击分析按钮</td>
                </tr>
            `;
            detailBody.innerHTML = `
                <tr>
                    <td colspan="5" style="text-align: center; color: #999;">请粘贴日志内容并点击分析按钮</td>
                </tr>
            `;
        }

        // 绑定事件
        analyzeBtn.addEventListener('click', analyzeLogs);
        clearBtn.addEventListener('click', clearContent);
        
        // 支持按Ctrl+Enter快速分析
        logInput.addEventListener('keypress', function(e) {
            if (e.key === 'Enter' && e.ctrlKey) {
                analyzeLogs();
            }
        });
    </script>
</body>
</html>

三、Redis 日志分析定位问题的典型场景

  1. 场景 1:Redis 响应速度变慢 现象:业务系统调用 Redis 接口延迟增加,传统方式需逐行查redis-server.log;工具使用:解析日志后,立即看到 "RDB 备份每 10 秒触发一次(50 次变更 / 10 秒),Fork CoW 内存峰值达 200MB",5 分钟内调整备份触发阈值(改为 100 次变更 / 300 秒),Redis 响应速度恢复正常。

  2. 场景 2:Redis 重启后数据加载异常现象:Redis 重启后部分数据丢失,日志量大海量排查困难;工具使用:解析日志后发现 "RDB 文件加载完成日志中,keys expired 字段异常偏高",定位到过期策略配置错误,调整后数据加载恢复正常。

  3. 场景 3:优化 Redis 内存使用现象:Redis 内存占用持续升高,无法确定内存消耗来源;工具使用:解析多时段日志后,发现 "RDB Fork CoW 平均内存从 50MB 升至 150MB",结合日志时间定位到新上线业务的大 key 问题,优化数据结构后内存占用下降 60%。

四、适配人群

  • 运维人员:快速定位 Redis 运行异常、备份问题、内存波动,提升 Redis 服务稳定性;
  • 服务器管理员:无需专业 Redis 知识,一键掌握备份态势、内存使用,制定精准运维策略;
  • 开发人员:快速关联 Redis 日志与业务代码,区分 "Redis 服务问题" 和 "代码调用问题",精准修复 bug;
  • 小型团队 / 个人站长:无专业运维资源时,低成本实现 Redis 故障监控,避免数据丢失和服务中断。

总结

  1. Redis 日志分析的核心价值是秒级定位运行异常、掌控备份态势、识别内存瓶颈,保障核心数据存储服务的稳定与安全;
  2. 网页版工具零安装、零服务器风险,智能解析日志并结构化呈现关键信息,1 分钟锁定 Redis 核心问题;
  3. 操作仅需 "复制 - 粘贴 - 解析" 三步,非专业人员也能快速定位 Redis 问题,大幅降低服务故障和数据丢失的风险。

该工具以 "科技修仙" 轻量化设计,将复杂的 Redis 日志分析简化为 "一键解析、问题秒现",让 Redis 运维从 "被动救火" 变为 "主动防御",真正实现 "洞察 Redis 日志玄机,秒级定位运行问题

阿雪技术观

在科技发展浪潮中,我们不妨积极投身技术共享。不满足于做受益者,更要主动担当贡献者。无论是分享代码、撰写技术博客,还是参与开源项目维护改进,每一个微小举动都可能蕴含推动技术进步的巨大能量。东方仙盟是汇聚力量的天地,我们携手在此探索硅基生命,为科技进步添砖加瓦。

Hey folks, in this wild tech - driven world, why not dive headfirst into the whole tech - sharing scene? Don't just be the one reaping all the benefits; step up and be a contributor too. Whether you're tossing out your code snippets, hammering out some tech blogs, or getting your hands dirty with maintaining and sprucing up open - source projects, every little thing you do might just end up being a massive force that pushes tech forward. And guess what? The Eastern FairyAlliance is this awesome place where we all come together. We're gonna team up

相关推荐
郝亚军23 分钟前
ubuntu通过samba,让win11可以访问其共享文件夹
linux·服务器·ubuntu
workflower39 分钟前
人机交互部分OOD
运维·人工智能·自动化·集成测试·人机交互·软件需求
农村小镇哥41 分钟前
nginx服务器的介绍
运维·服务器·nginx
小夏子_riotous1 小时前
Docker学习路径——3、常用命令
linux·运维·服务器·学习·docker·容器·centos
IMPYLH3 小时前
Linux 的 rm 命令
linux·运维·服务器·网络·bash
white-persist4 小时前
【vulhub shiro 漏洞复现】vulhub shiro CVE-2016-4437 Shiro反序列化漏洞复现详细分析解释
运维·服务器·网络·python·算法·安全·web安全
代码中介商5 小时前
手把手教你Linux 打包压缩与 gcc 编译详解
linux·运维·服务器·编译·打包·压缩
longerxin20205 小时前
阿里云AlmaLinux操作系统允许root登录配置步骤
linux·服务器·阿里云
xuanwenchao5 小时前
ROS2学习笔记 - 2、类的继承及使用
服务器·笔记·学习
HYNuyoah5 小时前
docker 安装win10系统
运维·docker·容器