PHP utility collection with hybrid and fluent APIs.

⌈⌋ branch:  hybrid7 libraries


Check-in [5cd584fcc8]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Separate ł::$app to be static, but ł()->section= and $min_prio to be logger-group specific property. Reorder class defaults, compact introduction text. Fix error handler context usage as :vars, apply :backtrace manually.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:5cd584fcc88f62a2d741202d02817b73ee0227e0
User & Date: mario 2015-01-13 18:50:54
Context
2015-02-16
20:14
Prepare macro preprocessing build. check-in: 0627bb6e05 user: mario tags: trunk
2015-01-13
18:50
Separate ł::$app to be static, but ł()->section= and $min_prio to be logger-group specific property. Reorder class defaults, compact introduction text. Fix error handler context usage as :vars, apply :backtrace manually. check-in: 5cd584fcc8 user: mario tags: trunk
18:47
Obsolete/unused bindings for Inspekt check-in: 2734b95caa user: mario tags: trunk
Changes

Changes to logstruck.php.

1
2
3
4
5
6
7
8
9
10
11
12
..
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
...
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
...
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
...
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
...
233
234
235
236
237
238
239




240
241








242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
...
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
...
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
...
522
523
524
525
526
527
528













529
530
531
532
533
534
535
...
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586


587
588
589
590
591
592
593
...
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
...
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
...
724
725
726
727
728
729
730
731

732
733
734
735
736
737
738
<?php
/**
 * title: logStruck
 * description: SQlite group/structured logging backend
 * version: 0.1.0
 * api: php
 * type: handler
 * category: logging
 * state: experimental
 * depends: php:sqlite >= 3.7, php >= 5.4
 * autoexec: true
 * config:
................................................................................
 *   { type: var, name: "ł::$app", description: "default application name" }
 *   { type: var, name: "ł::$section", description: "current application module/section" }
 * license: Public Domain
 * doc: https://fossil.include-once.org/hybrid7/wiki/log
 * 
 * 
 * Implements a terse but parametric logging API for structured journaling.
 * Basically meant for userland and direct GELF/Journald-style logging.
 * 
 *  · Storage in SQLite
 *  · Retain PHP process/runtime grouping, for tree-style event relationship
 *  · Application and sectioning names
 *  · Priority (err, crit, warn, info) and origin (app, lang, assert, sys)
 *  · Common fields as primary keys
 *  · Dictionary JSON blobs for arbitrary data
 *  · Auto-registering error and assertion handler
 *  · No configurability for alternative backend dispatching.
 * 
 * Function signatures:
 * 
 *  · Main logging function is  ł("..")
 *  · Use Ruby-style ':token' names for priority and section names
 *  · And HTTP-style 'field: value' parameters
 *  · Passed arrays stored as JSON blobs
 *  · Interpolation tokens :server, :backtrace, :file, :code, :version, :p
 *  · Calling ł::$PRIO_$MODULE("..") readily sets prio and module etc.
 * 
 * Usage examples
 * 
 *   ł(':alert', "No foobar() result")
 * 
 *   ł(":warn", ":openid", "No endpoint found", $handle);
 *
 *   ł("Page not found", ':exception', [$pagename])
 * 
 *   ł(':alert', "INSERT failed", ':database', $PDO->errorInfo(), ["vars"=>$params])
 *
 *   ł(':assert', "Got #$num args.", ':debug', ":plugin", "\$wiki->add()", $args, "doc: ?tktid=2421")
 *
 *   ł(':emerg', "no space left on device", ':sys', "errno: $errno", $fileobject);
 * 
 * 
 * Fields, message strings, data arrays and :tokens may be in any order, but some
 * coherency is advisable of course. Only :section identifiers and :error level
 * tokens should be used prudently.
 * 
 * It's however as important to retain context values where available. Passing an
................................................................................
        return $logger;
    }
}



/**
 * @static Class which implements message struct assembly and storage.
 * Name is UTF-8 to deter cargo culters, and simplify source discoverabiliy.
 * SQLite gets reopened/closed mercilessly to prevent locking.
 *
 */
class ł {


    /**
     * Built-in defaults
     * ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
     *  · Defines a few default values ($app name, primary $section, ...)
     *  · Declares a list of built-in :token names.
     *  · Maps error number magic values.
     *  · And lastly defines the list of primary database field names.
     *
     */

    // SQLite database storage path, should be absolute or docroot-based etc.
    var $db = "config/log.db";

    // Runtime defaults
    static $app = "undef";
    static $section = "main";
    static $min_prio = 7;
    
    // Token aliases
    static $alias = [
        // prios
        "emerg" => "emergency",
        "alrt" => "alert",
        "crit" => "critical",
................................................................................
        "exc" => "exception",
        "app" => "log",
        // injectors
        "trace" => "backtrace",
        "stack" => "backtrace",
        "env" => "server",
    ];
    
    // Log level and sources
    static $prio = ["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"];
    static $source = ["sys", "log", "lang", "assert", "exception"];

    // PHP error constants and priority mapping
    static $phperr = [
        E_ERROR => "ERROR", E_WARNING => "WARNING", E_NOTICE => "NOTICE", E_PARSE => "PARSE", E_STRICT => "STRICT", E_DEPRECATED
        => "DEPRECATED", E_RECOVERABLE_ERROR => "RECOVERABLE_ERROR", E_USER_ERROR => "USER_ERROR", E_USER_WARNING =>
        "USER_WARNING", E_USER_NOTICE => "USER_NOTICE", E_USER_DEPRECATED => "USER_DEPRECATED", E_CORE_ERROR => "CORE_ERROR",
        E_CORE_WARNING => "CORE_WARNING", E_COMPILE_ERROR => "COMPILE_ERROR", E_COMPILE_WARNING => "COMPILE_WARNING",
................................................................................
    ];
    static $phpprio = [
        ':error'  => 0x1111,  # E_RECOVERABLE_ERROR | E_USER_ERROR | E_CORE_ERROR | E_ERROR
        ':warn'   => 0x2222,  # E_DEPRECATED | E_USER_WARNING | E_CORE_WARNING | E_WARNING
        ':notice' => 0x4C88,  # E_USER_DEPRECATED | E_USER_NOTICE | E_STRICT | E_COMPILE_WARNING | E_NOTICE 
        ':crit'   => 0x0044,  # E_PARSE | E_COMPILE_ERROR
    ];


    // Indexed errno map
    static $errno = [
        "EOK", "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", "E2BIG", "ENOEXEC", "EBADF", "ECHILD", "EAGAIN", "ENOMEM", "EACCES", "EFAULT", "ENOTBLK", "EBUSY",
        "EEXIST", "EXDEV", "ENODEV", "ENOTDIR", "EISDIR", "EINVAL", "ENFILE", "EMFILE", "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE", "EROFS", "EMLINK", "EPIPE",
        "EDOM", "ERANGE", "EDEADLK", "ENAMETOOLONG", "ENOLCK", "ENOSYS", "ENOTEMPTY", "ELOOP", "EWOULDBLOCK", "ENOMSG", "EIDRM", "ECHRNG", "EL2NSYNC", "EL3HLT",
        "EL3RST", "ELNRNG", "EUNATCH", "ENOCSI", "EL2HLT", "EBADE", "EBADR", "EXFULL", "ENOANO", "EBADRQC", "EBADSLT", "n/a", "EBFONT", "ENOSTR", "ENODATA", "ETIME",
................................................................................
    ];
    


    /**
     * Instance defaults
     * ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾




     *
     */









    // log event template values
    public $template = [];
    
    // handful of :tokens to apply on each log call
    public $iparams = [];
    
    // log event parent and group id
    var /*&*/ $p = 0;
    protected $g = NULL; // ROWID or :g





    /**
     * Prepare a logging group.
     * ‾‾‾‾‾‾‾
................................................................................

        // prepare log event defaults
        $this->template = array(
            "p" =>   $this->p,
            "g" => & $this->g,
            "prio" => "info",
            "host" => gethostname(),
            "app" => "-",
            "section" => "-",
            "timestamp" => 0,
            "source" => "log",
            "errno" => NULL,
            "file" => NULL,
            "line" => NULL,
            "version" => NULL,
            "message" => NULL,
................................................................................
     * @return int     log-event id
     */
    public function __invoke($vars) {

        // from defaults
        $event = array_merge($this->template, [
            "timestamp" => microtime(TRUE),
            "app" => self::$app,
            "section" => self::$section,
            "p" => $this->p, // parent
            "g" => $this->g, // group
        ]);

        // all this just to turn token/array/string list into structurized blob
        $this->map_params($event, array_merge($this->iparams, $vars));

        // normalize params
        $this->convert_string_params($event);
        $this->move_context_params($event);

        // check prio
        if ($event["pri"] <= self::$min_prio) {

            // store
            $p = $this->store($event);
            
            // keep log event id as group and parent references
            $this->p or $this->p = $p;
            $this->g or $this->g = $p;
................................................................................
        $db->query("DETACH `logdb`");
        $db = NULL;
        return $p;
    }



















    /**
     * Logging handlers
     * ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
     * Alternative logger invocation functions, that basically wrap __invoke() via ł().
................................................................................

    /**
     * PHP error handler.
     *
     * Maps PHP :errno codes, retains :backtrace, and inserts :code excerpt.
     *
     */
    public static function error_handler($errno, $message, $file, $line, $trace) {

        // convert bitmask prio
        foreach (self::$phpprio as $prio => $errmask){
            if ($errmask & $errno) break;
        }

        // Prevent recursion on ł:: originating errors
        if (isset($trace[0]["file"]) and $trace[0]["file"] == __FILE__) {
            fwrite(STDERR, self::$phperr[$errno] . ": $errstr in $file line $line\n");
        }
        
        // Pass values on (- could be way more readable with "field:value" strings)
        else {
            ł(':lang', $prio, $message, ["errno" => $errno, "file" => $file, "line" => $line, "backtrace" => $trace, "error" => self::$phperr[$errno]], ':code', ':version');


        }
        
        // populate $php_errormsg;
        return false;
    }


................................................................................
    public static function exception_handler($e) {
    
        // keep exception class, also use as message if empty
        $class = get_class($e);
        strlen($message = $e->getMessage()) or ($message = $class);
        
        // pass on
        ł(':exception', ':crit', $message, "exception: $class",
          ["backtrace" => $e->getTrace(), "file" => $e->getFile(), "line"=>$e->getLine(), "errno"=>$e->getCode()],
          ':code', ':version'
        );
    }


    /**
     * PHP assert() handler.
     *
................................................................................
     *
     * @param  array   log event data bag; gets updated per reference
     */    


    // inject backtrace list
    public function inj_backtrace(& $data) {
        $data["backtrace"] = ł::filter_backtrace(debug_backtrace(0));
    }
    
    // $_SERVER environment variables
    public function inj_server(& $data) {
        $data["_SERVER"] = is_array($_SERVER) ? $_SERVER : $_SERVER->__vars;
    }

................................................................................
     * Utility calls
     * ‾‾‾‾‾‾‾‾‾‾‾‾‾
     *
     */


    // Remove self-references to `ł.php` from backtrace.
    public static function filter_backtrace($trace) {

        return array_values(array_filter($trace, function($row) {
            return (empty($row["class"]) or ($row["class"] !== "ł"))
               and (empty($row["file"]) or ($row["file"] !== __FILE__));
        }));
    }

    // Turn arrays into JSON struct.




|







 







|

|
|
|
|
|
|
<
<

|
<

|
|





<

<

<

<

<

<
|







 







|
|
|






|
|
<






|
|
|
|
|
|
<







 







<
<
<
<







 







<







 







>
>
>
>


>
>
>
>
>
>
>
>










<







 







|
|







 







<













|







 







>
>
>
>
>
>
>
>
>
>
>
>
>







 







|













|
>
>







 







|
|
<







 







|







 







|
>







1
2
3
4
5
6
7
8
9
10
11
12
..
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28


29
30

31
32
33
34
35
36
37
38

39

40

41

42

43

44
45
46
47
48
49
50
51
...
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144

145
146
147
148
149
150
151
152
153
154
155
156

157
158
159
160
161
162
163
...
176
177
178
179
180
181
182




183
184
185
186
187
188
189
...
190
191
192
193
194
195
196

197
198
199
200
201
202
203
...
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247

248
249
250
251
252
253
254
...
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
...
313
314
315
316
317
318
319

320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
...
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
...
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
...
610
611
612
613
614
615
616
617
618

619
620
621
622
623
624
625
...
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
...
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
<?php
/**
 * title: logStruck
 * description: SQlite group/structured logging backend
 * version: 0.1.1
 * api: php
 * type: handler
 * category: logging
 * state: experimental
 * depends: php:sqlite >= 3.7, php >= 5.4
 * autoexec: true
 * config:
................................................................................
 *   { type: var, name: "ł::$app", description: "default application name" }
 *   { type: var, name: "ł::$section", description: "current application module/section" }
 * license: Public Domain
 * doc: https://fossil.include-once.org/hybrid7/wiki/log
 * 
 * 
 * Implements a terse but parametric logging API for structured journaling.
 * Primarily meant for userland and application-level inspection.
 * 
 * Uses SQLite as primary storage. Retains process/runtime grouping with
 * tree-style event relationships.  Classifies priority, :section and
 * generator/source.  Common fields are primary keys, while arbitrary and
 * extra values are stored as JSON blobs.  Automatically registers PHP
 * error, exception and assertion handler.  Eschews configurability for
 * alternative storage backends.


 * 
 * Function signatures

 *  · Main logging function is  ł("..")
 *  · Ruby-style ':token' names for priority and section names
 *  · HTTP-style 'field: value' parameters
 *  · Passed arrays stored as JSON blobs
 *  · Interpolation tokens :server, :backtrace, :file, :code, :version, :p
 *  · Calling ł::$PRIO_$MODULE("..") readily sets prio and module etc.
 * 
 * Usage examples

 *   ł(':alert', "No foobar() result")

 *   ł(":warn", ":openid", "No endpoint found", $handle);

 *   ł("Page not found", ':exception', [$pagename])

 *   ł(':alert', "INSERT failed", ':database', $PDO->errorInfo(), ["vars"=>$params])

 *   ł(':assert', "Got #$num args.", ':debug', ":plugin", "\$wiki->add()", $args, "doc: ?tktid=2421")

 *   ł(':emerg', "no space left on device", ':sys', "errno: $errno", ':vars', $fileobject);
 * 
 * 
 * Fields, message strings, data arrays and :tokens may be in any order, but some
 * coherency is advisable of course. Only :section identifiers and :error level
 * tokens should be used prudently.
 * 
 * It's however as important to retain context values where available. Passing an
................................................................................
        return $logger;
    }
}



/**
 * Semi-static class which combines message struct assembling and storage.
 * · UTF-8 identifier to deter cargo culters, and simplify source discoverabiliy.
 * · SQLite storage gets reopened/closed mercilessly to prevent locking.
 *
 */
class ł {


    /**
     * Built-in identifiers
     * ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾

     *  · Declares a list of built-in :token names.
     *  · Maps error number magic values.
     *  · And lastly defines the list of primary database field names.
     *
     */


    // Log level / priority names
    static $prio = ["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"];

    // Event generator / source
    static $source = ["sys", "log", "lang", "assert", "exception"];

    
    // Token aliases
    static $alias = [
        // prios
        "emerg" => "emergency",
        "alrt" => "alert",
        "crit" => "critical",
................................................................................
        "exc" => "exception",
        "app" => "log",
        // injectors
        "trace" => "backtrace",
        "stack" => "backtrace",
        "env" => "server",
    ];





    // PHP error constants and priority mapping
    static $phperr = [
        E_ERROR => "ERROR", E_WARNING => "WARNING", E_NOTICE => "NOTICE", E_PARSE => "PARSE", E_STRICT => "STRICT", E_DEPRECATED
        => "DEPRECATED", E_RECOVERABLE_ERROR => "RECOVERABLE_ERROR", E_USER_ERROR => "USER_ERROR", E_USER_WARNING =>
        "USER_WARNING", E_USER_NOTICE => "USER_NOTICE", E_USER_DEPRECATED => "USER_DEPRECATED", E_CORE_ERROR => "CORE_ERROR",
        E_CORE_WARNING => "CORE_WARNING", E_COMPILE_ERROR => "COMPILE_ERROR", E_COMPILE_WARNING => "COMPILE_WARNING",
................................................................................
    ];
    static $phpprio = [
        ':error'  => 0x1111,  # E_RECOVERABLE_ERROR | E_USER_ERROR | E_CORE_ERROR | E_ERROR
        ':warn'   => 0x2222,  # E_DEPRECATED | E_USER_WARNING | E_CORE_WARNING | E_WARNING
        ':notice' => 0x4C88,  # E_USER_DEPRECATED | E_USER_NOTICE | E_STRICT | E_COMPILE_WARNING | E_NOTICE 
        ':crit'   => 0x0044,  # E_PARSE | E_COMPILE_ERROR
    ];


    // Indexed errno map
    static $errno = [
        "EOK", "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", "E2BIG", "ENOEXEC", "EBADF", "ECHILD", "EAGAIN", "ENOMEM", "EACCES", "EFAULT", "ENOTBLK", "EBUSY",
        "EEXIST", "EXDEV", "ENODEV", "ENOTDIR", "EISDIR", "EINVAL", "ENFILE", "EMFILE", "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE", "EROFS", "EMLINK", "EPIPE",
        "EDOM", "ERANGE", "EDEADLK", "ENAMETOOLONG", "ENOLCK", "ENOSYS", "ENOTEMPTY", "ELOOP", "EWOULDBLOCK", "ENOMSG", "EIDRM", "ECHRNG", "EL2NSYNC", "EL3HLT",
        "EL3RST", "ELNRNG", "EUNATCH", "ENOCSI", "EL2HLT", "EBADE", "EBADR", "EXFULL", "ENOANO", "EBADRQC", "EBADSLT", "n/a", "EBFONT", "ENOSTR", "ENODATA", "ETIME",
................................................................................
    ];
    


    /**
     * Instance defaults
     * ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
     *  · Defines default states ($app name, $section, $min_prio).
     *  · Summarizes values for new events in $template.
     *  · Hold :tokens in $iparams that always get applied.
     *  · Track parent and group event ids.
     *
     */

    // SQLite database storage path, should be absolute or docroot-based etc.
    var $db = "config/log.db";

    // Runtime defaults
    static $app = "undef";
    var $section = "main";
    var $min_prio = 7;

    // log event template values
    public $template = [];
    
    // handful of :tokens to apply on each log call
    public $iparams = [];
    
    // log event parent and group id
    var /*&*/ $p = 0;
    protected $g = NULL; // ROWID or :g





    /**
     * Prepare a logging group.
     * ‾‾‾‾‾‾‾
................................................................................

        // prepare log event defaults
        $this->template = array(
            "p" =>   $this->p,
            "g" => & $this->g,
            "prio" => "info",
            "host" => gethostname(),
            "app" => self::$app,
            "section" => & $this->section,
            "timestamp" => 0,
            "source" => "log",
            "errno" => NULL,
            "file" => NULL,
            "line" => NULL,
            "version" => NULL,
            "message" => NULL,
................................................................................
     * @return int     log-event id
     */
    public function __invoke($vars) {

        // from defaults
        $event = array_merge($this->template, [
            "timestamp" => microtime(TRUE),

            "section" => self::$section,
            "p" => $this->p, // parent
            "g" => $this->g, // group
        ]);

        // all this just to turn token/array/string list into structurized blob
        $this->map_params($event, array_merge($this->iparams, $vars));

        // normalize params
        $this->convert_string_params($event);
        $this->move_context_params($event);

        // check prio
        if ($event["pri"] <= $this->min_prio) {

            // store
            $p = $this->store($event);
            
            // keep log event id as group and parent references
            $this->p or $this->p = $p;
            $this->g or $this->g = $p;
................................................................................
        $db->query("DETACH `logdb`");
        $db = NULL;
        return $p;
    }



    /**
     * Set $template values through `ł()->section = "xyz"`
     *
     * @param  string   Field name
     * @param  mixed    Default value
     *
     */
    function __set($name, $value) {
        $this->template[$name] = $value;
    }






    /**
     * Logging handlers
     * ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
     * Alternative logger invocation functions, that basically wrap __invoke() via ł().
................................................................................

    /**
     * PHP error handler.
     *
     * Maps PHP :errno codes, retains :backtrace, and inserts :code excerpt.
     *
     */
    public static function error_handler($errno, $message, $file, $line, $varscope) {

        // convert bitmask prio
        foreach (self::$phpprio as $prio => $errmask){
            if ($errmask & $errno) break;
        }

        // Prevent recursion on ł:: originating errors
        if (isset($trace[0]["file"]) and $trace[0]["file"] == __FILE__) {
            fwrite(STDERR, self::$phperr[$errno] . ": $errstr in $file line $line\n");
        }
        
        // Pass values on (- could be way more readable with "field:value" strings)
        else {
            ł(':lang', $prio, $message, ':code', ':version', ["errno" => $errno, "file" => $file, "line" => $line,
              "vars" => $varscope, "error" => self::$phperr[$errno], "backtrace" => self::filter_backtrace()]
            );
        }
        
        // populate $php_errormsg;
        return false;
    }


................................................................................
    public static function exception_handler($e) {
    
        // keep exception class, also use as message if empty
        $class = get_class($e);
        strlen($message = $e->getMessage()) or ($message = $class);
        
        // pass on
        ł(':exception', ':crit', $message, "exception: $class", ':code', ':version',
          ["backtrace" => $e->getTrace(), "file" => $e->getFile(), "line"=>$e->getLine(), "errno"=>$e->getCode()]

        );
    }


    /**
     * PHP assert() handler.
     *
................................................................................
     *
     * @param  array   log event data bag; gets updated per reference
     */    


    // inject backtrace list
    public function inj_backtrace(& $data) {
        $data["backtrace"] = self::filter_backtrace(debug_backtrace(0));
    }
    
    // $_SERVER environment variables
    public function inj_server(& $data) {
        $data["_SERVER"] = is_array($_SERVER) ? $_SERVER : $_SERVER->__vars;
    }

................................................................................
     * Utility calls
     * ‾‾‾‾‾‾‾‾‾‾‾‾‾
     *
     */


    // Remove self-references to `ł.php` from backtrace.
    public static function filter_backtrace($trace = NULL) {
        $trace or $trace = debug_backtrace(0);
        return array_values(array_filter($trace, function($row) {
            return (empty($row["class"]) or ($row["class"] !== "ł"))
               and (empty($row["file"]) or ($row["file"] !== __FILE__));
        }));
    }

    // Turn arrays into JSON struct.