可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a script that I run using php artisan (with root user), and sometimes it causes the daily log file to be created before the apache www-data user does - which means that when a real user uses my web application, I get the folder permission error:
Failed to open stream: Permission denied
I change the permissions back to www-data everytime but I want to solve this by having the log file always created with the correct permissions.
I've considered creating a cron job that creates the file or touches it to make sure it has the right permission everyday, but I'm looking for a better solution that doesn't rely on another script.
We've also considered wrapping php artisan in another script to make sure that it is always run with the www-data credentials, but somethings that we want to do are actually root procedures that apache should not be allowed to do.
Any more suggestions?
回答1:
Let's start with what is constant.
You have a php artisan
command, run by root
.
It is safe to assume that this command is executed daily.
Solution No 1:
Given that the user that creates the files is the one that has the permission to write to it by default, we can separate the logs by user as such:
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-'.get_current_user().'.log');
If your www-data user were to create an error log, it would result in: storage/logs/laravel-www-data-2015-4-27.log
.
If your root user were to create an error log, it would result in: storage/logs/laravel-root-2015-4-27.log
.
Solution No 2:
Change the log used by your artisan command, in your php script.
In your run()
function, add this line at the start:
Log::useFiles(storage_path().'/logs/laravel-'.__CLASS__.'-'.Carbon::now()->format('Y-m-d').'.log');
If your class's name is ArtisanRunner
, then your log file will be:
storage/logs/laravel-ArtisanRunner-2015-4-27.log
.
Conclusion: Solution number 1 is better, given that it delineates your logs by user, and hence no errors will occur.
EDIT: As pointed out by jason, get_current_user()
returns the script's owner name. Hence, for solution no.1 to apply, chown
your artisan class files to the required username.
回答2:
For Laravel 5.1 I use the following towards the bottom of bootstrap/app.php
(as mentioned in the docs):
/**
* 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);
});
There are lots of other Handlers that you can use instead, of course.
回答3:
Laravel version 5.6.10 and later has support for a permission
element in the configuration for the single
and the daily
driver:
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 7,
'permission' => 0664,
],
No need to juggle with Monolog in the bootstrap script.
Specifically, support was added in https://github.com/laravel/framework/commit/4d31633dca9594c9121afbbaa0190210de28fed8.
回答4:
For such purposes you should use advanced ACL on your files and directories. setfacl
would be your answer here. If you want to give www-data user permissions to write on root's files in specific directory, you can do it like this:
setfacl -d -m default:www-data:you-chosen-group:rwx /my/folder
After issuing this you're setting permissions to rwx
for www-data user on all files in /my/folder/
no matter who created those. Please, see this and this question for reference. Also, you can check docs for setfacl
.
Let me know if this helps.
回答5:
For me this issue was much more than log permissions...I had issues with anything related to the bootstrap/cache and storage folders where one user would create a file/folder and the other would be unable to edit/delete due to the standard 644 and 755 permissions.
Typical scenarios are:
The bootstrap/cache/compiled.php file being created by the apache user but being uneditable by the composer user when performing the composer install command
The apache user creating cache which cannot be cleared using the composer user
- The dreaded log race conditions described above.
The dream is that no matter which user creates the file/folder, the other users that need to access have exactly the same permissions as the original author.
TL;DR?
Here's how it's done.
We need to create a shared user group called laravel, the group consists of all the users that need access to the storage and bootstrap/cache directories.
Next we need to ensure newly created files and folders have the laravel group and 664 and 775 permissions respectively.
It's easy doing this for existing files/directories, but a little magic is needed to tweak the default file/folder creating rules...
## 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: 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
Purely for debugging purposes I found splitting the logs out into both cli/web + users was beneficial so I modified Sam Wilson's answer slightly. My use case was the queue ran under it's own user so it helped distinguish between the composer user using the cli (e.g. unit tests) and the queue daemon.
$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);
});
回答6:
I had this worked very simple way:
I ran into the same problem on Laravel 5.6
In config/logging.php
I just updated daily channel's path value with php_sapi_name()
in it.
This creates seperate durectory for different php_sapi_name and puts log file with the time stamp into their perticular directory.
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/' . php_sapi_name() . '/laravel.log'),
'level' => 'debug',
'days' => 7,
]
So for me,
- Log files are created under
fpm-fcgi
directory: Logs from website, owner: www-data
- Log files are created under
cli
directory: from the artisan command(cronjob). owner: root
More info on Laravel 5.6 logging: https://laravel.com/docs/5.6/logging
Here is my config/logging.php
file:
<?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',
],
],
];
回答7:
Laravel 5.1
In our case we wanted to create log files so that the all the processes and users in deploy
group had read/write permissions - so we needed new created files with permission 0664. Default for new log files is 0644. So this was our solution.
Also we added a formatter to add newlines and a more readable log
$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);
});
Also it's possible to combine this with the accepted answer
$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);
});
回答8:
Laravel 5.5
Add this code to 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);
});
- It will store files like this:
laravel-2018-01-27-cli-raph.log
and laravel-2018-01-27-fpm-cgi-raph.log
which is more readable.
- New lines are preserved (as of default Laravel behavior)
- It works with Laravel Log Viewer
Laravel 5.6
You have to create a class for your logger:
<?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;
}
}
Then, you have to register it in config/logging.php
:
'channels' => [
'custom' => [
'driver' => 'custom',
'via' => App\Logging\CreateCustomLogger::class,
],
],
Same behavior as for 5.5:
- It will store files like this:
laravel-2018-01-27-cli-raph.log
and laravel-2018-01-27-fpm-cgi-raph.log
which is more readable.
- New lines are preserved (as of default Laravel behavior)
- It works with Laravel Log Viewer
回答9:
Add something like the following to the start of your app/start/artisan.php
file (this is with Laravel 4):
// If effectively root, touch the log file and make sure it belongs to www-data
if (posix_geteuid() === 0) {
$file = storage_path() . '/logs/laravel.log';
touch($file);
chown($file, 'www-data');
chgrp($file, 'www-data');
chmod($file, 0664);
}
Adjust the path if the daily log file you mention is not the standard Laravel log file. You also may not want to change the group or set the permissions as I am doing here. The above sets the group to www-data
and sets group write permissions. I've then added my regular user to the www-data
group so that running artisan commands as my regular user can still write to the log.
A related tweak is to put the following at the start of your app/start/global.php
file:
umask(0002);
If you do this the chmod
line above becomes moot. With the umask set to this, any new files PHP (and therefore Laravel) makes will have their permissions masked only so that "other" users won't have write permissions. This means directories will start as rwxrwxr-x
and files as rw-rw-r--
. So if www-data
is running PHP, any cache and log files it makes will be writeable by default by anyone in that user's main group, which is www-data
.
回答10:
One non-Laravel way to make this work is to simply executure your cronjob as www-data.
eg https://askubuntu.com/questions/189189/how-to-run-crontab-as-userwww-data
/etc/crontab
*/5 * * * * www-data php /var/www/public/voto_m/artisan top >/dev/null 2>&1
回答11:
Laravel 5.4
\Log::getMonolog()->popHandler();
\Log::useDailyFiles(storage_path('/logs/laravel-').get_current_user().'.log');
add to boot
function in AppServiceProvider
回答12:
(Laravel 5.6) I recently ran into the same problem and I simply set a scheduled command to run in /app/Console/Kernel.php
.
$schedule->exec('chown -R www-data:www-data /var/www/**********/storage/logs')->everyMinute();
I know it's a little bit of overkill, but it works like a charm and haven't had any issues since.
回答13:
The best way I found is that fideloper suggest, http://fideloper.com/laravel-log-file-name, you can set laravel log configuration without touch Log class.
Have differents names for Console programs and Http programs, I think, is the best solution.