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

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 <[email protected]>
  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 <[email protected]>
  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.     protected string $fileOpenMode;
  34.     /** @var true|null */
  35.     private bool|null $dirCreated null;
  36.     private bool $retrying false;
  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.      * @param string          $fileOpenMode   The fopen() mode used when opening a file, if $stream is a file path
  42.      *
  43.      * @throws \InvalidArgumentException If stream is not a resource or string
  44.      */
  45.     public function __construct($streamint|string|Level $level Level::Debugbool $bubble true, ?int $filePermission nullbool $useLocking falsestring $fileOpenMode 'a')
  46.     {
  47.         parent::__construct($level$bubble);
  48.         if (($phpMemoryLimit Utils::expandIniShorthandBytes(\ini_get('memory_limit'))) !== false) {
  49.             if ($phpMemoryLimit 0) {
  50.                 // use max 10% of allowed memory for the chunk size, and at least 100KB
  51.                 $this->streamChunkSize min(static::MAX_CHUNK_SIZEmax((int) ($phpMemoryLimit 10), 100 1024));
  52.             } else {
  53.                 // memory is unlimited, set to the default 10MB
  54.                 $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE;
  55.             }
  56.         } else {
  57.             // no memory limit information, set to the default 10MB
  58.             $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE;
  59.         }
  60.         if (\is_resource($stream)) {
  61.             $this->stream $stream;
  62.             stream_set_chunk_size($this->stream$this->streamChunkSize);
  63.         } elseif (\is_string($stream)) {
  64.             $this->url Utils::canonicalizePath($stream);
  65.         } else {
  66.             throw new \InvalidArgumentException('A stream must either be a resource or a string.');
  67.         }
  68.         $this->fileOpenMode $fileOpenMode;
  69.         $this->filePermission $filePermission;
  70.         $this->useLocking $useLocking;
  71.     }
  72.     /**
  73.      * @inheritDoc
  74.      */
  75.     public function reset(): void
  76.     {
  77.         parent::reset();
  78.         // auto-close on reset to make sure we periodically close the file in long running processes
  79.         // as long as they correctly call reset() between jobs
  80.         if ($this->url !== null && $this->url !== 'php://memory') {
  81.             $this->close();
  82.         }
  83.     }
  84.     /**
  85.      * @inheritDoc
  86.      */
  87.     public function close(): void
  88.     {
  89.         if (null !== $this->url && \is_resource($this->stream)) {
  90.             fclose($this->stream);
  91.         }
  92.         $this->stream null;
  93.         $this->dirCreated null;
  94.     }
  95.     /**
  96.      * Return the currently active stream if it is open
  97.      *
  98.      * @return resource|null
  99.      */
  100.     public function getStream()
  101.     {
  102.         return $this->stream;
  103.     }
  104.     /**
  105.      * Return the stream URL if it was configured with a URL and not an active resource
  106.      */
  107.     public function getUrl(): ?string
  108.     {
  109.         return $this->url;
  110.     }
  111.     public function getStreamChunkSize(): int
  112.     {
  113.         return $this->streamChunkSize;
  114.     }
  115.     /**
  116.      * @inheritDoc
  117.      */
  118.     protected function write(LogRecord $record): void
  119.     {
  120.         if (!\is_resource($this->stream)) {
  121.             $url $this->url;
  122.             if (null === $url || '' === $url) {
  123.                 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));
  124.             }
  125.             $this->createDir($url);
  126.             $this->errorMessage null;
  127.             set_error_handler($this->customErrorHandler(...));
  128.             try {
  129.                 $stream fopen($url$this->fileOpenMode);
  130.                 if ($this->filePermission !== null) {
  131.                     @chmod($url$this->filePermission);
  132.                 }
  133.             } finally {
  134.                 restore_error_handler();
  135.             }
  136.             if (!\is_resource($stream)) {
  137.                 $this->stream null;
  138.                 throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage$url) . Utils::getRecordMessageForException($record));
  139.             }
  140.             stream_set_chunk_size($stream$this->streamChunkSize);
  141.             $this->stream $stream;
  142.         }
  143.         $stream $this->stream;
  144.         if ($this->useLocking) {
  145.             // ignoring errors here, there's not much we can do about them
  146.             flock($streamLOCK_EX);
  147.         }
  148.         $this->errorMessage null;
  149.         set_error_handler($this->customErrorHandler(...));
  150.         try {
  151.             $this->streamWrite($stream$record);
  152.         } finally {
  153.             restore_error_handler();
  154.         }
  155.         if ($this->errorMessage !== null) {
  156.             $error $this->errorMessage;
  157.             // close the resource if possible to reopen it, and retry the failed write
  158.             if (!$this->retrying && $this->url !== null && $this->url !== 'php://memory') {
  159.                 $this->retrying true;
  160.                 $this->close();
  161.                 $this->write($record);
  162.                 return;
  163.             }
  164.             throw new \UnexpectedValueException('Writing to the log file failed: '.$error Utils::getRecordMessageForException($record));
  165.         }
  166.         $this->retrying false;
  167.         if ($this->useLocking) {
  168.             flock($streamLOCK_UN);
  169.         }
  170.     }
  171.     /**
  172.      * Write to stream
  173.      * @param resource $stream
  174.      */
  175.     protected function streamWrite($streamLogRecord $record): void
  176.     {
  177.         fwrite($stream, (string) $record->formatted);
  178.     }
  179.     private function customErrorHandler(int $codestring $msg): bool
  180.     {
  181.         $this->errorMessage preg_replace('{^(fopen|mkdir|fwrite)\(.*?\): }'''$msg);
  182.         return true;
  183.     }
  184.     private function getDirFromStream(string $stream): ?string
  185.     {
  186.         $pos strpos($stream'://');
  187.         if ($pos === false) {
  188.             return \dirname($stream);
  189.         }
  190.         if ('file://' === substr($stream07)) {
  191.             return \dirname(substr($stream7));
  192.         }
  193.         return null;
  194.     }
  195.     private function createDir(string $url): void
  196.     {
  197.         // Do not try to create dir if it has already been tried.
  198.         if (true === $this->dirCreated) {
  199.             return;
  200.         }
  201.         $dir $this->getDirFromStream($url);
  202.         if (null !== $dir && !is_dir($dir)) {
  203.             $this->errorMessage null;
  204.             set_error_handler(function (...$args) {
  205.                 return $this->customErrorHandler(...$args);
  206.             });
  207.             $status mkdir($dir0777true);
  208.             restore_error_handler();
  209.             if (false === $status && !is_dir($dir) && strpos((string) $this->errorMessage'File exists') === false) {
  210.                 throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and it could not be created: '.$this->errorMessage$dir));
  211.             }
  212.         }
  213.         $this->dirCreated true;
  214.     }
  215. }