Why PHP Error Logging Matters in Production
When a PHP application fails in production, the difference between a ten-minute fix and a two-day investigation comes down to one thing: what you logged before it crashed. Effective error logging is not optional overhead. It is the difference between knowing something went wrong and knowing exactly what happened, where it occurred, and why.
This article covers what to capture in PHP error logs, where to send logs so they are actually useful, and how to build a logging setup that helps rather than buries you when something goes wrong at 2am. Whether you are setting up logging for a new project or reviewing an existing application, the principles here apply to any PHP environment running in production.
What PHP Logs by Default
PHP has a built-in error reporting system that handles syntax errors, missing files, and runtime warnings. By default, it can log errors to a file, the web server error log, or standard error output. In development environments, errors print directly to the browser, which is useful for debugging. In production, that default behaviour is dangerous because it exposes internal paths, code structure, and potentially sensitive configuration details to anyone who triggers an error.
The first step is setting the right error reporting level. Use E_ALL during development to catch every possible issue. In production, E_ALL & ~E_DEPRECATED catches everything except removed-feature warnings that do not affect the current release.
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/php/application.log');
Setting display_errors to off in production is non-negotiable. What PHP considers an error worth displaying is broader than what most developers expect, and the stack traces it outputs contain information that is valuable to attackers, not just developers.
What to Capture Beyond PHP Errors
PHP error logs capture syntax errors, missing files, and runtime warnings. They do not capture application-level events: a user uploaded the wrong file type, a payment gateway returned an unexpected code, or a search returned zero results when the database was under load. These events matter more than PHP errors in most production incidents. A missing PHP file causes an obvious crash. A subtly broken business logic path can silently produce wrong results for days before anyone notices.
Use a logging library like Monolog to capture structured application events. Monolog writes to multiple channels simultaneously: a file for audit purposes, a database table for querying, a Slack channel for immediate alerting, or a log aggregation service for distributed systems.
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\SlackWebhookHandler;
$log = new Logger('application');
$log->pushHandler(new RotatingFileHandler('/var/log/php/app.log', 30, Logger::DEBUG));
$log->pushHandler(new SlackWebhookHandler('https://hooks.slack.com/services/YOUR/WEBHOOK/URL', Logger::ERROR));
$log->info('User uploaded file', ['user_id' => $userId, 'filename' => $filename]);
$log->error('Payment gateway error', ['gateway' => 'stripe', 'code' => $errorCode]);
If you are building PHP applications and want to understand how automated deployment fits into a production workflow, the CI/CD for PHP Projects guide covers how logging integrates with deployment pipelines.
Choosing Log Levels Correctly
Monolog and PSR-3 define eight log levels: DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY. The most common mistake is treating everything as ERROR or not differentiating at all. Effective log analysis depends on being able to filter by level.
- DEBUG: Detailed diagnostic information during development or when investigating a specific issue. SQL queries, variable states, and loop counters fall into this category. Not needed in production under normal conditions.
- INFO: Significant business events such as user registrations, file uploads, and completed transactions. These are useful for auditing and understanding usage patterns.
- NOTICE: Normal but significant events that are worth noting but do not indicate a problem.
- WARNING: Unexpected situations that the code handled gracefully but should be reviewed. A deprecated API call that still works, or a slow database query.
- ERROR: Something failed but the application continued running. A single API call failed or a file write failed. The application recovered but the event should be investigated.
- CRITICAL: A component stopped working. Database connection lost or payment processor unreachable. The application is in a degraded state.
- ALERT: Action must be taken immediately. A whole website is down or a critical security event occurred.
- EMERGENCY: System is unusable. The application cannot function at all.
When setting up your logging library, take time to establish team conventions for which level to use in specific scenarios. Inconsistent log levels make filtering useless during an incident.
What to Include in Every Log Entry
A log entry is only useful if it contains enough context to act on. Every entry should include a timestamp in UTC ISO 8601 format, the log level for filtering, application context like request ID and user ID, the action the code was attempting, the result of what happened, and environment details like server name and PHP version.
Correlate log entries using a unique request ID passed through the entire request lifecycle. This makes it possible to trace a single user action across multiple log files and service boundaries.
$requestId = bin2hex(random_bytes(16));
$context = [
'request_id' => $requestId,
'user_id' => $userId ?? null,
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'action' => 'process_booking'
];
$log->info('Processing booking request', $context);
Propagate the request ID through all subsequent log entries and include it in error responses sent to the client. When a user reports an error with a reference ID, you can search for that ID across all log entries immediately.
Server-level context matters too. Including hostname, PHP version, and memory usage at the time of the log entry helps reconstruct the environment when investigating issues that only occur under specific conditions.
Where to Send Logs
Writing logs to a file on the application server is the baseline. It is insufficient for anything beyond a single-server setup. When the server fills its disk, crashes, or gets replaced, logs disappear with it.
For single servers, use log rotation to prevent disk exhaustion. The logrotate utility on Ubuntu manages this automatically.
/var/log/php/application.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
/etc/init.d/apache2 reload > /dev/null
endscript
}
For multiple servers or production environments, ship logs to a central aggregation service. The ELK stack (Elasticsearch, Logstash, Kibana) is popular and self-hosted. Cloud options include AWS CloudWatch, Datadog, or Grafana Loki. Centralised logs make it possible to search across all servers simultaneously and build dashboards for error rates, warning frequencies, and application health over time.
If you are setting up staging or production environments for PHP applications, it is worth planning your logging infrastructure alongside your server configuration. A properly configured staging environment lets you test logging behaviour before deploying to production.
Setting Up Alerting
Logs are only useful if someone reads them. In production, logs that nobody monitors are logs that nobody sees until a customer reports the problem. Set up alerts on error thresholds so that the right people get notified before users notice.
Alert on error rate, not individual errors. A spike from five errors per hour to two hundred per minute is a problem that needs immediate attention, even if each individual error was handled gracefully by the code.
- ERROR rate: Alert when ERROR-level entries exceed 10 per minute for five consecutive minutes.
- CRITICAL events: Alert immediately on any CRITICAL, ALERT, or EMERGENCY entry.
- Warning rate: Track WARNING trends. A rising warning rate often precedes an error rate increase.
- Disk usage: Alert when the log partition exceeds 80 percent capacity to prevent log-related downtime.
Configure alerting channels carefully. Too many alerts cause alert fatigue. Too few and critical issues go unnoticed. Start conservative and adjust based on what actually requires attention in your environment.
What to Log and What to Skip
Logging too much is as bad as logging too little. Excessive logs create noise, consume disk space, and make it harder to find the relevant entries when something goes wrong.
Never log passwords, full credit card numbers, sensitive personal data, or API keys from third-party services. Always log authentication events including login success and failure, authorisation failures, data mutations for financial or user data, external service calls, and business-critical events like orders, bookings, and sign-ups.
Log enough to reconstruct what happened, not everything about what was happening. You do not need to log every line of a loop; you need to log the input to the loop and the output. This gives you the debugging information you need without creating log files that grow to gigabytes overnight.
Security note: Treat log files as sensitive data. They contain information about your application structure, user behaviour, and system state. Restrict access appropriately and rotate logs that may contain sensitive information.
Reading Logs Effectively
A log file with thousands of entries per hour is useless without tools to search it. The grep, awk, and sort utilities handle most single-server log analysis tasks.
grep -E "ERROR|CRITICAL" /var/log/php/application.log | grep "$(date -d '1 hour ago' +%Y-%m-%dT%H)"
For structured logs in JSON format, use jq to query and filter.
cat /var/log/php/application.json | jq '. | select(.level == "ERROR") | {timestamp, message, user_id, request_id}'
Build common queries into a script or alias so that they are easy to run during an incident without having to remember the syntax under pressure. Keep a incident-response script in version control that your team can use during outages.
Common PHP Logging Mistakes
- Logging without context: A log entry that says "Error occurred" with no request ID, user ID, or action description is unactionable. Always include what the code was doing and who it affected.
- Not rotating logs: Filling a disk partition with log files brings down the application. Set up log rotation on day one.
- Logging sensitive data: Exposing passwords, tokens, or personal data in logs creates compliance violations and security risks. Sanitise inputs before logging.
- Ignoring warnings: Warnings are often precursors to errors. Track warning trends alongside error rates to catch problems before they become incidents.
- No alerting: Logs nobody reads are logs that provide no operational value. Even simple email alerts on CRITICAL events are better than nothing.
Building a Practical Logging Workflow
Start with the basics: enable PHP error logging to a dedicated file, set display_errors to off, and set up log rotation. That gives you a foundation that works without external dependencies.
Then add application-level logging with Monolog. Log business events, external API calls, and authentication events. Use the right log level for each entry so that filtering by severity is possible. Configure your logging library early in your application bootstrap so that it is available throughout the request lifecycle.
Set up alerting even with simple email alerts on CRITICAL events. The goal is always the same: when something goes wrong, you want to be able to answer the question "what happened, to whom, and why" in under five minutes without having to reproduce the problem.
If you are working on improving your development environment alongside your production setup, it is worth understanding how debugging tools like Xdebug fit into a complete PHP workflow. A good local debugging setup complements production logging by letting you investigate issues in detail when they are reported.