| 1 | <?php |
|---|
| 2 | |
|---|
| 3 | // This file contains a simple logging/debug api. You should always use these |
|---|
| 4 | // functions instead of print, print_r and echo. |
|---|
| 5 | // |
|---|
| 6 | // These functions wrap around php's error reporting, generating E_USER |
|---|
| 7 | // messages. Things like whether they are displayed in the output or merely |
|---|
| 8 | // logged is configurable in php.ini. |
|---|
| 9 | // |
|---|
| 10 | // The optional $include_origin parameter determines if the origin of the |
|---|
| 11 | // message (file and line right now) is appended to the message. It defaults |
|---|
| 12 | // to false except for assertions. |
|---|
| 13 | // |
|---|
| 14 | // NOTE: this is for development messages only. Users should never get to |
|---|
| 15 | // see them. There are other functions to deal with bad form values, or |
|---|
| 16 | // insufficient permissions or other usual errors. |
|---|
| 17 | // |
|---|
| 18 | // WARNING: when included this file will change the default error handler. |
|---|
| 19 | // This is good, we never want to run without logging support anyway. |
|---|
| 20 | |
|---|
| 21 | // Private function. |
|---|
| 22 | // Formats a backtrace level as a string. |
|---|
| 23 | // $backtrace is the backtrace as received from a call to debug_backtrace. |
|---|
| 24 | // If ommited debug_backtrace will be called |
|---|
| 25 | // |
|---|
| 26 | // false is returned on error (like level out of range). |
|---|
| 27 | function format_backtrace($level, $backtrace = false) { |
|---|
| 28 | // Generate backtrace if missing. |
|---|
| 29 | if ($backtrace === false) { |
|---|
| 30 | $backtrace = debug_backtrace(); |
|---|
| 31 | } |
|---|
| 32 | |
|---|
| 33 | // Level out of range. |
|---|
| 34 | if (!isset($backtrace[$level])) { |
|---|
| 35 | return false; |
|---|
| 36 | } |
|---|
| 37 | |
|---|
| 38 | // Filename. Strips IA_ROOT_DIR. |
|---|
| 39 | $file = false; |
|---|
| 40 | if (isset($backtrace[$level]['file'])) { |
|---|
| 41 | $file = $backtrace[$level]['file']; |
|---|
| 42 | $file = preg_replace("/^".preg_quote(IA_ROOT_DIR, '/')."/", "", $file); |
|---|
| 43 | } |
|---|
| 44 | |
|---|
| 45 | // Source line. |
|---|
| 46 | $line = false; |
|---|
| 47 | if (isset($backtrace[$level]['line'])) { |
|---|
| 48 | $line = $backtrace[$level]['line']; |
|---|
| 49 | } |
|---|
| 50 | |
|---|
| 51 | // Function name. Includes class name for methods. |
|---|
| 52 | $func = false; |
|---|
| 53 | if (isset($backtrace[$level + 1]) && isset($backtrace[$level + 1]['function'])) { |
|---|
| 54 | $btl = $backtrace[$level + 1]; |
|---|
| 55 | if (isset($btl['class']) && isset($btl['type'])) { |
|---|
| 56 | $func = $btl['class'].$btl['type'].$btl['function']; |
|---|
| 57 | } else { |
|---|
| 58 | $func = $btl['function']; |
|---|
| 59 | } |
|---|
| 60 | } |
|---|
| 61 | // Don't print these functions. |
|---|
| 62 | if ($func == 'require_once' || $func == 'require' || |
|---|
| 63 | $func == 'include_once' || $func == 'include') { |
|---|
| 64 | $func = false; |
|---|
| 65 | } |
|---|
| 66 | |
|---|
| 67 | $msg = ""; |
|---|
| 68 | if ($func !== false) { |
|---|
| 69 | $msg .= "function $func "; |
|---|
| 70 | } |
|---|
| 71 | if ($file !== false) { |
|---|
| 72 | $msg .= "file $file "; |
|---|
| 73 | if ($line !== false) { |
|---|
| 74 | $msg .= "line $line "; |
|---|
| 75 | } |
|---|
| 76 | } |
|---|
| 77 | return substr($msg, 0, strlen($msg) - 1); |
|---|
| 78 | } |
|---|
| 79 | |
|---|
| 80 | // Private function. |
|---|
| 81 | // Add backtrace info to a message. |
|---|
| 82 | function format_message_backtrace($message, $backtrace_level = 0) { |
|---|
| 83 | return $message . " in " . format_backtrace($backtrace_level + 2); |
|---|
| 84 | } |
|---|
| 85 | |
|---|
| 86 | // trigger_error which split multi-line strings. |
|---|
| 87 | function trigger_error_split($error_msg, $error_type) |
|---|
| 88 | { |
|---|
| 89 | if (IA_DEVELOPMENT_MODE && IA_HTTP_ENV) { |
|---|
| 90 | // FIXME: Un-hack this |
|---|
| 91 | // when in development mode, also save mesage in log buffer |
|---|
| 92 | // so it can be displayed to user inside rendering page |
|---|
| 93 | global $execution_stats; |
|---|
| 94 | log_assert($execution_stats); |
|---|
| 95 | $execution_stats['log_copy'] .= $error_msg."\n"; |
|---|
| 96 | } |
|---|
| 97 | |
|---|
| 98 | $error_msg = (string)$error_msg; |
|---|
| 99 | $error_lines = explode("\n", $error_msg); |
|---|
| 100 | foreach ($error_lines as $error_line) { |
|---|
| 101 | trigger_error($error_line, $error_type); |
|---|
| 102 | } |
|---|
| 103 | } |
|---|
| 104 | |
|---|
| 105 | // Print a simple message to the log. Use for informative messages. |
|---|
| 106 | function log_print($message, $include_origin = false) { |
|---|
| 107 | if ($include_origin) { |
|---|
| 108 | $message = format_message_backtrace($message); |
|---|
| 109 | } |
|---|
| 110 | trigger_error_split($message, E_USER_NOTICE); |
|---|
| 111 | } |
|---|
| 112 | |
|---|
| 113 | // Use this for warning messages. |
|---|
| 114 | function log_warn($message, $include_origin = false, $backtrace_level = 0) { |
|---|
| 115 | if ($include_origin) { |
|---|
| 116 | $message = format_message_backtrace($message, $backtrace_level); |
|---|
| 117 | } |
|---|
| 118 | trigger_error_split($message, E_USER_WARNING); |
|---|
| 119 | } |
|---|
| 120 | |
|---|
| 121 | if (!defined("IA_FROM_SMF")) { |
|---|
| 122 | // SMF already defines `log_error`. The function signature is *almost* |
|---|
| 123 | // the same so why not use it? :) PHP style |
|---|
| 124 | |
|---|
| 125 | // Use this when you hit a serious problem, and can't recover. |
|---|
| 126 | // You might want to use log_warn() instead. |
|---|
| 127 | function log_error($message, $include_origin = false) { |
|---|
| 128 | if ($include_origin) { |
|---|
| 129 | $message = format_message_backtrace($message); |
|---|
| 130 | } |
|---|
| 131 | // Splitting makes no sense for fatal errors because the first line makes |
|---|
| 132 | // make the script die(). |
|---|
| 133 | trigger_error($message, E_USER_ERROR); |
|---|
| 134 | } |
|---|
| 135 | } |
|---|
| 136 | |
|---|
| 137 | // Print a complete backtrace, using log_print. |
|---|
| 138 | function log_backtrace($start_level = 0, $backtrace = false, $straight_to_log = false) |
|---|
| 139 | { |
|---|
| 140 | // Generate backtrace if missing. |
|---|
| 141 | if ($backtrace === false) { |
|---|
| 142 | $backtrace = debug_backtrace(); |
|---|
| 143 | } |
|---|
| 144 | |
|---|
| 145 | // Do the dew. |
|---|
| 146 | for ($i = $start_level; $i < count($backtrace); ++$i) { |
|---|
| 147 | $msg = " - Backtrace Level $i: ".format_backtrace($i, $backtrace); |
|---|
| 148 | if ($straight_to_log) { |
|---|
| 149 | error_log($msg); |
|---|
| 150 | } else { |
|---|
| 151 | log_print($msg, false); |
|---|
| 152 | } |
|---|
| 153 | } |
|---|
| 154 | } |
|---|
| 155 | |
|---|
| 156 | // var_dump to the log. |
|---|
| 157 | function log_var_dump($var) |
|---|
| 158 | { |
|---|
| 159 | ob_start(); |
|---|
| 160 | var_dump($var); |
|---|
| 161 | $msg = ob_get_clean(); |
|---|
| 162 | log_print($msg); |
|---|
| 163 | } |
|---|
| 164 | |
|---|
| 165 | // print_r to the log. |
|---|
| 166 | function log_print_r($var) |
|---|
| 167 | { |
|---|
| 168 | $msg = print_r($var, true); |
|---|
| 169 | log_print($msg); |
|---|
| 170 | } |
|---|
| 171 | |
|---|
| 172 | // Check if $value is true, and if it isn't it prints and error and dies. |
|---|
| 173 | function log_assert($value, $message = "Assertion failed", $include_origin = true) { |
|---|
| 174 | if (!$value) { |
|---|
| 175 | if ($include_origin) { |
|---|
| 176 | $message = format_message_backtrace($message); |
|---|
| 177 | } |
|---|
| 178 | log_error($message, false); |
|---|
| 179 | } |
|---|
| 180 | } |
|---|
| 181 | |
|---|
| 182 | // Assert that something is valid; to be used with form-like validation functions. |
|---|
| 183 | // This passed if $errors is an empty array, other |
|---|
| 184 | function log_assert_valid($errors) |
|---|
| 185 | { |
|---|
| 186 | log_assert(is_array($errors) || $errors == null, "Invalid argument"); |
|---|
| 187 | |
|---|
| 188 | if ($errors) { |
|---|
| 189 | foreach ($errors as $k => $v) { |
|---|
| 190 | log_print("Invalid $k ($v)"); |
|---|
| 191 | } |
|---|
| 192 | log_error("Validation failed"); |
|---|
| 193 | } |
|---|
| 194 | } |
|---|
| 195 | |
|---|
| 196 | // Check if two values are equal. |
|---|
| 197 | // Prints arguments if it fails, so it's generally a good idea. |
|---|
| 198 | function log_assert_equal($v1, $v2) { |
|---|
| 199 | if ($v1 != $v2) { |
|---|
| 200 | $message = format_message_backtrace("$v1 != $v2"); |
|---|
| 201 | if (is_array($v1)) { |
|---|
| 202 | log_print_r($v1); |
|---|
| 203 | } |
|---|
| 204 | if (is_array($v2)) { |
|---|
| 205 | log_print_r($v2); |
|---|
| 206 | } |
|---|
| 207 | log_error($message, false); |
|---|
| 208 | } |
|---|
| 209 | } |
|---|
| 210 | |
|---|
| 211 | // Custom error_handler. |
|---|
| 212 | // This behaves as close standard error handler as possible, it uses |
|---|
| 213 | // error_log and all. file/line information is not printed for messages |
|---|
| 214 | // originating in the log_* family of functions. |
|---|
| 215 | // |
|---|
| 216 | // FIXME: print function name too? could be done. |
|---|
| 217 | function logging_error_handler($errno, $errstr, $errfile, $errline) { |
|---|
| 218 | // Obey error_reporting from php_ini. |
|---|
| 219 | // The @ operator works by changing error_reporting, so it will |
|---|
| 220 | // work just fine. |
|---|
| 221 | if ((error_reporting() & $errno) == 0) { |
|---|
| 222 | return; |
|---|
| 223 | } |
|---|
| 224 | |
|---|
| 225 | // Maps error type constant names to message prefixes. |
|---|
| 226 | $errortypestr_msgprefix = array( |
|---|
| 227 | 'E_ERROR' => 'PHP Error: ', |
|---|
| 228 | 'E_WARNING' => 'PHP Warning: ', |
|---|
| 229 | 'E_NOTICE' => 'PHP Notice: ', |
|---|
| 230 | 'E_PARSE' => 'Parse Error: ', |
|---|
| 231 | 'E_CORE_ERROR' => 'Core Error: ', |
|---|
| 232 | 'E_CORE_WARNING' => 'Core Warning: ', |
|---|
| 233 | 'E_COMPILE_ERROR' => 'Compile Error: ', |
|---|
| 234 | 'E_COMPILE_WARNING' => 'Compile Warning: ', |
|---|
| 235 | 'E_STRICT' => 'Runtime Notice: ', |
|---|
| 236 | 'E_USER_ERROR' => 'Error: ', |
|---|
| 237 | 'E_USER_WARNING' => 'Warning: ', |
|---|
| 238 | 'E_USER_NOTICE' => '', |
|---|
| 239 | 'E_RECOVERABLE_ERRROR' => 'Catchable Fatal Error: ', |
|---|
| 240 | ); |
|---|
| 241 | |
|---|
| 242 | // Maps error type constant to messages. |
|---|
| 243 | // Built from errortypestr_message to cleanly handler missing constants in |
|---|
| 244 | // older php versions. |
|---|
| 245 | $errortype_msgprefix = array(); |
|---|
| 246 | |
|---|
| 247 | // Build errortype_msgprefix. |
|---|
| 248 | // FIXME: cache? |
|---|
| 249 | foreach ($errortypestr_msgprefix as $errortypestr => $message) { |
|---|
| 250 | if (defined($errortypestr)) { |
|---|
| 251 | $errortype_msgprefix[constant($errortypestr)] = $message; |
|---|
| 252 | } |
|---|
| 253 | } |
|---|
| 254 | |
|---|
| 255 | // Hack for log_print and all. |
|---|
| 256 | $include_backtrace = true; |
|---|
| 257 | if ($errno == E_USER_ERROR || $errno == E_USER_WARNING || $errno == E_USER_NOTICE) { |
|---|
| 258 | $backtrace = debug_backtrace(); |
|---|
| 259 | // backtrace[0] is the current function, so we check for backtrace[1]. |
|---|
| 260 | if (($errno == E_USER_NOTICE && $backtrace[1]['function'] = 'log_print') || |
|---|
| 261 | ($errno == E_USER_WARNING && $backtrace[1]['function'] = 'log_warn' ) || |
|---|
| 262 | ($errno == E_USER_ERROR && $backtrace[1]['function'] = 'log_error')) { |
|---|
| 263 | $include_backtrace = false; |
|---|
| 264 | } |
|---|
| 265 | } |
|---|
| 266 | |
|---|
| 267 | if ($include_backtrace) { |
|---|
| 268 | //$errstr = format_message_backtrace($errstr, 1); |
|---|
| 269 | $errstr = "$errstr in $errfile line $errline"; |
|---|
| 270 | } |
|---|
| 271 | |
|---|
| 272 | // Include message prefix. |
|---|
| 273 | $errstr = $errortype_msgprefix[$errno] . $errstr; |
|---|
| 274 | |
|---|
| 275 | // HACK: Add timestamp if desired. |
|---|
| 276 | if (defined('IA_LOG_TIMESTAMP_FORMAT')) { |
|---|
| 277 | $errstr = date(IA_LOG_TIMESTAMP_FORMAT) . ": " . $errstr; |
|---|
| 278 | } |
|---|
| 279 | |
|---|
| 280 | // The behaviour of this function is defined with error_log in php.ini |
|---|
| 281 | if (IA_PHP_FATAL_ERROR_MASK & $errno) { |
|---|
| 282 | $errstr = "Fatal:\t>>>" . $errstr . "<<<\t"; |
|---|
| 283 | } |
|---|
| 284 | error_log($errstr); |
|---|
| 285 | |
|---|
| 286 | // Die on certain fatal errors: |
|---|
| 287 | if (IA_PHP_FATAL_ERROR_MASK & $errno) { |
|---|
| 288 | // Print a full backtrace on fatal errors. |
|---|
| 289 | error_log("Caught a fatal error, printing a full backtrace"); |
|---|
| 290 | log_backtrace(2, false, true); |
|---|
| 291 | |
|---|
| 292 | if (IA_HTTP_ENV && IA_DEVELOPMENT_MODE) { |
|---|
| 293 | $msg = $errstr."\nPrinting full backtrace:\n"; |
|---|
| 294 | $backtrace = debug_backtrace(); |
|---|
| 295 | for ($i = 1; $i < count($backtrace); ++$i) { |
|---|
| 296 | $msg .= "Backtrace Level $i: "; |
|---|
| 297 | $msg .= format_backtrace($i, $backtrace) . "\n"; |
|---|
| 298 | } |
|---|
| 299 | $msg .= "Full server log:\n"; |
|---|
| 300 | global $execution_stats; |
|---|
| 301 | $msg .= $execution_stats['log_copy']; |
|---|
| 302 | if (function_exists("html_escape")) { |
|---|
| 303 | print("<pre>".html_escape($msg)."</pre>"); |
|---|
| 304 | } else { |
|---|
| 305 | print("<pre>".htmlentities($msg)."</pre>"); |
|---|
| 306 | } |
|---|
| 307 | } |
|---|
| 308 | if (IA_HTTP_ENV && !IA_DEVELOPMENT_MODE) { |
|---|
| 309 | header('HTTP/1.0 500 Internal Error'); |
|---|
| 310 | } |
|---|
| 311 | die(); |
|---|
| 312 | } |
|---|
| 313 | } |
|---|
| 314 | |
|---|
| 315 | // Put execution stats into log |
|---|
| 316 | function log_execution_stats() { |
|---|
| 317 | global $execution_stats; |
|---|
| 318 | log_assert($execution_stats); |
|---|
| 319 | log_assert(IA_DEVELOPMENT_MODE); |
|---|
| 320 | |
|---|
| 321 | $msg = 'Time=' |
|---|
| 322 | .(round((microtime(true)-$execution_stats['timestamp'])*100)/100) |
|---|
| 323 | .'s'; |
|---|
| 324 | $msg .= '; Queries='.$execution_stats['queries']; |
|---|
| 325 | $msg .= '; Memory='.number_format(memory_get_usage()/1024/1024, 2).'MB'; |
|---|
| 326 | $msg .= '; Memory='.memory_get_usage().'bytes'; |
|---|
| 327 | |
|---|
| 328 | log_print("Execution stats: ".$msg); |
|---|
| 329 | } |
|---|
| 330 | |
|---|
| 331 | // Change the default error handler. |
|---|
| 332 | set_error_handler("logging_error_handler"); |
|---|
| 333 | |
|---|
| 334 | ?> |
|---|