Node.js解析网宿直播分发的CDN日志

网宿直播分发的日志记录,每行以 || 分割字段,如:

-||180.109.168.43||2025-02-18||23:42:11||-||example.org||-||-||10166||404||rtmp||-||rtmp://example.org/appName/CAA6E93B1962272D28E92A08B1FA91AD?txSecret=33bb87d80352441bfc15aa21d2f5d8bf&txTime=67BD1356||/appName/CAA6E93B1962272D28E92A08B1FA91AD?txSecret=33bb87d80352441bfc15aa21d2f5d8bf&txTime=67BD1356||txSecret=33bb87d80352441bfc15aa21d2f5d8bf&txTime=67BD1356||-||-||-||wsSP://example.org/appName/CAA6E93B1962272D28E92A08B1FA91AD||4236||4236||-||4434||4294||rtmp-8686e6cb875e8b8317935ae6-223||117.91.185.1

PS. 网宿直播分发和点播分发的日志格式不一样

各个字段表示:

分段日志标示
客户端IP
请求时的日期
请求时的时间
%x-adaptor
加速域名
发布点
具体流名
响应时间(毫秒)
HTTP服务状态码
协议
%c-proto-ver
请求完整uri
请求uri
URI扩展参数
请求头referer
请求头UA
%x-suri
频道名
文件内容大小
传输文件长度
%x-page-url
客户端传递字节数
响应大小(包括头部)
请求唯一标记符
服务器IP

  • calcWangsuLiveCdnTraffic.js

    const fs = require('fs');
    const readline = require('readline');
    const path = require('path');
    
    async function streamProcessLog(logFilePath) {
        return new Promise((resolve, reject) => {
            const inputStream = fs.createReadStream(path.resolve(logFilePath));
            let totalSize = 0;
            let lineCount = 0;
    
            const rl = readline.createInterface({
                input: inputStream,
                crlfDelay: Infinity
            });
    
            rl.on('line', (line) => {
                lineCount++;
                const trimmed = line.trim();
                if (!trimmed) return;
    
                const fields = trimmed.split('||');
                if (fields.length < 3) {
                    console.warn(`[Line ${lineCount}] 字段不足`);
                    return;
                }
    
                const sizeStr = fields[fields.length - 3];
                const size = parseInt(sizeStr, 10);
                if (!isNaN(size)) {
                    totalSize += size;
                } else {
                    console.warn(`[Line ${lineCount}] 无效数值: ${sizeStr}`);
                }
            });
    
            rl.on('close', () => resolve(totalSize));
            inputStream.on('error', (err) => reject(err));
        });
    }
    
    if (require.main === module) {
        if (!process.argv[2]) {
            console.log(`Usage: node ${path.basename(__filename)} <path_to_log_file>`);
            process.exit(1);
        }
    
        console.time('处理耗时');
        streamProcessLog(process.argv[2])
            .then(total => {
                console.log(`总响应大小: ${total} bytes`);
                console.timeEnd('处理耗时');
                process.exit(0);
            })
            .catch(err => {
                console.error(`处理失败: ${err.message}`);
                process.exit(1);
            });
    }
    

使用示例:

node .\calcWangsuLiveCdnTraffic.js .\0218.log
[Line 1194006] 字段不足
总响应大小: 495909821566 bytes
处理耗时: 3.348s

node .\calcWangsuLiveCdnTraffic.js .\0219.log
[Line 750393] 字段不足
总响应大小: 717324504282 bytes
处理耗时: 2.125s

node .\calcWangsuLiveCdnTraffic.js .\0220.log
[Line 1584327] 字段不足
总响应大小: 1036742635009 bytes
处理耗时: 4.553s