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.     /** @const int */
  25.     protected const MAX_CHUNK_SIZE 2147483647;
  26.     /** @const int 10MB */
  27.     protected const DEFAULT_CHUNK_SIZE 10 1024 1024;
  28.     /** @var int */
  29.     protected $streamChunkSize;
  30.     /** @var resource|null */
  31.     protected $stream;
  32.     /** @var ?string */
  33.     protected $url null;
  34.     /** @var ?string */
  35.     private $errorMessage null;
  36.     /** @var ?int */
  37.     protected $filePermission;
  38.     /** @var bool */
  39.     protected $useLocking;
  40.     /** @var true|null */
  41.     private $dirCreated null;
  42.     /**
  43.      * @param resource|string $stream         If a missing path can't be created, an UnexpectedValueException will be thrown on first write
  44.      * @param int|null        $filePermission Optional file permissions (default (0644) are only for owner read/write)
  45.      * @param bool            $useLocking     Try to lock log file before doing any writes
  46.      *
  47.      * @throws \InvalidArgumentException If stream is not a resource or string
  48.      */
  49.     public function __construct($stream$level Logger::DEBUGbool $bubble true, ?int $filePermission nullbool $useLocking false)
  50.     {
  51.         parent::__construct($level$bubble);
  52.         if (($phpMemoryLimit Utils::expandIniShorthandBytes(ini_get('memory_limit'))) !== false) {
  53.             if ($phpMemoryLimit 0) {
  54.                 // use max 10% of allowed memory for the chunk size, and at least 100KB
  55.                 $this->streamChunkSize min(static::MAX_CHUNK_SIZEmax((int) ($phpMemoryLimit 10), 100 1024));
  56.             } else {
  57.                 // memory is unlimited, set to the default 10MB
  58.                 $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE;
  59.             }
  60.         } else {
  61.             // no memory limit information, set to the default 10MB
  62.             $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE;
  63.         }
  64.         if (is_resource($stream)) {
  65.             $this->stream $stream;
  66.             stream_set_chunk_size($this->stream$this->streamChunkSize);
  67.         } elseif (is_string($stream)) {
  68.             $this->url Utils::canonicalizePath($stream);
  69.         } else {
  70.             throw new \InvalidArgumentException('A stream must either be a resource or a string.');
  71.         }
  72.         $this->filePermission $filePermission;
  73.         $this->useLocking $useLocking;
  74.     }
  75.     /**
  76.      * {@inheritDoc}
  77.      */
  78.     public function close(): void
  79.     {
  80.         if ($this->url && is_resource($this->stream)) {
  81.             fclose($this->stream);
  82.         }
  83.         $this->stream null;
  84.         $this->dirCreated null;
  85.     }
  86.     /**
  87.      * Return the currently active stream if it is open
  88.      *
  89.      * @return resource|null
  90.      */
  91.     public function getStream()
  92.     {
  93.         return $this->stream;
  94.     }
  95.     /**
  96.      * Return the stream URL if it was configured with a URL and not an active resource
  97.      *
  98.      * @return string|null
  99.      */
  100.     public function getUrl(): ?string
  101.     {
  102.         return $this->url;
  103.     }
  104.     /**
  105.      * @return int
  106.      */
  107.     public function getStreamChunkSize(): int
  108.     {
  109.         return $this->streamChunkSize;
  110.     }
  111.     /**
  112.      * {@inheritDoc}
  113.      */
  114.     protected function write(array $record): void
  115.     {
  116.         if (!is_resource($this->stream)) {
  117.             $url $this->url;
  118.             if (null === $url || '' === $url) {
  119.                 throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().');
  120.             }
  121.             $this->createDir($url);
  122.             $this->errorMessage null;
  123.             set_error_handler([$this'customErrorHandler']);
  124.             $stream fopen($url'a');
  125.             if ($this->filePermission !== null) {
  126.                 @chmod($url$this->filePermission);
  127.             }
  128.             restore_error_handler();
  129.             if (!is_resource($stream)) {
  130.                 $this->stream null;
  131.                 throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage$url));
  132.             }
  133.             stream_set_chunk_size($stream$this->streamChunkSize);
  134.             $this->stream $stream;
  135.         }
  136.         $stream $this->stream;
  137.         if (!is_resource($stream)) {
  138.             throw new \LogicException('No stream was opened yet');
  139.         }
  140.         if ($this->useLocking) {
  141.             // ignoring errors here, there's not much we can do about them
  142.             flock($streamLOCK_EX);
  143.         }
  144.         $this->streamWrite($stream$record);
  145.         if ($this->useLocking) {
  146.             flock($streamLOCK_UN);
  147.         }
  148.     }
  149.     /**
  150.      * Write to stream
  151.      * @param resource $stream
  152.      * @param array    $record
  153.      *
  154.      * @phpstan-param FormattedRecord $record
  155.      */
  156.     protected function streamWrite($stream, array $record): void
  157.     {
  158.         fwrite($stream, (string) $record['formatted']);
  159.     }
  160.     private function customErrorHandler(int $codestring $msg): bool
  161.     {
  162.         $this->errorMessage preg_replace('{^(fopen|mkdir)\(.*?\): }'''$msg);
  163.         return true;
  164.     }
  165.     private function getDirFromStream(string $stream): ?string
  166.     {
  167.         $pos strpos($stream'://');
  168.         if ($pos === false) {
  169.             return dirname($stream);
  170.         }
  171.         if ('file://' === substr($stream07)) {
  172.             return dirname(substr($stream7));
  173.         }
  174.         return null;
  175.     }
  176.     private function createDir(string $url): void
  177.     {
  178.         // Do not try to create dir if it has already been tried.
  179.         if ($this->dirCreated) {
  180.             return;
  181.         }
  182.         $dir $this->getDirFromStream($url);
  183.         if (null !== $dir && !is_dir($dir)) {
  184.             $this->errorMessage null;
  185.             set_error_handler([$this'customErrorHandler']);
  186.             $status mkdir($dir0777true);
  187.             restore_error_handler();
  188.             if (false === $status && !is_dir($dir)) {
  189.                 throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and it could not be created: '.$this->errorMessage$dir));
  190.             }
  191.         }
  192.         $this->dirCreated true;
  193.     }
  194. }