0

I am using the Monolog LineFormatter as follows:

$output = "[%datetime%] %level_name% %channel%: %message%\n";
$stream_handler->setFormatter(new LineFormatter($output));

This results in the following log lines:

[2023-07-03T05:30:31.327443+02:00] DEBUG test: This is a debug message.
[2023-07-03T05:30:31.327555+02:00] INFO test: This is an info level message.
[2023-07-03T05:30:31.327683+02:00] WARNING test: This is a warning level message.
[2023-07-03T05:30:31.327806+02:00] ERROR test: This is an error level message.

How would you set the %level% width (like padding in printf)? I am trying to get this output:

[2023-07-03T05:30:31.327443+02:00] DEBUG   test: This is a debug message.
[2023-07-03T05:30:31.327555+02:00] INFO    test: This is an info level message.
[2023-07-03T05:30:31.327683+02:00] WARNING test: This is a warning level message.
[2023-07-03T05:30:31.327806+02:00] ERROR   test: This is an error level message.

I tried adding "-10s " to the $output %-10s level_name% but this just added the literal text -10s to the log lines.

Thanks in advance.

DarkShade
  • 103
  • 6
Valon
  • 1
  • 1

2 Answers2

0

To set the width (padding) for the %level_name% placeholder in Monolog's LineFormatter, you can use the %-10s format specifier. However, you need to make sure you pass it as a string to the LineFormatter constructor.

Here's the modified code to achieve the desired output:

$output = "[%datetime%] %-10s %channel%: %message%\n";
$stream_handler->setFormatter(new LineFormatter($output));

By using %-10s, you indicate that %level_name% should be left-aligned with a width of 10 characters. The padding will be added after the log level name to reach the desired width.

DarkShade
  • 103
  • 6
  • Hi @DarkShade. $output = "[%datetime%] %-10s %channel%: %message%\n"; results in this logged message with the literal %-10s not the padded level. I'm just running PHP from the command line. : [2023-07-04T05:25:28.152908+02:00] %-10s test: This is a debug message. – Valon Jul 04 '23 at 04:13
0

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.");
Valon
  • 1
  • 1