├── .github └── workflows │ └── ci.yaml ├── LICENSE.txt ├── build └── .gitignore ├── composer.json └── src ├── Exception.php ├── LogEntry.php ├── LogFormatter.php ├── LogFormatterInterface.php ├── Logger.php └── Logger ├── AbstractLogger.php ├── ErrorLog.php ├── File.php ├── LoggerInterface.php ├── Mail.php ├── Nil.php ├── Runtime.php ├── Sapi.php └── Stream.php /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: "Continuous Integration" 2 | 3 | on: 4 | - pull_request 5 | - push 6 | 7 | env: 8 | COMPOSER_ROOT_VERSION: 1.99 9 | 10 | jobs: 11 | tests: 12 | name: "Tests" 13 | 14 | runs-on: "ubuntu-latest" 15 | 16 | strategy: 17 | matrix: 18 | php-version: 19 | - "8.0" 20 | - "8.1" 21 | 22 | dependencies: 23 | - "highest" 24 | 25 | steps: 26 | - name: "Checkout" 27 | uses: "actions/checkout@v2" 28 | 29 | - name: "Install PHP" 30 | uses: "shivammathur/setup-php@v2" 31 | with: 32 | php-version: "${{ matrix.php-version }}" 33 | ini-values: zend.assertions=1 34 | 35 | - name: "Get composer cache directory" 36 | id: composercache 37 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 38 | 39 | - name: "Cache dependencies" 40 | uses: actions/cache@v2 41 | with: 42 | path: ${{ steps.composercache.outputs.dir }} 43 | key: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-${{ hashFiles('**/composer.json') }} 44 | restore-keys: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer- 45 | 46 | - name: "Install highest dependencies" 47 | run: "composer update --no-interaction --no-progress" 48 | 49 | - name: "Run tests" 50 | timeout-minutes: 3 51 | run: "vendor/bin/phpunit" 52 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | New BSD License 2 | =============== 3 | 4 | Copyright (c) 2010-2015, Franck Cassedanne, OUARZ Technology Ltd. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | * Neither the names of the copyright holders nor the names of its 16 | contributors may be used to endorse or promote products derived from this 17 | software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /build/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apix/log/995d85334f083b4d96d2317d12ecad94e2eb0a49/build/.gitignore -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apix/log", 3 | "description": "Minimalist, thin and fast PSR-3 compliant (multi-bucket) logger.", 4 | "type": "library", 5 | "keywords": ["psr", "psr-3", "psr-log", "log", "logger", "logging", "tracker", "tracking", "apix"], 6 | "homepage": "https://github.com/frqnck/apix-log", 7 | "license": "BSD-3-Clause", 8 | "authors": [ 9 | { 10 | "name": "Franck Cassedanne", 11 | "email": "franck@ouarz.net" 12 | }, 13 | { 14 | "name": "Apix Log Community", 15 | "homepage": "https://github.com/apix/log/contributors" 16 | } 17 | ], 18 | "support": { 19 | "irc": "irc://irc.freenode.org/ouarz" 20 | }, 21 | "require": { 22 | "php": ">=8.0", 23 | "psr/log": "^2.0 || ^3.0" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "^8.0|^9.0" 27 | }, 28 | "suggest": { 29 | "PHPMailer/apix-log-phpmailer": "Allow sending log messages via PHPMailer", 30 | "jspalink/apix-log-pushover": "Allow sending log messages via Pushover", 31 | "apix/log-tracker": "Allow sending log messages to logger/tracker such as Google Analytics, Dashbot, etc." 32 | }, 33 | "autoload": { 34 | "psr-4": { "Apix\\Log\\": "src/" } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { "Apix\\Log\\tests\\": "tests/" } 38 | }, 39 | "provide": { 40 | "psr/log-implementation": "^1.0" 41 | }, 42 | "minimum-stability": "dev", 43 | "prefer-stable": true 44 | } 45 | -------------------------------------------------------------------------------- /src/Exception.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License 9 | */ 10 | 11 | namespace Apix\Log; 12 | 13 | /** 14 | * Main Log Exception. 15 | */ 16 | class Exception extends \Exception 17 | { 18 | } -------------------------------------------------------------------------------- /src/LogEntry.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License 9 | */ 10 | 11 | namespace Apix\Log; 12 | 13 | use Psr\Log\InvalidArgumentException; 14 | 15 | /** 16 | * Describes a log Entry. 17 | * 18 | * @author Franck Cassedanne 19 | */ 20 | class LogEntry 21 | { 22 | 23 | /** 24 | * Holds this log timestamp. 25 | * @var integer 26 | */ 27 | public $timestamp; 28 | 29 | /** 30 | * Holds this log name. 31 | * @var string 32 | */ 33 | public $name; 34 | 35 | /** 36 | * Holds this log level code. 37 | * @var integer 38 | */ 39 | public $level_code; 40 | 41 | /** 42 | * Holds this log message. 43 | * @var string 44 | */ 45 | public $message; 46 | 47 | /** 48 | * Holds this log context. 49 | * @var array 50 | */ 51 | public $context; 52 | 53 | /** 54 | * Holds this log formatter. 55 | * @var LogFormatter 56 | */ 57 | public $formatter; 58 | 59 | /** 60 | * Constructor. 61 | * 62 | * @param string $name The level name. 63 | * @param string $message The message for this log entry. 64 | * @param array $context The contexts for this log entry. 65 | */ 66 | public function __construct($name, $message, array $context = array()) 67 | { 68 | $this->timestamp = time(); 69 | 70 | $this->name = $name; 71 | $this->level_code = Logger::getLevelCode($name); 72 | 73 | // Message is not a string let assume it is a context -- and permute. 74 | if (!is_string($message)) { 75 | $context = array('ctx' => $message); 76 | $message = '{ctx}'; 77 | } 78 | $this->message = $message; 79 | $this->context = $context; 80 | } 81 | 82 | public function setFormatter(LogFormatter $formatter) 83 | { 84 | $this->formatter = $formatter; 85 | } 86 | 87 | /** 88 | * Returns the formated string for this log entry. 89 | * 90 | * @return string 91 | */ 92 | public function __toString() 93 | { 94 | return $this->formatter->format($this); 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /src/LogFormatter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License 9 | */ 10 | 11 | namespace Apix\Log; 12 | 13 | /** 14 | * Standard log formatter. 15 | * 16 | * @author Franck Cassedanne 17 | */ 18 | class LogFormatter implements LogFormatterInterface 19 | { 20 | 21 | /** 22 | * Holds this log separator. 23 | * @var string 24 | */ 25 | public $separator = PHP_EOL; 26 | 27 | /** 28 | * Interpolates context values into the message placeholders. 29 | * 30 | * Builds a replacement array with braces around the context keys. 31 | * It replaces {foo} with the value from $context['foo']. 32 | * 33 | * @param string $message 34 | * @param array $context 35 | * @return string 36 | */ 37 | public function interpolate($message, array $context = array()) 38 | { 39 | $replaces = array(); 40 | foreach ($context as $key => $val) { 41 | if (is_bool($val)) { 42 | $val = '[bool: ' . (int) $val . ']'; 43 | } elseif (is_null($val) 44 | || is_scalar($val) 45 | || ( is_object($val) && method_exists($val, '__toString') ) 46 | ) { 47 | $val = (string) $val; 48 | } elseif (is_array($val) || is_object($val)) { 49 | $val = @json_encode($val); 50 | } else { 51 | $val = '[type: ' . gettype($val) . ']'; 52 | } 53 | $replaces['{' . $key . '}'] = $val; 54 | } 55 | 56 | return strtr($message, $replaces); 57 | } 58 | 59 | /** 60 | * Formats the given log entry. 61 | * 62 | * @param LogEntry $log The log entry to format. 63 | * @return string 64 | */ 65 | public function format(LogEntry $log) 66 | { 67 | return sprintf( 68 | '[%s] %s %s', 69 | date('Y-m-d H:i:s', $log->timestamp), 70 | strtoupper($log->name), 71 | self::interpolate($log->message, $log->context) 72 | ); 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /src/LogFormatterInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License 9 | */ 10 | 11 | namespace Apix\Log; 12 | 13 | /** 14 | * Log Formatter Interface. 15 | * 16 | * To contribute a formatter, essentially it needs to: 17 | * 1.) Extends the `LogFormatter` 18 | * 2.) Implements this interface `LogFormatterInterface` 19 | * 20 | * @example 21 | * class MyJsonFormatter extends LogFormatter 22 | * { 23 | * public function format(LogEntry $log) 24 | * { 25 | * return json_encode($log); 26 | * } 27 | * } 28 | * 29 | * @see tests/InterfacesTest.php For a more detailed example. 30 | * 31 | * @author Franck Cassedanne 32 | */ 33 | interface LogFormatterInterface 34 | { 35 | 36 | /** 37 | * Formats the given log entry. 38 | * 39 | * @param LogEntry $log The log entry to format. 40 | * @return string 41 | */ 42 | public function format(LogEntry $log); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/Logger.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License 9 | */ 10 | 11 | namespace Apix\Log; 12 | 13 | use Apix\Log\Logger\AbstractLogger; 14 | use Psr\Log\InvalidArgumentException; 15 | 16 | /** 17 | * Minimalist logger implementing PSR-3 relying on PHP's error_log(). 18 | * 19 | * @author Franck Cassedanne 20 | */ 21 | class Logger extends AbstractLogger 22 | { 23 | /** 24 | * Holds all the registered loggers as buckets. 25 | * 26 | * @var Logger\LoggerInterface[]. 27 | */ 28 | protected $buckets = array(); 29 | 30 | /** 31 | * Constructor. 32 | * 33 | * @param Logger\LoggerInterface[] $loggers 34 | */ 35 | public function __construct(array $loggers = array()) 36 | { 37 | foreach ($loggers as $key => $logger) { 38 | if ($logger instanceof Logger\LoggerInterface) { 39 | $this->buckets[] = $logger; 40 | } else { 41 | throw new InvalidArgumentException( 42 | sprintf( 43 | '"%s" must interface "%s".', 44 | get_class($logger), 45 | __NAMESPACE__.'\Logger\LoggerInterface' 46 | ) 47 | ); 48 | } 49 | } 50 | $this->sortBuckets(); 51 | } 52 | 53 | /** 54 | * Processes the given log. 55 | * (overwrite abstract). 56 | * 57 | * @param LogEntry $log The log record to handle. 58 | * 59 | * @return bool False when not processed. 60 | */ 61 | public function process(LogEntry $log) 62 | { 63 | $i = $this->getIndexFirstBucket($log->level_code); 64 | if (false !== $i) { 65 | while ( 66 | isset($this->buckets[$i]) 67 | && $this->buckets[$i]->process($log) 68 | ) { 69 | ++$i; 70 | } 71 | 72 | return true; 73 | } 74 | 75 | return false; 76 | } 77 | 78 | /** 79 | * Checks if any log bucket can hanle the given code. 80 | * 81 | * @param int $level_code 82 | * 83 | * @return int|false 84 | */ 85 | protected function getIndexFirstBucket($level_code) 86 | { 87 | foreach ($this->buckets as $key => $logger) { 88 | if ($logger->isHandling($level_code)) { 89 | return $key; 90 | } 91 | } 92 | 93 | return false; 94 | } 95 | 96 | /** 97 | * Gets the name of the PSR-3 logging level. 98 | * 99 | * @param string $level_name 100 | * 101 | * @return string 102 | * 103 | * @throws InvalidArgumentException 104 | */ 105 | public static function getPsrLevelName($level_name) 106 | { 107 | $logLevel = '\Psr\Log\LogLevel::'.strtoupper($level_name); 108 | if (!defined($logLevel)) { 109 | throw new InvalidArgumentException( 110 | sprintf('Invalid PSR-3 log level "%s"', $level_name) 111 | ); 112 | } 113 | 114 | return $level_name; 115 | } 116 | 117 | /** 118 | * Adds a logger. 119 | * 120 | * @param Logger\LoggerInterface $logger 121 | * 122 | * @return bool Returns TRUE on success or FALSE on failure. 123 | */ 124 | public function add(Logger\LoggerInterface $logger) 125 | { 126 | $this->buckets[] = $logger; 127 | 128 | return $this->sortBuckets(); 129 | } 130 | 131 | /** 132 | * Sorts the log buckets, prioritizes top-down by minimal level. 133 | * Beware: Exisiting level will be in FIFO order. 134 | * 135 | * @return bool Returns TRUE on success or FALSE on failure. 136 | */ 137 | protected function sortBuckets() 138 | { 139 | return usort( 140 | $this->buckets, function ($a, $b) { 141 | return $a->getMinLevel() - $b->getMinLevel(); 142 | } 143 | ); 144 | } 145 | 146 | /** 147 | * Returns all the registered log buckets. 148 | * 149 | * @return array 150 | */ 151 | public function getBuckets() 152 | { 153 | return $this->buckets; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/Logger/AbstractLogger.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License 9 | */ 10 | 11 | namespace Apix\Log\Logger; 12 | 13 | use Psr\Log\AbstractLogger as PsrAbstractLogger; 14 | use Psr\Log\InvalidArgumentException; 15 | use Stringable; 16 | use Apix\Log\LogEntry; 17 | use Apix\Log\LogFormatter; 18 | 19 | /** 20 | * Abstratc class. 21 | * 22 | * @author Franck Cassedanne 23 | */ 24 | abstract class AbstractLogger extends PsrAbstractLogger 25 | { 26 | /** 27 | * The PSR-3 logging levels. 28 | * @var array 29 | */ 30 | protected static $levels = array( 31 | 'emergency', 32 | 'alert', 33 | 'critical', 34 | 'error', 35 | 'warning', 36 | 'notice', 37 | 'info', 38 | 'debug' 39 | ); 40 | 41 | /** 42 | * Holds the minimal level index supported by this logger. 43 | * @var int 44 | */ 45 | protected $min_level = 7; 46 | 47 | /** 48 | * Whether this logger will cascade downstream. 49 | * @var bool 50 | */ 51 | protected $cascading = true; 52 | 53 | /** 54 | * Whether this logger will be deferred (push the logs at destruct time). 55 | * @var bool 56 | */ 57 | protected $deferred = false; 58 | 59 | /** 60 | * Holds the deferred logs. 61 | * @var array 62 | */ 63 | protected $deferred_logs = array(); 64 | 65 | /** 66 | * Holds the log formatter. 67 | * @var LogFormatter|null 68 | */ 69 | protected $log_formatter = null; 70 | 71 | /** 72 | * Holds the logger options (useful to set default options). 73 | * @var array 74 | */ 75 | protected $options = array(); 76 | 77 | /** 78 | * Gets the named level code. 79 | * 80 | * @param string $level_name The name of a PSR-3 level. 81 | * @return int 82 | * @throws InvalidArgumentException 83 | */ 84 | public static function getLevelCode($level_name) 85 | { 86 | $level_code = array_search($level_name, static::$levels); 87 | if (false === $level_code) { 88 | throw new InvalidArgumentException( 89 | sprintf('Invalid log level "%s"', $level_name) 90 | ); 91 | } 92 | 93 | return $level_code; 94 | } 95 | 96 | /** 97 | * {@inheritdoc} 98 | */ 99 | public function log($level, Stringable|string $message, array $context = array()) : void 100 | { 101 | $entry = new LogEntry($level, $message, $context); 102 | $entry->setFormatter($this->getLogFormatter()); 103 | $this->process($entry); 104 | } 105 | 106 | /** 107 | * Processes the given log. 108 | * 109 | * @param LogEntry $log The log entry to process. 110 | * @return bool Wether this logger cascade downstream. 111 | */ 112 | public function process(LogEntry $log) 113 | { 114 | if ($this->deferred) { 115 | $this->deferred_logs[] = $log; 116 | } else { 117 | $this->write($log); 118 | } 119 | 120 | return $this->cascading; 121 | } 122 | 123 | /** 124 | * Checks whether the given level code is handled by this logger. 125 | * 126 | * @param int $level_code 127 | * @return bool 128 | */ 129 | public function isHandling($level_code) 130 | { 131 | return $this->min_level >= $level_code; 132 | } 133 | 134 | /** 135 | * Sets the minimal level at which this logger will be triggered. 136 | * 137 | * @param string $name 138 | * @param bool|true $cascading Should the logs continue pass that level. 139 | * @return self 140 | */ 141 | public function setMinLevel($name, $cascading = true) 142 | { 143 | $this->min_level = self::getLevelCode(strtolower($name)); 144 | $this->cascading = (boolean) $cascading; 145 | 146 | return $this; 147 | } 148 | 149 | /** 150 | * Alias to self::setMinLevel(). 151 | * 152 | * @param string $name 153 | * @param bool|false $blocking Should the logs continue pass that level. 154 | * @return self 155 | */ 156 | public function interceptAt($name, $blocking = false) 157 | { 158 | return $this->setMinLevel($name, !$blocking); 159 | } 160 | 161 | /** 162 | * Returns the minimal level at which this logger will be triggered. 163 | * 164 | * @return int 165 | */ 166 | public function getMinLevel() 167 | { 168 | return $this->min_level; 169 | } 170 | 171 | /** 172 | * Sets wether to enable/disable cascading. 173 | * 174 | * @param bool $bool 175 | * @return self 176 | */ 177 | public function setCascading($bool) 178 | { 179 | $this->cascading = (boolean) $bool; 180 | 181 | return $this; 182 | } 183 | 184 | /** 185 | * Get cascading property 186 | * @return bool 187 | */ 188 | public function cascading() 189 | { 190 | return $this->cascading; 191 | } 192 | 193 | /** 194 | * Sets wether to enable/disable log deferring. 195 | * 196 | * @param bool $bool 197 | * @return self 198 | */ 199 | public function setDeferred($bool) 200 | { 201 | $this->deferred = (boolean) $bool; 202 | 203 | return $this; 204 | } 205 | 206 | /** 207 | * Get deferred property 208 | * @return bool 209 | */ 210 | public function deferred() 211 | { 212 | return $this->deferred; 213 | } 214 | 215 | /** 216 | * Returns all the deferred logs. 217 | * 218 | * @return array 219 | */ 220 | public function getDeferredLogs() 221 | { 222 | return $this->deferred_logs; 223 | } 224 | 225 | /** 226 | * Process any accumulated deferred log if there are any. 227 | */ 228 | final public function __destruct() 229 | { 230 | if ($this->deferred && !empty($this->deferred_logs)) { 231 | 232 | $messages = array_map( 233 | function ($log) { 234 | return (string) $log; 235 | }, 236 | $this->deferred_logs 237 | ); 238 | 239 | $formatter = $this->getLogFormatter(); 240 | 241 | $messages = implode($formatter->separator, $messages); 242 | 243 | $entries = new LogEntry('notice', $messages); 244 | $entries->setFormatter( $formatter ); 245 | $this->write($entries); 246 | 247 | // return $this->formatter->format($this); 248 | } 249 | 250 | $this->close(); 251 | } 252 | 253 | /** 254 | * Closes the logger ~ acts as the last resort garbage collector. 255 | * 256 | * This method is called last at __destruct() time. 257 | */ 258 | public function close() 259 | { 260 | // empty 261 | } 262 | 263 | /** 264 | * Sets a log formatter. 265 | * 266 | * @param LogFormatter $formatter 267 | */ 268 | public function setLogFormatter(LogFormatter $formatter) 269 | { 270 | $this->log_formatter = $formatter; 271 | } 272 | 273 | /** 274 | * Returns the current log formatter. 275 | * 276 | * @return LogFormatter 277 | */ 278 | public function getLogFormatter() 279 | { 280 | if(!$this->log_formatter) { 281 | $this->setLogFormatter(new LogFormatter); 282 | } 283 | 284 | return $this->log_formatter; 285 | } 286 | 287 | /** 288 | * Sets and merges the options for this logger, overriding any default. 289 | * 290 | * @param array|null $options 291 | */ 292 | public function setOptions(array $options=null) 293 | { 294 | if (null !== $options) { 295 | $this->options = $options+$this->options; 296 | } 297 | } 298 | 299 | } -------------------------------------------------------------------------------- /src/Logger/ErrorLog.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License 9 | */ 10 | 11 | namespace Apix\Log\Logger; 12 | 13 | use Apix\Log\LogEntry; 14 | 15 | /** 16 | * Minimalist logger implementing PSR-3 relying on PHP's error_log(). 17 | * 18 | * @author Franck Cassedanne 19 | */ 20 | class ErrorLog extends AbstractLogger implements LoggerInterface 21 | { 22 | const PHP = 0; 23 | const MAIL = 1; 24 | const FILE = 3; 25 | const SAPI = 4; 26 | 27 | /** 28 | * Holds the destination string (filename path or email address). 29 | * @var string 30 | */ 31 | protected $destination; 32 | 33 | /** 34 | * Holds the message/delivery type: 35 | * 0: message is sent to PHP's system logger. 36 | * 1: message is sent by email to the address in the destination. 37 | * 3: message is appended to the file destination. 38 | * 4: message is sent directly to the SAPI. 39 | * @var integer 40 | */ 41 | protected $type; 42 | 43 | /** 44 | * Holds a string of additional (mail) headers. 45 | * @var string|null 46 | * @see http://php.net/manual/en/function.mail.php 47 | */ 48 | protected $headers = null; 49 | 50 | /** 51 | * Constructor. 52 | * @param string|null $file The filename to log messages to. 53 | * @param integer $type The messag/delivery type. 54 | */ 55 | public function __construct($file = null, $type = self::PHP) 56 | { 57 | $this->destination = $file; 58 | $this->type = $type; 59 | } 60 | 61 | /** 62 | * {@inheritDoc} 63 | */ 64 | public function write(LogEntry $log) 65 | { 66 | $message = (string) $log; 67 | 68 | if(!$this->deferred && $this->type == self::FILE) { 69 | $message .= $log->formatter->separator; 70 | } 71 | 72 | return error_log( 73 | $message, 74 | $this->type, 75 | $this->destination, 76 | $this->headers 77 | ); 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /src/Logger/File.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License 9 | */ 10 | 11 | namespace Apix\Log\Logger; 12 | 13 | use Psr\Log\InvalidArgumentException; 14 | 15 | /** 16 | * Minimalist file based PSR-3 logger relying on PHP's error_log(). 17 | * 18 | * @author Franck Cassedanne 19 | */ 20 | class File extends ErrorLog 21 | { 22 | 23 | /** 24 | * Constructor. 25 | * 26 | * @param string $file The file to append to. 27 | * @throws InvalidArgumentException If the file cannot be created or written. 28 | */ 29 | public function __construct($file) 30 | { 31 | if (null === $file || !file_exists($file) && !touch($file)) { 32 | throw new InvalidArgumentException( 33 | sprintf('Log file "%s" cannot be created', $file), 1 34 | ); 35 | } 36 | if (!is_writable($file)) { 37 | throw new InvalidArgumentException( 38 | sprintf('Log file "%s" is not writable', $file), 2 39 | ); 40 | } 41 | 42 | $this->destination = $file; 43 | $this->type = static::FILE; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/Logger/LoggerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License 9 | */ 10 | 11 | namespace Apix\Log\Logger; 12 | 13 | use Apix\Log\LogEntry; 14 | 15 | /** 16 | * Logger Interface providing PSR-3 (PSR Log) compliency. 17 | * 18 | * To contribute a logger, essentially it needs to: 19 | * 1.) Extends the `AbstractLogger` 20 | * 2.) Implements this interface `LoggerInterface` 21 | * 3.) Cast to string the provided `LogEntry $log` e.g. (string) $log 22 | * 23 | * @example 24 | * class StandardOutput extends AbstractLogger implements LoggerInterface 25 | * { 26 | * public function write(LogEntry $log) 27 | * { 28 | * echo $log; 29 | * } 30 | * } 31 | * 32 | * @see tests/InterfacesTest.php For a more detailed example. 33 | * 34 | * @author Franck Cassedanne 35 | */ 36 | interface LoggerInterface 37 | { 38 | 39 | /** 40 | * Writes the given log entry. 41 | * 42 | * @param LogEntry $log 43 | * @return bool Wether the log entry was successfully written or not. 44 | */ 45 | public function write(LogEntry $log); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/Logger/Mail.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License 9 | */ 10 | 11 | namespace Apix\Log\Logger; 12 | 13 | use Psr\Log\InvalidArgumentException; 14 | 15 | /** 16 | * Minimalist mail based PSR-3 logger relying on PHP's error_log(). 17 | * 18 | * @author Franck Cassedanne 19 | */ 20 | class Mail extends ErrorLog 21 | { 22 | 23 | /** 24 | * Constructor. 25 | * 26 | * @param string $email The email to append to. 27 | * @param string|null $headers A string of additional (mail) headers. 28 | * @throws Psr\Log\InvalidArgumentException If the email does not validate. 29 | */ 30 | public function __construct($email, $headers = null) 31 | { 32 | if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { 33 | throw new InvalidArgumentException( 34 | sprintf('"%s" is an invalid email address', $email) 35 | ); 36 | } 37 | 38 | $this->destination = $email; 39 | $this->type = static::MAIL; 40 | $this->headers = (string) $headers; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/Logger/Nil.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License 9 | */ 10 | 11 | namespace Apix\Log\Logger; 12 | 13 | use Apix\Log\LogEntry; 14 | 15 | /** 16 | * Nil (Null) log wrapper. 17 | * 18 | * @author Franck Cassedanne 19 | * @codeCoverageIgnore 20 | */ 21 | class Nil extends AbstractLogger implements LoggerInterface 22 | { 23 | 24 | /** 25 | * {@inheritDoc} 26 | */ 27 | public function write(LogEntry $log) 28 | { 29 | return false; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/Logger/Runtime.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License 9 | */ 10 | 11 | namespace Apix\Log\Logger; 12 | 13 | use Apix\Log\LogEntry; 14 | 15 | /** 16 | * Runtime (Array/ArrayObject) log wrapper. 17 | * 18 | * @author Franck Cassedanne 19 | */ 20 | class Runtime extends AbstractLogger implements LoggerInterface 21 | { 22 | /** 23 | * Holds the logged items. 24 | * @var array 25 | */ 26 | protected $items = array(); 27 | 28 | /** 29 | * {@inheritDoc} 30 | */ 31 | public function write(LogEntry $log) 32 | { 33 | $this->items[] = (string) $log; 34 | } 35 | 36 | /** 37 | * Returns the logged items. 38 | * 39 | * @return array 40 | */ 41 | public function getItems() 42 | { 43 | return $this->items; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/Logger/Sapi.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License 9 | */ 10 | 11 | namespace Apix\Log\Logger; 12 | 13 | /** 14 | * Sapi (Server API) log wrapper. 15 | * 16 | * @author Franck Cassedanne 17 | * @codeCoverageIgnore 18 | */ 19 | class Sapi extends ErrorLog 20 | { 21 | 22 | /** 23 | * Constructor. 24 | * 25 | * @param string $destination 26 | */ 27 | public function __construct($destination = \PHP_SAPI) 28 | { 29 | $this->destination = $destination; 30 | $this->type = static::SAPI; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/Logger/Stream.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License 9 | */ 10 | 11 | namespace Apix\Log\Logger; 12 | 13 | use Psr\Log\InvalidArgumentException; 14 | use Apix\Log\LogEntry; 15 | 16 | /** 17 | * Stream log wrapper. 18 | * 19 | * @author Franck Cassedanne 20 | */ 21 | class Stream extends AbstractLogger implements LoggerInterface 22 | { 23 | 24 | /** 25 | * Holds the stream. 26 | * @var resource 27 | */ 28 | protected $stream; 29 | 30 | /** 31 | * Constructor. 32 | * 33 | * @param resource|string $stream The stream to append to. 34 | * @throws InvalidArgumentException If the stream cannot be created/opened. 35 | */ 36 | public function __construct($stream = 'php://stdout', $mode = 'a') 37 | { 38 | if (!is_resource($stream)) { 39 | $stream = @fopen($stream, $mode); 40 | } 41 | 42 | if (!is_resource($stream)) { 43 | throw new InvalidArgumentException(sprintf( 44 | 'The stream "%s" cannot be created or opened', $stream 45 | )); 46 | } 47 | 48 | $this->stream = $stream; 49 | } 50 | 51 | /** 52 | * {@inheritDoc} 53 | */ 54 | public function write(LogEntry $log) 55 | { 56 | if (!is_resource($this->stream)) { 57 | throw new \LogicException( 58 | 'The stream resource has been __destruct() too early' 59 | ); 60 | } 61 | return (bool) fwrite($this->stream, $log . $log->formatter->separator); 62 | } 63 | 64 | /** 65 | * {@inheritDoc} 66 | */ 67 | public function close() 68 | { 69 | if (is_resource($this->stream)) { 70 | fclose($this->stream); 71 | } 72 | } 73 | 74 | } --------------------------------------------------------------------------------