CodeIgniter 不内置管理员审计日志,必须通过钩子+自定义类+专用数据库表实现;log_message()仅输出无结构文本,缺失用户ID、URI、操作类型、参数等关键字段,无法满足可追溯、可关联、可查询的审计要求。

直接说结论:CodeIgniter 本身不内置管理员行为审计日志,必须靠钩子(hooks)+ 自定义类 + 数据库表三者组合实现,不能只改 $config['log_threshold'] 或依赖 log_message()。
为什么 log_message() 不适合做后台操作审计
它只写文本日志,没用户 ID、没请求 URI、没操作类型(增/删/改)、没参数快照,且默认按天切文件,查某次「删除订单」根本没法反向定位。审计日志的核心是「可追溯、可关联、可查询」,不是记录「谁在什么时候干了什么」的流水账。
常见错误现象:
– 在控制器里写 log_message('info', '管理员 '.$user.' 删除了订单 #'.$id),结果日志里只有字符串,没有 user_id 字段,无法 JOIN 用户表
– 把所有操作都记进 application/logs/,几个月后日志文件超百 MB,grep 查一条记录要等 8 秒
– 没记录请求方法(GET/POST)、IP、HTTP Referer,安全事件复盘时缺关键上下文
用 post_controller 钩子捕获真实操作行为
CodeIgniter 的 post_controller 钩子在控制器执行完、输出发送前触发,此时 $this->router、$this->session、$this->input 全部可用,是最稳妥的埋点时机。
实操建议:
- 确保
$config['enable_hooks'] = TRUE已开启(application/config/config.php) - 在
application/config/hooks.php中注册钩子,不要用post_system—— 那时 Session 可能未加载 - 钩子函数里优先取
$this->session->userdata('admin_id'), fallback 到$this->input->ip_address(),避免「guest」泛化 - 用
$this->router->class和$this->router->method记录实际访问的控制器/方法,比解析 URI 更可靠
示例钩子调用逻辑:
$hook['post_controller'] = [
'class' => 'Admin_audit',
'function' => 'record',
'filename' => 'Admin_audit.php',
'filepath' => 'hooks'
];
审计日志表结构必须包含这 5 个字段
别直接往 logs 表里硬塞,建专用表 admin_audit_logs,否则和 RestServer 日志或系统错误日志混在一起,后期清洗成本爆炸。
关键字段(MySQL 示例):
-
id:自增主键 -
admin_id:INT,关联管理员表,允许为 NULL(如登录失败场景) -
uri:VARCHAR(255),存$this->uri->uri_string(),不是$_SERVER['REQUEST_URI'](含 query string 冗余) -
method:ENUM('GET','POST','PUT','DELETE'),用$this->input->method(TRUE)获取大写值 -
params:TEXT,存json_encode($this->input->post() ?: $this->input->get()),但要过滤敏感字段(如 password、token) - 额外建议字段:
ip_address、user_agent、created_at(用date('Y-m-d H:i:s'))
注意:params 字段别用 PHP 序列化——JSON 更易被外部 BI 工具解析,也方便 MySQL 5.7+ 的 JSON_EXTRACT 查询。
钩子类里怎么安全记录数据库操作
别在钩子里直接写 $this->db->insert(),容易和当前事务冲突(比如你在删用户,钩子又插日志,事务回滚时日志却留着)。正确做法是用独立 DB 实例。
实操建议:
- 在钩子类构造函数中,用
$db = \Config\Database::connect('audit')(CodeIgniter 4)或$db = $this->load->database('audit', TRUE)(CI3)获取隔离连接 - 如果没配多数据库,至少用
$db->simple_query("INSERT INTO ...")绕过 Active Record 的事务管理 - 加 try/catch 包裹插入逻辑,失败时写到文件日志兜底:
log_message('error', 'Audit log insert failed: '.$e->getMessage()) - 对 POST/PUT 参数做白名单过滤:
unset($params['password'], $params['api_key'], $params['csrf_token'])
容易踩的坑:
– 直接 dump $_POST,导致 token、密码明文落库
– 忘记转义 JSON 字符串里的双引号,插入 SQL 报错
– 用 microtime(TRUE) 当时间戳,但数据库字段是 DATETIME,得先 date('Y-m-d H:i:s', $ts)
真正难的不是写进去,而是查出来——审计日志一旦量大,LIKE %keyword% 会拖垮数据库。上线前必须给 admin_id、created_at 加联合索引,URI 长度超过 191 就别索引全文了,用 Elasticsearch 同步更实际。


















