Why Choosing Log Errors Over Display Errors in PHP Can Save Your Site and Your Sanity

Hire a PHP developer for your project — click here.

by admin
display_errors_vs_log_errors_php

Display errors vs log errors in PHP: The quiet choice that saves your sanity

Hey, fellow PHP developers. Picture this: it's 2 AM, your site's live, users are poking around, and suddenly—bam—a fat parse error splatters across their screen. Your database credentials? Right there in plain text. Your heart sinks. We've all been there, that cold sweat moment when display_errors betrays you in production.

But what if errors whispered instead of screamed? That's the magic of logging them away. Today, we're diving deep into display_errors versus log_errors—not just configs, but the real-world rhythm of debugging without drama. I'll share stories from the trenches, code that actually works, and why this switch feels like finally breathing easy after holding your breath for too long.

Why errors hit different in dev versus prod

Remember your first big project? Mine was a messy e-commerce site back in PHP 5 days. I'd flip display_errors = On in php.ini, watch notices and warnings flood the screen like confetti. Helpful? Sure, in dev. But push to prod? Disaster. Users see stack traces, hackers sniff paths. Security nightmare.

The fix starts simple. In development:

error_reporting(E_ALL);
ini_set("display_errors", 1);

This catches everything—fatal errors, notices, the works. Your monitor glows with truth. But production demands silence. Turn off display, turn on logs. Why? Errors leak info: file paths, variables, server details. One Reddit thread I saw had a dev's entire API key exposed. Brutal lesson.

Have you ever tailed a log file during a outage, piecing together the puzzle while the boss paces? That's logging's gift—log_errors keeps the mess server-side, user-side clean.

The configs that control it all

PHP's error game lives in php.ini, but runtime tweaks via ini_set() save the day when you can't touch the server.

Key players:

  • display_errors: On/Off/1/0. Shows errors to the browser. Dev: yes. Prod: never.
  • log_errors: On to write to error_log file. Pairs with error_log path.
  • error_reporting: E_ALL for max verbosity. Bitwise magic like E_ERROR | E_WARNING for fine control.

Prod setup I swear by:

display_errors = Off
log_errors = On
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
error_log = /var/log/php_errors.log

Separate PHP logs from Apache noise. No more sifting 404s for your real bugs.

Quick runtime switch? Slap this at script top:

if (!defined('ENV_PROD')) {
    ini_set('display_errors', 1);
    error_reporting(E_ALL);
} else {
    ini_set('display_errors', 0);
    ini_set('log_errors', 1);
}

Feels clean. No more "why isn't this error showing?" panics.

Real pain: When display_errors ghosts you

Ever had a fatal error during PHP startup? display_startup_errors stays off by design—no output. Sneaky. Or overrides in .htaccess clobber your ini_set. Check with phpinfo()—I've wasted hours chasing ghosts.

One client site: errors vanished mid-deploy. Culprit? Shared host with display_errors=Off forced. Solution? Custom logger function:

function logger($message, array $data, $logFile = "error.log") {
    foreach ($data as $key => $val) {
        $message = str_replace("%{$key}%", $val, $message);
    }
    $message .= PHP_EOL;
    error_log($message, 3, $logFile);
}

// Usage
logger("User %id% failed login from %ip%", ['id' => 123, 'ip' => $_SERVER['REMOTE_ADDR']]);

Structured. Contextual. Better than raw dumps.

See also
Why PHP Is the Silent Giant Behind Secure Fintech and Scalable SaaS Success

Leveling up: Custom handlers that feel human

PHP 7+ flipped the script—fatal errors are now Error exceptions. Catch 'em with try-catch. But non-fatals? set_error_handler().

My go-to skeleton:

function myCustomErrorHandler(int $errNo, string $errMsg, string $file, int $line) {
    $errorType = match($errNo) {
        E_USER_ERROR => 'Fatal',
        E_WARNING => 'Warning',
        default => 'Error'
    };
    error_log("[$errorType] $errMsg in $file:$line");
    // Don't die on warnings
    if ($errNo === E_USER_ERROR) {
        exit(1);
    }
    return true; // Suppress default handler
}

set_error_handler('myCustomErrorHandler');

Triggers? trigger_error("Oh no!", E_USER_WARNING); Manual logs without halting flow.

Uncaught exceptions? set_exception_handler() as global safety net:

function globalExceptionHandler(Throwable $e) {
    error_log("Uncaught: " . $e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine());
    http_response_code(500);
    echo "Something went wrong. We're on it.";
}

set_exception_handler('globalExceptionHandler');

Prod gold. Logs everything, shows users a friendly "oops."

Beyond basics: Exceptions, @ suppression, and modern logging

That @ operator? Suppresses errors like file_get_contents(@nonexistent);. Tempting for quick fixes. But it's a relic—hides bugs, murders debuggability. Ditch it.

Exceptions shine brighter. Custom ones for your app:

class DatabaseException extends Exception {}

try {
    // DB call
} catch (DatabaseException $e) {
    logger("DB fail: " . $e->getMessage(), ['trace' => $e->getTraceAsString()]);
    throw $e; // Propagate if needed
} catch (Exception $e) {
    logger("Generic: " . $e->getMessage());
} finally {
    // Cleanup always
}

Handle, log, rethrow. Clean separation.

Multiple catches? Union types in PHP 8+: catch (MyException | AnotherException $e). Elegant.

Production war stories and pro tips

Flashback: Black Friday rush, my API choked on undefined index. display_errors=Off hid it from users—logs caught it. Fixed in 5 mins via tail -f. Win.

Tip 1: Rotate logs. logrotate or tools like Sentry/Rollbar for alerts. No more 10GB monsters.

Tip 2: E_ALL in prod? Almost. Mask deprecations: E_ALL & ~E_DEPRECATED. Keeps noise low.

Tip 3: Test handlers. Throw dummies in dev:

trigger_error("Test warning", E_USER_WARNING);
throw new Error("Test fatal");

Watch logs fill. Peace of mind.

What about monoliths vs microservices? Same rules—log centrally. I've piped PHP logs to ELK stack; queries like "error AND user_id:123" unearth patterns fast.

Tools that make logging sing

Built-ins rock, but level up:

  • Monolog: PSR-3 logger. Channels, handlers, rotations. Dead simple.
  • Sentry: Real-time alerts, breadcrumbs. Catches what slips handlers.
  • error_log(3, '/path/log'): Direct file writes.

Custom rotator I built once:

function safeErrorLog($msg) {
    $log = __DIR__ . '/logs/' . date('Y-m-d') . '.log';
    if (file_exists($log) && filesize($log) > 10*1024*1024) { // 10MB
        rename($log, $log . '.old');
    }
    error_log($msg, 3, $log);
}

No bloat. Reliable.

The emotional side: Errors as teachers, not tyrants

Late nights debugging blind? That's display_errors addiction talking. Logging shifts power back—you control the narrative. Errors become mentors in a file, not attackers on screen.

I've mentored juniors who panic at blank screens. Teach 'em logs first. "Check the file, not the fear." Their relief? Priceless.

In the end, it's about trust. Users trust your site stays up. You trust your logs catch the falls.

Switch today. Let errors log quietly, and code with the calm that comes from knowing they're watched, not waved in faces. Your future self—and your users—will thank you in the silence.
перейти в рейтинг

Related offers