<?php 
 
/* 
 * This file is part of the Monolog package. 
 * 
 * (c) Jordi Boggiano <[email protected]> 
 * 
 * For the full copyright and license information, please view the LICENSE 
 * file that was distributed with this source code. 
 */ 
 
namespace Monolog\Handler; 
 
use Monolog\Logger; 
use Monolog\Utils; 
 
/** 
 * Stores to any stream resource 
 * 
 * Can be used to store into php://stderr, remote and local files, etc. 
 * 
 * @author Jordi Boggiano <[email protected]> 
 */ 
class StreamHandler extends AbstractProcessingHandler 
{ 
    protected $stream; 
    protected $url; 
    private $errorMessage; 
    protected $filePermission; 
    protected $useLocking; 
    private $dirCreated; 
 
    /** 
     * @param resource|string $stream 
     * @param int             $level          The minimum logging level at which this handler will be triggered 
     * @param bool            $bubble         Whether the messages that are handled can bubble up the stack or not 
     * @param int|null        $filePermission Optional file permissions (default (0644) are only for owner read/write) 
     * @param bool            $useLocking     Try to lock log file before doing any writes 
     * 
     * @throws \Exception                If a missing directory is not buildable 
     * @throws \InvalidArgumentException If stream is not a resource or string 
     */ 
    public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) 
    { 
        parent::__construct($level, $bubble); 
        if (is_resource($stream)) { 
            $this->stream = $stream; 
        } elseif (is_string($stream)) { 
            $this->url = Utils::canonicalizePath($stream); 
        } else { 
            throw new \InvalidArgumentException('A stream must either be a resource or a string.'); 
        } 
 
        $this->filePermission = $filePermission; 
        $this->useLocking = $useLocking; 
    } 
 
    /** 
     * {@inheritdoc} 
     */ 
    public function close() 
    { 
        if ($this->url && is_resource($this->stream)) { 
            fclose($this->stream); 
        } 
        $this->stream = null; 
        $this->dirCreated = null; 
    } 
 
    /** 
     * Return the currently active stream if it is open 
     * 
     * @return resource|null 
     */ 
    public function getStream() 
    { 
        return $this->stream; 
    } 
 
    /** 
     * Return the stream URL if it was configured with a URL and not an active resource 
     * 
     * @return string|null 
     */ 
    public function getUrl() 
    { 
        return $this->url; 
    } 
 
    /** 
     * {@inheritdoc} 
     */ 
    protected function write(array $record) 
    { 
        if (!is_resource($this->stream)) { 
            if (null === $this->url || '' === $this->url) { 
                throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); 
            } 
            $this->createDir(); 
            $this->errorMessage = null; 
            set_error_handler(array($this, 'customErrorHandler')); 
            $this->stream = fopen($this->url, 'a'); 
            if ($this->filePermission !== null) { 
                @chmod($this->url, $this->filePermission); 
            } 
            restore_error_handler(); 
            if (!is_resource($this->stream)) { 
                $this->stream = null; 
 
                throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage, $this->url)); 
            } 
        } 
 
        if ($this->useLocking) { 
            // ignoring errors here, there's not much we can do about them 
            flock($this->stream, LOCK_EX); 
        } 
 
        $this->streamWrite($this->stream, $record); 
 
        if ($this->useLocking) { 
            flock($this->stream, LOCK_UN); 
        } 
    } 
 
    /** 
     * Write to stream 
     * @param resource $stream 
     * @param array $record 
     */ 
    protected function streamWrite($stream, array $record) 
    { 
        fwrite($stream, (string) $record['formatted']); 
    } 
 
    private function customErrorHandler($code, $msg) 
    { 
        $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); 
    } 
 
    /** 
     * @param string $stream 
     * 
     * @return null|string 
     */ 
    private function getDirFromStream($stream) 
    { 
        $pos = strpos($stream, '://'); 
        if ($pos === false) { 
            return dirname($stream); 
        } 
 
        if ('file://' === substr($stream, 0, 7)) { 
            return dirname(substr($stream, 7)); 
        } 
 
        return; 
    } 
 
    private function createDir() 
    { 
        // Do not try to create dir if it has already been tried. 
        if ($this->dirCreated) { 
            return; 
        } 
 
        $dir = $this->getDirFromStream($this->url); 
        if (null !== $dir && !is_dir($dir)) { 
            $this->errorMessage = null; 
            set_error_handler(array($this, 'customErrorHandler')); 
            $status = mkdir($dir, 0777, true); 
            restore_error_handler(); 
            if (false === $status && !is_dir($dir)) { 
                throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $dir)); 
            } 
        } 
        $this->dirCreated = true; 
    } 
}