CarefreeCMS 文档CarefreeCMS 文档
指南
  • 内容管理
  • 多站点管理
  • AI文章生成
  • SEO优化
  • 静态化生成
API
  • FAQ
  • 更新日志
  • 贡献指南
  • v1.3.0
  • v1.2.0
  • v1.1.0
GitHub
指南
  • 内容管理
  • 多站点管理
  • AI文章生成
  • SEO优化
  • 静态化生成
API
  • FAQ
  • 更新日志
  • 贡献指南
  • v1.3.0
  • v1.2.0
  • v1.1.0
GitHub
  • 开始使用

    • 介绍
    • 安装指南
    • 快速开始
    • 系统配置
  • 基础功能

    • 文章管理
    • 分类管理
    • 标签管理
    • 单页管理
    • 媒体库
  • 高级功能

    • 模板开发
    • 静态化生成
    • 搜索功能
    • 权限管理
    • 用户管理
  • AI 功能

    • AI 服务商配置
    • AI 模型配置
    • 提示词工程
  • 系统管理

    • 定时任务
    • 日志管理
    • 安全指南
    • 性能优化

日志管理

本文介绍 CarefreeCMS 的日志系统配置和使用方法。

日志概述

日志用于记录系统运行信息、错误、用户行为等,是排查问题和监控系统的重要工具。

日志类型

  • 应用日志:业务逻辑日志
  • 错误日志:系统错误和异常
  • 访问日志:HTTP 请求日志
  • SQL 日志:数据库查询日志
  • 操作日志:用户操作记录
  • 安全日志:登录、权限等安全相关

日志配置

基础配置

编辑 config/log.php:

return [
    // 默认日志驱动
    'default' => 'file',

    // 日志通道
    'channels' => [
        'file' => [
            'type' => 'File',
            'path' => runtime_path() . 'log/',
            'level' => ['error', 'warning', 'info'],
            'file_size' => 10 * 1024 * 1024, // 10MB
            'max_files' => 30, // 保留30天
        ],

        'daily' => [
            'type' => 'File',
            'path' => runtime_path() . 'log/',
            'level' => ['error'],
            'max_files' => 30,
        ],

        'database' => [
            'type' => 'Database',
            'table' => 'system_logs',
            'level' => ['error', 'warning'],
        ],
    ],
];

日志级别

'level' => [
    'emergency',  // 紧急:系统不可用
    'alert',      // 警报:必须立即采取行动
    'critical',   // 严重:严重错误
    'error',      // 错误:运行时错误
    'warning',    // 警告:警告信息
    'notice',     // 注意:正常但重要的事件
    'info',       // 信息:一般信息
    'debug',      // 调试:详细调试信息
],

多通道配置

'channels' => [
    // 错误日志写入文件
    'error' => [
        'type' => 'File',
        'path' => runtime_path() . 'log/error/',
        'level' => ['error', 'critical', 'emergency'],
    ],

    // SQL 日志单独记录
    'sql' => [
        'type' => 'File',
        'path' => runtime_path() . 'log/sql/',
        'level' => ['info'],
    ],

    // 操作日志写入数据库
    'operation' => [
        'type' => 'Database',
        'table' => 'operation_logs',
        'level' => ['info'],
    ],
],

日志使用

基本用法

use think\facade\Log;

// 不同级别的日志
Log::emergency('系统不可用');
Log::alert('需要立即处理');
Log::critical('严重错误');
Log::error('运行错误');
Log::warning('警告信息');
Log::notice('注意事项');
Log::info('一般信息');
Log::debug('调试信息');

记录上下文

// 记录详细信息
Log::error('文章保存失败', [
    'article_id' => $id,
    'user_id' => $userId,
    'error' => $e->getMessage(),
    'trace' => $e->getTraceAsString(),
]);

// 记录请求信息
Log::info('用户登录', [
    'user_id' => $user->id,
    'username' => $user->username,
    'ip' => request()->ip(),
    'user_agent' => request()->header('user-agent'),
    'timestamp' => time(),
]);

指定通道

// 使用特定通道
Log::channel('error')->error('数据库连接失败');
Log::channel('sql')->info('慢查询', ['sql' => $sql, 'time' => $time]);
Log::channel('operation')->info('删除文章', ['id' => $id]);

应用场景

错误日志

try {
    // 业务逻辑
    $result = $service->process();
} catch (\Exception $e) {
    Log::error('业务处理失败', [
        'class' => get_class($e),
        'message' => $e->getMessage(),
        'file' => $e->getFile(),
        'line' => $e->getLine(),
        'trace' => $e->getTraceAsString(),
    ]);

    throw $e;
}

SQL 日志

use think\facade\Db;

// 监听 SQL 执行
Db::listen(function($sql, $time, $master) {
    // 记录慢查询
    if ($time > 1000) {
        Log::channel('sql')->warning('慢查询', [
            'sql' => $sql,
            'time' => $time . 'ms',
            'master' => $master,
        ]);
    }

    // 调试模式记录所有 SQL
    if (app()->isDebug()) {
        Log::channel('sql')->info('SQL 执行', [
            'sql' => $sql,
            'time' => $time . 'ms',
        ]);
    }
});

操作日志

class ArticleController
{
    public function save()
    {
        $article = Article::create(request()->post());

        // 记录操作日志
        OperationLog::create([
            'user_id' => request()->user->id,
            'action' => 'create',
            'module' => 'article',
            'content' => "创建文章:{$article->title}",
            'ip' => request()->ip(),
        ]);

        return json(['code' => 200, 'msg' => '保存成功']);
    }

    public function delete($id)
    {
        $article = Article::find($id);

        // 记录删除操作
        OperationLog::create([
            'user_id' => request()->user->id,
            'action' => 'delete',
            'module' => 'article',
            'content' => "删除文章:{$article->title}",
            'data' => json_encode($article->toArray()),
            'ip' => request()->ip(),
        ]);

        $article->delete();

        return json(['code' => 200, 'msg' => '删除成功']);
    }
}

登录日志

public function login()
{
    $username = request()->post('username');
    $user = User::where('username', $username)->find();

    if ($user && password_verify($password, $user->password)) {
        // 登录成功
        LoginLog::create([
            'user_id' => $user->id,
            'username' => $user->username,
            'ip' => request()->ip(),
            'user_agent' => request()->header('user-agent'),
            'status' => 'success',
        ]);

        Log::info('用户登录成功', [
            'user_id' => $user->id,
            'ip' => request()->ip(),
        ]);
    } else {
        // 登录失败
        LoginLog::create([
            'username' => $username,
            'ip' => request()->ip(),
            'status' => 'failed',
            'reason' => '用户名或密码错误',
        ]);

        Log::warning('登录失败', [
            'username' => $username,
            'ip' => request()->ip(),
        ]);
    }
}

性能监控

class PerformanceMiddleware
{
    public function handle($request, \Closure $next)
    {
        $startTime = microtime(true);
        $startMemory = memory_get_usage();

        $response = $next($request);

        $endTime = microtime(true);
        $endMemory = memory_get_usage();

        $duration = ($endTime - $startTime) * 1000; // 毫秒
        $memory = ($endMemory - $startMemory) / 1024 / 1024; // MB

        // 记录慢请求
        if ($duration > 1000) {
            Log::warning('慢请求', [
                'url' => $request->url(),
                'method' => $request->method(),
                'duration' => round($duration, 2) . 'ms',
                'memory' => round($memory, 2) . 'MB',
            ]);
        }

        return $response;
    }
}

日志查看

后台查看

class LogController
{
    // 日志列表
    public function index()
    {
        $type = request()->param('type', '');
        $level = request()->param('level', '');
        $date = request()->param('date', date('Y-m-d'));

        $query = SystemLog::query();

        if ($type) {
            $query->where('type', $type);
        }

        if ($level) {
            $query->where('level', $level);
        }

        $query->whereTime('create_time', $date);

        $logs = $query->order('id', 'desc')->paginate(50);

        return view('log/index', ['logs' => $logs]);
    }

    // 日志详情
    public function detail($id)
    {
        $log = SystemLog::find($id);
        return view('log/detail', ['log' => $log]);
    }

    // 清理日志
    public function clear()
    {
        $days = request()->param('days', 30);

        SystemLog::where('create_time', '<', time() - $days * 86400)
            ->delete();

        return json(['code' => 200, 'msg' => '清理成功']);
    }
}

命令行查看

# 查看实时日志
tail -f runtime/log/202401/15.log

# 查看错误日志
tail -f runtime/log/error.log

# 搜索关键词
grep "ERROR" runtime/log/*.log

# 统计错误数量
grep -c "ERROR" runtime/log/*.log

日志分析

# 分析访问日志
php think log:analyze --type=access --date=2024-01-15

# 分析错误日志
php think log:analyze --type=error --date=2024-01-15

# 生成报表
php think log:report --date=2024-01-15

日志存储

文件存储

// 按日期分割
'daily' => [
    'type' => 'File',
    'path' => runtime_path() . 'log/',
    'apart_level' => ['error', 'warning'],
    'max_files' => 30,
],

// 按大小分割
'size' => [
    'type' => 'File',
    'file_size' => 10 * 1024 * 1024, // 10MB
    'max_files' => 10,
],

数据库存储

创建日志表

CREATE TABLE `system_logs` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `level` varchar(20) DEFAULT NULL,
  `message` text,
  `context` text,
  `create_time` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `level` (`level`),
  KEY `create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

配置

'database' => [
    'type' => 'Database',
    'table' => 'system_logs',
    'level' => ['error', 'warning', 'info'],
],

Redis 存储

'redis' => [
    'type' => 'Redis',
    'host' => '127.0.0.1',
    'port' => 6379,
    'key' => 'cms_log',
    'expire' => 86400, // 1天
],

日志清理

自动清理

// 定时任务清理
class CronJob
{
    public function cleanLogs()
    {
        $days = 30;

        // 清理文件日志
        $logPath = runtime_path() . 'log';
        $files = glob($logPath . '/*.log');

        foreach ($files as $file) {
            if (time() - filemtime($file) > $days * 86400) {
                unlink($file);
                Log::info('清理日志文件', ['file' => basename($file)]);
            }
        }

        // 清理数据库日志
        SystemLog::where('create_time', '<', time() - $days * 86400)
            ->delete();
    }
}

手动清理

# 清理30天前的日志
php think log:clear --days=30

# 清理所有日志
php think log:clear --all

# 清理指定类型
php think log:clear --type=error --days=7

日志分析工具

ELK Stack

Logstash 配置

input {
  file {
    path => "/var/www/cms/runtime/log/*.log"
    start_position => "beginning"
  }
}

filter {
  json {
    source => "message"
  }
}

output {
  elasticsearch {
    hosts => ["localhost:9200"]
    index => "cms-logs-%{+YYYY.MM.dd}"
  }
}

日志统计

class LogAnalyzer
{
    public function analyze($date)
    {
        $logs = SystemLog::whereTime('create_time', $date)->select();

        $stats = [
            'total' => $logs->count(),
            'by_level' => [],
            'by_hour' => [],
            'top_errors' => [],
        ];

        // 按级别统计
        foreach ($logs as $log) {
            $level = $log->level;
            $stats['by_level'][$level] = ($stats['by_level'][$level] ?? 0) + 1;
        }

        // 按小时统计
        foreach ($logs as $log) {
            $hour = date('H', $log->create_time);
            $stats['by_hour'][$hour] = ($stats['by_hour'][$hour] ?? 0) + 1;
        }

        // 高频错误
        $errors = $logs->where('level', 'error');
        $groupedErrors = [];
        foreach ($errors as $error) {
            $key = md5($error->message);
            if (!isset($groupedErrors[$key])) {
                $groupedErrors[$key] = [
                    'message' => $error->message,
                    'count' => 0,
                ];
            }
            $groupedErrors[$key]['count']++;
        }

        // 按次数排序
        usort($groupedErrors, function($a, $b) {
            return $b['count'] - $a['count'];
        });

        $stats['top_errors'] = array_slice($groupedErrors, 0, 10);

        return $stats;
    }
}

告警通知

错误告警

class LogHandler
{
    public function handle($level, $message, $context)
    {
        // 严重错误发送通知
        if (in_array($level, ['error', 'critical', 'emergency'])) {
            $this->sendAlert($level, $message, $context);
        }
    }

    protected function sendAlert($level, $message, $context)
    {
        // 发送邮件
        Queue::push('app\job\SendEmail', [
            'email' => config('app.admin_email'),
            'subject' => "系统告警:$level",
            'content' => $message . "\n\n" . json_encode($context, JSON_PRETTY_PRINT),
        ]);

        // 发送钉钉通知
        $this->sendDingTalk($level, $message);
    }

    protected function sendDingTalk($level, $message)
    {
        $webhook = config('app.dingtalk_webhook');

        $data = [
            'msgtype' => 'text',
            'text' => [
                'content' => "[$level] $message",
            ],
        ];

        $ch = curl_init($webhook);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_exec($ch);
        curl_close($ch);
    }
}

阈值告警

class LogMonitor
{
    public function checkErrorRate()
    {
        // 统计最近1小时的错误率
        $total = SystemLog::whereTime('create_time', '-1 hour')->count();
        $errors = SystemLog::whereTime('create_time', '-1 hour')
            ->where('level', 'error')
            ->count();

        $errorRate = $total > 0 ? ($errors / $total) * 100 : 0;

        // 错误率超过阈值告警
        if ($errorRate > 10) {
            $this->sendAlert("错误率过高:{$errorRate}%");
        }
    }
}

最佳实践

日志规范

// ✓ 好的日志
Log::error('文章保存失败', [
    'article_id' => $id,
    'user_id' => $userId,
    'error' => $e->getMessage(),
]);

// ✗ 不好的日志
Log::error('保存失败');

避免敏感信息

// 过滤敏感字段
$data = $user->toArray();
unset($data['password']);

Log::info('用户注册', $data);

性能优化

// 异步写入日志
Queue::push('app\job\WriteLog', [
    'level' => 'info',
    'message' => '...',
    'context' => [...],
]);

// 批量写入
$logs = [];
foreach ($items as $item) {
    $logs[] = ['message' => '...'];
}
SystemLog::insertAll($logs);

相关资源

  • ThinkPHP 日志文档
  • 性能优化
  • 安全指南
在 GitHub 上编辑此页
Prev
定时任务
Next
安全指南