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

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