Laravel每日日志创建时权限设置错误

157
我有一个脚本,使用php artisan(使用root用户)运行,有时它会在apache的www-data用户之前创建每日日志文件,这意味着当真正的用户使用我的Web应用程序时,我会收到文件夹权限错误:

Failed to open stream: Permission denied

我每次都会将权限更改回www-data,但我希望通过始终以正确的权限创建日志文件来解决此问题。

我已考虑创建一个cron作业来创建文件或触摸文件,以确保每天都具有正确的权限,但我正在寻找不依赖于另一个脚本的更好的解决方法。

我们还考虑将php artisan包装在另一个脚本中,以确保始终使用www-data凭据运行,但有些操作实际上是root过程,Apache不应该被允许执行。还有其他建议吗?

设置一个 cron 任务,在每天午夜时分(当然要在正确的用户下)touch 一个新的日志文件。 - Ben Harold
@BenHarold 谢谢,我们已经考虑过了,但我不想涉及更多的脚本。 - NiRR
2
在这种情况下,您需要以要创建日志文件的用户身份运行 php artisan - Ben Harold
@BenHarold 再次感谢,我们也考虑过这种方法,可能是最好的选择,但我已经更新了问题,解释了为什么这也不是理想的选择。 - NiRR
2
我的解决方法是使用sudo crontab -u www-data -e以www-data用户身份执行cron。 - Nil Llisterri
你也可以在artisan命令中将所有者更改为脚本所有者,参见https://dev59.com/vl4c5IYBdhLWcg3wqbwC#56774146。 - Adam
18个回答

179

Laravel 5.6.10及以上版本的配置文件(config/logging.php)中,singledaily驱动程序均支持permission元素:

    'daily' => [
        'driver' => 'daily',
        'path' => storage_path('logs/laravel.log'),
        'level' => 'debug',
        'days' => 7,
        'permission' => 0664,
    ],

在引导脚本中无需使用Monolog。

具体来说,在https://github.com/laravel/framework/commit/4d31633dca9594c9121afbbaa0190210de28fed8中添加了支持。


25
应该将此内容放在官方文档中! - odupont
4
这个回答缺少了撇号,应该是 'permission' => '0664'。那么这个答案就完全正确了! - Phil
5
@Phil Nope - 这只是Monolog流处理程序的一个包装器,可以接受权限的整数。Monolog包装了http://php.net/manual/en/function.chmod.php-请注意,需要前导0以确保它是八进制值。 - Chris
16
'permission' => 0664 对我来说可行(不带引号) - Syclone
4
这并没有解决问题,因为它没有改变文件的所有者或组。两者仍然是“root”,当被“www-data”运行的脚本想要写入该文件时,仍然不被允许。@mysteryos的答案解决了这个问题(至少对我来说是这样)。 - Friedrich
显示剩余8条评论

77

重要提示:本答案与laravel 5.5+不兼容。请查看此答案:Custom (dynamic) log file names with laravel5.6

让我们从什么是常量开始。

你有一个由root运行的php artisan命令。

可以安全地假设该命令每天都会执行。

解决方法1:

鉴于默认情况下创建文件的用户具有写入权限,我们可以按用户将日志分离成这样:

App/start/global.php

/*
|--------------------------------------------------------------------------
| Application Error Logger
|--------------------------------------------------------------------------
|
| Here we will configure the error logger setup for the application which
| is built on top of the wonderful Monolog library. By default we will
| build a basic log file setup which creates a single file for logs.
|
*/

Log::useDailyFiles(storage_path().'/logs/laravel-'.posix_getpwuid(posix_geteuid())['name'].'.log');
如果您的www-data用户创建错误日志,结果将是:storage/logs/laravel-www-data-2015-4-27.log
如果您的root用户创建错误日志,结果将是:storage/logs/laravel-root-2015-4-27.log解决方案2: 更改Artisan命令使用的日志,在您的php脚本中。
在您的run()函数开头添加这一行:
Log::useFiles(storage_path().'/logs/laravel-'.__CLASS__.'-'.Carbon::now()->format('Y-m-d').'.log');

如果你的班级名称是 ArtisanRunner,那么你的日志文件将是:

storage/logs/laravel-ArtisanRunner-2015-4-27.log

结论:鉴于解决方案1能够按用户划分日志,因此更好,因为这样就不会发生任何错误。

编辑:正如Jason指出的那样,get_current_user() 返回脚本所有者的名称。因此,为了应用第一种解决方案,请将你的 Artisan 类文件的所有者更改为所需的用户名。


14
请注意,get_current_user()返回当前PHP脚本的所有者(根据php.net说明),而不是当前运行脚本的用户。我使用php_sapi_name()代替,它会提供PHP处理程序的名称(例如apache或cli),这通常会以不同的用户身份运行。 - Jason
1
我可以建议同时使用执行脚本的用户名称和php_sapi_name进行组合,因为许多用户可以从CLI执行Laravel,例如一些DBA访问您的服务器或者您可能希望Laravel CRON作为apache运行。您可以使用posix_getpwuid(posix_geteuid())['name']获取执行此脚本的进程名称。 请查看我的完整帖子。 - Andrew
3
需要更新为最新的Laravel版本:v5+。 - Andrew
@ShankarSBavan 这不兼容 Laravel 5.5+。 - Mysteryos
@ShankarSBavan 请查看此答案以获取兼容的解决方案:https://dev59.com/3qvka4cB1Zd3GeqPrVJz - Mysteryos

64

对于Laravel 5.1,我在bootstrap/app.php文件的底部使用以下代码(如文档中所述):

/**
 * Configure Monolog.
 */
$app->configureMonologUsing(function(Monolog\Logger $monolog) {
    $filename = storage_path('logs/laravel-'.php_sapi_name().'.log');
    $handler = new Monolog\Handler\RotatingFileHandler($filename);
    $monolog->pushHandler($handler);
});

当然,你还可以使用许多其他处理程序。


1
我真的很喜欢这个答案,因为1)它已经更新到5.1,并且2)使用文档中的方法扩展了日志行为。 - Dylan Pierce
非常好,额外的前置闪存不是必需的,但仍然有效。它应该读取... $filename = storage_path('logs/laravel-'.php_sapi_name().'.log'); - Andrew
我可以建议同时使用执行脚本的用户名称和php_sapi_name来结合使用,因为许多用户可以从CLI执行Laravel,例如有几个DBA访问您的服务器,或者您可能希望Laravel CRON以apache身份运行。您可以使用posix_getpwuid(posix_geteuid())['name']来获取执行此脚本的进程名称。请查看我的完整帖子。 - Andrew
1
如何在Laravel 5.6中使用它?因为Laravel 5.6拥有全新的日志系统。 - Hamed Kamrava
这个适用于Laravel 5.3吗?请回复! - Mohal

31

为此,您应该在文件和目录上使用高级ACL。在这里,setfacl将是您的答案。如果您想让www-data用户有权限在特定目录中写入root文件,可以像这样操作:

setfacl -d -m default:www-data:you-chosen-group:rwx /my/folder

执行此命令会将www-data用户对/my/folder/下所有文件的权限设置为rwx,无论是谁创建的。请参考此问题此问题。此外,您可以查看setfacl文档

如果这有帮助,请告诉我。


5
以下命令对我起作用:setfacl -d -m g:www-data:rw /full/path/to/laravel/storage/logs,然后运行 php artisan cache:clearcomposer dump-autoload - Sawny

28

我用了一种非常简单的方法:

我在Laravel 5.6上遇到了同样的问题。

config/logging.php文件中,我只需将daily通道的路径值更新为php_sapi_name()

这将为不同的php_sapi_name创建单独的目录,并将日志文件放置在它们各自的目录中。

'daily' => [
            'driver' => 'daily',
            'path' => storage_path('logs/' . php_sapi_name() . '/laravel.log'),
            'level' => 'debug',
            'days' => 7,
        ]

对我而言,

  • 日志文件创建在fpm-fcgi目录下:来自网站的日志,owner: www-data
  • 日志文件创建在cli目录下:来自终端命令(cronjob)的日志,owner: root

Laravel 5.6日志记录的更多信息请参阅:https://laravel.com/docs/5.6/logging

这是我的config/logging.php文件:

<?php

return [
    /*
    |--------------------------------------------------------------------------
    | Default Log Channel
    |--------------------------------------------------------------------------
    |
    | This option defines the default log channel that gets used when writing
    | messages to the logs. The name specified in this option should match
    | one of the channels defined in the "channels" configuration array.
    |
    */
    'default' => env('LOG_CHANNEL', 'stack'),
    /*
    |--------------------------------------------------------------------------
    | Log Channels
    |--------------------------------------------------------------------------
    |
    | Here you may configure the log channels for your application. Out of
    | the box, Laravel uses the Monolog PHP logging library. This gives
    | you a variety of powerful log handlers / formatters to utilize.
    |
    | Available Drivers: "single", "daily", "slack", "syslog",
    |                    "errorlog", "custom", "stack"
    |
    */
    'channels' => [
        'stack' => [
            'driver' => 'stack',
            'channels' => ['daily'],
        ],
        'single' => [
            'driver' => 'single',
            'path' => storage_path('logs/laravel.log'),
            'level' => 'debug',
        ],
        'daily' => [
            'driver' => 'daily',
            'path' => storage_path('logs/' . php_sapi_name() . '/laravel.log'),
            'level' => 'debug',
            'days' => 7,
        ],
        'slack' => [
            'driver' => 'slack',
            'url' => env('LOG_SLACK_WEBHOOK_URL'),
            'username' => 'Laravel Log',
            'level' => 'critical',
        ],
        'syslog' => [
            'driver' => 'syslog',
            'level' => 'debug',
        ],
        'errorlog' => [
            'driver' => 'errorlog',
            'level' => 'debug',
        ],
    ],
];

很好,你的解决方案更简洁了。我正在测试它。 - Sina Miandashti
1
正如另一条评论所指出的那样,日志只是故事的一部分。编译视图、数据缓存、预缓存源代码,这些都可能由Web或CLI用户创建为本地文件。 - Jason
3
如果你使用 artisan config:cache 命令缓存配置,那么这个方法就不会生效,因为该命令会在命令行 SAPI 中创建一个配置缓存文件,该文件将同时用于命令行和 Web 请求。 - leeb
1
这对我有效,尝试了 get_current_user 不起作用,但是 php_sapi_name 起作用(尽管看起来更丑)。 - Richard Fu
我认为这是最快捷和最好的方式。修改配置不会修改Laravel的基本结构,只是修改配置。 - William Prigol Lopes

20

对我来说,这个问题不仅限于日志权限...我遇到过与 bootstrap/cache 和 storage 文件夹相关的任何问题,其中一个用户创建文件/文件夹时,另一个用户由于标准的 644 和 755 权限而无法编辑/删除。

典型的情况包括:

  • apache 用户创建的bootstrap/cache/compiled.php 文件无法被 composer 用户在执行 composer install 命令时编辑

  • apache 用户创建了缓存,composer 用户无法清除

  • 上述可怕的日志竞争条件。

理想状态是,无论哪个用户创建文件/文件夹,需要访问的其他用户都具有与原始作者完全相同的权限。

简洁版:

下面是操作步骤。

我们需要创建一个名为 laravel 的共享用户组,该组由需要访问 storage 和 bootstrap/cache 目录的所有用户组成。 接下来,我们需要确保新创建的文件和文件夹具有 laravel 组和分别为 664 和 775 的权限。

对于现有文件/目录,这样做很容易,但需要进行一些魔法调整默认的文件/文件夹创建规则...

## create user group
sudo groupadd laravel

## add composer user to group
sudo gpasswd -a composer-user laravel

## add web server to group
sudo gpasswd -a apache laravel

## jump to laravel path
sudo cd /path/to/your/beautiful/laravel-application

## optional: temporary disable any daemons that may read/write files/folders
## For example Apache & Queues

## optional: if you've been playing around with permissions
## consider resetting all files and directories to the default
sudo find ./ -type d -exec chmod 755 {} \;
sudo find ./ -type f -exec chmod 644 {} \;

## give users part of the laravel group the standard RW and RWX
## permissions for the existing files and folders respectively
sudo chown -R :laravel ./storage
sudo chown -R :laravel ./bootstrap/cache
sudo find ./storage -type d -exec chmod 775 {} \;
sudo find ./bootstrap/cache -type d -exec chmod 775 {} \;
sudo find ./storage -type f -exec chmod 664 {} \;
sudo find ./bootstrap/cache -type f -exec chmod 664 {} \;


## give the newly created files/directories the group of the parent directory 
## e.g. the laravel group
sudo find ./bootstrap/cache -type d -exec chmod g+s {} \;
sudo find ./storage -type d -exec chmod g+s {} \;

## let newly created files/directories inherit the default owner 
## permissions up to maximum permission of rwx e.g. new files get 664, 
## folders get 775
sudo setfacl -R -d -m g::rwx ./storage
sudo setfacl -R -d -m g::rwx ./bootstrap/cache

## Reboot so group file permissions refresh (required on Debian and Centos)
sudo shutdown now -r

## optional: enable any daemons we disabled like Apache & Queues

仅出于调试目的,我发现将日志拆分为cli/web +用户对于我的情况非常有益,因此我稍微修改了Sam Wilson的答案。我的用例是队列在自己的用户下运行,因此它有助于区分使用cli(例如单元测试)和队列守护程序的composer用户之间的区别。

$app->configureMonologUsing(function(MonologLogger $monolog) {
     $processUser = posix_getpwuid(posix_geteuid());
     $processName= $processUser['name'];

     $filename = storage_path('logs/laravel-'.php_sapi_name().'-'.$processName.'.log');
     $handler = new MonologHandlerRotatingFileHandler($filename);
     $monolog->pushHandler($handler);
}); 

这非常好。但是,一旦您运行了“setfacl”命令,您的“configureMonologUsing”代码是否仍然必要? - jeff-h

10

7

Laravel 5.1

在我们的案例中,我们想要创建所有日志文件,以便部署组中的所有内容都具有读写权限。因此,我们需要使用0664权限来创建所有新文件,而不是默认的0644权限。

我们还添加了一个格式化程序来添加换行符,以提高可读性:

$app->configureMonologUsing(function(Monolog\Logger $monolog) {
    $filename = storage_path('/logs/laravel.log');
    $handler = new Monolog\Handler\RotatingFileHandler($filename, 0, \Monolog\Logger::DEBUG, true, 0664);
    $handler->setFormatter(new \Monolog\Formatter\LineFormatter(null, null, true, true));
    $monolog->pushHandler($handler);
});

此外,还有可能将此与被接受的答案相结合。
$app->configureMonologUsing(function(Monolog\Logger $monolog) {
    $filename = storage_path('/logs/laravel-' . php_sapi_name() . '.log');
    $handler = new Monolog\Handler\RotatingFileHandler($filename, 0, \Monolog\Logger::DEBUG, true, 0664);
    $handler->setFormatter(new \Monolog\Formatter\LineFormatter(null, null, true, true));
    $monolog->pushHandler($handler);
});

6

Laravel 5.5

将以下代码添加到bootstrap/app.php文件中:

$app->configureMonologUsing(function (Monolog\Logger $monolog) {
    $filename = storage_path('logs/' . php_sapi_name() . '-' . posix_getpwuid(posix_geteuid())['name'] . '.log');
    $monolog->pushHandler($handler = new Monolog\Handler\RotatingFileHandler($filename, 30));
    $handler->setFilenameFormat('laravel-{date}-{filename}', 'Y-m-d');
    $formatter = new \Monolog\Formatter\LineFormatter(null, null, true, true);
    $formatter->includeStacktraces();
    $handler->setFormatter($formatter);
});
  • 文件会以这种方式存储:laravel-2018-01-27-cli-raph.loglaravel-2018-01-27-fpm-cgi-raph.log,更易读。
  • 新行被保留(默认情况下适用于Laravel行为)。
  • 它与Laravel日志查看器兼容。

Laravel 5.6

您需要为记录器创建一个类

<?php

namespace App;

use Monolog\Logger as MonologLogger;

class Logger {
    public function __invoke(array $config)
    {
        $monolog = new MonologLogger('my-logger');
        $filename = storage_path('logs/' . php_sapi_name() . '-' . posix_getpwuid(posix_geteuid())['name'] . '.log');
        $monolog->pushHandler($handler = new \Monolog\Handler\RotatingFileHandler($filename, 30));
        $handler->setFilenameFormat('laravel-{date}-{filename}', 'Y-m-d');
        $formatter = new \Monolog\Formatter\LineFormatter(null, null, true, true);
        $formatter->includeStacktraces();
        $handler->setFormatter($formatter);
        return $monolog;
    }
}

然后,您需要在 config/logging.php 中注册它:
'channels' => [
    'custom' => [
        'driver' => 'custom',
        'via' => App\Logging\CreateCustomLogger::class,
    ],
],

与5.5版本的行为相同:
  • 它会存储类似这样的文件:laravel-2018-01-27-cli-raph.loglaravel-2018-01-27-fpm-cgi-raph.log,更易读。
  • 换行符被保留(遵循Laravel的默认行为)。
  • 它可以与Laravel日志查看器一起使用。

最佳答案!赞! - Shahid Karimi

4

(Laravel 5.6) 我最近遇到了同样的问题,我只需在/app/Console/Kernel.php中设置一个定时命令即可。

$schedule->exec('chown -R www-data:www-data /var/www/**********/storage/logs')->everyMinute();

我知道这有点过度,但它非常有效,并且自那以后我没有遇到任何问题。


1
它能工作吗?是的,但是这是最佳实践吗?我认为不是。 - Pablo Papalardo
我认为这太过于复杂了...如果你要做这样的事情,为什么不直接在cron中完成呢? - Gary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接