Glukinho's avatar
Level 33

Different log channels for different classes

Hi all, I develop a complex Laravel app that consists of several logical "parts" (I believe people call them domains). Every "part" consists of a set of classes (controllers, jobs, services, helpers, actions...). There is some logging inside classes, using Log facade. I want different parts/domains/classes to put logs to different files, this is obviously done with log channels, right now I have to specify log channel on each call of Log facade:

/* in classes used by telegram bot functionality */
Log::channel('telegram-bot')->info('logging some data related to telegram bot functionality');

/* in classes used by sending email */
Log::channel('email')->debug('logging some data related to sending emails');

What I WANT:

  • use only Log facade to keep code cleaner:
/* this data goes to the right log channel based on a class where it was called from */
Log::info('some data');
  • have some centralized config where I can specify which classes write logs to which channels, something like this:
<?php

return [
    'default'                       => 'default-channel',
    HandleTelegramBotMessage::class => 'telegram-bot-channel',
    SendTelegramBotReply::class     => 'telegram-bot-channel',
    SendGreetingByEmailJob::class   => 'email-log-channel',
    /* etc... */
];

What I DO NOT WANT:

  • specify log channel on each call of Log facade
  • inject any Logger objects in class constructors and do logging like this (as I believe logging logic must be apart of the class itself and should be handled fully by a framework):
public function __construct(private TelegramBotLogger $logger) { }

private function sendEmail()
{
    $this->logger->info('log some data regarding Telegram bot');
}

Can anyone suggest an idea how can I achieve this with Laravel? Or maybe I'm totally wrong and should reconcider all my logging logic?

Thanks a lot for your replies and suggestions.

0 likes
1 reply
Glukinho's avatar
Level 33

While experimenting, I was able to achieve this behavior by writing my own Monolog handler:

  1. It seems this can be done with generic debug_backtrace(), but I liked the package:
composer require spatie/backtrace
  1. app\Logging\SourceClassAwareHandler.php
<?php

namespace App\Logging;

use Monolog\Handler\AbstractProcessingHandler;
use Monolog\LogRecord;
use Spatie\Backtrace\Backtrace;

class SourceClassAwareHandler extends AbstractProcessingHandler
{
    public function write(LogRecord $record): void
    {
        $frames = Backtrace::create()->frames();
        $offset = 8; /* you should pick this value with an experiment, it depends on backtrace content */

        $class = $frames[$offset]->class;

        $log_file = match ($class) {
            \App\Console\Commands\TestCommand::class => 'command.log',
            \App\Console\Commands\MyCommand::class   => 'my.log',
            \App\Jobs\TestJob::class                 => 'job.log',
            default                                  => 'laravel.log',
        };

        file_put_contents(storage_path('logs/' . $log_file), $record->formatted, FILE_APPEND);
    }
}
  1. config/logging.php
'channels' => [
    'class-aware-logger' => [
        'driver' => 'monolog',
        'handler' => \App\Logging\SourceClassAwareHandler::class,
    ],
],
  1. .env
LOG_CHANNEL=class-aware-logger

Now if I call this inside \App\Jobs\TestJob class:

Log::info("log data from TestJob class");

the following log entry appears in 'job.log' file:

[2025-05-03 01:58:59] local.INFO: log data from TestJob class  

Honestly, I don't really like the solution, but it works as I want it to work. Any ideas how to make it more elegant way?

Please or to participate in this conversation.