Class it:

/**
 * If display_errors is on or stdout is a tty, shows error in page or on stdout respectively
 * If display_errors is off, mails (with rate limiting) diagnostics to .diffnotice addresses or file owner or SERVER_ADMIN
 *
 * Controlling error mail content
 *  @param $p either error title or assoc array of params, see below
 *  @param $p['title'] error title, one line. also accepted in $p[0] (with priority). false means suppress error
 *  @param $p['body'] error body: multiline string or any data type (will be dumped)
 *  @param $p['backtraceskip'] number of stack levels to drop
 *  @param $p['skipfiles'] files to skip in backtrace
 *  @param $p['omitdebuginfo'] value 1 omits long stack and var dumps, value 2 also minimal infos
 *
 * Controlling the sending of mails
 *  @param $p['to'] comma separated recipient list
 *  @param $p['timewindow'] in secs. "5-35" means for an mail to be sent, another err must occur 5 to 35 seconds after first one
 *  @param $p['id'] id of error, used with timewindow, defaults to file and line of caller
 *  @param $p['blockmailid'] block mail for $p['blockmail'] seconds with same id. Default: $p['to']
 *  @param $p['blockmail'] number of seconds to block mails after having sent a mail [3600]
 *  @param $p['okstate'] give current ok label and ok state, e.g. telresult=1 for working. see failcount
 *  @param $p['failcount'] give number of consecutive okstate failures needed for creating an error [2]
 *  @param $p['fatal'] exit after displaying error
 *  @param $p['graceperiod'] in seconds. DEPRECATED, see $p['timewindow']
 *  Use debug param 'verboseerrors' to include $p['body'] when interactive
 *
 * @return always null (so users can return it::error() in error cases)
 *
 * TIMEWINDOW 5-35 (x = error occurs, no mail; m = error occurs, mail sent)
 *   time        ---|5 secs|30 secs      |------|5 secs|30 secs
 *                                                          |5 secs|30 secs
 *                  x  x                        x   x       m   x     m   x
 * BLOCKMAIL
 *   time        ---|blockmail|--|blockmail|---->
 *   blockmailid    m  x     x   m   x
 *
 * GRACPERIOD/TIMEWINDOW (DEPRECATED, graceperiod is first argument of 5-35, timewindow is difference between 5 and 35)
 *   time        ---|graceperiod|timewindow|
 *                                   |graceperiod|timewindow|--.
 *                                                             |graceperiod|timewindow|------>
 *   id             x  x     x       m    x   x                x     x
 */
static function error($p = array(), $extra = null)
{
    $p = $origp = (array)$p;

    if (error_reporting() == @error_reporting() || $p[0] === false || $p['title'] === false) # called with @ or suppressed
        return $p['fatal'] ? self::_exit($p) : null;

    $p['title'] = $p[0] ?: $p['title']; # handle 'it_error' => "oops" that was cast to array on the way
    $p['title'] = rtrim(grapheme_substr($p['title'], 0, 2000) ?: substr($p['title'], 0, 2000));
    $p += is_callable(self::$error_context) ? (self::$error_context)() : [];

    $home = $GLOBALS['ULTRAHOME'];

    # support for errlike() in tests
    $GLOBALS['ULTRAERROR'] = $p['title'];
    if ($GLOBALS['ULTRANOERRORS'])
        return null;

    if ($extra)
        $p = ['title' => 'extraneous params passed to it::error', 'fatal' => $p['fatal']];

    foreach (array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10), 1) as $level)
        if ($level["class"] . $level["type"] . $level["function"] == "it::error")
            return null; # prevent recursion

    if ($_SERVER['REMOTE_ADDR'])
        $url = ($_SERVER['HTTPS'] ? "https://" : "http://") . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; # HTTP OK
    else
        $url = $_SERVER['SCRIPT_NAME'] . " " . implode(" ", array_slice($GLOBALS['argv'], 1)) . " (pwd " . $_SERVER['PWD'] . ")";

    $authors = it::replace(['\*' => ""], join(", ", it::grep('\*', (array)@it::cat("$home/.diffnotice")->lines, ['invert' => true])));
    if (!$p['to'])
        unset($p['to']); # allow defaults to kick in
    $p += array(
        'title' => "it::error",
        'to' => $authors ? $authors : (get_current_user() ? get_current_user() : $_SERVER['SERVER_ADMIN']),
        'graceperiod' => 60,
        'timewindow' => 25*3600,
        'blockmail' => $p['okstate'] ? 60 : 3600,
        'omitdebuginfo' => false,
        'failcount' => 2,
        'id' => $p['timewindow'] ? "it_error_id_" . it_debug::backtrace(['skipfiles' => "it.class", 'levels' => 1]) : 0,
    );

    if (it::match('-', $p['timewindow']) && ($parts = explode("-", $p['timewindow'])))
        list($p['graceperiod'], $p['timewindow']) = [$parts[0], $parts[1] - $parts[0]];
    $p += array('blockmailid' => $GLOBALS['ULTRASITE'] . "." . md5($p['to']));
    $traceline = it_debug::backtrace(['skiplevels' => $p['backtraceskip'] + ($p['trace'] ? 0 : 1), 'skipfiles' => $p['skipfiles'], 'trace' => $p['trace']]);
    $mailid = sprintf("mail%04d", crc32($traceline ?: $p['title']) % 10000);

    @mkdir("/tmp/alertdata");
    @chmod("/tmp/alertdata", 0777);

    if ($p['okstate'])
    {
        list($okfn, $okstatus) = explode("=", "$home/tmp/okstate_" . $p['okstate']);
        $failcount = $okstatus ? 0 : (int)@file_get_contents($okfn) + 1; # NOPHPLINT
        file_put_contents($okfn, "$failcount\n"); # NOPHPLINT
        if ($failcount != $p['failcount'] && $failcount != $p['failcount'] * 30)
            return $p['fatal'] && !$okstatus ? self::_exit($p) : null; # nothing to report
    }

    $toscreen = getenv('NOERRORMAILS') || ini_get('display_errors') || (defined("STDOUT") && posix_isatty(STDERR)) || EDC('astwin') || EDC('asdevel') || $p['toscreen'];

    if ($toscreen && !it::is_live())
        $GLOBALS['debug_noredir'] = 1;

    if (!$toscreen) # this error can only be sent by mail: find out if we want to suppress it
    {
        if (!$p['id'])
            $sendmail = true;
        else
        {
            $errstampfn = "/tmp/alertdata/errstamp_" . urlencode($p['id']);
            $errstamp = @filemtime($errstampfn);
            $errstampage = time() - $errstamp;
            $sendmail = $errstampage >= $p['graceperiod'] && $errstampage <= $p['graceperiod'] + $p['timewindow'];
            if ($errstampage > $p['graceperiod'])
            {
                @unlink($errstampfn);
                @touch($errstampfn);
            }
        }

        if ($sendmail)
        {
            $lastsentfn = "/tmp/alertdata/it_error_mailsent_" . urlencode($p['blockmailid']);
            clearstatcache();
            $sendmail = time() - max(@filemtime($lastsentfn), it_cache::get($lastsentfn, ['distributed' => true])) > $p['blockmail'] && !it_cache::get('it_error_mute_' . $GLOBALS['ULTRASITE'], ['distributed' => true]);
            if ($sendmail)
            {
                @unlink($lastsentfn);
                @touch($lastsentfn);
                if (it::is_live())
                    @it_cache::put($lastsentfn, time(), ['distributed' => true, 'ttl' => 7*86400, 'safety' => 0]);
            }
        }
    }

    if ($p['fatal'] && $_SERVER['REMOTE_ADDR'])
    {
        http_response_code(500);
        echo "<!DOCTYPE html><html><head><title>Internal Server Error</title></head><body>Internal Server Error</body></html>\n";
    }

    if ($toscreen || $sendmail)
    {
        $p['body'] = is_string($p['body']) || !array_key_exists('body', $p) ? $p['body'] : it_debug::dump([$p['body']], ['html' => false, 'short' => true, 'color' => false]);
        if (strlen($p['body']) > 500000 || it::match('[\x00-\x08\x0E-\x1F]', $p['body'], ['utf8' => false]) !== null)
        {
            @file_put_contents($datafn = "$home/tmp/error-" . substr(md5($p['body']), 0, 2), $p['body']); # NOPHPLINT
            $p['body'] = "  See " . getenv('HOSTNAME') . ":$datafn";
        }
        $service = it::match('[^.]*', $GLOBALS['ULTRASITE']);

        $body =
            ($p['omitdebuginfo'] >= 2 ? "" : ($url && !$toscreen? "Title:  {$p['title']}\nUrl:    $url\n" : "") .
            ($traceline ? ($sendmail ? "" : "  ") . "Trace:  $traceline\n" : "")) .
            (!$sendmail || $p['omitdebuginfo'] >= 2 ? "" : "Host:   " . getenv('HOSTNAME') . " at " . date("Y-m-d H:i:s") . (($t = time() - $_SERVER['REQUEST_TIME']) ? " (invoked {$t}s before)" : "") . " id $mailid\n") . # no it::date() due to time- debug param
            ($sendmail && it::is_live() && $p['omitdebuginfo'] < 2 ? "Log:    see $service/log/alertlog $mailid\n" : "") .
            ($sendmail ? $p['head'] : "") .
            ($p['id'] && !$toscreen ? "Filter: timewindow=" . $p['graceperiod'] . "-" . ($p['graceperiod'] + $p['timewindow']) . "  (previous err: " . it::date('', $errstamp) . ")\n" : "") .
            (!$origp['blockmail'] || $p['omitdebuginfo'] || $toscreen ? "" : "Block-resend: " . $origp['blockmail'] . " seconds\n") .
            ($p['body'] ? ($p['omitdebuginfo'] ? "" : "Body:\n") . trim($p['body'])."\n\n" : "");

        if ($sendmail || EDC('verboseerrors')) # we're mailing: send maximum info
        {
            $p['title'] = it::replace(['alert:|server:|^: "' => "", '(pw|passw|password\d*|secret|api.?key)(=)[^&\s]*' => '$1$2*****'], "$service: " . $p['title']) . " (via " . getenv('HOSTNAME') . ")";

            if (!$p['omitdebuginfo'])
            {
                $trace = $p['trace'] ?: array_slice(debug_backtrace(0), $p['backtraceskip']);
                $mediumstack = it_debug::backtrace(['trace' => $trace, 'format' => "medium"]);
                $longstack   = substr(it_debug::backtrace(['trace' => $trace, 'format' => "long"]), 0, 200000); # use substr() because data might be binary
                $reqbody = $_POST ? "" : it::file_get_contents("php://input");

                $body .= ($traceline and $t = it::exec('grep -h {0} `ls 2>/dev/null {1}/log/alertlog-*|tail -3` /dev/null 2>/dev/null | grep ^2 | cut -d : -f 1-2 | sort | uniq -c | tail -10', $traceline, $home)) ? "Histogram: (last 10 affected minutes in 3 days)\n$t\n" : "";
                $body .= $mediumstack ? "Stack:\n  " . it::replace(["\n" => "\n  "], $mediumstack) . "\n" : "";
                $body .= $_GET        ? "\$_GET: "    . it::json_encode($_GET, ['pretty' => true]) . "\n" : "";
                $body .= $_POST       ? "\$_POST: "   . it::json_encode($_POST, ['pretty' => true]) . "\n" : "";
                $body .= $reqbody     ? "\$reqbody: " . it::json_encode($reqbody, ['pretty' => true]) . "\n" : "";
                $body .= $_COOKIE     ? "\$_COOKIE: " . it::json_encode($_COOKIE, ['pretty' => true]) . "\n" : "";
                $body .= $_SERVER['REMOTE_ADDR'] ? "" : "Pstree:\n" . it::exec("pstree -als {pid} | head -n -3", ['pid' => getmypid()]) . "\n";
                $body .= $_SERVER     ? "\$_SERVER: " . it::json_encode($_SERVER, ['pretty' => true]) . "\n" : "";
                $body .= $_FILES      ? "\$_FILES:  " . it::json_encode($_FILES, ['pretty' => true]) . "\n" : "";
                $body .= "Processes:\n" . it::exec('ps auxf | egrep -v "rotatelogs|getbanner|logaction|httpd|systemd|sd-pam"|egrep "^www|^cron"') . "\n";
                $body .= $longstack  ? "Full stack: "     . "$longstack\n" : "";

                $body  = it::replace(['(pw|passw|password\d*|secret|api.?key)(\' => |\] => |=)[^&\s\']*' => '$1$2********'], $body, ['utf8' => false]);
                $body  = it::replace(['"(pw|passw|password\d*|secret|api.?key)": *"[^"]*"' => '"$1": "*******"'], $body, ['utf8' => false]);
            }

            $type = ($p['fatal'] ? (it::is_live() ? "FATAL: " : "Fatal: ") : "Error: ");
            $debugparams = it::match('\.([^-.]+)', $_SERVER['HTTP_HOST'], ['all' => true]);
            if (!it::is_live() || array_diff($debugparams, it::match('[-\w]+', $home, ['all' => true]), ["devel", "twin", gethostname()]))
                $type = mb_strtolower($type);

            $user = posix_getpwuid(posix_geteuid())['name'];

            if ($sendmail)
                 it::mail([
                     'From' => "\"$user@" . gethostname() . " $mailid\" <$user>",
                     'To' => $p['to'],
                     'Reply-To' => $p['to'],
                     'Cc' => $GLOBALS['it_defaultconfig']['error_cc'],
                     'Subject' => $type . substr($p['title'], 0, 160),
                     'Body' => it::replace(['\x00' => "[NULLBYTE]"], $body, ['utf8' => false]),
                     'forcemail' => !it::is_devel(),
                 ]);
            else
                echo $body;

             $p['title'] = "Mail: " . $p['title'];
        }
        else if ($_SERVER['REMOTE_ADDR'] && EDC('edplain')) # toscreen mode: web
            echo $p['title'] . "\n";
        else if ($_SERVER['REMOTE_ADDR']) # toscreen mode: web
            echo "<pre style='z-index:10000; position:relative; background:white'>" . htmlspecialchars($p['title'] . "\n" . rtrim($body), ENT_COMPAT, "iso-8859-1") . "</pre>"; # works with iso-8859-1 or utf-8, UTF8SAFE
        else  # toscreen mode: shell (outputs to stderr)
            error_log(substr($p['title'], 0, 100000) . ($p['omitdebuginfo'] >= 2 ? "" : " in " . ($traceline ? $traceline : " {$p['file']}:{$p['line']}") . " Url: $url " . (EDC('verboseerrors') ? D($p['body']) : "")));
    }

    if ($_SERVER['REMOTE_ADDR']) # additional entry in log/error_log
        error_log("it::error: " . $p['title'] . " Url: $url");

    it::log("alertlog", $p['title'] . " ($mailid) in " . ($traceline ? $traceline : "{$p['file']}:{$p['line']}") . " Url: $url" . ($p['body'] ? "\n" . substr(D($p['body']), 0, 5000) . "\n" : ""));

    return $p['fatal'] ? self::_exit($p) : null;
}