Skip to content

Latest commit

 

History

History
774 lines (581 loc) · 20.5 KB

File metadata and controls

774 lines (581 loc) · 20.5 KB

异步日志器 (AsyncLogger)

🎯 适用场景

✅ 强烈推荐使用

场景 日志量 性能收益
百万级设备平台 10M-100M/秒 500x
高频交易系统 100K+/秒 50-100x
实时数据采集 50K+/秒 20-50x
高并发 Web API 10K+/秒 10-20x

⚠️ 不推荐使用

  • 日志量 < 1K/秒:使用同步 Logger 即可
  • 单线程应用:异步优势不明显
  • 强顺序要求:异步日志有微小延迟
  • 调试场景:建议使用同步模式便于排查

🚀 核心优势

1. 极低延迟

  • 主线程延迟: < 1μs (同步版 50μs)
  • 不阻塞业务逻辑: 日志写入在后台线程
  • 适合实时系统: 毫秒级响应要求

2. 高吞吐量

  • 单线程 QPS: 1M-10M 条/秒
  • 多线程 QPS: 5M-50M 条/秒
  • 可扩展: 容量可配置

3. 无锁设计

  • 无竞争: 单生产者单消费者
  • 原子操作: lock-free 环形队列
  • CPU 友好: 减少上下文切换

📖 快速开始

1. 基本使用

const std = @import("std");
const AsyncLogger = @import("zzig").AsyncLogger;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // 创建异步日志器(使用默认配置)
    const logger = try AsyncLogger.AsyncLogger.init(allocator, .{});
    defer logger.deinit();

    // 记录日志(非阻塞)
    logger.info("系统启动完成", .{});
    logger.warn("内存使用率: {d}%", .{85});
    logger.err("连接失败: {s}", .{"timeout"});

    // 等待日志处理完成(可选)
    std.Thread.sleep(1 * std.time.ns_per_s);
}

2. 自定义配置

const config = AsyncLogger.AsyncLoggerConfig{
    .queue_capacity = 16384,     // 队列容量(建议 2 的幂)
    .idle_sleep_us = 50,         // 空闲休眠时间(微秒)
    .global_level = .info,       // 全局日志级别
    .enable_drop_counter = true, // 启用丢弃计数
};

const logger = try AsyncLogger.AsyncLogger.init(allocator, config);

3. 百万级设备场景

// 配置:适应高并发
const config = AsyncLogger.AsyncLoggerConfig{
    .queue_capacity = 32768, // 32K 消息缓冲
    .idle_sleep_us = 10,     // 更短休眠
    .global_level = .info,
};

const logger = try AsyncLogger.AsyncLogger.init(allocator, config);

// 多线程并发记录(模拟设备上报)
var threads: [16]std.Thread = undefined;
for (0..16) |i| {
    threads[i] = try std.Thread.spawn(.{}, deviceWorker, .{ logger, i });
}

fn deviceWorker(logger: *AsyncLogger.AsyncLogger, thread_id: usize) void {
    for (0..100_000) |i| {
        const device_id = thread_id * 1_000_000 + i;
        logger.info("设备{d}: 状态正常", .{device_id});
    }
}

⚙️ 配置参数详解

AsyncLoggerConfig

参数 类型 默认值 说明
queue_capacity usize 8192 队列容量(自动向上取 2 的幂)
idle_sleep_us u64 100 队列空时休眠时间(微秒)
global_level Level .debug 全局日志级别
enable_drop_counter bool true 是否启用丢弃计数器

队列容量建议

场景 推荐容量 说明
中等负载 (1K-10K QPS) 8K-16K 默认配置
高负载 (10K-100K QPS) 16K-32K 增加缓冲
极高负载 (100K+ QPS) 32K-64K 防止丢弃
低负载 (< 1K QPS) 4K 节省内存

注意: 容量越大,内存占用越高 (每条消息 ~1KB)

休眠时间建议

场景 推荐值 (μs) 说明
实时性要求高 10-50 更快响应,但 CPU 占用高
一般应用 100-200 平衡性能与资源
低频日志 500-1000 节省 CPU

📊 API 参考

核心方法

日志记录

// 按级别记录
pub fn debug(self: *AsyncLogger, comptime fmt: []const u8, args: anytype) void
pub fn info(self: *AsyncLogger, comptime fmt: []const u8, args: anytype) void
pub fn warn(self: *AsyncLogger, comptime fmt: []const u8, args: anytype) void
pub fn err(self: *AsyncLogger, comptime fmt: []const u8, args: anytype) void

// 通用方法
pub fn log(self: *AsyncLogger, level: Level, comptime fmt: []const u8, args: anytype) void

监控与控制

// 获取统计信息
pub fn getProcessedCount(self: *AsyncLogger) usize  // 已处理数量
pub fn getDroppedCount(self: *AsyncLogger) usize    // 丢弃数量
pub fn getQueueSize(self: *AsyncLogger) usize       // 当前队列大小

// 设置日志级别
pub fn setLevel(self: *AsyncLogger, level: Level) void

日志级别

pub const Level = enum {
    debug,  // 调试信息
    info,   // 一般信息
    warn,   // 警告
    err,    // 错误
};

🔍 监控与诊断

1. 检查队列健康状态

// 定期检查
const processed = logger.getProcessedCount();
const dropped = logger.getDroppedCount();
const queue_size = logger.getQueueSize();

const drop_rate = (@as(f64, @floatFromInt(dropped)) / 
                   @as(f64, @floatFromInt(processed + dropped))) * 100.0;

if (drop_rate > 1.0) {
    // 丢弃率过高,需要:
    // 1. 增加队列容量
    // 2. 降低日志频率
    // 3. 提高日志级别(过滤更多日志)
}

if (queue_size > config.queue_capacity * 0.8) {
    // 队列接近满,即将开始丢弃
    // 建议触发告警
}

2. 性能基准测试

const start = std.time.nanoTimestamp();

const count = 100_000;
for (0..count) |i| {
    logger.info("Benchmark message {d}", .{i});
}

const end = std.time.nanoTimestamp();
const duration_ns = @as(u64, @intCast(end - start));
const qps = (count * std.time.ns_per_s) / duration_ns;
const avg_latency_ns = duration_ns / count;

std.debug.print("QPS: {d}\n", .{qps});
std.debug.print("平均延迟: {d} ns\n", .{avg_latency_ns});

⚠️ 注意事项

1. 消息截断

  • 每条日志最大 1KB (固定缓冲区)
  • 超出部分自动截断并添加 [TRUNCATED] 标记
  • 建议长日志拆分为多条

2. 队列满处理

  • 队列满时 直接丢弃 新日志(不阻塞)
  • 通过 getDroppedCount() 监控丢弃数量
  • 生产环境需配置告警

3. 优雅关闭

  • deinit() 会自动:
    1. 标记停止标志
    2. 等待后台线程结束
    3. 清空队列中剩余消息
    4. 释放所有资源

4. 线程安全

  • 多生产者: 支持多线程并发调用 log() 等方法
  • 单消费者: 内部自动管理后台线程
  • 不支持: 多个异步日志器共享队列

5. 内存占用

  • 队列内存: capacity × 1KB
  • 示例: 16K 容量 = 16MB 内存

📈 性能对比

同步 vs 异步

指标 同步 Logger 异步 Logger 提升
单线程延迟 ~50μs <1μs 50x
16线程延迟 ~50μs <1μs 50x
单线程 QPS ~20K ~10M 500x
16线程 QPS ~300K ~50M 166x
内存占用 最小 16MB (16K 队列) -
复杂度 简单 中等 -

实际测试数据

=== 异步日志 - 百万级设备压力测试 ===

🚀 测试 1: 顺序压测(单线程)
   ✓ 完成: 100000 条日志
   ✓ 耗时: 8523 μs
   ✓ QPS: 11731394 条/秒
   ✓ 平均延迟: 85 ns/条 (≈ 0.09 μs)

🚀 测试 2: 多线程并发(16 线程)
   ✓ 完成: 800000 条日志 (16 线程 × 50000)
   ✓ 耗时: 62341 μs (0.06 秒)
   ✓ QPS: 12831547 条/秒
   ✓ 平均延迟: 77 ns/条 (≈ 0.08 μs)

📈 最终统计:
   已处理: 900000 条
   已丢弃: 0 条
   丢弃率: 0.0000%

💡 性能分析:
   ✅ 单线程延迟 < 1μs: 极速模式
   ✅ 丢弃率 < 0.1%: 队列容量充足

🛠️ 故障排查

问题 1: 日志丢失

症状: getDroppedCount() > 0

原因与解决:

  1. 队列容量不足 → 增加 queue_capacity
  2. 日志频率过高 → 提高日志级别 (debug → info → warn)
  3. 后台线程慢 → 检查磁盘 IO(未来扩展)

问题 2: 延迟高

症状: 主线程调用 log() 耗时 > 10μs

原因与解决:

  1. 队列接近满 → 增加容量
  2. CPU 占用高 → 降低日志频率
  3. 格式化开销 → 减少复杂格式化

问题 3: 内存占用高

症状: 进程内存持续增长

原因与解决:

  1. 队列容量过大 → 降低 queue_capacity
  2. 未及时 deinit → 检查生命周期管理


🎯 零分配模式 (ARM/嵌入式优化)

概述

零分配模式专为 ARM/嵌入式设备 设计,消除运行时堆分配,显著提升性能并降低功耗。

核心优势

优势 动态分配 零分配 提升
延迟 (ARM) 800-1000ns 100-200ns 5-10x
内存碎片 有 (7天运行 ~150MB) 质变
功耗 基准 降低 30-100% 显著
长期稳定性 碎片化影响 恒定 优秀

适用场景

✅ 强烈推荐

  • ARM Cortex-A7/A53 设备 (600MHz-1.5GHz, 256MB-1GB)
  • ARM Cortex-M4/M7 MCU (100-300MHz, 64KB-2MB)
  • MIPS/RISC-V 嵌入式系统
  • 物联网边缘设备
  • 电池供电设备

⚠️ 不推荐

  • x86/x64 服务器 (资源充足,动态分配更灵活)
  • 需要超长日志的场景 (TLS 缓冲区 4KB 限制)

配置方式

1. 自动检测 (推荐)

const config = AsyncLogger.AsyncLoggerConfig{
    .queue_capacity = 4096,
    .allocation_strategy = .auto,  // 自动检测平台
};

// ARM 设备自动选择 .zero_alloc
// x86/x64 自动选择 .dynamic
const logger = try AsyncLogger.AsyncLogger.init(allocator, config);

2. 强制零分配

const config = AsyncLogger.AsyncLoggerConfig{
    .queue_capacity = 4096,
    .allocation_strategy = .zero_alloc,        // 强制零分配
    .tls_format_buffer_size = 2048,           // TLS 缓冲区 (ARM 可用 2KB)
    .worker_file_buffer_size = 16384,         // 文件缓冲 (ARM 可用 16KB)
    .idle_sleep_us = 200,                     // 节省 CPU
    .global_level = .info,
};

const logger = try AsyncLogger.AsyncLogger.init(allocator, config);

不同平台配置建议

ARM Cortex-A53 (1GB RAM)

const config = AsyncLogger.AsyncLoggerConfig{
    .queue_capacity = 4096,              // 4MB
    .allocation_strategy = .zero_alloc,
    .tls_format_buffer_size = 2048,      // 每线程 2KB
    .worker_file_buffer_size = 16384,    // 16KB
    .idle_sleep_us = 200,
    .global_level = .info,
    .batch_size = 50,
};

// 预期性能: 500K-1M QPS, 内存 ~4-5MB

ARM Cortex-M4 (256KB RAM)

const config = AsyncLogger.AsyncLoggerConfig{
    .queue_capacity = 1024,              // 1MB
    .allocation_strategy = .zero_alloc,
    .tls_format_buffer_size = 1024,      // 1KB
    .worker_file_buffer_size = 4096,     // 4KB
    .idle_sleep_us = 1000,               // 极度节能
    .global_level = .warn,               // 只记录警告
    .batch_size = 20,
};

// 预期性能: 50K-100K QPS, 内存 ~1-2MB

x86/x64 服务器 (对比)

const config = AsyncLogger.AsyncLoggerConfig{
    .queue_capacity = 32768,             // 32MB
    .allocation_strategy = .auto,        // 自动 → .dynamic
    .idle_sleep_us = 50,
    .global_level = .info,
};

// 预期性能: 10M-50M QPS, 内存 ~32MB

工作原理

内存布局

┌─────────────────────────────────────────┐
│  零分配模式内存分配 (启动时一次性)       │
├─────────────────────────────────────────┤
│  1. 环形队列: capacity × 1KB            │
│     示例: 4096 × 1KB = 4MB             │
├─────────────────────────────────────────┤
│  2. 线程本地存储 (每线程运行时分配)     │
│     - format_buffer: 2-4KB             │
│     自动管理,无堆分配                  │
├─────────────────────────────────────────┤
│  3. 工作线程预分配缓冲区 (启动时)       │
│     - worker_format_buffer: 2-4KB      │
│     - worker_utf16_buffer: 4KB         │
│     - worker_file_buffer: 16-32KB      │
│     总计: ~22-40KB                      │
├─────────────────────────────────────────┤
│  总内存: 4MB + 2KB×线程数 + 40KB       │
│  运行期间: 零额外分配                   │
└─────────────────────────────────────────┘

日志路径对比

【动态分配路径】
log() → 栈上 1KB buf → 格式化 → malloc LogMessage → push 队列
                                  ↑ 每次分配 ~800ns (ARM)

【零分配路径】
log() → TLS 4KB buf → 格式化 → 直接拷贝到队列预分配槽位
                              ↑ 零分配,~100ns (ARM)

性能验证

演示程序

# 运行零分配演示
zig build zero-alloc-demo -Doptimize=ReleaseFast

# 输出示例:
# ✅ 自动检测模式: .dynamic (x64)
# ✅ 强制零分配: 10000 条日志, QPS: 5M
# � 推荐配置: ARM 设备使用 .zero_alloc

实际测试数据 (ARM Cortex-A53 理论估算)

指标 动态分配 零分配 提升
单条延迟 900ns 150ns 6x
单核 QPS 1.1M 6.6M 6x
4核 QPS 4M 25M 6x
内存碎片 (7天) 150MB 0MB
功耗 (相对) 100% 60-70% 30-40%↓

监控与诊断

检查当前模式

// 通过统计判断是否零分配模式
const stats = logger.getStats();
if (stats.drop_rate < 0.1) {
    // 零分配模式通常丢弃率极低
    std.debug.print("零分配模式运行良好\n", .{});
}

// 检查内存使用 (外部工具)
// - 动态分配: 内存持续波动
// - 零分配: 内存恒定

性能基准

// ARM 设备性能测试
const start = std.time.nanoTimestamp();
for (0..100_000) |i| {
    logger.info("测试 {d}", .{i});
}
const end = std.time.nanoTimestamp();

const duration_ns = @as(u64, @intCast(end - start));
const qps = (100_000 * std.time.ns_per_s) / duration_ns;
const avg_latency = duration_ns / 100_000;

std.debug.print("QPS: {d}\n", .{qps});
std.debug.print("平均延迟: {d} ns\n", .{avg_latency});

// 预期结果 (Cortex-A53):
// - 动态分配: QPS ~1M, 延迟 ~900ns
// - 零分配: QPS ~6M, 延迟 ~150ns

注意事项

1. TLS 缓冲区限制

  • 默认大小: 4KB (可配置 tls_format_buffer_size)
  • 超长日志: 自动截断并标记 [TRUNCATED]
  • 建议: 单条日志 < 2KB,超长日志拆分

2. 文件 I/O 批量延迟

  • 批量写入: 100ms 或 80% 满时触发
  • 延迟影响: 单条日志可能延迟最多 100ms 到达磁盘
  • 适用场景: 日志记录,不适合实时审计

3. 平台检测覆盖

  • 支持: ARM, MIPS, RISC-V
  • 未覆盖: PowerPC, SPARC 等
  • 默认行为: 未知平台使用 .dynamic

4. 递归调用保护

  • 限制: 日志格式化中触发日志会被丢弃
  • 影响: 极端情况 (错误处理代码中日志)
  • 解决: is_formatting 标志自动防护

故障排查

问题 1: 日志截断

症状: 日志末尾出现 [TRUNCATED]

原因: 单条日志超过 TLS 缓冲区大小

解决:

const config = AsyncLogger.AsyncLoggerConfig{
    .tls_format_buffer_size = 8192,  // 增大到 8KB
    .allocation_strategy = .zero_alloc,
};

问题 2: 性能不如预期

症状: ARM 设备上延迟仍然 > 500ns

检查清单:

  1. 确认使用 .zero_alloc.auto
  2. 编译优化模式 -Doptimize=ReleaseFast
  3. 检查队列容量是否充足
  4. 确认日志级别过滤正确

问题 3: 内存占用高

症状: 进程内存超过预期

原因与解决:

// ARM 设备优化配置
const config = AsyncLogger.AsyncLoggerConfig{
    .queue_capacity = 2048,              // 降低容量 (2MB)
    .tls_format_buffer_size = 1024,      // 降低 TLS (1KB)
    .worker_file_buffer_size = 8192,     // 降低文件缓冲 (8KB)
    .allocation_strategy = .zero_alloc,
};

// 预期内存: ~2-3MB

迁移指南

从动态分配迁移

步骤 1: 评估平台

// 检查当前平台
const arch = @import("builtin").cpu.arch;
std.debug.print("CPU 架构: {}\n", .{arch});

// ARM/MIPS/RISC-V → 推荐零分配

步骤 2: 修改配置

// 旧配置 (动态分配)
const old_config = AsyncLogger.AsyncLoggerConfig{
    .queue_capacity = 8192,
    // allocation_strategy 未指定 (默认 .auto)
};

// 新配置 (零分配)
const new_config = AsyncLogger.AsyncLoggerConfig{
    .queue_capacity = 4096,              // ARM 设备可降低
    .allocation_strategy = .zero_alloc,  // 明确指定
    .tls_format_buffer_size = 2048,      // 新增配置
    .worker_file_buffer_size = 16384,    // 新增配置
};

步骤 3: 性能测试

# 编译测试
zig build -Doptimize=ReleaseFast

# 运行基准测试
zig build zero-alloc-demo

步骤 4: 监控验证

  • 检查丢弃率 < 1%
  • 验证内存恒定 (无持续增长)
  • 确认延迟降低 (ARM 设备应 < 200ns)

�🔗 相关示例

完整示例代码

压力测试运行

# 编译并运行压力测试
zig build-exe examples/async_logger_stress_test.zig --dep zzig -Mzzig=src/zzig.zig
./async_logger_stress_test.exe

# 零分配演示
zig build zero-alloc-demo -Doptimize=ReleaseFast

🚀 未来计划

第一阶段 (已完成)

  • ✅ 无锁环形队列
  • ✅ 异步后台线程
  • ✅ 级别过滤
  • ✅ 丢弃计数
  • ✅ 优雅关闭

第二阶段 (未来)

  • 文件输出支持
  • 日志轮转
  • 自定义格式化
  • 反压机制(队列满时阻塞)
  • 健康监控钩子
  • 内存池优化

💡 最佳实践

1. 生产环境配置

const config = AsyncLogger.AsyncLoggerConfig{
    .queue_capacity = 32768,      // 足够大的缓冲
    .idle_sleep_us = 100,         // 平衡响应与 CPU
    .global_level = .info,        // 过滤 debug
    .enable_drop_counter = true,  // 必须监控
};

2. 定期监控

// 每 10 秒检查一次
const Timer = struct {
    logger: *AsyncLogger.AsyncLogger,
    
    pub fn check(self: *Timer) void {
        const dropped = self.logger.getDroppedCount();
        const processed = self.logger.getProcessedCount();
        const queue_size = self.logger.getQueueSize();
        
        if (dropped > 0) {
            // 发送告警
            self.logger.warn("日志丢弃: {d} 条", .{dropped});
        }
    }
};

3. 适时降级

// 高负载时降低日志级别
if (load > 0.8) {
    logger.setLevel(.warn);  // 只记录警告和错误
}

📚 技术细节

平台兼容性

AsyncLogger 支持所有主流平台,自动适配架构特性:

平台类型 64位原子操作 实现方式 性能
x86_64, AArch64 ✅ 支持 硬件原子指令 100%
ARMv6, ARMv7, x86 ❌ 不支持 Mutex保护 ~95%
RISC-V 64, MIPS64 ✅ 支持 硬件原子指令 100%
RISC-V 32, MIPS ❌ 不支持 Mutex保护 ~95%

说明:

  • 库会在编译时自动检测目标平台
  • 不支持64位原子的平台使用Mutex代替,性能损失<5%
  • 详见 ARMv6兼容性说明

环形队列实现

  • 容量: 自动向上取 2 的幂(便于位运算)
  • 指针: 原子类型 std.atomic.Value(usize)
  • 内存序: acquire/release 确保可见性
  • 满判断: (write + 1) % capacity == read

线程模型

  • 生产者: 多个业务线程(调用 log() 等方法)
  • 消费者: 单个后台线程(workerLoop 方法)
  • 同步: 无锁队列 + 原子操作

批量处理

  • 每轮最多处理 100 条 消息(减少原子操作开销)
  • 队列空时休眠 idle_sleep_us 微秒(避免 CPU 空转)

版本: v1.0.0
兼容性: Zig 0.15.2+
授权: MIT License