How to access Logger inside routes with Slim skeleton

Hi,
I’m enjoying using Slim. I opted to take the advice to plunge straight in and use the skeleton.
Because I don’t know how things are connected together in the Container, I’m having difficulty knowing how to access classes that have been injected elsewhere.

For e.g., I’m writing a simple action that takes place in routes:

routes.php
...

    $app->get('/gallery', function (Request $request, Response $response) {

        $gallery = array_filter(glob(__DIR__ . '/../public/gallery_images/thumbs/*'), 'is_file');

        $view = Twig::fromRequest($request);
        return $view->render($response, "gallery.twig", [
            'gallery' => $gallery,
        ]);
    })->setName('gallery');

How do I access the Logger that has already been injected into the DI Container within that route?
I’ve tried:

$this->logger->debug("Message here")

ERROR: Undefined property: DI\\Container::$logger

and

$logger = $this->get('logger')

"No entry or class found for 'logger'"

but no success.
The settings.php file is, apart from the name change and setting the log values to true is as it was from installation:

use App\Application\Settings\Settings;
use App\Application\Settings\SettingsInterface;
use DI\ContainerBuilder;
use Monolog\Logger;

return function (ContainerBuilder $containerBuilder) {

    // Global Settings Object
    $containerBuilder->addDefinitions([
        SettingsInterface::class => function () {
            return new Settings([
                'displayErrorDetails' => true, // Should be set to false in production
                'logError'            => true,
                'logErrorDetails'     => true,
                'logger' => [
                    'name' => 'my_app',
                    'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__ . '/../logs/app.log',
                    'level' => Logger::DEBUG,
                ],
            ]);
        }
    ]);
};

Can anyone help?

You can do it by @Odan Slim 4 - Logging, or prepare your own set of loggers (simple extensions) as a package or just classes declared within your project. Personally I prefer the second way (package with EventLogger, HttpLogger, ExceptionLogger, …) so my exaple follows its idea.

src/Logger/HttpClientLogger.php

<?php

declare(strict_types=1);

namespace App\Logger;

use Monolog\Logger;

class HttpClientLogger extends Logger
{
}

src/config/dependencies/packages.php

<?php

declare(strict_types=1);

use Slim\App;
use Slim\Factory\AppFactory;
use Psr\Container\ContainerInterface;
use Paneric\Logger\HttpClientLogger;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\StreamHandler;

return [
...

    App::class => static function (ContainerInterface $container) {
        return AppFactory::createFromContainer($container);
    },

    HttpClientLogger::class => static function (
        ContainerInterface $container
    ): HttpClientLogger {
        $logger = new HttpClientLogger('http_client_logger');
        $streamHandler = new StreamHandler('./../var/log/http-client-logger.log', Level::Debug);
        $streamHandler->setFormatter(
            new LineFormatter(null, null, false, true)
        );
        $logger->pushHandler($streamHandler);
        return $logger;
    },
...
];

src/bootstrap/bootstrap.php

<?php

declare(strict_types=1);

use DI\ContainerBuilder;

...
$builder = new ContainerBuilder();
$builder->useAutowiring(true);
$builder->useAnnotations(false);
$definitions = require './../src/config/dependencies/packages.php';
$builder->addDefinitions($definitions);
$container = $builder->build();

$slim = $container->get(App::class);
$slim->run();
...

The last file src/bootstrap/bootstrap.php you just need to:

require './../src/bootstrap/bootstrap.php' 

in your front controller index.php.

This way your logger is accesible within your routes declarations by simple:

$logger = $this->get(HttpClientLogger::class);

or injected to any class constructor if you need to:

public function __construct(private HttpClientLogger $logger)
{
}

Thanks for your reply!
Unfortunately, I found it more confusing than helpful.

The Skeleton is already instantiating the Monolog Logger class so adding it again in a more convoluted file structure seems overly complicated.

The article in the link has sadly been removed.

How do I access the already instantiated Logger class from with a route within the given framework of the Skeleton?

You can slightly modify your ErrorMiddleware instantiation in your container definitions:

config/container.php

return [
...
    ErrorMiddleware::class => function (ContainerInterface $container) {
        $settings = $container->get('settings')['error'];
        $app = $container->get(App::class);

        $logger = $container->get(LoggerFactory::class)
            ->addFileHandler('error.log')
            ->createLogger();

        $errorMiddleware = new ErrorMiddleware(
            $app->getCallableResolver(),
            $app->getResponseFactory(),
            (bool)$settings['display_error_details'],
            (bool)$settings['log_errors'],
            (bool)$settings['log_error_details'],
            $logger
        );

        $container->set('error_logger', $logger); // <--- HERE

        $errorMiddleware->setDefaultErrorHandler($container->get(DefaultErrorHandler::class));

        return $errorMiddleware;
    },
];
...

but at the same time keep in mind that (as far as I remember) instances added this way:

  1. don’t take part in container compilation process;
  2. are accessible only after their creation, so:
  • logger created in ErrorMiddleware definition will be accesible within your route definition (look at the order of middlewares loop)
  • logger created in SomeController definition (or any other object instantiated as a reason of a call to some action) won’t exist yet.

Summarizing, if you need a total flexibility, you need to:

  1. Remove LoggerFactory completely from skeleton
  2. Create your loggers the way I presented in my previous post.
  3. Change the ErrorMiddleware definition:

config/container.php

return [
...
    ErrorMiddleware::class => function (ContainerInterface $container) {
        $settings = $container->get('settings')['error'];
        $app = $container->get(App::class);

        $errorMiddleware = new ErrorMiddleware(
            $app->getCallableResolver(),
            $app->getResponseFactory(),
            (bool)$settings['display_error_details'],
            (bool)$settings['log_errors'],
            (bool)$settings['log_error_details'],
            $container->get(ErrorLogger::class) // <-- LOGGER CREATED AS ABOVE
        );

        $errorMiddleware->setDefaultErrorHandler($container->get(DefaultErrorHandler::class));

        return $errorMiddleware;
    },
];
...

Again, thanks for your response @tj_gumis.

It’s pretty much gone over my head. I feel encouraged read the Slim docs from the ground up to understand what’s going on.