UserLog.md 24 KB

用户日志系统 - 原时间排序版本

系统概述

用户日志系统是一个高性能的日志收集和展示系统,专门用于将各模块的原始业务日志转换为用户友好的日志消息。系统采用原时间排序设计理念,确保用户看到的日志顺序与实际业务发生的时间顺序完全一致。

核心特性

  • 原时间排序:日志按业务发生的原始时间排序,而非收集时间
  • 多模块支持:支持资金、物品、农场、积分等多个业务模块
  • 高性能收集:采用批量处理和时间线追踪,支持高频数据收集
  • 实时性保证:2秒延迟收集机制,平衡实时性和系统稳定性
  • 完整监控:提供详细的收集统计和健康检查功能

系统架构

整体架构图

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   原始日志表     │    │   收集器系统     │    │   用户日志表     │
│                │    │                │    │                │
│ fund_logs      │───▶│ FundCollector  │───▶│                │
│ item_logs      │───▶│ ItemCollector  │───▶│   user_logs    │
│ farm_logs      │───▶│ FarmCollector  │───▶│                │
│ point_logs     │───▶│ PointCollector │───▶│                │
└─────────────────┘    └─────────────────┘    └─────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │   调度管理器     │
                    │                │
                    │ CollectorManager│
                    │ ScheduleService │
                    └─────────────────┘

核心组件

1. 收集器系统 (Collectors)

  • BaseLogCollector: 收集器基类,提供通用功能
  • FundLogCollector: 资金日志收集器
  • ItemLogCollector: 物品日志收集器
  • FarmLogCollector: 农场日志收集器
  • PointLogCollector: 积分日志收集器

2. 管理层 (Management)

  • UserLogCollectorManager: 收集器管理器,统一调度所有收集器
  • UserLogService: 对外服务接口

3. 数据层 (Data)

  • UserLog: 用户日志模型
  • UserLogLogic: 日志业务逻辑
  • 用户日志表: 统一的日志存储表

4. 控制层 (Control)

  • CollectUserLogsCommand: 命令行工具
  • UserLogController: 后台管理界面
  • LogDataHandler: 前端接口处理

原时间排序实现

设计理念

传统的日志系统通常按收集时间排序,这会导致用户看到的日志顺序与实际业务发生顺序不一致。本系统采用原时间排序机制,确保:

  1. 时间一致性:日志显示顺序 = 业务发生顺序
  2. 跨模块同步:不同模块的日志能够按统一时间线排序
  3. 数据完整性:基于ID进度追踪,确保不遗漏任何记录

实现机制

1. 双时间戳设计

// 收集时同时记录原始时间和收集时间
$userLogData = [
    'user_id' => $record->user_id,
    'message' => $this->generateMessage($record),
    'source_type' => $this->sourceType,
    'source_id' => $record->id,
    'source_table' => $this->sourceTable,
    'original_time' => $record->created_at,  // 原始业务时间
    'collected_at' => now(),                 // 收集时间
    'created_at' => now(),                   // 兼容字段
];

2. ID进度追踪

每个收集器基于自增ID维护进度,确保数据完整性:

// 获取最后处理的记录ID
$lastProcessedId = $this->getLastProcessedId();

// 按ID顺序获取新记录
$records = $sourceModel::where('id', '>', $lastProcessedId)
    ->orderBy('id')
    ->limit($this->maxRecords)
    ->get();

3. 原时间排序

收集到的记录按原始时间排序后批量保存:

// 按原始时间排序
usort($userLogs, function($a, $b) {
    return strtotime($a['original_time']) <=> strtotime($b['original_time']);
});

// 批量保存
UserLogService::batchLog($userLogs);

进度追踪优势

基于ID vs 基于时间

特性 基于ID追踪 基于时间追踪
数据完整性 ✅ 保证不遗漏 ❌ 可能遗漏相同时间戳的记录
时钟容错 ✅ 不受时钟调整影响 ❌ 受服务器时间影响
并发安全 ✅ 自增ID唯一 ❌ 并发时时间戳可能重复
恢复能力 ✅ 可精确断点续传 ❌ 时间重叠可能重复处理

收集流程详解

主流程

启动脚本 → 遍历所有收集器 → 执行收集 → 输出结果
    │              │              │         │
    │              │              │         └─ 统计信息
    │              │              │         └─ 错误报告
    │              │              │         └─ 性能指标
    │              │              │
    │              │              └─ 批量写入user_logs
    │              │              └─ 更新进度追踪
    │              │
    │              └─ 获取收集进度
    │              └─ 读取原始日志
    │              └─ 转换消息格式
    │
    └─ 初始化收集器
    └─ 检查系统状态

单个收集器流程

1. 进度确认阶段

// 从user_logs表获取本收集器最新记录的时间戳
$lastTimestamp = UserLog::where('source_type', $this->sourceType)
    ->where('source_table', $this->sourceTable)
    ->max('created_at');

2. 数据获取阶段

// 获取未收集的记录(延迟2秒 + 限制数量)
$cutoffTime = now()->subSeconds(2);
$records = $this->getNewRecordsByTime($lastTimestamp)
    ->where('created_at', '<=', $cutoffTime)
    ->limit($this->maxRecords);

3. 消息转换阶段

foreach ($records as $record) {
    $userLogData = $this->convertToUserLog($record);
    if ($userLogData) {
        $userLogs[] = $userLogData;
    }
}

4. 批量保存阶段

if (!empty($userLogs)) {
    UserLogService::batchLog($userLogs);
}

配置系统

数据库配置

用户日志系统的配置已迁移到数据库,存储在 kku_game_configs 表中,支持运行时动态修改。

配置项说明

配置键 名称 类型 默认值 说明
user_log.enabled 用户日志系统启用 布尔值 true 控制整个用户日志系统是否启用
user_log.auto_collect_enabled 自动收集日志启用 布尔值 true 控制是否允许自动收集用户日志
user_log.max_records_per_run 单次最大处理记录数 整数 1000 每次收集日志时的最大处理记录数
user_log.collection_interval 收集间隔 整数 2 日志收集的间隔时间(秒)
user_log.retention_days 日志保留天数 整数 30 用户日志的保留天数
user_log.auto_cleanup 自动清理启用 布尔值 true 是否启用自动清理过期日志

配置访问方式

use App\Module\Game\Services\GameConfigService;

// 获取完整的用户日志配置
$config = GameConfigService::getUserLogConfig();

// 配置结构示例
[
    'enabled' => true,
    'auto_collect_enabled' => true,
    'max_records_per_run' => 1000,
    'collection_interval' => 2,
    'retention_days' => 30,
    'auto_cleanup' => true,
]

后台管理

可以通过后台管理界面修改配置:

  • 访问路径:game-system-configs
  • 支持在线修改配置值
  • 支持重置为默认值
  • 自动缓存管理

数据库设计

用户日志表 (kku_user_logs)

CREATE TABLE `kku_user_logs` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `user_id` int NOT NULL COMMENT '用户ID',
  `message` text COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '日志消息内容',
  `source_type` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '来源类型(fund, item, farm等)',
  `source_id` int DEFAULT NULL COMMENT '来源记录ID',
  `source_table` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '来源表名',
  `original_time` timestamp NULL DEFAULT NULL COMMENT '原始日志时间(业务发生时间)',
  `collected_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '收集时间(日志收集时间)',
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(兼容字段,等同于collected_at)',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_original_time` (`original_time`),
  KEY `idx_collected_at` (`collected_at`),
  KEY `idx_created_at` (`created_at`),
  KEY `idx_source` (`source_type`,`source_id`),
  KEY `idx_user_original_time` (`user_id`, `original_time`),
  KEY `idx_user_collected_at` (`user_id`, `collected_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户日志表';

索引设计说明

  1. 主键索引 (id): 自增主键,保证唯一性
  2. 用户索引 (user_id): 按用户查询日志的核心索引
  3. 原始时间索引 (original_time): 支持按业务发生时间查询和排序
  4. 收集时间索引 (collected_at): 支持按收集时间查询和监控
  5. 兼容时间索引 (created_at): 保持向后兼容性
  6. 来源索引 (source_type, source_id): 支持按来源类型和ID查询
  7. 复合索引 (user_id, original_time): 优化用户日志按原始时间排序查询
  8. 复合索引 (user_id, collected_at): 优化按收集时间的查询

字段说明

  • original_time: 存储业务发生的原始时间,用于排序和展示
  • collected_at: 存储日志收集时间,用于监控和追踪收集进度
  • created_at: 兼容字段,等同于collected_at,保持向后兼容性
  • source_type: 来源模块类型,如 fund、item、farm、point
  • source_table: 原始日志表名,便于追溯和调试
  • message: 用户友好的日志消息,已经过格式化处理

时间字段设计理念

双时间戳设计

系统采用双时间戳设计,分别记录:

  1. 原始时间 (original_time): 业务实际发生的时间

    • 用于用户界面展示和排序
    • 确保日志顺序与业务发生顺序一致
    • 支持跨模块的统一时间线
  2. 收集时间 (collected_at): 日志被收集系统处理的时间

    • 用于监控收集系统的性能
    • 追踪收集延迟和系统健康状况
    • 支持收集进度管理

时间一致性保证

// 收集器创建日志时的时间设置
$userLogData = [
    'original_time' => $record->created_at,  // 使用原始业务时间
    'collected_at' => now(),                 // 使用当前收集时间
    'created_at' => now(),                   // 兼容字段
];

命令行工具

基本用法

# 执行日志收集
php artisan game:collect-user-logs

# 显示详细处理过程
php artisan game:collect-user-logs --detail

# 限制单次处理记录数
php artisan game:collect-user-logs --limit=500

# 显示收集器信息
php artisan game:collect-user-logs --info

# 显示统计信息
php artisan game:collect-user-logs --statistics

命令选项详解

--detail 详细模式

显示每个收集器的详细执行过程:

🚀 开始按时间线收集用户日志...
📊 执行各收集器的日志收集...
  fund: 处理了 45 条记录
  item: 处理了 23 条记录
  farm: 处理了 12 条记录
  point: 处理了 8 条记录
✅ 收集完成,总共处理了 88 条记录,耗时 234.56ms

--info 信息模式

显示所有收集器的状态信息:

📋 收集器信息:
  💰 fund: FundLogCollector (fund_logs → user_logs)
  📦 item: ItemLogCollector (item_transaction_logs → user_logs)
  🌾 farm: FarmLogCollector (farm_harvest_logs, farm_upgrade_logs → user_logs)
  ⭐ point: PointLogCollector (point_logs → user_logs)

--statistics 统计模式

显示系统统计信息:

📊 用户日志统计:
  📝 总日志数: 12,345
  📊 按类型统计:
    💰 资金日志: 5,678
    📦 物品日志: 3,456
    🌾 农场日志: 2,345
    ⭐ 积分日志: 866

进度管理

系统采用基于已处理记录的进度追踪机制:

  • 进度通过user_logs表中的记录自动追踪
  • 每个收集器会查找最后处理的记录,从该位置继续收集
  • 无需手动重置,系统会自动从上次中断的位置继续
  • 如需重新收集,可直接删除user_logs表中对应的记录

计划任务配置

Laravel 调度器配置

如需启用自动日志收集,可在 routes/console.php 中添加:

use Illuminate\Support\Facades\Schedule;

// 每分钟执行用户日志收集(高频收集)
Schedule::command('game:collect-user-logs --limit=100')->everyMinute();

// 每天凌晨2点清理过期日志
Schedule::command('game:clean-expired-user-logs')->dailyAt('02:00');

注意:默认情况下计划任务已被注释,需要根据实际需求启用。系统通过数据库配置 user_log.auto_collect_enabled 控制是否允许自动收集。

Crontab 配置

如果需要更高频率的收集,可以直接配置 crontab:

# 每30秒执行一次(需要注意服务器负载)
* * * * * cd /path/to/project && php artisan game:collect-user-logs --limit=50 >/dev/null 2>&1
* * * * * sleep 30; cd /path/to/project && php artisan game:collect-user-logs --limit=50 >/dev/null 2>&1

执行方式

日志收集系统采用同步执行方式,通过命令行工具直接执行:

# 手动执行日志收集
php artisan game:collect-user-logs

# 通过计划任务定时执行
# 在 routes/console.php 中配置调度任务

监控和告警

健康检查

系统提供基本的健康检查功能:

# 检查收集器状态
php artisan game:collect-user-logs --info

# 查看统计信息
php artisan game:collect-user-logs --statistics

通过命令行工具可以检查:

  • 收集器注册状态
  • 日志收集统计
  • 系统运行状态

性能监控

收集统计

# 查看收集统计信息
php artisan game:collect-user-logs --statistics

# 输出示例
📊 收集器统计信息:
收集器数量: 5
- fund: ⚙️ 资金日志收集器
- item: ⚙️ 物品日志收集器
- farm_harvest: 🌾 农场收获日志收集器
- farm_upgrade: 🌾 农场升级日志收集器
- point: ⭐ 积分日志收集器

📋 用户日志表统计:
  📝 总日志数: 119163
  🕐 最新日志时间: 2025-06-22 23:35:56
  🕐 最旧日志时间: 2025-06-21 16:30:12

执行时间监控

每次收集都会记录详细的执行时间:

[
    'total_processed' => 88,
    'total_execution_time' => 234.56, // 毫秒
    'collectors' => [
        'fund' => [
            'processed_count' => 45,
            'execution_time' => 123.45,
            'status' => 'success'
        ],
        // ...
    ]
]

错误监控

系统会自动记录所有收集过程中的错误:

// 错误日志示例
Log::error("日志收集失败", [
    'collector' => 'FundLogCollector',
    'error' => 'Database connection timeout',
    'trace' => '...',
    'timestamp' => '2025-06-13 11:16:04'
]);

API 接口

前端日志查询接口

LogDataHandler

处理前端的日志查询请求:

// 请求参数
[
    'page' => 1,           // 页码
    'page_size' => 20,     // 每页数量
    'source_type' => '',   // 来源类型过滤
    'start_time' => '',    // 开始时间
    'end_time' => ''       // 结束时间
]

// 响应格式(protobuf)
message LogDataResponse {
    repeated LogItem logs = 1;
    int32 total = 2;
    int32 page = 3;
    int32 page_size = 4;
}

message LogItem {
    int64 id = 1;
    string message = 2;
    string time = 3;        // 格式化时间 "MM-dd HH:mm"
    string source_type = 4;
}

ClearLogHandler

处理用户清空日志请求:

// 请求:空
// 响应:标准成功响应
// 注意:实际不删除数据,只记录清理时间

后台管理接口

UserLogController

提供完整的后台管理功能:

  • 列表查询:支持按用户、类型、时间范围筛选
  • 详情查看:查看单条日志的详细信息
  • 批量操作:批量查看和分析(不支持删除)
  • 统计分析:提供各种统计图表

扩展开发

添加新的收集器

1. 创建收集器类

<?php

namespace App\Module\Game\Logics\UserLogCollectors;

class CustomLogCollector extends BaseLogCollector
{
    protected string $sourceTable = 'custom_logs';
    protected string $sourceType = 'custom';

    protected function getNewRecords(int $lastProcessedId)
    {
        return CustomLog::where('id', '>', $lastProcessedId)
            ->orderBy('id')
            ->limit($this->maxRecords)
            ->get();
    }

    protected function getNewRecordsByTime(int $lastProcessedTimestamp)
    {
        $cutoffTime = now()->subSeconds(2);
        return CustomLog::where('created_at', '>', date('Y-m-d H:i:s', $lastProcessedTimestamp))
            ->where('created_at', '<=', $cutoffTime)
            ->orderBy('created_at')
            ->orderBy('id')
            ->limit($this->maxRecords)
            ->get();
    }

    protected function getRecordTimestamp($record): int
    {
        return $record->created_at->timestamp;
    }

    protected function convertToUserLog($record): ?array
    {
        // 转换逻辑
        return $this->createUserLogData(
            $record->user_id,
            "自定义操作:{$record->action}",
            $record->id,
            $record->created_at->toDateTimeString()
        );
    }
}

2. 注册收集器

UserLogCollectorManager 中注册:

private function registerCollectors(): void
{
    $this->collectors = [
        'fund' => new FundLogCollector(),
        'item' => new ItemLogCollector(),
        'farm' => new FarmLogCollector(),
        'point' => new PointLogCollector(),
        'custom' => new CustomLogCollector(), // 新增
    ];
}

3. 添加配置

在数据库中添加配置项:

INSERT INTO `kku_game_configs` (`key`, `name`, `description`, `group`, `type`, `value`, `default_value`, `sort_order`, `remark`) VALUES
('user_log.custom_enabled', '自定义收集器启用', '控制自定义收集器是否启用', 'user_log', 1, '1', '1', 70, '');

或通过后台管理界面添加配置项。

自定义消息模板

1. 配置模板

'message_templates' => [
    'custom' => [
        'action1' => '执行了{action_name}操作',
        'action2' => '完成了{task_name}任务,获得{reward}奖励',
    ],
],

2. 使用模板

protected function convertToUserLog($record): ?array
{
    $template = config('game_user_log.message_templates.custom.' . $record->action_type);
    $message = str_replace(
        ['{action_name}', '{task_name}', '{reward}'],
        [$record->action_name, $record->task_name, $record->reward],
        $template
    );

    return $this->createUserLogData(
        $record->user_id,
        $message,
        $record->id,
        $record->created_at->toDateTimeString()
    );
}

性能优化

数据库优化

1. 索引优化

  • 确保所有查询都有对应的索引
  • 定期分析慢查询并优化
  • 考虑分区表设计(按时间分区)

2. 查询优化

// 使用复合索引优化用户日志查询
UserLog::where('user_id', $userId)
    ->where('created_at', '>=', $startTime)
    ->orderBy('created_at', 'desc')
    ->limit(20)
    ->get();

3. 批量处理优化

// 使用批量插入减少数据库连接
UserLog::insert($userLogs); // 而不是逐条插入

内存优化

1. 分批处理

// 避免一次性加载大量数据
$records->chunk(100, function ($chunk) {
    $this->processChunk($chunk);
});

2. 及时释放内存

unset($records, $userLogs); // 处理完成后释放变量

缓存优化

1. 进度缓存

// 缓存收集进度,减少数据库查询
Cache::remember("collector_progress:{$this->sourceType}", 300, function () {
    return $this->getLastProcessedTimestamp();
});

2. 配置缓存

// 配置自动缓存,通过GameConfigService访问
$config = GameConfigService::getUserLogConfig();

// 手动清除配置缓存(如果需要)
GameConfigLogic::clearCache('user_log.enabled');

系统清理说明

已移除的废弃组件

为了保持系统整洁,以下组件已被移除:

1. 废弃的配置文件

  • config/game_user_log.php - 配置已迁移到数据库

2. 废弃的服务类

  • UserLogScheduleService - 未被实际使用,功能已由命令行工具替代
  • CollectUserLogJob - 异步队列任务,未被实际使用

3. 废弃的protobuf类

  • Request_RequestUserLogData - 已被新命名空间替代
  • Request_RequestUserClearLog - 已被新命名空间替代

迁移说明

如果您的代码中引用了上述废弃组件,请按以下方式迁移:

  1. 配置访问:使用 GameConfigService::getUserLogConfig() 替代文件配置
  2. 日志收集:直接使用 CollectUserLogsCommandUserLogCollectorManager
  3. 异步处理:如需异步处理,可以自行实现队列任务调用 UserLogCollectorManager
  4. protobuf类:使用新的命名空间下的类

故障排除

常见问题

1. 收集器不工作

症状:命令执行但没有新日志生成

排查步骤

# 检查收集器状态
php artisan game:collect-user-logs --info

# 检查原始日志表是否有新数据
# 检查配置是否正确
# 检查数据库连接是否正常

2. 时间顺序错误

症状:用户日志时间顺序混乱

排查步骤

  • 检查 created_at 字段是否使用原始时间
  • 检查时区设置是否正确
  • 检查延迟收集是否生效

3. 性能问题

症状:收集速度慢,影响系统性能

优化方案

  • 调整 max_records_per_run 参数
  • 启用队列异步处理
  • 优化数据库索引
  • 增加服务器资源

4. 内存溢出

症状:收集过程中出现内存不足错误

解决方案

// 减少批量处理大小
'batch_size' => 50, // 从100减少到50

// 增加内存限制
ini_set('memory_limit', '512M');

// 使用分块处理
$records->chunk(50, function ($chunk) {
    // 处理逻辑
});

调试工具

1. 详细日志

# 启用详细模式查看执行过程
php artisan game:collect-user-logs --detail

2. 性能分析

// 在收集器中添加性能监控
$startTime = microtime(true);
// 执行收集逻辑
$endTime = microtime(true);
Log::info("收集器性能", [
    'collector' => $this->collectorName,
    'execution_time' => ($endTime - $startTime) * 1000,
    'processed_count' => $processedCount
]);

3. 数据验证

# 验证数据一致性
SELECT source_type, COUNT(*) FROM kku_user_logs GROUP BY source_type;

# 检查时间范围
SELECT MIN(created_at), MAX(created_at) FROM kku_user_logs;

总结

用户日志系统采用原时间排序设计,确保用户看到的日志顺序与业务发生顺序完全一致。系统具有以下优势:

核心优势

  1. 时间一致性:日志按业务原始时间排序,保证时序正确性
  2. 高性能:批量处理 + 时间线追踪,支持高频数据收集
  3. 模块化设计:收集器独立运行,易于扩展和维护
  4. 完整监控:提供详细的统计和健康检查功能
  5. 灵活配置:支持多种配置方式和环境变量

技术特点

  • 延迟收集:2秒延迟机制,平衡实时性和稳定性
  • 进度追踪:基于时间戳的进度管理,支持断点续传
  • 批量处理:减少数据库连接,提升处理效率
  • 错误恢复:完善的异常处理和错误恢复机制

适用场景

  • 游戏用户行为日志收集
  • 业务操作审计日志
  • 多模块统一日志展示
  • 实时数据分析和监控

通过合理的配置和部署,本系统能够稳定高效地处理大量用户日志数据,为业务分析和用户体验提供强有力的支持。