vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php line 26

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. /*
  3.  * This file is part of the Monolog package.
  4.  *
  5.  * (c) Jordi Boggiano <j.boggiano@seld.be>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Monolog\Handler;
  11. use Monolog\Logger;
  12. use Monolog\Utils;
  13. /**
  14.  * Stores to any stream resource
  15.  *
  16.  * Can be used to store into php://stderr, remote and local files, etc.
  17.  *
  18.  * @author Jordi Boggiano <j.boggiano@seld.be>
  19.  *
  20.  * @phpstan-import-type FormattedRecord from AbstractProcessingHandler
  21.  */
  22. class StreamHandler extends AbstractProcessingHandler
  23. {
  24.     protected const MAX_CHUNK_SIZE 2147483647;
  25.     /** @var resource|null */
  26.     protected $stream;
  27.     /** @var ?string */
  28.     protected $url null;
  29.     /** @var ?string */
  30.     private $errorMessage null;
  31.     /** @var ?int */
  32.     protected $filePermission;
  33.     /** @var bool */
  34.     protected $useLocking;
  35.     /** @var true|null */
  36.     private $dirCreated null;
  37.     /**
  38.      * @param resource|string $stream         If a missing path can't be created, an UnexpectedValueException will be thrown on first write
  39.      * @param int|null        $filePermission Optional file permissions (default (0644) are only for owner read/write)
  40.      * @param bool            $useLocking     Try to lock log file before doing any writes
  41.      *
  42.      * @throws \InvalidArgumentException If stream is not a resource or string
  43.      */
  44.     public function __construct($stream$level Logger::DEBUGbool $bubble true, ?int $filePermission nullbool $useLocking false)
  45.     {
  46.         parent::__construct($level$bubble);
  47.         if (is_resource($stream)) {
  48.             $this->stream $stream;
  49.             stream_set_chunk_size($this->streamself::MAX_CHUNK_SIZE);
  50.         } elseif (is_string($stream)) {
  51.             $this->url Utils::canonicalizePath($stream);
  52.         } else {
  53.             throw new \InvalidArgumentException('A stream must either be a resource or a string.');
  54.         }
  55.         $this->filePermission $filePermission;
  56.         $this->useLocking $useLocking;
  57.     }
  58.     /**
  59.      * {@inheritDoc}
  60.      */
  61.     public function close(): void
  62.     {
  63.         if ($this->url && is_resource($this->stream)) {
  64.             fclose($this->stream);
  65.         }
  66.         $this->stream null;
  67.         $this->dirCreated null;
  68.     }
  69.     /**
  70.      * Return the currently active stream if it is open
  71.      *
  72.      * @return resource|null
  73.      */
  74.     public function getStream()
  75.     {
  76.         return $this->stream;
  77.     }
  78.     /**
  79.      * Return the stream URL if it was configured with a URL and not an active resource
  80.      *
  81.      * @return string|null
  82.      */
  83.     public function getUrl(): ?string
  84.     {
  85.         return $this->url;
  86.     }
  87.     /**
  88.      * {@inheritDoc}
  89.      */
  90.     protected function write(array $record): void
  91.     {
  92.         if (!is_resource($this->stream)) {
  93.             $url $this->url;
  94.             if (null === $url || '' === $url) {
  95.                 throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().');
  96.             }
  97.             $this->createDir($url);
  98.             $this->errorMessage null;
  99.             set_error_handler([$this'customErrorHandler']);
  100.             $stream fopen($url'a');
  101.             if ($this->filePermission !== null) {
  102.                 @chmod($url$this->filePermission);
  103.             }
  104.             restore_error_handler();
  105.             if (!is_resource($stream)) {
  106.                 $this->stream null;
  107.                 throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage$url));
  108.             }
  109.             stream_set_chunk_size($streamself::MAX_CHUNK_SIZE);
  110.             $this->stream $stream;
  111.         }
  112.         $stream $this->stream;
  113.         if (!is_resource($stream)) {
  114.             throw new \LogicException('No stream was opened yet');
  115.         }
  116.         if ($this->useLocking) {
  117.             // ignoring errors here, there's not much we can do about them
  118.             flock($streamLOCK_EX);
  119.         }
  120.         $this->streamWrite($stream$record);
  121.         if ($this->useLocking) {
  122.             flock($streamLOCK_UN);
  123.         }
  124.     }
  125.     /**
  126.      * Write to stream
  127.      * @param resource $stream
  128.      * @param array    $record
  129.      *
  130.      * @phpstan-param FormattedRecord $record
  131.      */
  132.     protected function streamWrite($stream, array $record): void
  133.     {
  134.         fwrite($stream, (string) $record['formatted']);
  135.     }
  136.     private function customErrorHandler(int $codestring $msg): bool
  137.     {
  138.         $this->errorMessage preg_replace('{^(fopen|mkdir)\(.*?\): }'''$msg);
  139.         return true;
  140.     }
  141.     private function getDirFromStream(string $stream): ?string
  142.     {
  143.         $pos strpos($stream'://');
  144.         if ($pos === false) {
  145.             return dirname($stream);
  146.         }
  147.         if ('file://' === substr($stream07)) {
  148.             return dirname(substr($stream7));
  149.         }
  150.         return null;
  151.     }
  152.     private function createDir(string $url): void
  153.     {
  154.         // Do not try to create dir if it has already been tried.
  155.         if ($this->dirCreated) {
  156.             return;
  157.         }
  158.         $dir $this->getDirFromStream($url);
  159.         if (null !== $dir && !is_dir($dir)) {
  160.             $this->errorMessage null;
  161.             set_error_handler([$this'customErrorHandler']);
  162.             $status mkdir($dir0777true);
  163.             restore_error_handler();
  164.             if (false === $status && !is_dir($dir)) {
  165.                 throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and it could not be created: '.$this->errorMessage$dir));
  166.             }
  167.         }
  168.         $this->dirCreated true;
  169.     }
  170. }