1<?php 2/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ 3 4namespace Icinga\Application; 5 6use Exception; 7use Icinga\Data\ConfigObject; 8use Icinga\Application\Logger\Writer\FileWriter; 9use Icinga\Application\Logger\Writer\SyslogWriter; 10use Icinga\Exception\ConfigurationError; 11use Icinga\Exception\IcingaException; 12use Icinga\Util\Json; 13 14/** 15 * Logger 16 */ 17class Logger 18{ 19 /** 20 * Debug message 21 */ 22 const DEBUG = 1; 23 24 /** 25 * Informational message 26 */ 27 const INFO = 2; 28 29 /** 30 * Warning message 31 */ 32 const WARNING = 4; 33 34 /** 35 * Error message 36 */ 37 const ERROR = 8; 38 39 /** 40 * Log levels 41 * 42 * @var array 43 */ 44 public static $levels = array( 45 Logger::DEBUG => 'DEBUG', 46 Logger::INFO => 'INFO', 47 Logger::WARNING => 'WARNING', 48 Logger::ERROR => 'ERROR' 49 ); 50 51 /** 52 * This logger's instance 53 * 54 * @var static 55 */ 56 protected static $instance; 57 58 /** 59 * Log writer 60 * 61 * @var \Icinga\Application\Logger\LogWriter 62 */ 63 protected $writer; 64 65 /** 66 * Maximum level to emit 67 * 68 * @var int 69 */ 70 protected $level; 71 72 /** 73 * Error messages to be displayed prior to any other log message 74 * 75 * @var array 76 */ 77 protected $configErrors = array(); 78 79 /** 80 * Create a new logger object 81 * 82 * @param ConfigObject $config 83 * 84 * @throws ConfigurationError If the logging configuration directive 'log' is missing or if the logging level is 85 * not defined 86 */ 87 public function __construct(ConfigObject $config) 88 { 89 if ($config->log === null) { 90 throw new ConfigurationError('Required logging configuration directive \'log\' missing'); 91 } 92 93 $this->setLevel($config->get('level', static::ERROR)); 94 95 if (strtolower($config->get('log', 'syslog')) !== 'none') { 96 $this->writer = $this->createWriter($config); 97 } 98 } 99 100 /** 101 * Set the logging level to use 102 * 103 * @param mixed $level 104 * 105 * @return $this 106 * 107 * @throws ConfigurationError In case the given level is invalid 108 */ 109 public function setLevel($level) 110 { 111 if (is_numeric($level)) { 112 $level = (int) $level; 113 if (! isset(static::$levels[$level])) { 114 throw new ConfigurationError( 115 'Can\'t set logging level %d. Logging level is invalid. Use one of %s or one of the' 116 . ' Logger\'s constants.', 117 $level, 118 implode(', ', array_keys(static::$levels)) 119 ); 120 } 121 122 $this->level = $level; 123 } else { 124 $level = strtoupper($level); 125 $levels = array_flip(static::$levels); 126 if (! isset($levels[$level])) { 127 throw new ConfigurationError( 128 'Can\'t set logging level "%s". Logging level is invalid. Use one of %s.', 129 $level, 130 implode(', ', array_keys($levels)) 131 ); 132 } 133 134 $this->level = $levels[$level]; 135 } 136 137 return $this; 138 } 139 140 /** 141 * Return the logging level being used 142 * 143 * @return int 144 */ 145 public function getLevel() 146 { 147 return $this->level; 148 } 149 150 /** 151 * Register the given message as config error 152 * 153 * Config errors are logged every time a log message is being logged. 154 * 155 * @param mixed $arg,... A string, exception or format-string + substitutions 156 * 157 * @return $this 158 */ 159 public function registerConfigError() 160 { 161 if (func_num_args() > 0) { 162 $this->configErrors[] = static::formatMessage(func_get_args()); 163 } 164 165 return $this; 166 } 167 168 /** 169 * Create a new logger object 170 * 171 * @param ConfigObject $config 172 * 173 * @return static 174 */ 175 public static function create(ConfigObject $config) 176 { 177 static::$instance = new static($config); 178 return static::$instance; 179 } 180 181 /** 182 * Create a log writer 183 * 184 * @param ConfigObject $config The configuration to initialize the writer with 185 * 186 * @return \Icinga\Application\Logger\LogWriter The requested log writer 187 * @throws ConfigurationError If the requested writer cannot be found 188 */ 189 protected function createWriter(ConfigObject $config) 190 { 191 $class = 'Icinga\\Application\\Logger\\Writer\\' . ucfirst(strtolower($config->log)) . 'Writer'; 192 if (! class_exists($class)) { 193 throw new ConfigurationError( 194 'Cannot find log writer of type "%s"', 195 $config->log 196 ); 197 } 198 return new $class($config); 199 } 200 201 /** 202 * Log a message 203 * 204 * @param int $level The logging level 205 * @param string $message The log message 206 */ 207 public function log($level, $message) 208 { 209 if ($this->writer !== null && $this->level <= $level) { 210 foreach ($this->configErrors as $error_message) { 211 $this->writer->log(static::ERROR, $error_message); 212 } 213 214 $this->writer->log($level, $message); 215 } 216 } 217 218 /** 219 * Return a string representation of the passed arguments 220 * 221 * This method provides three different processing techniques: 222 * - If the only passed argument is a string it is returned unchanged 223 * - If the only passed argument is an exception it is formatted as follows: 224 * <name> in <file>:<line> with message: <message>[ <- <name> ...] 225 * - If multiple arguments are passed the first is interpreted as format-string 226 * that gets substituted with the remaining ones which can be of any type 227 * 228 * @param array $arguments The arguments to format 229 * 230 * @return string The formatted result 231 */ 232 protected static function formatMessage(array $arguments) 233 { 234 if (count($arguments) === 1) { 235 $message = $arguments[0]; 236 237 if ($message instanceof Exception) { 238 $messages = array(); 239 $error = $message; 240 do { 241 $messages[] = IcingaException::describe($error); 242 } while ($error = $error->getPrevious()); 243 $message = implode(' <- ', $messages); 244 } 245 246 return $message; 247 } 248 249 return vsprintf( 250 array_shift($arguments), 251 array_map( 252 function ($a) { 253 return is_string($a) ? $a : ($a instanceof Exception 254 ? IcingaException::describe($a) 255 : Json::encode($a)); 256 }, 257 $arguments 258 ) 259 ); 260 } 261 262 /** 263 * Log a message with severity ERROR 264 * 265 * @param mixed $arg,... A string, exception or format-string + substitutions 266 */ 267 public static function error() 268 { 269 if (static::$instance !== null && func_num_args() > 0) { 270 static::$instance->log(static::ERROR, static::formatMessage(func_get_args())); 271 } 272 } 273 274 /** 275 * Log a message with severity WARNING 276 * 277 * @param mixed $arg,... A string, exception or format-string + substitutions 278 */ 279 public static function warning() 280 { 281 if (static::$instance !== null && func_num_args() > 0) { 282 static::$instance->log(static::WARNING, static::formatMessage(func_get_args())); 283 } 284 } 285 286 /** 287 * Log a message with severity INFO 288 * 289 * @param mixed $arg,... A string, exception or format-string + substitutions 290 */ 291 public static function info() 292 { 293 if (static::$instance !== null && func_num_args() > 0) { 294 static::$instance->log(static::INFO, static::formatMessage(func_get_args())); 295 } 296 } 297 298 /** 299 * Log a message with severity DEBUG 300 * 301 * @param mixed $arg,... A string, exception or format-string + substitutions 302 */ 303 public static function debug() 304 { 305 if (static::$instance !== null && func_num_args() > 0) { 306 static::$instance->log(static::DEBUG, static::formatMessage(func_get_args())); 307 } 308 } 309 310 /** 311 * Get the log writer to use 312 * 313 * @return \Icinga\Application\Logger\LogWriter 314 */ 315 public function getWriter() 316 { 317 return $this->writer; 318 } 319 320 /** 321 * Is the logger writing to Syslog? 322 * 323 * @return bool 324 */ 325 public static function writesToSyslog() 326 { 327 return static::$instance && static::$instance->getWriter() instanceof SyslogWriter; 328 } 329 330 /** 331 * Is the logger writing to a file? 332 * 333 * @return bool 334 */ 335 public static function writesToFile() 336 { 337 return static::$instance && static::$instance->getWriter() instanceof FileWriter; 338 } 339 340 /** 341 * Get this' instance 342 * 343 * @return static 344 */ 345 public static function getInstance() 346 { 347 return static::$instance; 348 } 349} 350