




最直接安全的PHP日志写入方案是用fopen()('a'模式)、fwrite()、flock()加锁、date('c')时间戳、HTML转义用户输入、绝对路径及分级隔离存储。
fopen()和fwrite()写日志文件最直接不需要额外扩展,原生函数就能搞定。关键不是“能不能写”,而是“怎么写才安全、可读、不丢日志”。fopen()打开文件时必须用'a'(追加)模式,绝不能用'w',否则每次写入都会清空旧内容。
'a' 模式自动定位到文件末尾,适合日志累加fopen()返回值是否为false,权限不足或路径不存在时会失败fclose(),否则可能缓存未刷盘,进程崩溃时丢日志/var/log/myapp/app.log,避免相对路径在CLI/Web下行为不一致纯文本日志要能快速定位问题,至少得有“什么时候”“什么级别”“发生了什么”。PHP的date('c')输出ISO 8601格式(如2025-05-22T14:30:45+08:00),兼容性好,也方便后续用awk或ELK解析。
[date('c')] [$level] $message\n
INFO、WARNING、ERROR等大写字符串,比数字更直观htmlspecialchars($msg, ENT_QUOTES, 'UTF-8')或json_encode()转义,防换行注入和乱码\n,否则所有内容会挤在同一行flock()锁Web服务器多进程或CLI多线程场景下,多个PHP进程同时fwrite()同一文件,会导致日志错乱甚至截断——这是最常被忽略的坑。
fwrite()前调用flock($fp, LOCK_EX),写完立即flock($fp, LOCK_UN)
file_put_contents(..., FILE_APPEND),它内部没做锁,高并发下照样乱syslog()或写入数据库,但本地文件仍是最快最轻量的选择error_log()触发系统级记录PHP自身的警告、致命错误(如Parse error)不会经过你的日志函数。想统一捕获,得靠set_error_handler()和register_shutdown_function(),但注意:前者抓不到Fatal error,后者才能补全。
error_log($msg, 3, '/path/to/php_error.log') 是绕过自定义函数、直写文件的保底方式register_shutdown_function()里用error_get_last()判断是否发生致命错误,再格式化写入
app.log和error.log分开更清晰