English · Русский
The debugger side of wapp_php_debugger: it writes the events of every request (messages, timers, errors, the outcome) into JSONL logs, one file per request, and the viewer shows them live. PHP 7.4 and newer, no required dependencies.
With Composer:
composer require --dev hightemp/wapp-php-debuggerThe package is not on Packagist yet. Until it is, point Composer at a checkout of this repository:
composer config repositories.wapp-debugger path /path/to/wapp_php_debugger/client-php
composer require --dev hightemp/wapp-php-debugger:@devWithout Composer, take the one-file build from a release (everything except the PSR-3 and Monolog bridges, which need Composer anyway):
curl -fsSLO https://github.com/hightemp/wapp_php_debugger/releases/latest/download/wapp-debugger.phprequire __DIR__ . '/wapp-debugger.php';or copy client-php/src into the project and require src/autoload.php. make php-bundle builds the one file from the sources.
Call boot() as early as possible, in the front controller or the CLI entry point:
WappDebugger\Debugger::boot(__DIR__ . '/dws_debugger.json');and put dws_debugger.json into the project root:
{
"enabled": true,
"logs_dir": "tmp/logs"
}Without the file, or with "enabled": false, the library does nothing: a request costs one is_file() and a small JSON read. On, boot() starts the request, registers the error handlers (the handlers of the application keep working and PHP still prints errors as usual) and finishes the request at shutdown with the HTTP status or the exit code. The viewer turns enabled on and off for you.
__d(DWS_INFO, 'Order created', ['order' => $order]); // level, message, named variables
$total = __log($price) * $quantity; // logs a value and returns it
__start('db'); /* ... */ __stop('db'); // timer: info, warning over 1 s, error over 2 sThe same through the object:
use WappDebugger\Debugger;
use WappDebugger\Level;
$debugger = Debugger::instance();
$debugger->log(Level::DEBUG, 'SELECT * FROM items WHERE id = ?', ['params' => [$id]], ['sql']); // tags
$debugger->exception($caught); // with its trace and previous exceptions
$debugger->startTimer('render');
$debugger->stopTimer('render');Levels: error, warning, info, message (neutral), debug. Every event knows where it came from (file and line, relative to the project root) and, by default, the last 10 frames of the call stack.
Values are made safe for the log: depth, items and string length are limited, objects are shown with all their properties, recursion is cut. Keys that look like secrets (password, token, api_key, authorization, cookie, dsn… — the same rule as library v1) are written as ***.
A page and its AJAX calls, or a job and the processes it starts, are linked in the viewer:
- web: send the page's id in the
X-Wapp-Debugger-Parentheader of its AJAX calls; the page gets it fromDebugger::instance()->requestId(); - CLI: nothing to do — the library sets
WAPP_DEBUGGER_PARENTfor processes started from a request.
Queries and outgoing requests are written with their duration; the viewer lists them on the SQL tab (with repeated queries — N+1 — pointed out) and on the request timeline.
use WappDebugger\Debugger;
use WappDebugger\Sql;
Sql::watch($pdo); // every prepared statement of $pdo logs itself: SQL, parameters, rows, time
$pdo->prepare('SELECT * FROM orders WHERE user_id = ?')->execute([$userId]);
// Anything else, by hand
$started = Debugger::instance()->monotonicMs();
$rows = $legacyDb->query($sql);
Debugger::instance()->sql($sql, $params, Debugger::instance()->monotonicMs() - $started, count($rows));
Debugger::instance()->http('GET', $url, $status, $durationMs); // or ..., null, $durationMs, $errorPDO::query() and PDO::exec() bypass the statements of Sql::watch(): prepare such queries or log them by hand. Parameters are masked like any context, secret query parameters of URLs too (?token=***). A failing statement is logged as an error with its message, and the exception goes on to the application.
Guzzle (no Guzzle needed by the library itself):
$stack = GuzzleHttp\HandlerStack::create();
$stack->push(WappDebugger\Bridge\GuzzleMiddleware::create());
$client = new GuzzleHttp\Client(['handler' => $stack]);$logger = new WappDebugger\Bridge\PsrLogger(); // Psr\Log\LoggerInterface: psr/log 1, 2 or 3
$monolog->pushHandler(new WappDebugger\Bridge\MonologHandler()); // Monolog 2 or 3, whichever is installedPSR-3 levels map onto the five levels above (critical, alert, emergency → error, notice → info); an exception in the exception key of the context is written with its trace.
Laravel, Symfony, WordPress and 1C-Bitrix take a few lines or one file: integrations/README.md. Laravel gets a log channel, WappDebugger\Bridge\LaravelChannel; WordPress a ready mu-plugin; Bitrix a file for init.php; Symfony a config/packages file.
dws_debugger.json; relative paths are resolved against its directory. File and directory modes are octal strings: JSON has no octal numbers.
| Key | Default | Meaning |
|---|---|---|
enabled |
false |
Write logs |
allowed_envs |
none | Environments the debugger may work in, by env_var, e.g. ["local", "dev", "stage"]. Elsewhere — or with the variable unset — it is off whatever enabled says, and so is the HTTP mode: a config copied to production stays silent |
env_var |
"APP_ENV" |
Variable with the environment name: getenv(), then $_SERVER (SetEnv, fastcgi_param), then $_ENV; case does not matter |
project_root |
directory of the file | Paths in the logs are relative to it |
logs_dir |
<project_root>/tmp/logs |
Where the log files go; must be writable by PHP |
file_mode, dir_mode |
"0664", "0775" |
Modes of new files and directories (the viewer often runs as another user) |
trace_frames |
10 |
Frames of the call stack of a message; 0 keeps only the call site |
max_depth, max_items, max_string_bytes |
6, 100, 2000 |
Limits for logged values |
mask_keys |
[] |
More keys to write as *** |
log_globals |
["get", "post", "files"] |
Superglobals logged at start; add server, session, cookie when needed — they hold secrets |
max_age_days, max_files, max_logs_size |
30, 5000, 268435456 |
Old logs are deleted past these limits (bytes for the size); 0 turns a limit off |
push_url, push_token |
none | Send the events to the viewer's receiver instead of files (http(s)://…/ingest/v2, the project's push token, 32+ characters); see Pushing to the viewer |
http_token |
none | Turns the HTTP mode on (at least 32 characters) |
http_allowed_ips |
[] |
Addresses or CIDR networks allowed to use the HTTP mode; empty means any |
http_rate_limit |
600 |
HTTP mode requests per minute |
With push_url and push_token (made in the viewer's project settings, serve --ingest-addr) the events go to the viewer over HTTP instead of files: in batches of 100, every second in a long run, the rest when the request ends. PHP streams send them — no extension is needed (https:// needs openssl, like any HTTPS in PHP). One failed send (2 s timeout) stops logging for that request. allowed_envs turns pushing off like the rest.
Debugger::instance()->jsTag() prints the <script> tag of the viewer's JS client for the page: its uncaught errors, unhandled rejections and console.error/warn come in as a child of this request. It is empty when pushing is off. The token is in the page, so this is for dev and stage.
For servers the viewer cannot reach over SSH. Off by default; with http_token set, the library answers under /__wapp_debugger/v1/ (status, turning logging on and off, listing, reading and deleting logs — see spec/library-http.yaml). The token goes only in the X-Debugger-Token header; without it the request reaches the application as if the mode did not exist. Use HTTPS.
- Replace
require_once 'Debugger.class.php'with the autoloader (Composer orautoload.php) andDebugger::boot(). __d,__log,__startand__stopstay as they are.fnDebugLog(),fnDebugAddFoundField()andfnDebugAddNotFoundField()still work but are deprecated (oneE_USER_DEPRECATEDper request) and go away in 3.0, as do theDWS_*constants.- The old keys of
dws_debugger.jsonare still read and the log gets a warning; rename them:bIsOnFlag→enabled,sProjectRootPath→project_root,sMessagesFilesPath→logs_dir,iArraySizeLimit→max_items,iLogsSizeLimit(kilobytes) →max_logs_size(bytes),bLogSessionAndCookies: true→log_globalswithsessionandcookie;sTempPathandiPageSizeare not used. $_SERVERis no longer logged by default (addservertolog_globalsif you need it).- The library writes log format v2; the viewer reads both v1 and v2, so old logs stay readable.