Advanced Debugging Techniques for MediaWiki Developers
Turning on the lights: PHP‑level insights
When a MediaWiki instance starts spitting out a “Fatal error” page, the first instinct is to reach for error_reporting and display_errors. In a fresh clone, those settings are deliberately muted – the platform assumes you’ll be developing locally, not on a public wiki. To get the raw stack trace you simply drop the following into the top of LocalSettings.php (right after the opening <?php line):
error_reporting( -1 );
ini_set( 'display_errors', 1 );
ini_set( 'display_startup_errors', 1 );
That alone reveals the line numbers and function names that led to the blow‑up. If you’re working behind a shared host where ini_set is ignored, a quick .htaccess tweak does the trick:
php_value error_reporting -1
php_flag display_errors On
php_flag display_startup_errors On
Remember to flip those switches off once the bug is nailed; otherwise you’re handing attackers a roadmap.
Peeking under the hood: SQL diagnostics
MediaWiki’s database layer is notoriously lazy – it only logs the query that caused an exception. That’s fine for the occasional typo, but when a deadlock or a missing index crashes the page you’ll want the full history. Set the global flag in LocalSettings.php:
$wgDebugDumpSql = true;
Now every SELECT, INSERT or UPDATE that runs during the request ends up in the debug output (or in $wgDebugLogFile if you’ve routed logs elsewhere). Pair this with the $wgDebugLogFile configuration to keep a persistent log:
$wgDebugLogFile = '/var/log/mediawiki/debug.log';
Tip: the log grows fast. Rotate it with logrotate or, on a dev box, just wipe it every few runs.
Interactive debugging with XDebug
If “echo‑this‑value” isn’t cutting it, XDebug is the gold standard. A modern MediaWiki dev environment usually runs in Docker, Vagrant, or a bare‑metal VM; all three ship with ready‑made XDebug recipes. The minimal PHP‑ini additions look like this:
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=host.docker.internal # Docker on macOS/Windows
xdebug.client_port=9003
Most IDEs (PhpStorm, VS Code, NetBeans) will pick up the connection automatically. Breakpoints work inside core files, extensions, and even maintenance scripts. For command‑line runs you can wrap the call:
xdebug_on; php maintenance/runPeriodic.php --wiki=enwiki; xdebug_off
If you’re on a remote VM and the IDE sits on your laptop, adjust xdebug.client_host to the VM’s gateway IP (often 10.0.2.2 inside Vagrant).
The built‑in Debug Toolbar
When you need a quick look at timing, memory usage, and log messages without leaving the browser, enable the toolbar:
$wgDebugToolbar = true;
It appears as a thin gray bar at the top of every page, expanding into panels for Performance, SQL, and Hooks. The toolbar also injects a tiny “View source” link that shows the raw PHP output – handy when $wgShowDebug is off but you still want to see where trigger_error calls are coming from.
Fine‑grained logging with custom groups
MediaWiki’s logging system is built on Monolog, and you can carve out new groups for your extension:
$wgDebugLogGroups['myextension'] = '/var/log/mediawiki/myextension.log';
Inside your PHP you then write:
wfLog( 'myextension', 'Something odd happened', 'debug' );
The log receives a timestamp, the wiki ID and the pid – perfect for post‑mortem analysis.
Structured logging and JSON payloads
For larger teams that aggregate logs in ELK or Splunk, turning on structured logging is a boon. Add this snippet:
$wgDebugLogConfig['myextension'] = [
'driver' => 'jsonfile',
'path' => '/var/log/mediawiki/myextension.json',
];
Now each entry is a valid JSON object. You can query on fields like level or wiki without parsing free‑form strings.
JavaScript errors in the back‑end log
MediaWiki’s front‑end isn’t just a static mash‑up; many extensions ship with heavy JavaScript. To capture uncaught exceptions in the same PHP log, enable the JS logger:
$wgDebugLogJS = true; // send to the PHP debug log
$wgDebugLogFile = '/var/log/mediawiki/debug.log';
Every window.onerror event now appears as a line prefixed with “JS”. This is extremely valuable when a UI regression corrupts wikitext parsing but the PHP stack stays untouched.
Statistics and profiling via MediaWiki’s built‑in tools
When you suspect a performance regression, the simple php maintenance/rebuildLocalisationCache.php won’t help. Instead, fire up the “stats” module:
$wgEnableAPI = true; // ensure maintenance scripts can talk to API
$wgStatsdServer = '127.0.0.1:8125';
With a local StatsD daemon you’ll start seeing metrics like mediawiki.request.time and mediawiki.db.query.count. Plot them in Grafana, compare across releases, and you’ll spot spikes before they turn into outages.
Embedding debug data in HTML comments
Sometimes you can’t afford a visible toolbar (think a public beta). MediaWiki lets you hide all debug details inside an HTML comment that appears only in the source view:
$wgDebugComments = true;
Open “View source” and you’ll see a block like:
This is a neat compromise: the UI stays clean, yet developers can still inspect the internals without touching the filesystem.
Callable updates – debugging deferred jobs
Deferred updates (e.g., the link cache refresher) run after the request has finished. If those jobs misbehave, you won’t see any error on the page. Enable the “verbose” mode for the job queue:
$wgJobRunRate = 1.0; // run all jobs immediately
$wgDebugJobQueue = true;
Now each job’s start and finish timestamps appear in the debug log, along with any trigger_error messages they emit. For production you can keep the rate low and still have the logs for the occasional failing job.
Interactive shell – mwscript and the REPL
MediaWiki ships with mwscript, a thin wrapper around PHP‑CLI that loads the full MediaWiki environment. Combined with psysh you get a REPL where you can poke at objects:
composer require psy/psysh
mwscript -c 'require "vendor/autoload.php"; $shell = new \Psy\Shell; $shell->run();'
From there you can instantiate WikiPage objects, call Parser::parse, or even fire off a hook. It’s an underrated way to reproduce a bug that only shows up after a certain Ajax call.
Callable updates – debugging deferred jobs
Deferred updates (e.g., the link cache refresher) run after the request has finished. If those jobs misbehave, you won’t see any error on the page. Enable the “verbose” mode for the job queue:
$wgJobRunRate = 1.0; // run all jobs immediately
$wgDebugJobQueue = true;
Now each job’s start and finish timestamps appear in the debug log, along with any trigger_error messages they emit. For production you can keep the rate low and still have the logs for the occasional failing job.
Putting it all together
Advanced debugging isn’t about sprinkling a few var_dump statements; it’s a layered approach:
- Surface‑level: PHP error reporting, SQL dump, debug toolbar.
- Deep‑dive: XDebug breakpoints, structured logs, interactive shell.
- Operational: StatsD metrics, job‑queue verbosity, HTML‑comment output.
Pick the slice that matches the symptom you’re chasing. A blank page? Start with $wgShowExceptionDetails. A slow edit form? Turn on the toolbar and look at $wgEnableStats. A flaky JavaScript widget? Enable $wgDebugLogJS and watch the PHP log for “JS” entries.
Finally, never forget to clean up. The same settings that make a developer’s life easier can expose secrets on a live wiki. Wrap your debug configuration in a conditional that checks MW_ENTRY_POINT or a custom $wgIsDevEnvironment flag, and you’ll keep production pristine while still enjoying a sandbox of diagnostic power.