1<?php 2 3 4/** 5 * Base class for SimpleSAMLphp Exceptions 6 * 7 * This class tries to make sure that every exception is serializable. 8 * 9 * @author Thomas Graff <thomas.graff@uninett.no> 10 * @package SimpleSAMLphp 11 */ 12class SimpleSAML_Error_Exception extends Exception 13{ 14 15 /** 16 * The backtrace for this exception. 17 * 18 * We need to save the backtrace, since we cannot rely on 19 * serializing the Exception::trace-variable. 20 * 21 * @var array 22 */ 23 private $backtrace; 24 25 26 /** 27 * The cause of this exception. 28 * 29 * @var SimpleSAML_Error_Exception 30 */ 31 private $cause; 32 33 34 /** 35 * Constructor for this error. 36 * 37 * Note that the cause will be converted to a SimpleSAML_Error_UnserializableException unless it is a subclass of 38 * SimpleSAML_Error_Exception. 39 * 40 * @param string $message Exception message 41 * @param int $code Error code 42 * @param Exception|null $cause The cause of this exception. 43 */ 44 public function __construct($message, $code = 0, Exception $cause = null) 45 { 46 assert(is_string($message)); 47 assert(is_int($code)); 48 49 parent::__construct($message, $code); 50 51 $this->initBacktrace($this); 52 53 if ($cause !== null) { 54 $this->cause = SimpleSAML_Error_Exception::fromException($cause); 55 } 56 } 57 58 59 /** 60 * Convert any exception into a SimpleSAML_Error_Exception. 61 * 62 * @param Exception $e The exception. 63 * 64 * @return SimpleSAML_Error_Exception The new exception. 65 */ 66 public static function fromException(Exception $e) 67 { 68 69 if ($e instanceof SimpleSAML_Error_Exception) { 70 return $e; 71 } 72 return new SimpleSAML_Error_UnserializableException($e); 73 } 74 75 76 /** 77 * Load the backtrace from the given exception. 78 * 79 * @param Exception $exception The exception we should fetch the backtrace from. 80 */ 81 protected function initBacktrace(Exception $exception) 82 { 83 84 $this->backtrace = array(); 85 86 // position in the top function on the stack 87 $pos = $exception->getFile().':'.$exception->getLine(); 88 89 foreach ($exception->getTrace() as $t) { 90 $function = $t['function']; 91 if (array_key_exists('class', $t)) { 92 $function = $t['class'].'::'.$function; 93 } 94 95 $this->backtrace[] = $pos.' ('.$function.')'; 96 97 if (array_key_exists('file', $t)) { 98 $pos = $t['file'].':'.$t['line']; 99 } else { 100 $pos = '[builtin]'; 101 } 102 } 103 104 $this->backtrace[] = $pos.' (N/A)'; 105 } 106 107 108 /** 109 * Retrieve the backtrace. 110 * 111 * @return array An array where each function call is a single item. 112 */ 113 public function getBacktrace() 114 { 115 return $this->backtrace; 116 } 117 118 119 /** 120 * Retrieve the cause of this exception. 121 * 122 * @return SimpleSAML_Error_Exception|null The cause of this exception. 123 */ 124 public function getCause() 125 { 126 return $this->cause; 127 } 128 129 130 /** 131 * Retrieve the class of this exception. 132 * 133 * @return string The name of the class. 134 */ 135 public function getClass() 136 { 137 return get_class($this); 138 } 139 140 141 /** 142 * Format this exception for logging. 143 * 144 * Create an array of lines for logging. 145 * 146 * @param boolean $anonymize Whether the resulting messages should be anonymized or not. 147 * 148 * @return array Log lines that should be written out. 149 */ 150 public function format($anonymize = false) 151 { 152 $ret = array( 153 $this->getClass().': '.$this->getMessage(), 154 ); 155 return array_merge($ret, $this->formatBacktrace($anonymize)); 156 } 157 158 159 /** 160 * Format the backtrace for logging. 161 * 162 * Create an array of lines for logging from the backtrace. 163 * 164 * @param boolean $anonymize Whether the resulting messages should be anonymized or not. 165 * 166 * @return array All lines of the backtrace, properly formatted. 167 */ 168 public function formatBacktrace($anonymize = false) 169 { 170 $ret = array(); 171 $basedir = SimpleSAML_Configuration::getInstance()->getBaseDir(); 172 173 $e = $this; 174 do { 175 if ($e !== $this) { 176 $ret[] = 'Caused by: '.$e->getClass().': '.$e->getMessage(); 177 } 178 $ret[] = 'Backtrace:'; 179 180 $depth = count($e->backtrace); 181 foreach ($e->backtrace as $i => $trace) { 182 if ($anonymize) { 183 $trace = str_replace($basedir, '', $trace); 184 } 185 186 $ret[] = ($depth - $i - 1).' '.$trace; 187 } 188 $e = $e->cause; 189 } while ($e !== null); 190 191 return $ret; 192 } 193 194 195 /** 196 * Print the backtrace to the log if the 'debug' option is enabled in the configuration. 197 */ 198 protected function logBacktrace($level = \SimpleSAML\Logger::DEBUG) 199 { 200 // see if debugging is enabled for backtraces 201 $debug = SimpleSAML_Configuration::getInstance()->getArrayize('debug', array('backtraces' => false)); 202 203 if (!(in_array('backtraces', $debug, true) // implicitly enabled 204 || (array_key_exists('backtraces', $debug) && $debug['backtraces'] === true) // explicitly set 205 // TODO: deprecate the old style and remove it in 2.0 206 || (array_key_exists(0, $debug) && $debug[0] === true) // old style 'debug' configuration option 207 )) { 208 return; 209 } 210 211 $backtrace = $this->formatBacktrace(); 212 213 $callback = array('\SimpleSAML\Logger'); 214 $functions = array( 215 \SimpleSAML\Logger::ERR => 'error', 216 \SimpleSAML\Logger::WARNING => 'warning', 217 \SimpleSAML\Logger::INFO => 'info', 218 \SimpleSAML\Logger::DEBUG => 'debug', 219 ); 220 $callback[] = $functions[$level]; 221 222 foreach ($backtrace as $line) { 223 call_user_func($callback, $line); 224 } 225 } 226 227 228 /** 229 * Print the exception to the log, by default with log level error. 230 * 231 * Override to allow errors extending this class to specify the log level themselves. 232 * 233 * @param int $default_level The log level to use if this method was not overridden. 234 */ 235 public function log($default_level) 236 { 237 $fn = array( 238 SimpleSAML\Logger::ERR => 'logError', 239 SimpleSAML\Logger::WARNING => 'logWarning', 240 SimpleSAML\Logger::INFO => 'logInfo', 241 SimpleSAML\Logger::DEBUG => 'logDebug', 242 ); 243 call_user_func(array($this, $fn[$default_level]), $default_level); 244 } 245 246 247 /** 248 * Print the exception to the log with log level error. 249 * 250 * This function will write this exception to the log, including a full backtrace. 251 */ 252 public function logError() 253 { 254 SimpleSAML\Logger::error($this->getClass().': '.$this->getMessage()); 255 $this->logBacktrace(\SimpleSAML\Logger::ERR); 256 } 257 258 259 /** 260 * Print the exception to the log with log level warning. 261 * 262 * This function will write this exception to the log, including a full backtrace. 263 */ 264 public function logWarning() 265 { 266 SimpleSAML\Logger::warning($this->getClass().': '.$this->getMessage()); 267 $this->logBacktrace(\SimpleSAML\Logger::WARNING); 268 } 269 270 271 /** 272 * Print the exception to the log with log level info. 273 * 274 * This function will write this exception to the log, including a full backtrace. 275 */ 276 public function logInfo() 277 { 278 SimpleSAML\Logger::info($this->getClass().': '.$this->getMessage()); 279 $this->logBacktrace(\SimpleSAML\Logger::INFO); 280 } 281 282 283 /** 284 * Print the exception to the log with log level debug. 285 * 286 * This function will write this exception to the log, including a full backtrace. 287 */ 288 public function logDebug() 289 { 290 SimpleSAML\Logger::debug($this->getClass().': '.$this->getMessage()); 291 $this->logBacktrace(\SimpleSAML\Logger::DEBUG); 292 } 293 294 295 /** 296 * Function for serialization. 297 * 298 * This function builds a list of all variables which should be serialized. It will serialize all variables except 299 * the Exception::trace variable. 300 * 301 * @return array Array with the variables that should be serialized. 302 */ 303 public function __sleep() 304 { 305 306 $ret = array_keys((array) $this); 307 308 foreach ($ret as $i => $e) { 309 if ($e === "\0Exception\0trace") { 310 unset($ret[$i]); 311 } 312 } 313 314 return $ret; 315 } 316} 317