How to inject request into the logger?

:wave: Hi. I am using Slim as API with tokens access and want to use psr-3 logger (monolog) to see the incoming requests. In particular, I want to see more detailed information about the user: id, email, role. The target log record should look like this:
[2021-05-11 15:32:48.011] uid [127.0.0.1.POST] NOTICE: 123/email@test.com/admin ... any other log info
To get this info first I add middleware, that search user by access token and add id, email & role as attributes to request:

class AddCurrentUserToRequestMiddleware implements MiddlewareInterface {
	private $user;

	public function __construct(Users $user) {
		$this->user = $user;
	}

	public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
		$token = $request->getHeaderLine('Authorization');

		if ($token === NULL || ($user = $this->user->findByToken($token)) === NULL) {
			throw new HttpUnauthorizedException($request);
		}

		return $handler->handle($request
				->withAttribute('user.id', $user->getId())
				->withAttribute('user.email', $user->getEmail())
				->withAttribute('user.role', $user->getRole())
		);
	}
}

So, in logger I must get the request above. To do this, I wrote the custom processor which check the context data of the record, and if instance of ServerRequestInterface found, get user attributes from it:

class AddUserInfoProcessor implements ProcessorInterface {
	public function __invoke(array $records): array {
		foreach ($records['context'] as $context) {
			if ($context instanceof ServerRequestInterface) {
				$id = $context->getAttribute('user.id');
				$login = $context->getAttribute('user.email');
				$role = $context->getAttribute('user.role');

				$message = empty($id) ? '-' : implode('/', [$id, $login, $role]);
				$records['message'] = $message . ' | ' . $records['message'];
				break;
			}
		}
		
		return $records;
	}
}

Finally, the logger must be call like this (using DI):

$app->get('/test', \App\Application\Actions\Controller1::class);

class Controller1 {
	private $logger;

	public function __construct(LoggerInterface $logger) {
		$this->logger = $logger;
	}

	public function __invoke(ServerRequestInterface $request, ResponseInterface $response) {
		$this->logger->notice('user want to do some action', [$request]);
		$this->logger->notice('Any log info', [$request]);

		if (...) {
			$this->logger->error('Houston, we have a problem!', [$request]);
		}

		return $this->response->withStatus(...);
	}
}

All work fine, but I’m not sure that adding [$this->request] to every log command is a good thing.

Another trouble, if I want to log any other info (in business logic, for example), I must to forward $request there. This look like piece of sh :slightly_smiling_face:

Maybe there is another way with auto injecting the result request (after passing all middlewares) into the logger?

Hi!

The request object should only be used within the HTTP layer.
So I would perhaps phrase the question a little differently like “How to log each request?”
Is this what you are trying to accomplish?

For this purpose you may try to add a custom Middleware (PSR-15) to your existing Slim middlware stack.

$app->add(HttpLoggerMiddeware::class);

Probably my question is not entirely correct. I want to add user info in every log row. In one request may be more than one log records. In code above there are three logger calls:

[2021-05-11 15:32:48.011] 535a035 [127.0.0.1.POST] NOTICE: 123/email@test.com/admin | user want to do some action
[2021-05-11 15:32:48.011] 535a035 [127.0.0.1.POST] NOTICE: 123/email@test.com/admin | Any log info
[2021-05-11 15:32:48.011] 535a035 [127.0.0.1.POST] NOTICE: 123/email@test.com/admin | Houston, we have a problem!
[2021-05-12 10:00:01.011] b2deb15 [127.0.0.1.POST] NOTICE: 22/bob@gmail.com/user | user want to do some action

And yes, if I use console app, of course the user info will be empty (-).

As one of possible solution, I can use the monolog UidProcessor. It mark all records in one request by unique id. So, I can add middleware to log user info before any log messages, and all next rows can be identify by this id. In example above id 535a035 and b2deb15. Maybe this way is more correct…

You could try to remove the AddUserInfoProcessor class and add this UserLoggerMiddleware
before the AddCurrentUserToRequestMiddleware:

Example:

class UserLoggerMiddleware implements MiddlewareInterface
{
	public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface 
    {
        $context = [
             'user.id' => $request->getAttribute('user.id'),
             'user.email' =>$request->getAttribute('user.email'),
             'user.role' =>$request->getAttribute('user.role'),
       ];

        $this->logger->info('User request', $context)

        return $handler->handle($request);
	}
}

In this order:

$app->add(UserLoggerMiddleware::class);
$app->add(AddCurrentUserToRequestMiddleware::class);