Skip to content

Using Monolog for Logging

Monolog is a comprehensive logging library for PHP that sends your log records to files, sockets, inboxes, databases and various web services. It’s perfect for tracking application behavior, debugging issues, and monitoring your REST API built with Slim framework.

Key benefits:

  • PSR-3 compliant logging interface
  • Multiple handlers for different output destinations
  • Processors to add extra information to log records
  • Flexible formatting options
  • Support for log levels from DEBUG to EMERGENCY

Install Monolog using Composer:

Terminal window
composer require monolog/monolog
  • However, Monolog is already included in the provided Slim template. So you can skip this step.

  1. Import Monolog classes in your application

    use Monolog\Logger;
    use Monolog\Handler\StreamHandler;
  2. Create a logger instance with a channel name

    // Create a logger with a channel name (e.g., 'app_access', 'api', 'database').
    $logger = new Logger('app_access');
  3. Add one or more handlers to define where logs go

    // Log to a file - INFO level and above.
    $logger->pushHandler(new StreamHandler(__DIR__ . '/logs/app.log', Logger::INFO));
    // You can add multiple handlers for different purposes.
    // For example, log errors to a separate file.
    $logger->pushHandler(new StreamHandler(__DIR__ . '/logs/errors.log', Logger::ERROR));
  4. Use the logger throughout your application

    $app->post('/api/planets', function ($request, $response) use ($logger) {
    $data = $request->getParsedBody();
    // Log the incoming request.
    $logger->info('Creating new planet', ['data' => $data]);
    // Your business logic here...
    return $response->withStatus(201);
    });

Monolog implements the PSR-3 logging standard with eight severity levels. Use the appropriate level based on the type of information you’re logging:

LevelWhen to UseExample
DEBUGDetailed debug informationVariable values, loop iterations
INFOInteresting eventsUser login, API requests
NOTICENormal but significant eventsConfiguration changes
WARNINGExceptional occurrences that are not errorsUse of deprecated APIs, poor API usage
ERRORRuntime errors that don’t require immediate actionDatabase connection failed
CRITICALCritical conditionsApplication component unavailable
ALERTAction must be taken immediatelyEntire website down
EMERGENCYSystem is unusableDatabase unavailable, system crash

The method name follows the level name in lowercase: $logger->info(), $logger->error(), $logger->warning(), etc.


  • Logging Request Information
  • Logging Database Operations
  • Logging API Calls
  • Logging Exceptions
  • Logging Authentication Events

Monolog allows you to add contextual information to your log messages using arrays:

// Simple context.
$logger->info('User logged in', ['user_id' => 123]);
// Rich context.
$logger->error('Payment processing failed', [
'user_id' => 123,
'amount' => 99.99,
'currency' => 'USD',
'error_code' => 'CARD_DECLINED',
'timestamp' => time()
]);

By default, Monolog uses UTC for timestamps. You can change this globally for all loggers:

use Monolog\Logger;
$logger = new Logger('access');
// Set the default timezone for this logger.
$logger->setTimezone(new \DateTimeZone('America/New_York'));

Monolog uses formatters to control how log records appear. The default LineFormatter can be customized:

use Monolog\Handler\StreamHandler;
use Monolog\Formatter\LineFormatter;
$handler = new StreamHandler(__DIR__ . '/logs/app.log', Logger::INFO);
// Create a custom format.
// Available placeholders: %datetime%, %channel%, %level_name%, %message%, %context%, %extra%
$dateFormat = "Y-m-d H:i:s";
$outputFormat = "[%datetime%] %channel%.%level_name%: %message% %context%\n";
$formatter = new LineFormatter($outputFormat, $dateFormat);
$handler->setFormatter($formatter);
$logger = new Logger('app');
$logger->pushHandler($handler);

Example output:

[2025-11-10 14:30:45] app.INFO: User logged in {"user_id":123}
[2025-11-10 14:31:12] app.ERROR: Database connection failed {"error":"Connection timeout"}

Passing JWT User Payload to a LoggingMiddleware

Section titled “Passing JWT User Payload to a LoggingMiddleware”

When securing your REST API with JWT, you can pass user information extracted from the token to a LoggingMiddleware so that every log entry includes the authenticated user’s details.

  1. Decode the JWT token in AuthMiddleware

    When you call JWT::decode() to decode the token, pass the token along with a Key object:

    $decoded = JWT::decode($token, new Key($secretKey, 'HS256'));
  2. Attach user data to the request in AuthMiddleware

    Use the withAttribute() method to add the email and user ID you extracted from the token as new request attributes. These statements must be added before calling $handler->handle($request), so that the modified request (with the attached attributes) is passed to the next middleware in the chain:

    $request = $request->withAttribute('user_id', $decoded->sub);
    $request = $request->withAttribute('email', $decoded->email);
    // Then pass the modified request to the next middleware.
    $response = $handler->handle($request);
  3. Retrieve user data in LoggingMiddleware

    In LoggingMiddleware, use the getAttribute() method to get the values attached by AuthMiddleware:

    $userId = $request->getAttribute('user_id');
    $email = $request->getAttribute('email');
    // Now you can include $user_id and $email in your log entries.
  4. Add both middleware to the protected route group

    Add the LoggerMiddleware and AuthMiddleware to the protected group of routes:

    $app->group('/protected', function (Group $group) {
    // Your protected routes here...
    })->add(LoggerMiddleware::class)->add(AuthMiddleware::class);