I don't understand how to do
Set %level_name%: field width in \Monolog\Formatter\LineFormatter (PHP)
The class LineFormatter or its parent NormalizerFormatter does not appear to have a set method.
I am pretty new to PHP. I tried extending LineFormatter but ran into a fatal string error using the new class.
I tried searching for how to add custom formatting to the $output fields, but nothing seemed to fit what I am trying to do.
I did the following hack to pad the level_name. Any detailed suggestions how to this this correctly would be great.
public function format(LogRecord $record): string
{
$vars = parent::format($record);
$output = $this->format;
foreach ($vars['extra'] as $var => $val) {
if (false !== strpos($output, '%extra.'.$var.'%')) {
$output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output);
unset($vars['extra'][$var]);
}
}
foreach ($vars['context'] as $var => $val) {
if (false !== strpos($output, '%context.'.$var.'%')) {
$output = str_replace('%context.'.$var.'%', $this->stringify($val), $output);
unset($vars['context'][$var]);
}
}
if ($this->ignoreEmptyContextAndExtra) {
if (\count($vars['context']) === 0) {
unset($vars['context']);
$output = str_replace('%context%', '', $output);
}
if (\count($vars['extra']) === 0) {
unset($vars['extra']);
$output = str_replace('%extra%', '', $output);
}
}
/*
// Original Code
foreach ($vars as $var => $val) {
$output = str_replace('%'.$var.'%', $this->stringify($val), $output);
}
*/
foreach ($vars as $var => $val) {
if (false !== strpos($output, '%' . $var . '%')) {
if ("level_name" == $var) {
// https://www.elated.com/formatting-php-strings-printf-sprintf/
$padded_level_name = sprintf("%-9s", $this->stringify($val));
// Pad the level_name to make the log messages start in the same column.
$output = str_replace('%' . $var . '%', $padded_level_name, $output);
} else {
$output = str_replace('%' . $var . '%', $this->stringify($val), $output);
}
}
}
// remove leftover %extra.xxx% and %context.xxx% if any
if (false !== strpos($output, '%')) {
$output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
if (null === $output) {
$pcreErrorCode = preg_last_error();
throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode));
}
}
return $output;
}
Now I see the expected padding:
C:\xampp\htdocs\login>php MonologTest.php
[2023-07-05T05:49:11.063827+02:00] <MonologTest>: DEBUG This is a debug message.
[2023-07-05T05:49:11.064226+02:00] <MonologTest>: INFO This is an info level message.
[2023-07-05T05:49:11.064372+02:00] <MonologTest>: WARNING This is a warning level message.
[2023-07-05T05:49:11.064509+02:00] <MonologTest>: ERROR This is an error level message.
[2023-07-05T05:49:11.064631+02:00] <MonologTest>: NOTICE This is a notice level message.
[2023-07-05T05:49:11.064757+02:00] <MonologTest>: CRITICAL This is a critical level message.
[2023-07-05T05:49:11.064879+02:00] <MonologTest>: ALERT This is an alert level message.
[2023-07-05T05:49:11.065005+02:00] <MonologTest>: EMERGENCY This is an emergency level message.
Here is the complete test file:
<?php
require_once('vendor/autoload.php');
use Monolog\Logger;
use Monolog\Level;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\LineFormatter;
use Monolog\LogRecord;
use Monolog\Utils;
/*
class MyLineFormatter extends LineFormatter {
public function format(LogRecord $record): string
{
$vars = parent::format($record);
$output = $this->format;
foreach ($vars['extra'] as $var => $val) {
if (false !== strpos($output, '%extra.'.$var.'%')) {
$output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output);
unset($vars['extra'][$var]);
}
}
foreach ($vars['context'] as $var => $val) {
if (false !== strpos($output, '%context.'.$var.'%')) {
$output = str_replace('%context.'.$var.'%', $this->stringify($val), $output);
unset($vars['context'][$var]);
}
}
if ($this->ignoreEmptyContextAndExtra) {
if (\count($vars['context']) === 0) {
unset($vars['context']);
$output = str_replace('%context%', '', $output);
}
if (\count($vars['extra']) === 0) {
unset($vars['extra']);
$output = str_replace('%extra%', '', $output);
}
}
// Original Code
// foreach ($vars as $var => $val) {
// $output = str_replace('%'.$var.'%', $this->stringify($val), $output);
// }
foreach ($vars as $var => $val) {
if (false !== strpos($output, '%' . $var . '%')) {
if ("level_name" == $var) {
// https://www.elated.com/formatting-php-strings-printf-sprintf/
$padded_level_name = sprintf("%-9s", $this->stringify($val));
// Pad the level_name to make the log messages start in the same column.
$output = str_replace('%' . $var . '%', $padded_level_name, $output);
} else {
$output = str_replace('%' . $var . '%', $this->stringify($val), $output);
}
}
}
// remove leftover %extra.xxx% and %context.xxx% if any
if (false !== strpos($output, '%')) {
$output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
if (null === $output) {
$pcreErrorCode = preg_last_error();
throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode));
}
}
return $output;
}
}
*/
$logger = new Logger("MonologTest");
// Log to the console.
$stream_handler = new StreamHandler("php://stdout", Level::Debug);
$output = "[%datetime%] <%channel%>: %level_name% %message%\n";
$stream_handler->setFormatter(new LineFormatter($output));
$logger->pushHandler($stream_handler);
// Log to a file.
$logger->pushHandler(new StreamHandler(__DIR__ . '/test.log', Level::Debug));
$logger->debug("This is a debug message.");
$logger->info("This is an info level message.");
$logger->warning("This is a warning level message.");
$logger->error("This is an error level message.");
$logger->notice("This is a notice level message.");
$logger->critical("This is a critical level message.");
$logger->alert("This is an alert level message.");
$logger->emergency("This is an emergency level message.");