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
LoggingMiddlewareclass 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)
Learning Objectives
Section titled “Learning Objectives”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.
Additional Resources
Section titled “Additional Resources”Why Logging Matters
Section titled “Why Logging Matters”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.
Prerequisites
Section titled “Prerequisites”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 ↗.
Part I: Logging Inside a Route Callback
Section titled “Part I: Logging Inside a Route Callback”In this part, you will add a dedicated test route and write the logging logic directly inside its callback.
Step 1: Add a Test Route
Section titled “Step 1: Add a Test Route”Objective: Add a new GET route that you will use to test your logging implementation.
Instructions:
- Open
app/Routes/routes.php. - Add the following route:
$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.});- Save the file.
Step 2: Set Up the Logger
Section titled “Step 2: Set Up the Logger”Objective: Inside the route callback, create a Monolog logger and configure it to write to a file.
Instructions:
- Inside the
/log-testroute callback, add the requiredusestatements at the top of the file:
use Monolog\Logger;use Monolog\Handler\StreamHandler;-
Inside the callback, write the code to:
- Create a
Loggerinstance with the channel name'access'. - Add a
StreamHandlerthat builds the log file path using the existingAPP_BASE_PATHconstant and writes tovar/logs/access.log, at theINFOlevel.
- Create a
-
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:
- Set the logger’s time zone to
America/Toronto(or your local time zone). - Create a custom
LineFormatterwith the following format:- Date format:
Y-m-d H:i:s - Output format:
[%datetime%] %channel%.%level_name%: %message% %context%\n
- Date format:
- Apply the formatter to the
StreamHandleryou created in Step 2. - Save the file.
Step 4: Log the Request Details
Section titled “Step 4: Log the Request Details”Objective: Extract information from the request object and write a log entry.
Instructions:
-
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.
-
Write a log entry at the
INFOlevel. Pass the extracted values as a context array. -
Save the file.
Step 5: Test Part I
Section titled “Step 5: Test Part I”Objective: Verify that the route logs request information correctly.
Instructions:
- Send a
GETrequest to/log-testusing a REST client (e.g., Bruno, Postman) or your browser. - Open
var/logs/access.logand 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":[]}Part II: Logging Using a Middleware
Section titled “Part II: Logging Using a Middleware”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:
- Create a new file
app/Middleware/LoggingMiddleware.php. - Use the following scaffold to get started:
<?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. }}- 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:
- Open
app/Routes/routes.php. - Add the
usestatement for your middleware at the top of the file:
use App\Middleware\LoggingMiddleware;- Attach the middleware to your route group using
->add():
$app->group('', function (RouteCollectorProxy $group) { // Your existing routes here...})->add(LoggingMiddleware::class);- Save the file.
Step 8: Test Part II
Section titled “Step 8: Test Part II”Objective: Verify that the middleware logs requests for all routes in the group.
Instructions:
- Send several requests to different routes in your API group using a REST client.
- Open
var/logs/access.logand 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}- Send a request to a route that does not exist and verify that the
404status code is logged.