49

I use Monolog as a stand-alone library in my application and recently I ran into an issue. Let's say, at some point in my application I catch an exception and I want to log it:

$mylogger->error('Exception caught', array('exception' => $exception));

This works perfectly except one tiny thing - it doesn't log whole stack trace. Is it possible to log exception's full stack trace using monolog build-in formatters?

Koen.
  • 25,449
  • 7
  • 83
  • 78
Wild One
  • 751
  • 1
  • 5
  • 11

9 Answers9

44

Actually since version 1.12.0 it is possible to include stacktrace at your log file: there is new method of LineFormatter called includeStacktraces.

To use this, you need to overwrite default behaviour of monolog formatter:

config.yml

monolog:
    handlers:
        main:
            formatter: your.monolog.service.id
            (rest of config is as usual)

services.yml

services:
    your.monolog.service.id:
        class: Monolog\Formatter\LineFormatter
        calls:
            - [includeStacktraces]

Check github for more info: Pull request

Tomasz Madeyski
  • 10,742
  • 3
  • 50
  • 62
  • It also exists in the JsonFormatter – magnetik Mar 28 '17 at 07:27
  • 1
    I've added a [separate answer](https://stackoverflow.com/a/46379123/1344955) with the code needed to use said method without a config file. Users aren't allowed to make substantial edits to someone else's answer, but if you want to edit it in I'll remove mine. – SeinopSys Sep 23 '17 at 11:35
  • 1
    Note that creating an instance is not required anymore, you just have to add `include_stacktraces: true` to your handler, see [this answer](https://stackoverflow.com/a/53101280/7283848) – homer Mar 15 '23 at 20:28
21

I have a very simple solution!!!

$mylogger->error((string) $exception);
Amir
  • 2,149
  • 4
  • 23
  • 32
19

You can simply use the config file (Symfony Bundle) with the "include_stacktraces" parameter:

monolog:
    handlers:
        main:
            type: stream
            path: "%kernel.logs_dir%/%kernel.environment%.log"
            level: info
            channels: ["!event"]
            max_files: 10
            include_stacktraces: true <--- SET TO TRUE

@see this commit

@see the full schema (configuration)

David DIVERRES
  • 1,811
  • 21
  • 30
11

Adding to Tomasz Madeyski's answer, this is how you can use it via code only:

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\ErrorHandler;
use Monolog\Formatter\LineFormatter;

$formatter = new LineFormatter(LineFormatter::SIMPLE_FORMAT, LineFormatter::SIMPLE_DATE);
$formatter->includeStacktraces(true); // <--

$stream = new StreamHandler('error.log');
$stream->setFormatter($formatter);

$logger = new Logger('logger');
$logger->pushHandler($stream);
SeinopSys
  • 8,787
  • 10
  • 62
  • 110
9

No, You cannot log the stack trace using the built-in formatters. See my question here.

If you take a look at LineFormatter.php you see that the normalizeException method is responsible for grabbing the exception data. So, I had to create a new formatter that extended LineFormatter. Here's the code:

<?php

namespace Monolog\Formatter;

use Exception;

class ExceptionLineFormatter extends LineFormatter
{
    protected function normalizeException(Exception $e)
    {
        return 'Message: ' . $e->getMessage() . 
                'Stack Trace: '. $e->getTraceAsString();
    }
}

And I initialized my logger like so:

$logFile = 'MyLogFile.txt';
$handler = new StreamHandler($logFile);
$handler->setFormatter(new ExceptionLineFormatter);
$log = new Logger('MyLogger');
$handler = self::getStreamHander();
$log->pushHandler($handler);

This will will print out your stack trace.

Rayhan Muktader
  • 2,038
  • 2
  • 15
  • 32
3

This is how I do it, yes years later...

$mylogger->error('Exception caught', $exception->getTrace());

since getTrace() returns an array, which is what Monolog wants.

ADJenks
  • 2,973
  • 27
  • 38
  • 3
    For anyone like me who used this, be mindful of the fact that if you're using a Gelf handler this will throw an exception. Due to the fact that the getTrace array starts counting at 0 which is an invalid key within the `GelfMessage::setAdditional()` – N.Schipper Feb 26 '18 at 09:49
1

The Upvoted answer works, but you are not forced to create a custom service for that.

The monolog.formatter.line already exists on Symfony 3.4 full stack. You can simply add a method call on it thanks to the CompilerPassInterface:

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use use Symfony\Component\HttpKernel\Kernel;

class AppKernel extends Kernel implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        $container->getDefinition('monolog.formatter.line')->addMethodCall('includeStacktraces');
    }
}

The monolog handler does not seems to receive the service by default, so you still have to specify it. Example:

monolog:
    handlers:
        main:
            type: rotating_file
            max_files: 12
            date_format: 'Y-m'
            path: '%kernel.logs_dir%/%kernel.environment%.log'
            level: debug
            formatter: monolog.formatter.line
Soullivaneuh
  • 3,553
  • 3
  • 37
  • 71
1

If you want to log stacktrace only when Exception is thrown, you can do this, in the AppServiceProvider:

public function register()
{
     $logger = Log::getMonolog();
     $logger->pushProcessor(function ($record) {
        if (isset($record['context']['exception']))
        {
            $record['extra']['stacktrace'] = $record['context']['exception']->getTraceAsString();
        }

        return $record;
    });
}

This will add the stacktrace to extra column, which then can be used per LineFormatter

eithed
  • 3,933
  • 6
  • 40
  • 60
0

getTraceAsString will give you the Stack Trace array as a 'End of Line' delimited string. Explode on PHP_EOL and then foreach through the array logging each element. Hope this helps.

<?php
function test() {
    throw new Exception;
}

try {
    test();
} catch(Exception $e) {
    $array = explode(PHP_EOL, $e->getTraceAsString());
    foreach($array as $line){
        $mylogger->error($line);
}

Should produce something like this:

#0 index.php(14): test()
#1 {main}
php_bob
  • 37
  • 1
  • 4