Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple logger instances out of the box #49

Closed
elcreator opened this issue Oct 25, 2023 · 10 comments
Closed

Support multiple logger instances out of the box #49

elcreator opened this issue Oct 25, 2023 · 10 comments

Comments

@elcreator
Copy link

If we want multiple logger instances for multiple parts of the system then we need to extend this class and copy-paste getStruct as it's based on self:: not static:: calls and getStruct is private not protected.
I.e. this way it will work with file loggers:

$subSystemOneLogger = createLogger('sys-one');
$subSystemTwoLogger = createLogger('sys-two');

$subSystemOneLogger->info('Something about subsystem one that will be logged to sys-one.debug.log and sys-one.info.log ');
...
$subSystemTwoLogger->info('Something about subsystem two that will be logged to sys-two.debug.log and sys-two.info.log ');


function createLogger($prefix = ''): AppLogger
{
    $logger = new AppLogger();
    $prefix = empty($prefix) ? '' : "$prefix.";
    $monthDir = LOG_DIR . date('Y/m');
    if (!is_dir($monthDir)) {
        mkdir($monthDir, 0775, true);
    }
    $monthDir = "$monthDir/";
    $logger->setHandler(Handler\Multi::init([
        Analog::INFO => [
            Handler\File::init(LOG_DIR . "{$prefix}debug.log"),
            Handler\File::init(LOG_DIR . "{$prefix}info.log"),
            Handler\File::init($monthDir . "{$date}_{$prefix}debug.log"),
            Handler\File::init($monthDir . "{$date}_{$prefix}info.log")
        ],
        Analog::DEBUG => [
            Handler\File::init(LOG_DIR . "{$prefix}debug.log"),
            Handler\File::init($monthDir . "{$date}_{$prefix}debug.log")
        ]
    ]));
    return $logger;
}

class AppLogger implements LoggerInterface
{
    private ?Closure $handler = null;

    public function debug($message, array $context = []): void
    {
        $this->write($this->getStruct($message, \Analog\Analog::DEBUG));
    }

    public function info($message, array $context = []): void
    {
        $this->write($this->getStruct($message, \Analog\Analog::INFO));
    }
// ... similar methods
    public function setHandler(Closure $handler): void
    {
        $this->handler = $handler;
    }

    private function write($struct): void
    {
        if (!$this->handler instanceof Closure) {
            $this->handler = \Analog\Handler\File::init(LOG_DIR . 'default.log');
        }
        call_user_func($this->handler, $struct);
    }

    private function getStruct($message, $level): array
    {
        $dt = new DateTime ('now', new DateTimeZone (\Analog\Analog::$timezone));
        return array(
            'machine' => '',
            'date' => $dt->format(\Analog\Analog::$date_format),
            'level' => $level,
            'message' => $message
        );
    }
}

My proposition is to make it less complex or add example similar to this one to achieve the same result.

@lux
Copy link
Collaborator

lux commented Oct 25, 2023

Let me know if this works as a solution to what you're thinking:

#50

@derpoho
Copy link

derpoho commented Dec 4, 2023

@lux not the initiator of the issue, but stumbled over it.

it does work as you did in #50 , but not for nested handlers like:

$this->logger->handler(LevelName::init(
    File::init($logFile)
));

due to the handler management in LevelName it will override the previous File::init.

Otherwise it's a good fix!

@elcreator
Copy link
Author

agree

@lux
Copy link
Collaborator

lux commented Dec 7, 2023

Ah yes, that is still an issue. Will think on a solution to carry the handler up the chain to sub-handlers.

@lux
Copy link
Collaborator

lux commented Dec 9, 2023

@derpoho and @elcreator, let me know if #51 works for you as well!

@elcreator
Copy link
Author

Thank you, I still use own wrapper for customizing getStruct (as I don't need machine name) and write to add suffix . Now it looks this way:

class AppLogger implements LoggerInterface
{
    private $handler = null;

    public function debug($message, array $context = []): void
    {
        $this->write($this->getStruct($message . $this->suffix($context), \Analog\Analog::DEBUG));
    }

    public function info($message, array $context = []): void
    {
        $this->write($this->getStruct($message . $this->suffix($context), \Analog\Analog::INFO));
    }

    public function warning($message, array $context = []): void
    {
        $this->write($this->getStruct($message . $this->suffix($context), \Analog\Analog::WARNING));
    }

    public function error($message, array $context = []): void
    {
        $this->write($this->getStruct($message . $this->suffix($context), \Analog\Analog::ERROR));
    }

    public function critical($message, array $context = []): void
    {
        $this->write($this->getStruct($message . $this->suffix($context), \Analog\Analog::CRITICAL));
    }

    private function suffix(array $context): string
    {
        $place = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
        $context = array_merge([substr($place['file'], strlen(dirname(realpath(__DIR__)))), $place['line']],
            $context);
// other project-specific additions
        return '   ' . implode(':', $context);
    }

    public function emergency($message, array $context = []): void
    {
        $this->critical($message, $context);
    }

    public function alert($message, array $context = []): void
    {
        $this->error($message, $context);
    }

    public function notice($message, array $context = []): void
    {
        $this->debug($message, $context);
    }

    public function log($level, $message, array $context = []): void
    {
        $level = ($level !== null) ? $level : \Analog\Analog::$default_level;
        $this->write($this->getStruct($message . $this->suffix($context), $level));
    }

    /**
     * @param $handler
     * @return void
     */
    public function setHandler($handler): void
    {
        $this->handler = $handler;
    }

    private function write($struct): void
    {
        if (!$this->handler instanceof Closure) {
            if (is_object($this->handler) && method_exists($this->handler, 'log')) {
                $this->handler->log($struct);
                return;
            }
            $this->handler = \Analog\Handler\File::init(LOG_DIR . 'default.log');
        }
        call_user_func($this->handler, $struct);
    }

    private function getStruct($message, $level): array
    {
        $dt = new DateTime ('now', new DateTimeZone (\Analog\Analog::$timezone));
        return array(
            'machine' => '',
            'date' => $dt->format(\Analog\Analog::$date_format),
            'level' => $level,
            'message' => $message
        );
    }
}

@lux
Copy link
Collaborator

lux commented Dec 12, 2023

Cool, that seems pretty close just with the blank machine and the suffix added, so would be easy to adapt to support nested handlers by changing Multi::init to new Multi.

Updating may make you have to tweak your method signatures slightly since I also moved to psr/log 3.0 which requires Stringable|string added to the $message parameter.

@elcreator
Copy link
Author

but why I need to change Multi::init to new Multi? I posted the code after update to the latest version with PSR3. My AppLogger above is not extending the class (because the methods I need are private not protected so I can't benefit from extending). It has own method setHandler to inject Analog handler:

    $logger->setHandler(Handler\Multi::init([
        Analog::INFO => [
            Handler\File::init(LOG_DIR . "{$prefix}info.log"),
            Handler\File::init($monthDir . "{$date}_{$prefix}info.log")
        ],
        Analog::DEBUG => [
            Handler\File::init(LOG_DIR . "{$prefix}debug.log"),
            Handler\File::init($monthDir . "{$date}_{$prefix}debug.log")
        ]
    ]));

@lux
Copy link
Collaborator

lux commented Dec 12, 2023

You know what, you actually don't need to change it to new Multi since I changed init() to return a new instance of Multi. Originally I didn't update how init() worked so you needed to change it for the inner handlers to work, but now you shouldn't have to.

Sounds like you've got a good solution and I'm glad Analog was extendable enough to work for you :) And I believe my last PR should solve the issue of handlers getting their wires crossed when using Multi, Buffer, etc. handlers.

@elcreator
Copy link
Author

Correct, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants