Laravel Error Logging via Better Stack (Logtail)

Logs (formally Logtail) is a sub-service of Better Stack that serves as a centralized logging platform for your applications. This guide will show you how to integrate your Laravel error logs in Better Stack and change the default logger to include the stack trace.

Better Stack

  1. Create your account on betterstack.com - Its free to get started.

  2. Under Logs & Metrics click connect source

  3. Give it a name and select php, then create source:

Better Stack
  1. Copy your Source token :
Better Stack Token

Laravel

  1. Install logtail/monolog-logtail package:
composer require logtail/monolog-logtail
  1. Edit .env:
LOG_CHANNEL=logtail
LOG_LEVEL=warning
LOGTAIL_SOURCE_TOKEN=7hAj17pv1SmRMvDqJpG8SDKy
  1. Edit config/logging.php:
use Logtail\Monolog\LogtailHandler;
...
...
...
'logtail'    => [
    'driver'  => 'monolog',
    'level'   => env('LOG_LEVEL', 'debug'),
    'handler' => LogtailHandler::class,
    'with'    => [
        'sourceToken' => env('LOGTAIL_SOURCE_TOKEN'),
    ],
],
...
...
...

At this point, your Laravel error logs will be sent to Better Stack. However, the default Laravel handler does not include the stack trace. This can be easily fixed by inheriting from the default Laravel handler and adding in the stack trace.

Adding the Stack Trace

edit app/Exceptions/Handler.php:

Laravel 8

public function report(Throwable $e)
{
    $e = $this->mapException($e);

    if ($this->shouldntReport($e)) {
        return;
    }

    if (Reflector::isCallable($reportCallable = [$e, 'report'])) {
        if ($this->container->call($reportCallable) !== false) {
            return;
        }
    }

    foreach ($this->reportCallbacks as $reportCallback) {
        if ($reportCallback->handles($e)) {
            if ($reportCallback($e) === false) {
                return;
            }
        }
    }

    try {
        $logger = $this->container->make(LoggerInterface::class);
    } catch (Exception $ex) {
        throw $e;
    }

    $context = $this->exceptionContext($e);
    $trace = $e->getTrace();
    $trace = array_slice($trace, 0, 20); // Limit the trace to 20 lines
    $context['trace'] = $trace;

    $logger->error($e->getMessage(), array_merge(
            $context,
            $this->context(),
            ['exception' => $e]
        )
    );
}

Laravel 9

public function report(Throwable $e)
{
    $e = $this->mapException($e);

    if ($this->shouldntReport($e)) {
        return;
    }

    if (Reflector::isCallable($reportCallable = [$e, 'report'])
        && $this->container->call($reportCallable) !== false) {
        return;
    }

    foreach ($this->reportCallbacks as $reportCallback) {
        if ($reportCallback->handles($e) && $reportCallback($e) === false) {
            return;
        }
    }

    try {
        $logger = $this->container->make(LoggerInterface::class);
    } catch (\Exception $ex) {
        throw $e;
    }

    $level = Arr::first(
        $this->levels, fn($level, $type) => $e instanceof $type, LogLevel::ERROR
    );

    $context = $this->buildExceptionContext($e);
    $trace = $e->getTrace();
    $trace = array_slice($trace, 0, 20); // Limit the trace to 20 lines
    $context['trace'] = $trace;
    method_exists($logger, $level)
        ? $logger->{$level}($e->getMessage(), $context)
        : $logger->log($level, $e->getMessage(), $context);
}

Laravel 10

protected function reportThrowable(Throwable $e): void
{
    $this->reportedExceptionMap[$e] = true;

    if (Reflector::isCallable($reportCallable = [$e, 'report']) &&
        $this->container->call($reportCallable) !== false) {
        return;
    }

    foreach ($this->reportCallbacks as $reportCallback) {
        if ($reportCallback->handles($e) && $reportCallback($e) === false) {
            return;
        }
    }

    try {
        $logger = $this->newLogger();
    } catch (Exception) {
        throw $e;
    }

    $level = Arr::first(
        $this->levels, fn ($level, $type) => $e instanceof $type, LogLevel::ERROR
    );

    $context = $this->buildExceptionContext($e);
    $trace = $e->getTrace();
    $trace = array_slice($trace, 0, 20); // Limit the trace to 20 lines
    $context['trace'] = $trace;

    method_exists($logger, $level)
        ? $logger->{$level}($e->getMessage(), $context)
        : $logger->log($level, $e->getMessage(), $context);
}

That's it!

Conclusion

Your Laravel error logs will now be centralized in Better Stack with the stack trace included, offering you and your team a better insight into your application's errors.

Better Stack Error Log