Custom (dynamic) log file names with laravel5.6 - php

With laravel 5.5 we had access to configureMonologUsing() method in $app which made it possible for things like this in bootstrap/app.php:
$app->configureMonologUsing(function (Monolog\Logger $monolog) {
$processUser = posix_getpwuid(posix_geteuid());
$processName= $processUser['name'];
$filename = storage_path('logs/laravel-' . php_sapi_name() . '-' . $processName . '.log');
$handler = new Monolog\Handler\RotatingFileHandler($filename);
$monolog->pushHandler($handler);
});
Doing this is useful when your app may be called from different contexts (eg CLI/HTTP) with different users (which is desirable) and file rotation. Doing this prevents write errors in case the log file was created by the HTTP user before the CLI one tries to add something in it and vice-versa.
Handling this is otherwise tricky or insecure as it involves to be able to set write perms on files that may not exist yet.
In addition, it's quite handy to have logs separated by contexts as they usually have little in common and it makes it easier to search among them.
Unfortunately, this way of doing things is not possible anymore with laravel 5.6 and I could not (yet) find a way to transparently do so for all file based logging.
Thanks

Customisation is now done through invoking a custom formatter for Monolog.
Here is an example using daily rotating filenames (as I do).
This can be setup in config/logging.php, note the non-default tap parameter:
'channels' => [
'daily' => [
'driver' => 'daily',
'tap' => [App\Logging\CustomFilenames::class],
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
],
]
In your custom formatter, you can manipulate the Monolog logger however you wish, similar to configureMonologUsing():
app\Logging\CustomFilenames.php
<?php
namespace App\Logging;
use Monolog\Handler\RotatingFileHandler;
class CustomFilenames
{
/**
* Customize the given logger instance.
*
* #param \Illuminate\Log\Logger $logger
* #return void
*/
public function __invoke($logger)
{
foreach ($logger->getHandlers() as $handler) {
if ($handler instanceof RotatingFileHandler) {
$sapi = php_sapi_name();
$handler->setFilenameFormat("{filename}-$sapi-{date}", 'Y-m-d');
}
}
}
}
One way to restore your original behaviour is to remove the {date} component from the handler's filenameFormat. A better way might be to manipulate the appropriate handler for the single driver.
See: https://laravel.com/docs/5.6/logging#advanced-monolog-channel-customization

Solution:
step1: create a channel inside the config/logging.php file
example :
'channels' => [
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
],
'web' => [
'driver' => 'single',
'path' => storage_path('logs/web/web.log'),
],
]
Step2: Now set dyanamic path from controller like this
config(['logging.channels.web.path' => storage_path('logs/web/'.time().'.log')]);
Step3 : now generate your log
Log::channel('web')->info("your message goes here");
Enjoy :)

Related

CakePHP4: Where should I put my custom PasswordHasher

I implemented CakePHP4 authentication following this:
https://book.cakephp.org/4/en/tutorials-and-examples/cms/authentication.html
It worked, then I need to use my custom PasswordHasher to satisfy the client requirements. I couldn't find any tutorial to do that, but figured out the following code works.
In Application.php:
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface {
// ....
$authenticationService->loadIdentifier('Authentication.Password', [
'fields' => [
'username' => 'username',
'password' => 'password',
],
'passwordHasher' => [
'className' => 'Authentication.MyCustom',
]
]);
The problem is that I need to put MyCustomPasswordHasher.php file in vendor\cakephp\authentication\src\PasswordHasher in order to make this work. Of course I don't want to put my own code under vendor directory.
Temporarily, I created and used src\Authentication\PasswordHasher directory and forced to make it work by doing this:
spl_autoload_register(function ($class) {
if (strpos($class, 'MyCustomPasswordHasher') !== false) {
require_once __DIR__ . '/' . str_replace(['\\', 'App/'], [DS, ''], $class) . '.php';
}
});
Is there any cleaner way to accomplish the purpose in CakePHP4? Where should I put custom codes?
Don't use plugin notation for the short name, pass only 'MyCustom', then it will look inside of your application, in the App\PasswordHasher namespace, so your class would accordingly go into
src/PasswordHasher/MyCustomPasswordHasher.php
Alternatively you can always pass a fully qualified name, meaning you could put your class wherever you want, as long as the composer autoloader can resolve it:
'passwordHasher' => [
'className' => \Some\Custom\Namespaced\PasswordHasher::class,
]

In Laravel can I set a default context for the Log facade

I'm using the Log:: facade a lot and have a helper class called LogHelper which provide me with a static method LogHelper::context() which include many key values I need to track the requests. But having to type it every time for each usage make it error prune and fill not so efficient.
I'm looking for a way to inject the values by default, and allow me to overwrite them if needed specifically.
At the moment this is how I use it,
Log::debug('Request Started', LogHelper::context());
what I'm looking for is to inject the context by default
Log::debug('Request Started');
and have the option to overwrite it, if need it:
Log::debug('Request Started', ['more' => 'context'] + LogHelper::context());
PS, the LogHelper::context() return a simple key => value array which include some staff i need to debug requests, and the reason it do not use the values directly in the message is because i log to graylog as structured data, and this way i can filter by any key.
I have solved this issue by using the tap functionality and $logger->withContext() (note: the latter was added in Laravel 8.49).
You want to create a new class which contains your context logic. I've created an extra Logging folder in app/ in which my logging customizations sit.
app/Logging/WithAuthContext.php:
<?php
namespace App\Logging;
use Illuminate\Log\Logger;
class WithAuthContext
{
public function __invoke(Logger $logger)
{
$logger->withContext([
'ip' => request()?->ip(),
'ua' => request()?->userAgent(),
]);
}
}
Depending on which logging channel(s) you use, you will have to add the class to each one you want to add context to. So in app/config/logging.php:
<?php
use App\Logging\WithAuthContext;
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
return [
// ...
'channels' => [
// ...
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'tap' => [WithAuthContext::class],
],
// ...
],
];
There is a way, but it is not pretty. You can create a custom monolog logger driver. The process is described at https://laravel.com/docs/8.x/logging#creating-monolog-handler-channels.
Here's a possible implementation:
class ContextEnrichingLogger extends \Monolog\Handler\AbstractHandler {
private $logger;
public function __construct($level = Monolog\Logger::DEBUG, bool $bubble = true, $underlyingLogger = 'single') {
$this->logger = Log::driver($underlyingLogger);
}
public function handle(array $record) {
$record['context'] += LogHelper::context();
return $this->logger->handle($record);
}
}
Then register this as a custom logger in your config/logging.php:
return [
'default' => 'enriched',
//...
'channels' => [
// ...
'enriched' => [
'driver' => 'monolog',
'handler' => ContextEnrichingLogger::class,
'level' => env('APP_LOG_LEVEL', 'debug'),
"with" => [
"underlyingLogger" => env('LOG_CHANNEL', 'single')
]
]
]
];
I haven't tested this particular one but this is how I've defined other custom loggers.
Note, this is probably also achievable via a custom formatter though I think it's probably the same trouble.

TYPO3 v10 - Getting feUser Object using context API in eID_include

Code that i used and need to update for V10
$this->feUser = EidUtility::initFeUser();
When using the following code (a random) controller, the context gives me the correct login feUser object.
$context = GeneralUtility::makeInstance(Context::class);
$user = $context->getAspect('frontend.user');
DebuggerUtility::var_dump($user);
When using the same code in an eID_include class No userObject is given.
Specificly in the following class
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['FileDumpEID.php']['checkFileAccess']['xxx'] = My\Class\Hooks\FileDumpHook:class
Is there a need of bootstrapping context?
Since the TYPO3\CMS\Frontend\Middleware\EidHandler middleware is executed before the TYPO3\CMS\Frontend\Middleware\FrontendUserAuthenticator middleware in the middlewares order i dont think, that this is possible.
If you need parts of the frontend handling you either can add an own middleware with depend of TYPO3\CMS\Frontend\Middleware\FrontendUserAuthenticator or use an page Object in typoscript.
I had the same problem. You may change the order of Middlewares: https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/RequestHandling/Index.html
I've created a new file RequestMiddlewares.php inside the "Configuration" directory of my extension:
<?php
return [
'frontend' => [
'typo3/cms-frontend/eid' => [
'disabled' => true
],
'typo3/cms-frontend/eid-new' => [
'target' => \TYPO3\CMS\Frontend\Middleware\EidHandler::class,
'after' => [
'typo3/cms-frontend/tsfe',
],
'before' => [
'typo3/cms-frontend/prepare-tsfe-rendering',
]
]
]
];
You have to flush TYPO3 and PHP Cache and check the ordering in "Configuration" backend module (select "HTTP Middlewares (PSR-15)").
With this setup it is possible to get the context property 'frontent.user'
$context = GeneralUtility::makeInstance(Context::class);
if($context->getPropertyFromAspect('frontend.user', 'isLoggedIn')) {

Laravel: Adding log channels outside logging.php configuration (plugin development)

I'm working on a laravel plugin/package.
We want to define our own loggers outside of the main project configuration (config/logger.php)
I have tried the following in the ServiceProvider register() function.
Based on the MonoLogger test code.
$partLogger = new Logger('vendor_part-log');
$partLogHandler = new StreamHandler(storage_path('logs/vendor/part-log.log', Logger::DEBUG));
$partLogger->pushHandler($partLogHandler);
// MonoLog Registry
Registry::addLogger($partLogger, 'vendor_part-log');
Sadly this doesn't work inside Laravel.
I also can't get the other existing loggers from Registry::
So the problem is that the new channel won't register.
Is there a different Registry in use inside Laravel or do I need an entirely different solution to achieve this?
While we'd still like a more automated solution, we've compromised and are using a static function as source of the configuration.
In config/logging.php:
At the top replace return [... with $config = [....
And at the bottom add the following lines:
$config['channels'] = array_merge(
$config['channels'],
\FooVendor\Bar\Classes\Logs::getLogs(),
);
return $config;
And create the mentioned class with the following function:
/**
* Configs to be merged in config/logging.php
* #return array
*/
public static function getLogs()
{
return [
'foo_partlogger' => [
'driver' => 'single',
'path' => storage_path('logs/foo/partlogger.log'),
'level' => 'debug',
],
'foo_second_partlogger' => [
'driver' => 'single',
'path' => storage_path('logs/foo/second_partlogger.log'),
'level' => 'debug',
],
];
}

ZF2 Zend Logger - Filters by priority

I'm using the Zend Logger with following configuration in the module_config.php:
'log' => [
\Base\Log\ConsoleLoggerAwareInterface::class => [
'writers' => [
'standard-output' => [
'name' => 'stream',
'options' => [
'stream' => 'php://output',
],
],
],
],
]
But as it is I can't influence the log to filter or suppress messages.
The goal is to improve the logging to be smarter.
I think I need to add a filter to the writer but I can't find an example how to do that in the module_config.php.
Is there also a way to call a script (e.g. from within a cron) by using a verbosity level?
I hope you know what I'm trying to achive.
Edit:
I have this example in my code:
$this->consoleLogger->emerg('EMERG');
$this->consoleLogger->alert('ALERT');
$this->consoleLogger->crit('CRIT');
$this->consoleLogger->err('ERR');
$this->consoleLogger->warn('WARN');
$this->consoleLogger->notice('NOTICE');
$this->consoleLogger->info('INFO');
$this->consoleLogger->debug('DEBUG');
Should it then not output the filtered ones?
Q: How to filter a Log Writer to a specific error level?
Add a filters key to the particular writer's configuration. Here's direct instantiation to remove any configuration peculiarities: this outputs only "WARN" and "EMERG" messages:
$config = [
'writers' => [
'standard-output' => [
'name' => 'stream',
'options' => [
'stream' => 'php://output',
'filters' => \Zend\Log\Logger::WARN,
],
],
],
];
$logger = new \Zend\Log\Logger($config);
$logger->emerg('EMERG');
$logger->warn('WARN');
$logger->debug('DEBUG');
Adding the filters configuration to your modules_config.php should have a similar effect. If not, check your zend-log version (with eg composer show) and advise.
Q: How to change error level filter with the -v command line parameter?
AFAIK, there is no automatic way to bind the standard verbose flag (-v) with a particular logging level. So you'll have to write your own filter. One thing that's neat to know is that the filters key can take:
an int (as done above, which translates to the built-in log level);
a string (corresponding to a class name implementing \Zend\Log\Filter\FilterInterface);
an object (instance of \Zend\Log\Filter\FilterInterface);
or an array of these.
You can use this flexibility to solve your need of binding a command line parameter to a log value. Here is a custom class that shows emergency events by default, but for every v on the command line increases the shown priority:
class CliLoggingFilter implements \Zend\Log\Filter\FilterInterface
{
public function __construct()
{
$this->level = \Zend\Log\Logger::EMERG;
if (array_key_exists('v', $args = getopt('v'))) {
$this->level += count($args['v']);
}
}
public function filter(array $event)
{
return ($event['priority'] <= $this->level);
}
}
You'd then have a configuration like: 'filters' => CliLoggingFilter::class.
$ php public/index.php
2016-07-25T10:57:28-04:00 EMERG (0): EMERG
$ php public/index.php -vvvv
2016-07-25T10:57:32-04:00 EMERG (0): EMERG
2016-07-25T10:57:32-04:00 WARN (4): WARN
$ php public/index.php -vvvvvvv
2016-07-25T10:57:34-04:00 EMERG (0): EMERG
2016-07-25T10:57:34-04:00 WARN (4): WARN
2016-07-25T10:57:34-04:00 DEBUG (7): DEBUG
Q: How to change all routes to use -v?
AFAIK, there is no way to specify a global command line parameter. You need to either (a) update all your console routes to accept the argument or (b) pass the log level a different way.
Updating all your routes isn't terribly hard. You can define a variable that holds the value and then include that in the configuration, like so:
$globalConsoleRouteParams = '[--verbose|-v]';
return [ 'console' => 'router' => 'routes' => [
'foo' => [ 'options' => [ 'route' => "foo $globalConsoleRouteParams ..." ] ],
'bar' => [ 'options' => [ 'route' => "bar $globalConsoleRouteParams ..." ] ],
// ...
]];
Alternatively, you could use say environment variables to pass your desired log level, plus perhaps any additional configuration you might desire. Modifying our earlier example:
class CliLoggingFilter implements \Zend\Log\Filter\FilterInterface
{
public function __construct()
{
$this->level = \Zend\Log\Logger::EMERG;
if (false !== ($level = getenv('LOG_LEVEL'))) {
$this->level = $level;
}
}
// ...
}
Then it may be invoked like
$ LOG_LEVEL=7 php index.php foo

Categories