服务器运维(三十七)日志分析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

相关推荐
Mr.小海2 小时前
Docker 数据卷挂载:从基础到生产的完整落地指南(含避坑实战)
运维·docker·容器
老实巴交的麻匪2 小时前
Exception异常架构设计:异常抛出(03)
运维·云原生·架构
ruxshui2 小时前
# Linux diff命令使用
linux·运维·服务器
Sheffield2 小时前
为什么大家都用iptables,不愿碰原生firewalld?
linux·运维·安全
枷锁—sha2 小时前
【SRC】前后端分离与API接口渗透
服务器·网络·安全·网络安全·系统安全
何中应2 小时前
Jenkins构建完,jar包启动不起来?
linux·运维·jenkins
柏木乃一2 小时前
Linux进程信号(1):信号概述,信号产生part 1
linux·运维·服务器·c++·信号·signal
暴力求解3 小时前
Linux---进程(一):初识进程
linux·运维·服务器
淡唱暮念3 小时前
Linux系统使用夸克网盘CLI上传服务器数据至网盘教程,解决大数据备份苦恼
linux·服务器·ubuntu