Skip to content

Lab: Logging Requests with Monolog

In this lab, you will practice using Monolog to log HTTP request information to a file in two different ways.

  • In Part I, you will log request information directly inside a route callback.
  • In Part II, you will move that logic into a dedicated LoggingMiddleware class so that logging applies automatically to an entire group of routes.

For each request, you will log:

  • The HTTP method and request URI
  • The client IP address
  • The HTTP response status code
  • The query string parameters (if any)

By completing this lab, you will:

  • Use Monolog to create a logger, configure a file handler, and write log entries.
  • Extract request data from Slim’s PSR-7 request object.
  • Understand the difference between inline logging in a route callback and middleware-based logging.
  • Implement a PSR-15 middleware class and register it on a route group.
  • Understand middleware execution order when combined with AuthMiddleware.


When a REST API is running in production, there is no direct visibility into what is happening inside it.

  • Without logging, tracking down bugs, detecting suspicious behavior, or understanding how the API is being used becomes guesswork. A log file provides a persistent record of activity that can be inspected after the fact.

Request logging in particular is useful for:

  • Tracking which endpoints are being called and how often,
  • Detecting failed or unauthorized requests,
  • Reproducing bugs by seeing exactly what was sent and what status was returned,
  • Auditing access when authentication is involved.

Before starting this lab, ensure you have:

  • The Slim template project set up and running.
  • At least one route defined in app/Routes/routes.php.
  • Reviewed the Monolog documentation.

In this part, you will add a dedicated test route and write the logging logic directly inside its callback.

Objective: Add a new GET route that you will use to test your logging implementation.

Instructions:

  1. Open app/Routes/routes.php.
  2. Add the following route:
app/Routes/routes.php
$app->get('/log-test', function (Request $request, Response $response): Response {
//TODO: Your logging code will go here (Steps 2 and 3).
//TODO: Return a simple JSON response for testing purposes.
});
  1. Save the file.

Objective: Inside the route callback, create a Monolog logger and configure it to write to a file.

Instructions:

  1. Inside the /log-test route callback, add the required use statements at the top of the file:
app/Routes/routes.php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
  1. Inside the callback, write the code to:

    • Create a Logger instance with the channel name 'access'.
    • Add a StreamHandler that builds the log file path using the existing APP_BASE_PATH constant and writes to var/logs/access.log, at the INFO level.
  2. Save the file.


Step 3: Configure the Time Zone and Log Format

Section titled “Step 3: Configure the Time Zone and Log Format”

Objective: Change the logger’s default time zone and customize the format of log entries.

Instructions:

  1. Set the logger’s time zone to America/Toronto (or your local time zone).
  2. Create a custom LineFormatter with the following format:
    • Date format: Y-m-d H:i:s
    • Output format: [%datetime%] %channel%.%level_name%: %message% %context%\n
  3. Apply the formatter to the StreamHandler you created in Step 2.
  4. Save the file.

Objective: Extract information from the request object and write a log entry.

Instructions:

  1. Still inside the route callback, write the code to extract:

    • The HTTP method from the request.
    • The request URI, cast to a string.
    • The client IP address from the server parameters. Use 'unknown' as a fallback if the key is not present.
    • The query string parameters from the request.
  2. Write a log entry at the INFO level. Pass the extracted values as a context array.

  3. Save the file.


Objective: Verify that the route logs request information correctly.

Instructions:

  1. Send a GET request to /log-test using a REST client (e.g., Bruno, Postman) or your browser.
  2. Open var/logs/access.log and verify that a log entry was written with the correct method, URI, and IP address.

A correctly written log entry should look similar to this:

[2025-04-07 14:22:10] access.INFO: Incoming request {"method":"GET","uri":"http://localhost/api/log-test","ip":"127.0.0.1","query":[]}

In Part I, logging was tied to a single route. In this part, you will move the logging logic into a middleware class so that it applies automatically to all routes in a group and also captures the response status code.

Step 6: Create the LoggingMiddleware Class

Section titled “Step 6: Create the LoggingMiddleware Class”

Objective: Create a PSR-15 middleware class that logs every request passing through it.

Instructions:

  1. Create a new file app/Middleware/LoggingMiddleware.php.
  2. Use the following scaffold to get started:
app/Middleware/LoggingMiddleware.php
<?php
declare(strict_types=1);
namespace App\Middleware;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class LoggingMiddleware implements MiddlewareInterface
{
public function process(Request $request, RequestHandler $handler): ResponseInterface
{
// TODO 1: Create a Logger instance with the channel name 'access'.
// TODO 2: Add a StreamHandler using APP_BASE_PATH to build the path to var/logs/access.log.
// TODO 3: Set the logger's time zone and customize the log format using a LineFormatter.
// Apply the formatter to the StreamHandler.
// TODO 4: Extract the HTTP method, request URI, client IP, and query string parameters from the request.
// TODO 5: Pass the request to the next middleware/handler and store the response.
// IMPORTANT: This call must come before logging the response status code.
// TODO 6: Log an INFO message with the method, URI, IP, and response status code as context.
// TODO 7: Return the response.
}
}
  1. Save the file.

Step 7: Register the Middleware on a Route Group

Section titled “Step 7: Register the Middleware on a Route Group”

Objective: Attach LoggingMiddleware to a route group so that all requests within that group are logged automatically.

Instructions:

  1. Open app/Routes/routes.php.
  2. Add the use statement for your middleware at the top of the file:
app/Routes/routes.php
use App\Middleware\LoggingMiddleware;
  1. Attach the middleware to your route group using ->add():
app/Routes/routes.php
$app->group('', function (RouteCollectorProxy $group) {
// Your existing routes here...
})->add(LoggingMiddleware::class);
  1. Save the file.

Objective: Verify that the middleware logs requests for all routes in the group.

Instructions:

  1. Send several requests to different routes in your API group using a REST client.
  2. Open var/logs/access.log and verify that each request was logged with the method, URI, IP, and status code.

A correctly written log entry should look similar to this:

[2025-04-07 14:22:10] access.INFO: Incoming request {"method":"GET","uri":"http://localhost/api/players","ip":"127.0.0.1","query":{"limit":"10"},"status":200}
  1. Send a request to a route that does not exist and verify that the 404 status code is logged.