'E_ERROR', 2 => 'E_WARNING', 4 => 'E_PARSE', 8 => 'E_NOTICE', 16 => 'E_CORE_ERROR', 32 => 'E_CORE_WARNING', 64 => 'E_COMPILE_ERROR', 128 => 'E_COMPILE_WARNING', 256 => 'E_USER_ERROR', 512 => 'E_USER_WARNING', 1024 => 'E_USER_NOTICE', 2048 => 'E_STRICT', 4096 => 'E_RECOVERABLE_ERROR'); static $options = NULL; if (!$options) { $options = array_filter(variable_get('trace_errors', array()), 'is_string'); } $output = array(sprintf("%s: %s in `%s':%s", $types[$errno], $message, _trace_format_filename($filename), $line)); if (TRACE_ERRORLINE && ($code = _trace_get_line_from_file($filename, $line))) { $output[] = trim($code); } if (TRACE_STACKTRACE) { $stacktrace = array_map('_trace_format_stack_frame', array_slice(debug_backtrace(), 1)); $output = array_merge($output, $stacktrace); } $type = 'error'; $type = ($errno == E_WARNING || $errno == E_USER_WARNING ? 'warning' : $type); $type = ($errno == E_NOTICE || $errno == E_USER_NOTICE || $errno == 2048 ? 'notice' : $type); // PHP4 compatible if (!empty($options[$type])) { trace($type, $output); } // Delegate to the standard Drupal error handler: return error_handler($errno, $message, $filename, $line); } ////////////////////////////////////////////////////////////////////////////// // TRACE PROCEDURAL API /** * */ function trace($type, $msg, $time = NULL) { call_user_func_array('module_invoke_all', array('trace', $type, $msg, $time)); } /** * Traces the entire function call chain leading up to this point, * outputting a log entry of the DEBUG type. */ function trace_backtrace($depth = -1) { // FIXME: add support for $depth. trace('debug', array_map('_trace_format_stack_frame', array_slice(debug_backtrace(), 1))); } /** * */ function trace_query($query, $caller = NULL, $time = NULL, $duration = NULL) { // TODO: only trace queries matching configuration settings. trace('query', sprintf('d=%.6fs %s: %s', $duration, $caller, $query), ($time ? $time : trace_time())); } /** * */ function trace_queries() { global $queries; static $last_query = 0; static $options = NULL; if (!$options) { $options = variable_get('trace_queries', array()); } if (count($queries) > $last_query) { $time = trace_time(); $new_queries = array(); foreach (array_reverse(array_slice($queries, $last_query)) as $query) { list($query, $diff) = $query; $query = array_reverse(explode("\n", $query)); $type = strtolower(reset(explode(' ', reset($query)))); if (array_key_exists($type, $options) && empty($options[$type])) continue; // skip any known query types that are not enabled if (!array_key_exists($type, $options) && empty($options['misc'])) continue; // skip query unless the 'Other queries' option is enabled $new_queries[] = array_merge($query, array($time -= $diff, $diff)); } foreach (array_reverse($new_queries) as $query) { call_user_func_array('trace_query', $query); } $last_query = count($queries); } } /** * */ function trace_hook($hook) { if (!trace_hook_enabled($hook)) { return; } if (TRACE_QUERIES) { trace_queries(); } $stacktrace = !TRACE_STACKTRACE ? NULL : array_map('_trace_format_stack_frame', array_slice(debug_backtrace(), 1)); trace('hook', _trace_format_hook_entry($hook, $stacktrace)); } /** * */ function trace_hook_implementors($hook) { static $modules = array(); if (!isset($modules[$hook])) { $modules[$hook] = module_implements($hook); // Remove this module from the list: if (($key = array_search('trace', $modules[$hook])) !== FALSE) unset($modules[$hook][$key]); } return empty($modules[$hook]) ? array() : $modules[$hook]; } /** * */ function trace_hook_enabled($hook) { static $enabled_hooks = NULL; if (!$enabled_hooks) { $enabled_hooks = array_filter(variable_get('trace_hooks', array()), 'is_string'); } return isset($enabled_hooks[$hook]); } /** * */ function trace_hook_defined($module, $hook) { return function_exists($module . '_' . $hook); } /** * */ function trace_hook_define($module, $hook) { if (!trace_hook_defined($module, $hook)) { eval("function {$module}_$hook() { trace_hook('$hook'); }"); return TRUE; } return FALSE; } /** * */ function trace_hook_list($module = NULL, $only_enabled = FALSE) { $hooks = parse_ini_file(TRACE_PATH . '/hooks.ini', !empty($module)); $hooks = array_merge(array_keys(!empty($module) ? $hooks[$module] : $hooks)); return !$only_enabled ? $hooks : array_filter($hooks, 'trace_hook_enabled'); } /** * */ function trace_hook_data() { return parse_ini_file(TRACE_PATH . '/hooks.ini', TRUE); } ////////////////////////////////////////////////////////////////////////////// // GENERIC HELPERS function _trace_get_http_response() { $status = '200 OK'; $headers = explode("\n", drupal_set_header()); foreach ($headers as $header) { if (preg_match('!^HTTP/!', $header)) { list($prefix, $status) = explode(' ', $header, 2); } } return $status; } function _trace_get_line_from_file($filename, $line) { $result = NULL; if (file_exists($filename) && ($fp = fopen($filename, 'r'))) { $counter = 0; while (!feof($fp) && ($buffer = fgets($fp))) { if (++$counter == $line) { $result = $buffer; break; } } fclose($fp); } return $result; } ////////////////////////////////////////////////////////////////////////////// // TRACE FORMAT HELPERS function _trace_format_timedelta($t = NULL) { $t = !is_null($t) ? $t : trace_time(TRUE); return $t === 0 ? sprintf('T=%.6f', 0.0) : sprintf('T%s%.6f', ($t < 0 ? '-' : '+'), abs($t)); } function _trace_format_timestamp() { list($usec, $time) = explode(' ', microtime()); if (function_exists('date_default_timezone_set')) { date_default_timezone_set('UTC'); // // avoid E_STRICT message in PHP 5.1+ } if (($timezone = variable_get('date_default_timezone', 0))) { $time += $timezone; // Adjust to Drupal default timezone } return sprintf('%s.%s', strftime('%Y-%m-%d %H:%M:%S', (int)$time), substr($usec, 2, 6)); } function _trace_format_hook_entry($hook, $stacktrace = NULL) { $modules = implode(', ', trace_hook_implementors($hook)); $output = array(sprintf('hook_%s: %s', $hook, $modules)); if (!empty($stacktrace)) { $output = array_merge($output, $stacktrace); } return $output; } function _trace_format_stack_frame($frame) { $function_call = _trace_format_function_call($frame['function'], $frame['args']); return (empty($frame['file']) || empty($frame['line']) ? $function_call : sprintf("%s in `%s':%s", $function_call, _trace_format_filename($frame['file']), $frame['line'])); } function _trace_format_filename($filename) { if (($cwd = getcwd()) && strpos($filename, $cwd) === 0) { return substr($filename, strlen($cwd) + 1); } return $filename; } function _trace_format_function_call($function, $args) { // NOTE: Can't use array_map() here due to weird warning output it causes on PHP5 //$args = implode(', ', array_map('trace_format_php', $args)); $output = array(); if (is_array($args)) { foreach ($args as $arg) { $output[] = trace_format_php($arg); } } return $function . '(' . implode(', ', $output) . ')'; } function _trace_format_watchdog_entry($severity, $msg) { // TODO } ////////////////////////////////////////////////////////////////////////////// // TRACE HELPERS function trace_format_php($value) { switch (gettype($value)) { case 'NULL': return 'NULL'; case 'boolean': return $value ? 'TRUE' : 'FALSE'; case 'integer': case 'double': return (string)$value; case 'string': if (TRACE_MAX_STRING > 0 && strlen($value) > TRACE_MAX_STRING) { $suffix = TRUE; $value = substr($value, 0, TRACE_MAX_STRING); } $value = str_replace(array("'", "\n", "\r", "\t"), array("\\'", '\n', '\r', '\t'), $value); return "'$value'" . (!empty($suffix) ? '...' : ''); case 'object': $class = get_class($value); $value = (array)$value; // fall through case 'array': if (array_keys($value) === range(0, sizeof($value) - 1)) { $array = array_map('trace_format_php', array_values($value)); } else { $array = array(); foreach ($value as $k => $v) { $array[] = trace_format_php($k) . ' => ' . trace_format_php($v); } } $array = implode(', ', $array); return (isset($class) ? "($class)" : '') . 'array(' . $array . ')'; case 'resource': return 'resource(' . get_resource_type($value) . ')'; default: return ''; } } //////////////////////////////////////////////////////////////////////////////