Booking System Reporting: Building Management Dashboards That Drive Better Decisions

12 min read 2,336 words
Booking System Reporting: Building Management Dashboards That Drive Better Decisions featured image

Why Application Logging Matters for PHP Projects

Every PHP application generates events worth recording. When something breaks in production, good logs are often the difference between finding the root cause in minutes and spending hours guessing. Logging is not just about capturing errors. It covers security events, business transactions, performance warnings, and system state changes that help you understand what your application is actually doing.

A well-structured logging approach supports debugging, security auditing, compliance requirements, and long-term system maintenance. Without it, troubleshooting becomes reactive and frustrating. With it, you can trace issues across requests, identify patterns, and make informed decisions about fixes and improvements.

What to Log in a PHP Application

Not every event needs to be logged. Logging too much creates noise that makes finding useful information harder. Logging too little leaves gaps when you need to investigate a problem. The key is identifying which events provide genuine value for debugging, monitoring, and security purposes.

Errors and Exceptions

Unhandled exceptions and PHP errors should always be logged. This includes fatal errors, warnings, notices, and caught exceptions. Each log entry should capture the error type, message, the file and line number where it occurred, and a stack trace when available.

Distinguish between different severity levels. A missing optional configuration file might warrant a warning, while a failed database connection is an error that needs immediate attention.

Security-Relevant Events

Authentication events belong in your logs. Record successful logins, failed login attempts, password reset requests, and account lockouts. These entries help you detect brute force attacks and unusual access patterns.

Authorisation failures matter too. When a user tries to access a resource they do not have permission for, log the user ID, the resource they tried to access, the timestamp, and the IP address.

Any unexpected input that fails validation, especially attempts to submit forms with manipulated data, should be logged. This helps identify injection attempts and other abuse patterns. A related topic worth reviewing is the OWASP Top 10 for business web applications, which covers common security vulnerabilities that logging can help detect.

Business Transactions and Key Operations

For booking systems and similar applications, log significant business events. This includes bookings created, bookings cancelled, appointments rescheduled, payments processed, and refunds issued. These logs support audit trails and help you understand usage patterns.

When building booking systems, decisions about what events to track affect both debugging and analytics. The architecture of multi-tenant booking systems often requires careful consideration of how logs are isolated and aggregated across tenants.

Performance Warnings

Slow database queries, high memory usage, and long response times are worth recording. These entries help identify performance degradation before it becomes a user-facing problem. Set thresholds that make sense for your application and log a warning when those thresholds are exceeded.

Where to Store PHP Application Logs

Logs must be stored outside the web root. If log files are accessible via a browser URL, anyone who discovers the path can read sensitive information about your application, its users, and its internal structure. This is a security risk that is easy to avoid with correct storage configuration.

Filesystem Storage

For most PHP applications, writing logs to files on the server filesystem works well. Choose a directory that the web server user can write to but that is not served publicly. A common pattern is storing logs in /var/log/yourapp/ or a dedicated logs directory within a non-public parent folder.

// Example: configuring a log file path outside web root
$logPath = '/var/log/yourapp/error.log';

if (!is_writable(dirname($logPath))) {
    // Fall back to a temporary location or alert administrators
    $logPath = sys_get_temp_dir() . '/yourapp_error.log';
}

When using shared hosting where you do not have control over directory structures, consider storing logs above the public_html or www directory, or in a directory protected by htaccess rules that deny web access.

Database Logging for High-Volume Applications

For high-volume applications, filesystem logging can create I/O bottlenecks. Writing logs to a database table allows for easier querying and aggregation. Use a separate database connection for logging to avoid interfering with your main application queries, and consider asynchronous writing using queues to prevent logging from slowing down request processing.

Centralised Logging for Distributed Systems

If your application runs across multiple servers, centralised logging tools help aggregate logs from all instances. Tools like the ELK stack (Elasticsearch, Logstash, Kibana) or cloud-based solutions allow you to search and analyse logs across your entire infrastructure from a single interface. This is particularly useful when debugging issues that span multiple services.

Implementing Logging in PHP

PHP offers several ways to implement logging. You can use built-in functions, third-party libraries, or build a custom solution that fits your needs.

Using PSR-3 Compatible Libraries

PSR-3 is a PHP standard recommendation that defines a common interface for logging libraries. Using a PSR-3 compatible library means you can swap the underlying logging implementation without changing your application code.

Popular PSR-3 libraries include Monolog, which is widely used and supports many handlers including files, databases, email, and external services. Setting up Monolog for a basic PHP application is straightforward.

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// Create the logger
$logger = new Logger('app');

// Configure a file handler for errors
$logger->pushHandler(new StreamHandler(
    '/var/log/yourapp/error.log',
    Logger::ERROR
));

// Configure a file handler for all debug messages
$logger->pushHandler(new StreamHandler(
    '/var/log/yourapp/debug.log',
    Logger::DEBUG
));

// Log messages at different levels
$logger->debug('Debug information', ['user_id' => 42]);
$logger->info('User performed action', ['action' => 'booking_create']);
$logger->warning('Slow query detected', ['query_time' => 2.5]);
$logger->error('Database connection failed', ['exception' => $e->getMessage()]);

Creating a Simple Custom Logger

For smaller projects, a simple custom logger may be sufficient. The key is consistency in format and structure so that log entries are easy to parse and search.

class SimpleLogger {
    private string $logFile;

    public function __construct(string $logFile) {
        $this->logFile = $logFile;
        $dir = dirname($logFile);
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }
    }

    public function log(string $level, string $message, array $context = []): void {
        $timestamp = date('Y-m-d H:i:s');
        $contextJson = !empty($context) ? ' ' . json_encode($context) : '';
        $entry = "[{$timestamp}] {$level}: {$message}{$contextJson}" . PHP_EOL;
        file_put_contents($this->logFile, $entry, FILE_APPEND);
    }

    public function error(string $message, array $context = []): void {
        $this->log('ERROR', $message, $context);
    }

    public function info(string $message, array $context = []): void {
        $this->log('INFO', $message, $context);
    }
}

Capturing PHP Errors and Exceptions

To log PHP errors and unhandled exceptions, set custom error and exception handlers at the start of your application.

set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool {
    $logger = new SimpleLogger('/var/log/yourapp/error.log');
    $logger->error("PHP Error [$errno]: $errstr in $errfile:$errline");
    return false; // Continue to standard error handler
});

set_exception_handler(function (Throwable $e): void {
    $logger = new SimpleLogger('/var/log/yourapp/error.log');
    $logger->error('Uncaught Exception: ' . $e->getMessage(), [
        'file' => $e->getFile(),
        'line' => $e->getLine(),
        'trace' => $e->getTraceAsString()
    ]);
    // Show user-friendly error page
    include '500.html';
});

Note: Always test error handlers in a development environment before deploying them to production. Incorrect handler implementations can cause infinite loops or suppress errors that should be visible during development.

Log Rotation and File Size Management

Without rotation, log files grow indefinitely. Eventually they consume all available disk space, which can crash your application or prevent new logs from being written. Log rotation addresses this by archiving old logs and starting fresh files on a schedule.

Using logrotate on Linux Servers

On Linux servers, the logrotate utility handles rotation automatically. Create a configuration file for your application logs.

/var/log/yourapp/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 www-data www-data
    sharedscripts
    postrotate
        /bin/kill -SIGUSR1 $(cat /var/run/php-fpm.pid 2>/dev/null) 2>/dev/null || true
    endscript
}

This configuration rotates logs daily, keeps 14 days of history, compresses old logs, and safely signals PHP-FPM to reopen log files after rotation.

Built-in Rotation for Custom Solutions

If you are building a custom logging solution, implement size-based rotation. Check the file size before writing and rotate when it exceeds your threshold.

class RotatingFileLogger {
    private string $basePath;
    private int $maxSizeBytes;
    private int $maxFiles;

    public function __construct(string $basePath, int $maxSizeMB = 10, int $maxFiles = 5) {
        $this->basePath = $basePath;
        $this->maxSizeBytes = $maxSizeMB * 1024 * 1024;
        $this->maxFiles = $maxFiles;
    }

    private function rotateIfNeeded(): void {
        $currentFile = $this->basePath;
        if (!file_exists($currentFile) || filesize($currentFile) < $this->maxSizeBytes) {
            return;
        }

        // Rename existing files
        for ($i = $this->maxFiles - 1; $i >= 1; $i--) {
            $oldName = "{$this->basePath}.{$i}";
            $newName = "{$this->basePath}." . ($i + 1);
            if (file_exists($oldName)) {
                rename($oldName, $newName);
            }
        }
        rename($currentFile, "{$this->basePath}.1");

        // Delete the oldest file if it exists
        $oldest = "{$this->basePath}." . ($this->maxFiles + 1);
        if (file_exists($oldest)) {
            unlink($oldest);
        }
    }

    public function write(string $level, string $message): void {
        $this->rotateIfNeeded();
        $timestamp = date('Y-m-d H:i:s');
        $entry = "[{$timestamp}] {$level}: {$message}" . PHP_EOL;
        file_put_contents($this->basePath, $entry, FILE_APPEND);
    }
}

Log Retention: How Long to Keep Records

How long you retain logs depends on several factors including regulatory requirements, storage costs, and your debugging needs. There is no single correct answer, but having a clear policy prevents either over-retaining (wasting storage) or under-retaining (losing useful evidence).

General Guidelines by Log Type

  • Security logs: Keep authentication failures, authorisation denials, and suspicious activity for at least 90 days. If your application handles sensitive data or operates in regulated industries, you may need to retain these for a year or longer.
  • Error logs: Keep detailed error logs for 30 to 90 days. After that, archive compressed versions if you need historical debugging capability.
  • Business transaction logs: Treat these as part of your business records. Keep them long enough to support audit requirements and dispute resolution, typically a minimum of one year.
  • Debug and verbose logs: These are valuable for recent troubleshooting but can be deleted after 7 to 14 days to save space.

Storage and Cost Considerations

Retaining logs indefinitely is not practical. Rotate them to compressed archives, move older archives to cheaper storage, and delete logs that are no longer needed. Cloud storage solutions offer lifecycle policies that automate this process based on age or size thresholds.

Security Considerations for PHP Application Logs

Logs are valuable for debugging, but they also contain sensitive information that needs protection. A compromised log file can reveal details about your application structure, your users, and potential vulnerabilities.

Protecting Log Data

Restrict file permissions so that only the web server user and administrators can read log files. Store logs on a separate partition when possible, so that a log-filling attack cannot crash the entire server.

Be careful about what you include in logs. Never log full credit card numbers, complete passwords, or API secrets. If you need to log request parameters for debugging, sanitise sensitive fields first.

function sanitizeForLogging(array $data): array {
    $sensitiveFields = ['password', 'credit_card', 'cvv', 'api_key', 'token'];
    foreach ($data as $key => $value) {
        if (in_array(strtolower($key), $sensitiveFields, true)) {
            $data[$key] = '[REDACTED]';
        } elseif (is_array($value)) {
            $data[$key] = sanitizeForLogging($value);
        }
    }
    return $data;
}

Log Injection Attacks

User input that is written directly to log files without sanitisation can enable log injection attacks. An attacker might inject fake log entries that, when viewed in a monitoring tool, could mislead administrators or exploit vulnerabilities in log viewing software. Always sanitise and escape input before writing it to log files.

Using Logs for Application Monitoring

Beyond debugging, logs feed into broader monitoring strategies. Set up alerts that trigger when error rates exceed normal thresholds, when specific security events occur, or when performance metrics degrade.

For booking systems and similar applications, monitoring booking success rates, payment failures, and availability check times provides early warning of problems. Log entries formatted consistently make it straightforward to aggregate metrics and build dashboards that show application health at a glance.

Frequently Asked Questions

Should I log every PHP warning and notice?
Not every warning needs to be logged at the same level. PHP generates some notices for minor issues that do not affect functionality. During development, log everything to identify potential problems. In production, log warnings and errors but consider adjusting error reporting levels so that noisy notices do not clutter your logs. The goal is logs that are actionable, not logs that require filtering through hundreds of irrelevant entries.
Can I use error_log() instead of a proper logging library?
PHP's built-in error_log() function works for basic logging but has limitations. It writes to a single configured destination, has limited formatting options, and does not support log levels. For simple projects, error_log() is acceptable. For applications that need structured logging, multiple destinations, or integration with monitoring tools, a library like Monolog provides much more flexibility.
How do I handle logging for CLI PHP scripts?
Command-line scripts often run in different contexts than web requests. Configure your logging to write to stdout or stderr for scripts that run manually, and to a file for scheduled cron jobs. Include process information like the script name and PID in log entries so you can trace which process generated each entry when multiple scripts run concurrently.
What should I do if the log directory becomes full?
If disk space runs low, your application may be unable to write new log entries, which can cause failures or silent errors. Monitor disk usage and set up alerts before space runs out. Implement log rotation if you have not already, and consider moving older logs to a separate partition or archiving them to object storage. In emergency situations, you can truncate the oldest log files, but this should not be a regular practice.
Do I need to log in a production environment?
Yes. Many developers disable logging in production to improve performance, but this removes your ability to diagnose problems when they occur. A better approach is to log selectively at lower verbosity in production, use efficient logging handlers, and rely on log rotation to manage file sizes. Performance impact from logging is usually minimal when using asynchronous or buffered logging handlers.