我们项目框架是 ThinkPHP5.0.24
,系统环境为 Linux(Red Hat 4.8.5-11)
。
因为一些原因,每次到月初,项目应用总会报错:
error_log(/******/runtime/log/201903/20_sql.log): failed to open stream: Permission denied
记录一下,解决的过程和方法。
根据报错信息可以知道,有可能是文件夹的权限问题。
解决方法是: 通过命令行或者图形化工具授予该日志文件夹 777 的权限 。
如果问题就这么解决了,就不会有这篇文章了。
这个月处理完之后,下个月同样会出现这个问题。
这就引起重视了呀。不然,难道每个月都要调闹钟、手动授权新增的文件夹写入的权限吗?
这不行。
欸!新增文件夹?
为什么新增的文件夹会权限不足呢?
查看各级日志文件夹信息:
> ll runtime/log/201903/
total 588
-rw-r--r-- 1 root root 370701 Mar 20 23:24 20.log
-rw-r--r-- 1 root root 217029 Mar 20 21:52 20_sql.log
-rw-r--r-- 1 root root 1192016 Mar 21 15:03 21_cli.log
此时文件夹内只能生成所属 root
的文件。
> ll runtime/log/
total 4
drwxr-xr-x 2 root root 4096 Mar 20 17:55 201903 // 此时报错,error 日志无权限写入
// 改成 drwxrwxrwx 2 root root 4096 Mar 20 17:55 201903 可解决,但是下月问题重现
// 删除此文件夹,主动请求接口生成:drwxr-xr-x 2 www www 4096 Mar 20 17:55 201903 可顺利写入
经过删除文件夹「runtime/log/201903/」,跑接口测试重新生成文件夹发现,此时生成的文件夹所属组和所属用户是 www
。
那么说明,出问题的这个文件夹(所属 root
)不是跑框架的接口记录日志时生成的。
那么这个 root
所属的文件夹是谁生成的呢?
观察成功生成的日志文件可发现有以 _cli.log
结尾的文件。这不是定时任务的日志文件吗?只有它能正常运行?
原因是:以 root
权限设置的 crontab
里每分钟执行的计划任务,在每个月初第一时间执行并在框架下创建了 _cli.log
类型的日志。所以该月的文件夹所属就为 root
,而用户 www
在此文件夹内默认没有写入的权限,从而导致的报错。
既然知道了是 crontab
的定时任务的问题,那就重点盘它吧。
在进一步了解 crontab
的知识1后得知,在 root
下可以设定指定用户(比如我的 www
用户)的时程表。原文如下:
-u user 是指设定指定 user 的时程表,这个前提是你必须要有其权限(比如说是 root)才能够指定他人的时程表。如果不使用 -u user 的话,就是表示设定自己的时程表。
通过以下操作:
> crontab -e -u www
把原来每分钟执行框架脚本的定时任务全部搬到了这个只针对 www
用户的时程表里。这样第一时间生成的文件夹「runtime/log/201903/」的所属就是 www
了。
搬到以 www
用户运行的时程表后,发现:
www
,也没有发生错误日志生成权限不足的问题(也就是上文提及的问题成功解决了,证明这个方向是可取的);_cli.log
等定时任务运行产生的日志文件,也就是说新的定时任务没有执行成功。排查为何定时任务没有执行成功。
先排查系统日志
> tail -f /var/log/cron
Mar 21 10:18:01 localhost CROND[22349]: (www) CMD (「时程表中的 command」)
... (www) MAIL (mailed 58 bytes of output but got status 0x004b#012)
注意到上面的 MAIL
的信息,查了一下:
这个其实是 postfix 的配置问题,本次的问题不在这,是计划任务在执行完之后会通过 postfix 发邮件给执行完的用户邮箱,现在发不出去导致的,修改下配置就行了。
修改配置:
> vim /etc/postfix/main.cf
inet_interfaces = all
inet_protocols = all
确保这两个都是all 就可以了,然后重启 postfix。
重启后确实没有了那条关于 MAIL
的信息,但是会有如下提醒:
You have new mail in /var/spool/mail/root
通过命令查看:
> tail -f /var/spool/mail/root
X-Cron-Env: <SHELL=/bin/bash>
X-Cron-Env: <HOME=/usr/share/httpd>
X-Cron-Env: <PATH=/usr/bin:/bin>
X-Cron-Env: <LOGNAME=www>
X-Cron-Env: <USER=www>
Message-Id: <***@***.localdomain>
Date: Thu, 21 Mar 2019 10:17:01 +0800 (CST)
/bin/bash: ***.log: Permission denied
好了,Permission denied,也就是说时程表里的任务后面的输出日志权限不足咯。
因为时程表改成了指定用户的了,所以权限不足也正常。
找到原因了,那么解决方案有两个:
www
用户写入的权限;基于种种原因想偷懒,比如时程表里跑的是框架内的 php
文件,会在框架内的日志文件夹生成 _cli.log
或 _error_cli.log
的日志,所以就选择了直接删掉了时程表中的输出日志部分。
删除了输出日志,但是如果报错,依旧会发送 mail
到 /var/spool/mail/root
,此时可以在命令后面加上 >/dev/null 2>&1
,就不会发送 mail
了。
至此,定时任务恢复正常。
也就是 KO!
通过命令:
> crontab -e
默认设定当前用户的时程表。
如果以 root
用户登陆(果然要尽量避免啊摔),设定的时程表就属于 root
的了。
测试如下:
try {
Log::write('test', 'error');
//Log::error('test:');
} catch (\Exception $e) {
echo '抛出异常啦!';
}
同样是无写入 error
文件的权限,Log::write
会抛出异常,中断程序;而 Log::error
不会抛出异常,也不生成文件。
虽然解决了困惑已久的问题很有成就感,但是归根结底是因为使用了 root
登陆管理服务器导致的问题。
流下了没有技术的泪水啊。希望同道中人能避免踩坑吧。
Linux crontab命令 ↩︎