1<?php 2 3/** 4 * @see https://github.com/laminas/laminas-log for the canonical source repository 5 * @copyright https://github.com/laminas/laminas-log/blob/master/COPYRIGHT.md 6 * @license https://github.com/laminas/laminas-log/blob/master/LICENSE.md New BSD License 7 */ 8 9namespace Laminas\Log\Writer; 10 11use Laminas\Log\Exception; 12use Laminas\Log\Filter; 13use Laminas\Log\FilterPluginManager as LogFilterPluginManager; 14use Laminas\Log\Formatter; 15use Laminas\Log\FormatterPluginManager as LogFormatterPluginManager; 16use Laminas\ServiceManager\ServiceManager; 17use Laminas\Stdlib\ErrorHandler; 18use Traversable; 19 20/** 21 * @todo Remove aliases for parent namespace's FilterPluginManager and 22 * FormatterPluginManager once the deprecated versions in the current 23 * namespace are removed (likely v3.0). 24 */ 25abstract class AbstractWriter implements WriterInterface 26{ 27 /** 28 * Filter plugins 29 * 30 * @var FilterPluginManager 31 */ 32 protected $filterPlugins; 33 34 /** 35 * Formatter plugins 36 * 37 * @var FormatterPluginManager 38 */ 39 protected $formatterPlugins; 40 41 /** 42 * Filter chain 43 * 44 * @var Filter\FilterInterface[] 45 */ 46 protected $filters = []; 47 48 /** 49 * Formats the log message before writing 50 * 51 * @var Formatter\FormatterInterface 52 */ 53 protected $formatter; 54 55 /** 56 * Use Laminas\Stdlib\ErrorHandler to report errors during calls to write 57 * 58 * @var bool 59 */ 60 protected $convertWriteErrorsToExceptions = true; 61 62 /** 63 * Error level passed to Laminas\Stdlib\ErrorHandler::start for errors reported during calls to write 64 * 65 * @var bool 66 */ 67 protected $errorsToExceptionsConversionLevel = E_WARNING; 68 69 /** 70 * Constructor 71 * 72 * Set options for a writer. Accepted options are: 73 * - filters: array of filters to add to this filter 74 * - formatter: formatter for this writer 75 * 76 * @param array|Traversable $options 77 * @throws Exception\InvalidArgumentException 78 */ 79 public function __construct($options = null) 80 { 81 if ($options instanceof Traversable) { 82 $options = iterator_to_array($options); 83 } 84 85 if (is_array($options)) { 86 if (isset($options['filter_manager'])) { 87 $this->setFilterPluginManager($options['filter_manager']); 88 } 89 90 if (isset($options['formatter_manager'])) { 91 $this->setFormatterPluginManager($options['formatter_manager']); 92 } 93 94 if (isset($options['filters'])) { 95 $filters = $options['filters']; 96 if (is_int($filters) || is_string($filters) || $filters instanceof Filter\FilterInterface) { 97 $this->addFilter($filters); 98 } elseif (is_array($filters)) { 99 foreach ($filters as $filter) { 100 if (is_int($filter) || is_string($filter) || $filter instanceof Filter\FilterInterface) { 101 $this->addFilter($filter); 102 } elseif (is_array($filter)) { 103 if (! isset($filter['name'])) { 104 throw new Exception\InvalidArgumentException( 105 'Options must contain a name for the filter' 106 ); 107 } 108 $filterOptions = (isset($filter['options'])) ? $filter['options'] : null; 109 $this->addFilter($filter['name'], $filterOptions); 110 } 111 } 112 } 113 } 114 115 if (isset($options['formatter'])) { 116 $formatter = $options['formatter']; 117 if (is_string($formatter) || $formatter instanceof Formatter\FormatterInterface) { 118 $this->setFormatter($formatter); 119 } elseif (is_array($formatter)) { 120 if (! isset($formatter['name'])) { 121 throw new Exception\InvalidArgumentException('Options must contain a name for the formatter'); 122 } 123 $formatterOptions = (isset($formatter['options'])) ? $formatter['options'] : null; 124 $this->setFormatter($formatter['name'], $formatterOptions); 125 } 126 } 127 } 128 } 129 130 /** 131 * Add a filter specific to this writer. 132 * 133 * @param int|string|Filter\FilterInterface $filter 134 * @param array|null $options 135 * @return AbstractWriter 136 * @throws Exception\InvalidArgumentException 137 */ 138 public function addFilter($filter, array $options = null) 139 { 140 if (is_int($filter)) { 141 $filter = new Filter\Priority($filter); 142 } 143 144 if (is_string($filter)) { 145 $filter = $this->filterPlugin($filter, $options); 146 } 147 148 if (! $filter instanceof Filter\FilterInterface) { 149 throw new Exception\InvalidArgumentException(sprintf( 150 'Filter must implement %s\Filter\FilterInterface; received "%s"', 151 __NAMESPACE__, 152 is_object($filter) ? get_class($filter) : gettype($filter) 153 )); 154 } 155 156 $this->filters[] = $filter; 157 return $this; 158 } 159 160 /** 161 * Get filter plugin manager 162 * 163 * @return LogFilterPluginManager 164 */ 165 public function getFilterPluginManager() 166 { 167 if (null === $this->filterPlugins) { 168 $this->setFilterPluginManager(new LogFilterPluginManager(new ServiceManager())); 169 } 170 return $this->filterPlugins; 171 } 172 173 /** 174 * Set filter plugin manager 175 * 176 * @param string|LogFilterPluginManager $plugins 177 * @return self 178 * @throws Exception\InvalidArgumentException 179 */ 180 public function setFilterPluginManager($plugins) 181 { 182 if (is_string($plugins)) { 183 $plugins = new $plugins; 184 } 185 if (! $plugins instanceof LogFilterPluginManager) { 186 throw new Exception\InvalidArgumentException(sprintf( 187 'Writer plugin manager must extend %s; received %s', 188 LogFilterPluginManager::class, 189 is_object($plugins) ? get_class($plugins) : gettype($plugins) 190 )); 191 } 192 193 $this->filterPlugins = $plugins; 194 return $this; 195 } 196 197 /** 198 * Get filter instance 199 * 200 * @param string $name 201 * @param array|null $options 202 * @return Filter\FilterInterface 203 */ 204 public function filterPlugin($name, array $options = null) 205 { 206 return $this->getFilterPluginManager()->get($name, $options); 207 } 208 209 /** 210 * Get formatter plugin manager 211 * 212 * @return LogFormatterPluginManager 213 */ 214 public function getFormatterPluginManager() 215 { 216 if (null === $this->formatterPlugins) { 217 $this->setFormatterPluginManager(new LogFormatterPluginManager(new ServiceManager())); 218 } 219 return $this->formatterPlugins; 220 } 221 222 /** 223 * Set formatter plugin manager 224 * 225 * @param string|LogFormatterPluginManager $plugins 226 * @return self 227 * @throws Exception\InvalidArgumentException 228 */ 229 public function setFormatterPluginManager($plugins) 230 { 231 if (is_string($plugins)) { 232 $plugins = new $plugins; 233 } 234 if (! $plugins instanceof LogFormatterPluginManager) { 235 throw new Exception\InvalidArgumentException( 236 sprintf( 237 'Writer plugin manager must extend %s; received %s', 238 LogFormatterPluginManager::class, 239 is_object($plugins) ? get_class($plugins) : gettype($plugins) 240 ) 241 ); 242 } 243 244 $this->formatterPlugins = $plugins; 245 return $this; 246 } 247 248 /** 249 * Get formatter instance 250 * 251 * @param string $name 252 * @param array|null $options 253 * @return Formatter\FormatterInterface 254 */ 255 public function formatterPlugin($name, array $options = null) 256 { 257 return $this->getFormatterPluginManager()->get($name, $options); 258 } 259 260 /** 261 * Log a message to this writer. 262 * 263 * @param array $event log data event 264 * @return void 265 */ 266 public function write(array $event) 267 { 268 foreach ($this->filters as $filter) { 269 if (! $filter->filter($event)) { 270 return; 271 } 272 } 273 274 $errorHandlerStarted = false; 275 276 if ($this->convertWriteErrorsToExceptions && ! ErrorHandler::started()) { 277 ErrorHandler::start($this->errorsToExceptionsConversionLevel); 278 $errorHandlerStarted = true; 279 } 280 281 try { 282 $this->doWrite($event); 283 } catch (\Exception $e) { 284 if ($errorHandlerStarted) { 285 ErrorHandler::stop(); 286 } 287 throw $e; 288 } 289 290 if ($errorHandlerStarted) { 291 $error = ErrorHandler::stop(); 292 if ($error) { 293 throw new Exception\RuntimeException("Unable to write", 0, $error); 294 } 295 } 296 } 297 298 /** 299 * Set a new formatter for this writer 300 * 301 * @param string|Formatter\FormatterInterface $formatter 302 * @param array|null $options 303 * @return self 304 * @throws Exception\InvalidArgumentException 305 */ 306 public function setFormatter($formatter, array $options = null) 307 { 308 if (is_string($formatter)) { 309 $formatter = $this->formatterPlugin($formatter, $options); 310 } 311 312 if (! $formatter instanceof Formatter\FormatterInterface) { 313 throw new Exception\InvalidArgumentException(sprintf( 314 'Formatter must implement %s\Formatter\FormatterInterface; received "%s"', 315 __NAMESPACE__, 316 is_object($formatter) ? get_class($formatter) : gettype($formatter) 317 )); 318 } 319 320 $this->formatter = $formatter; 321 return $this; 322 } 323 324 /** 325 * Get formatter 326 * 327 * @return Formatter\FormatterInterface 328 */ 329 protected function getFormatter() 330 { 331 return $this->formatter; 332 } 333 334 /** 335 * Check if the writer has a formatter 336 * 337 * @return bool 338 */ 339 protected function hasFormatter() 340 { 341 return $this->formatter instanceof Formatter\FormatterInterface; 342 } 343 344 /** 345 * Set convert write errors to exception flag 346 * 347 * @param bool $convertErrors 348 */ 349 public function setConvertWriteErrorsToExceptions($convertErrors) 350 { 351 $this->convertWriteErrorsToExceptions = $convertErrors; 352 } 353 354 /** 355 * Perform shutdown activities such as closing open resources 356 * 357 * @return void 358 */ 359 public function shutdown() 360 { 361 } 362 363 /** 364 * Write a message to the log 365 * 366 * @param array $event log data event 367 * @return void 368 */ 369 abstract protected function doWrite(array $event); 370} 371