Index: trunk/php/wmerrors/README |
— | — | @@ -0,0 +1,64 @@ |
| 2 | +This is an extension for enhancing and customising the handling of PHP errors. |
| 3 | + |
| 4 | +Compile and install it in the usual way, there are no special dependencies: |
| 5 | + |
| 6 | + phpize |
| 7 | + ./configure |
| 8 | + make |
| 9 | + make install |
| 10 | + |
| 11 | +The options are: |
| 12 | + |
| 13 | +wmerrors.enabled |
| 14 | + |
| 15 | + Set this to true to enable custom error handling. This allows the extension |
| 16 | + to be disabled with ini_set() at any time before the fatal error is |
| 17 | + generated. |
| 18 | + |
| 19 | +wmerrors.message_file |
| 20 | + |
| 21 | + Set this to the path to an HTML file which you wish to be displayed to your |
| 22 | + users. A sample HTML file is distributed with this extension in error.html. |
| 23 | + |
| 24 | + If this is set, display_errors should be disabled to prevent a duplicate |
| 25 | + message from being shown. |
| 26 | + |
| 27 | + The error HTML may contain the following special variables, which will be |
| 28 | + replaced with the appropriate values when the error message is generated. |
| 29 | + Unlike PHP's display_errors, these values will be properly escaped to avoid |
| 30 | + XSS: |
| 31 | + |
| 32 | + - $file: The filename in which the error occurred |
| 33 | + - $line: The line number at which the error occurred |
| 34 | + - $message: The error message |
| 35 | + |
| 36 | +wmerrors.log_file |
| 37 | + |
| 38 | + The name of a file to send error reports to. This is similar to PHP's |
| 39 | + error_log, except that it provides several additional features. Logging to |
| 40 | + a TCP or UDP network socket is supported, using a URL of the form: |
| 41 | + |
| 42 | + udp://<host>:<port> |
| 43 | + tcp://<host>:<port> |
| 44 | + |
| 45 | + For example: |
| 46 | + |
| 47 | + udp://logger:8420 |
| 48 | + |
| 49 | +wmerrors.log_backtrace |
| 50 | + |
| 51 | + Set this to true if you want the error report sent to wmerrors.log_file to |
| 52 | + include a backtrace. |
| 53 | + |
| 54 | +wmerrors.ignore_logging_errors |
| 55 | + |
| 56 | + If an error is encountered while opening or writing to the file specified |
| 57 | + in wmerrors.log_file, then the error will be ignored if this is set to |
| 58 | + true. If it is set to false, the error will be handled with PHP's standard |
| 59 | + error handling, and so will be available via display_errors or error_log. |
| 60 | + |
| 61 | +wmerrors.backtrace_in_php_error_message |
| 62 | + |
| 63 | + If this is true, a concise backtrace, listing base filenames (not including |
| 64 | + the path) and line numbers only, will be included in the error message |
| 65 | + which is passed through to PHP, for output into error_log. |
Index: trunk/php/wmerrors/php_wmerrors.h |
— | — | @@ -25,13 +25,12 @@ |
26 | 26 | |
27 | 27 | ZEND_BEGIN_MODULE_GLOBALS(wmerrors) |
28 | 28 | char * message_file; |
29 | | - char * logging_file; |
| 29 | + char * log_file; |
30 | 30 | int recursion_guard; |
31 | 31 | int enabled; |
32 | | - long int log_level; |
| 32 | + int log_backtrace; |
33 | 33 | int ignore_logging_errors; |
34 | | - int concise_backtrace_in_error_log; |
35 | | - smart_str log_buffer; |
| 34 | + int backtrace_in_php_error_message; |
36 | 35 | ZEND_END_MODULE_GLOBALS(wmerrors) |
37 | 36 | |
38 | 37 | |
Index: trunk/php/wmerrors/wmerrors.c |
— | — | @@ -17,9 +17,9 @@ |
18 | 18 | |
19 | 19 | void wmerrors_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); |
20 | 20 | static void wmerrors_show_message(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args TSRMLS_DC); |
21 | | -void wmerrors_get_backtrace(smart_str *s); |
| 21 | +static void wmerrors_get_concise_backtrace(smart_str *s TSRMLS_DC); |
| 22 | +static void write_full_backtrace(php_stream *logfile_stream); |
22 | 23 | |
23 | | - |
24 | 24 | ZEND_DECLARE_MODULE_GLOBALS(wmerrors) |
25 | 25 | |
26 | 26 | zend_function_entry wmerrors_functions[] = { |
— | — | @@ -52,10 +52,10 @@ |
53 | 53 | PHP_INI_BEGIN() |
54 | 54 | STD_PHP_INI_BOOLEAN("wmerrors.enabled", "0", PHP_INI_ALL, OnUpdateBool, enabled, zend_wmerrors_globals, wmerrors_globals ) |
55 | 55 | STD_PHP_INI_ENTRY("wmerrors.message_file", "", PHP_INI_ALL, OnUpdateString, message_file, zend_wmerrors_globals, wmerrors_globals) |
56 | | - STD_PHP_INI_ENTRY("wmerrors.logging_file", "", PHP_INI_ALL, OnUpdateString, logging_file, zend_wmerrors_globals, wmerrors_globals) |
57 | | - STD_PHP_INI_ENTRY("wmerrors.log_level", "0", PHP_INI_ALL, OnUpdateLong, log_level, zend_wmerrors_globals, wmerrors_globals) |
| 56 | + STD_PHP_INI_ENTRY("wmerrors.log_file", "", PHP_INI_ALL, OnUpdateString, log_file, zend_wmerrors_globals, wmerrors_globals) |
| 57 | + STD_PHP_INI_BOOLEAN("wmerrors.log_backtrace", "0", PHP_INI_ALL, OnUpdateBool, log_backtrace, zend_wmerrors_globals, wmerrors_globals) |
58 | 58 | STD_PHP_INI_BOOLEAN("wmerrors.ignore_logging_errors", "0", PHP_INI_ALL, OnUpdateBool, ignore_logging_errors, zend_wmerrors_globals, wmerrors_globals) |
59 | | - STD_PHP_INI_BOOLEAN("wmerrors.concise_backtrace_in_error_log", "0", PHP_INI_ALL, OnUpdateBool, concise_backtrace_in_error_log, zend_wmerrors_globals, wmerrors_globals) |
| 59 | + STD_PHP_INI_BOOLEAN("wmerrors.backtrace_in_php_error_message", "0", PHP_INI_ALL, OnUpdateBool, backtrace_in_php_error_message, zend_wmerrors_globals, wmerrors_globals) |
60 | 60 | PHP_INI_END() |
61 | 61 | |
62 | 62 | void (*old_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); |
— | — | @@ -63,8 +63,8 @@ |
64 | 64 | static void php_wmerrors_init_globals(zend_wmerrors_globals *wmerrors_globals) |
65 | 65 | { |
66 | 66 | wmerrors_globals->message_file = NULL; |
67 | | - wmerrors_globals->logging_file = NULL; |
68 | | - wmerrors_globals->log_level = 0; |
| 67 | + wmerrors_globals->log_file = NULL; |
| 68 | + wmerrors_globals->log_backtrace = 0; |
69 | 69 | } |
70 | 70 | |
71 | 71 | PHP_MINIT_FUNCTION(wmerrors) |
— | — | @@ -89,7 +89,6 @@ |
90 | 90 | PHP_RINIT_FUNCTION(wmerrors) |
91 | 91 | { |
92 | 92 | WMERRORS_G(recursion_guard) = 0; |
93 | | - WMERRORS_G(log_buffer).c = NULL; |
94 | 93 | return SUCCESS; |
95 | 94 | } |
96 | 95 | |
— | — | @@ -152,14 +151,14 @@ |
153 | 152 | wmerrors_show_message(type, error_filename, error_lineno, format, args TSRMLS_CC); |
154 | 153 | } |
155 | 154 | |
156 | | - if ( WMERRORS_G(enabled) && WMERRORS_G(log_level) ) { |
| 155 | + if ( WMERRORS_G(enabled) ) { |
157 | 156 | /* Log the error */ |
158 | 157 | wmerrors_log_error(type, error_filename, error_lineno, format, args TSRMLS_CC); |
159 | 158 | } |
160 | 159 | |
161 | 160 | /* Put a concise backtrace in the normal output */ |
162 | | - if (WMERRORS_G(concise_backtrace_in_error_log)) |
163 | | - wmerrors_get_backtrace(&new_filename); |
| 161 | + if (WMERRORS_G(backtrace_in_php_error_message)) |
| 162 | + wmerrors_get_concise_backtrace(&new_filename TSRMLS_CC); |
164 | 163 | smart_str_appendl(&new_filename, error_filename, strlen(error_filename)); |
165 | 164 | smart_str_0(&new_filename); |
166 | 165 | |
— | — | @@ -172,14 +171,13 @@ |
173 | 172 | } |
174 | 173 | |
175 | 174 | /* Obtain a concisely formatted backtrace */ |
176 | | -void wmerrors_get_backtrace(smart_str *s) { |
| 175 | +static void wmerrors_get_concise_backtrace(smart_str *s TSRMLS_DC) { |
177 | 176 | zval *trace, **entry, **file, **line, *line_copy; |
178 | 177 | HashPosition pos; |
179 | 178 | char *basename; |
180 | 179 | size_t basename_len; |
181 | 180 | int use_copy; |
182 | 181 | |
183 | | - TSRMLS_FETCH(); |
184 | 182 | ALLOC_INIT_ZVAL(trace); |
185 | 183 | zend_fetch_debug_backtrace(trace, 0, 0 TSRMLS_CC); |
186 | 184 | |
— | — | @@ -217,7 +215,7 @@ |
218 | 216 | FREE_ZVAL(trace); |
219 | 217 | } |
220 | 218 | |
221 | | -static php_stream * open_logging_file(const char* stream_name) { |
| 219 | +static php_stream * open_log_file(const char* stream_name) { |
222 | 220 | php_stream * stream; |
223 | 221 | int err; char *errstr = NULL; |
224 | 222 | struct timeval tv; |
— | — | @@ -228,7 +226,7 @@ |
229 | 227 | |
230 | 228 | if ( strncmp( stream_name, "tcp://", 6 ) && strncmp( stream_name, "udp://", 6 ) ) { |
231 | 229 | /* Is it a wrapper? */ |
232 | | - stream = php_stream_open_wrapper(stream_name, "ab", flags, NULL); |
| 230 | + stream = php_stream_open_wrapper((char*)stream_name, "ab", flags, NULL); |
233 | 231 | } else { |
234 | 232 | /* Maybe it's a transport? */ |
235 | 233 | double timeout = FG(default_socket_timeout); |
— | — | @@ -244,26 +242,19 @@ |
245 | 243 | return stream; |
246 | 244 | } |
247 | 245 | |
248 | | -/* Callback for zend_print_zval_r_ex() |
249 | | - * Writes to the global buffer |
250 | | - */ |
251 | | -static int wmerrors_write_trace(const char *str, uint str_length) { |
252 | | - TSRMLS_FETCH(); |
253 | | - smart_str_appendl(&WMERRORS_G(log_buffer), str, str_length); |
254 | | - return str_length; |
255 | | -} |
256 | | - |
257 | 246 | static void wmerrors_log_error(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args TSRMLS_DC) { |
258 | | - char *tmp1; zval *trace; char *error_time_str; |
259 | | - int tmp1_len; va_list my_args; |
| 247 | + char *tmp1; |
| 248 | + int tmp1_len; |
| 249 | + char *error_time_str; |
| 250 | + va_list my_args; |
260 | 251 | php_stream *logfile_stream; |
261 | 252 | |
262 | | - if ( !WMERRORS_G(enabled) || !WMERRORS_G(log_level) ) { |
| 253 | + if ( !WMERRORS_G(enabled) ) { |
263 | 254 | /* Redundant with the caller */ |
264 | 255 | return; |
265 | 256 | } |
266 | 257 | |
267 | | - if ( !WMERRORS_G(logging_file) || *WMERRORS_G(logging_file) == '\0') { |
| 258 | + if ( !WMERRORS_G(log_file) || *WMERRORS_G(log_file) == '\0') { |
268 | 259 | /* No log file configured */ |
269 | 260 | return; |
270 | 261 | } |
— | — | @@ -271,7 +262,7 @@ |
272 | 263 | /* Try opening the logging file */ |
273 | 264 | /* Set recursion_guard==2 whenever we're doing something to the log file */ |
274 | 265 | WMERRORS_G(recursion_guard) = 2; |
275 | | - logfile_stream = open_logging_file( WMERRORS_G(logging_file) ); |
| 266 | + logfile_stream = open_log_file( WMERRORS_G(log_file) ); |
276 | 267 | WMERRORS_G(recursion_guard) = 1; |
277 | 268 | if ( !logfile_stream ) { |
278 | 269 | return; |
— | — | @@ -282,28 +273,63 @@ |
283 | 274 | tmp1_len = vspprintf(&tmp1, 0, format, my_args); |
284 | 275 | va_end(my_args); |
285 | 276 | |
286 | | - /* Log the error (log_level >= 1) */ |
287 | | - error_time_str = php_format_date("d-M-Y H:i:s", 11, time(NULL), 0 TSRMLS_CC); |
288 | | - php_stream_printf(logfile_stream TSRMLS_CC, "[%s UTC] %s: %.*s at %s on line %u%s", error_time_str, error_type_to_string(type), tmp1_len, tmp1, error_filename, error_lineno, PHP_EOL); |
| 277 | + /* Log the error */ |
| 278 | + error_time_str = php_format_date("d-M-Y H:i:s", 11, time(NULL), 1 TSRMLS_CC); |
| 279 | + php_stream_printf(logfile_stream TSRMLS_CC, "[%s] %s: %.*s at %s on line %u%s", error_time_str, error_type_to_string(type), tmp1_len, tmp1, error_filename, error_lineno, PHP_EOL); |
289 | 280 | efree(error_time_str); |
290 | 281 | efree(tmp1); |
291 | 282 | |
292 | 283 | /* Write a backtrace */ |
293 | | - if ( WMERRORS_G(log_level) >= 2 ) { |
294 | | - ALLOC_INIT_ZVAL(trace); |
295 | | - zend_fetch_debug_backtrace(trace, 0, 0 TSRMLS_CC); |
296 | | - zend_print_zval_r_ex(wmerrors_write_trace, trace, 4 TSRMLS_CC); |
297 | | - FREE_ZVAL(trace); |
298 | | - |
299 | | - WMERRORS_G(recursion_guard) = 2; |
300 | | - php_stream_write(logfile_stream, WMERRORS_G(log_buffer).c, WMERRORS_G(log_buffer).len TSRMLS_CC); |
301 | | - WMERRORS_G(recursion_guard) = 1; |
302 | | - smart_str_free( &WMERRORS_G(log_buffer) ); /* Free and reset the buffer */ |
| 284 | + if ( WMERRORS_G(log_backtrace) ) { |
| 285 | + write_full_backtrace(logfile_stream TSRMLS_CC); |
303 | 286 | } |
304 | 287 | |
305 | 288 | php_stream_close( logfile_stream ); |
306 | 289 | } |
307 | 290 | |
| 291 | + |
| 292 | +/** |
| 293 | + * Write a full backtrace to a stream |
| 294 | + */ |
| 295 | +void write_full_backtrace(php_stream *logfile_stream) { |
| 296 | + zval *trace; |
| 297 | + zend_fcall_info fci = empty_fcall_info; |
| 298 | + zend_fcall_info_cache fcc = empty_fcall_info_cache; |
| 299 | + zval *backtrace_retval, backtrace_fname; |
| 300 | + int status; |
| 301 | + |
| 302 | + /* Start an output buffer */ |
| 303 | + php_start_ob_buffer(NULL, 0, 1 TSRMLS_CC); |
| 304 | + |
| 305 | + /* Call debug_print_backtrace */ |
| 306 | + ZVAL_STRING(&backtrace_fname, "debug_print_backtrace", 1); |
| 307 | + status = zend_fcall_info_init(&backtrace_fname, IS_CALLABLE_STRICT, &fci, &fcc, NULL, NULL); |
| 308 | + if (status != SUCCESS) { |
| 309 | + zval_dtor(&backtrace_fname); |
| 310 | + return; |
| 311 | + } |
| 312 | + |
| 313 | + fci.retval_ptr_ptr = &backtrace_retval; |
| 314 | + zend_call_function(&fci, &fcc TSRMLS_CC); |
| 315 | + if (backtrace_retval) { |
| 316 | + zval_ptr_dtor(&backtrace_retval); |
| 317 | + } |
| 318 | + zval_dtor(&backtrace_fname); |
| 319 | + |
| 320 | + /* Get the trace */ |
| 321 | + ALLOC_INIT_ZVAL(trace); |
| 322 | + php_ob_get_buffer(trace TSRMLS_CC); |
| 323 | + php_end_ob_buffer(0, 0 TSRMLS_CC); |
| 324 | + |
| 325 | + /* Write it */ |
| 326 | + convert_to_string(trace); |
| 327 | + WMERRORS_G(recursion_guard) = 2; |
| 328 | + php_stream_write(logfile_stream, Z_STRVAL_P(trace), Z_STRLEN_P(trace) TSRMLS_CC); |
| 329 | + WMERRORS_G(recursion_guard) = 1; |
| 330 | + |
| 331 | + zval_ptr_dtor(&trace); |
| 332 | +} |
| 333 | + |
308 | 334 | static void wmerrors_show_message(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args TSRMLS_DC) |
309 | 335 | { |
310 | 336 | php_stream *stream; |
— | — | @@ -386,30 +412,34 @@ |
387 | 413 | } |
388 | 414 | |
389 | 415 | static const char* error_type_to_string(int type) { |
390 | | - int i; |
391 | | - #define ErrorType(x) {x, #x} |
392 | | - static struct { int type; const char* name; } error_names[] = { |
393 | | - ErrorType(E_ERROR), |
394 | | - ErrorType(E_CORE_ERROR), |
395 | | - ErrorType(E_COMPILE_ERROR), |
396 | | - ErrorType(E_USER_ERROR), |
397 | | - ErrorType(E_RECOVERABLE_ERROR), |
398 | | - ErrorType(E_WARNING), |
399 | | - ErrorType(E_CORE_WARNING), |
400 | | - ErrorType(E_COMPILE_WARNING), |
401 | | - ErrorType(E_USER_WARNING), |
402 | | - ErrorType(E_PARSE), |
403 | | - ErrorType(E_NOTICE), |
404 | | - ErrorType(E_USER_NOTICE), |
405 | | - ErrorType(E_STRICT), |
406 | | - ErrorType(E_DEPRECATED), |
407 | | - ErrorType(E_USER_DEPRECATED) |
408 | | - }; |
409 | | - |
410 | | - for (i=0; i < sizeof(error_names)/sizeof(error_names[0]); i++) { |
411 | | - if (type == error_names[i].type) { |
412 | | - return error_names[i].name; |
413 | | - } |
| 416 | + /** Copied from php_error_cb() */ |
| 417 | + switch (type) { |
| 418 | + case E_ERROR: |
| 419 | + case E_CORE_ERROR: |
| 420 | + case E_COMPILE_ERROR: |
| 421 | + case E_USER_ERROR: |
| 422 | + return "Fatal error"; |
| 423 | + case E_RECOVERABLE_ERROR: |
| 424 | + return "Catchable fatal error"; |
| 425 | + case E_WARNING: |
| 426 | + case E_CORE_WARNING: |
| 427 | + case E_COMPILE_WARNING: |
| 428 | + case E_USER_WARNING: |
| 429 | + return "Warning"; |
| 430 | + break; |
| 431 | + case E_PARSE: |
| 432 | + return "Parse error"; |
| 433 | + case E_NOTICE: |
| 434 | + case E_USER_NOTICE: |
| 435 | + return "Notice"; |
| 436 | + case E_STRICT: |
| 437 | + return "Strict Standards"; |
| 438 | + case E_DEPRECATED: |
| 439 | + case E_USER_DEPRECATED: |
| 440 | + return "Deprecated"; |
| 441 | + default: |
| 442 | + return "Unknown error"; |
414 | 443 | } |
415 | | - return "Unknown error"; |
416 | 444 | } |
| 445 | + |
| 446 | + |