├── .editorconfig ├── composer.json ├── src ├── Helpers.php ├── ClassResolver.php ├── Cuttle.php └── Config.php └── README.md /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = false 10 | 11 | [*.{vue,js,scss}] 12 | charset = utf-8 13 | indent_style = space 14 | indent_size = 2 15 | end_of_line = lf 16 | insert_final_newline = true 17 | trim_trailing_whitespace = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overtrue/cuttle", 3 | "description": "A multi-module log wrapper.", 4 | "type": "library", 5 | "keywords": ["log", "multi logger", "monolog"], 6 | "require": { 7 | "php": ">=7.0", 8 | "monolog/monolog": "~1.22" 9 | }, 10 | "require-dev": { 11 | "phpunit/phpunit": "~6.2", 12 | "mockery/mockery": "1.0.x-dev" 13 | }, 14 | "autoload": { 15 | "psr-4": { 16 | "Overtrue\\Cuttle\\": "src" 17 | }, 18 | "files": ["src/Helpers.php"] 19 | }, 20 | "autoload-dev": { 21 | "psr-4": { 22 | "Overtrue\\Cuttle\\Tests\\": "tests" 23 | } 24 | }, 25 | "license": "MIT", 26 | "authors": [ 27 | { 28 | "name": "overtrue", 29 | "email": "i@overtrue.me" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/Helpers.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Overtrue\Cuttle; 13 | 14 | /** 15 | * To camel case. 16 | * 17 | * @param string $str 18 | * 19 | * @return string 20 | */ 21 | function camel_case(string $str) 22 | { 23 | return lcfirst(studly_case($str)); 24 | } 25 | 26 | /** 27 | * To snake case. 28 | * 29 | * @param string $str 30 | * 31 | * @return string 32 | */ 33 | function snake_case(string $str) 34 | { 35 | if (ctype_lower($str)) { 36 | return $str; 37 | } 38 | 39 | return mb_strtolower(preg_replace('/(.)(?=[A-Z])/u', '$1_', preg_replace('/\s+/u', '', $str)), 'utf-8'); 40 | } 41 | 42 | /** 43 | * To studly case. 44 | * 45 | * @param string $str 46 | * 47 | * @return string 48 | */ 49 | function studly_case(string $str) 50 | { 51 | return ucwords(str_replace(['-', '_'], ' ', trim($str))); 52 | } 53 | -------------------------------------------------------------------------------- /src/ClassResolver.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Overtrue\Cuttle; 13 | 14 | use ReflectionClass; 15 | 16 | /** 17 | * Class ClassResolver. 18 | * 19 | * @author overtrue 20 | */ 21 | class ClassResolver 22 | { 23 | /** 24 | * @var string 25 | */ 26 | protected $class; 27 | 28 | /** 29 | * ClassResolver constructor. 30 | * 31 | * @param string $class 32 | */ 33 | public function __construct(string $class) 34 | { 35 | $this->class = $class; 36 | } 37 | 38 | /** 39 | * @param array $args 40 | * 41 | * @return object 42 | */ 43 | public function resolve(array $args = []) 44 | { 45 | $reflected = new ReflectionClass($this->class); 46 | 47 | if (0 == $reflected->getConstructor()->getNumberOfParameters()) { 48 | return $reflected->newInstanceWithoutConstructor(); 49 | } 50 | 51 | $args = $this->resolveConstructorArgs($reflected, $args); 52 | 53 | return $reflected->newInstanceArgs($args); 54 | } 55 | 56 | /** 57 | * @param \ReflectionClass $reflected 58 | * @param array $inputArgs 59 | * 60 | * @return array 61 | */ 62 | protected function resolveConstructorArgs(ReflectionClass $reflected, array $inputArgs) 63 | { 64 | $args = []; 65 | 66 | foreach ($reflected->getConstructor()->getParameters() as $parameter) { 67 | $name = $parameter->getName(); 68 | $value = $this->tryToGetArgsFromInput($inputArgs, $name); 69 | 70 | if (is_null($value)) { 71 | if (!$parameter->isOptional()) { 72 | throw new InvalidArgumentException(sprintf( 73 | 'No value configured for parameter "%s" of %s::__constructor method.', 74 | $name, 75 | $reflected->getName() 76 | )); 77 | } 78 | $value = $parameter->getDefaultValue(); 79 | } 80 | 81 | $args[$name] = $value; 82 | } 83 | 84 | return $args; 85 | } 86 | 87 | /** 88 | * @param array $input 89 | * @param string $name 90 | * 91 | * @return mixed 92 | */ 93 | public function tryToGetArgsFromInput(array $input, string $name) 94 | { 95 | return $input[camel_case($name)] ?? $input[snake_case($name)] ?? null; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Cuttle

2 | 3 |

:page_with_curl: A multi-module log wrapper.

4 | 5 |

6 | Build Status 7 | Latest Stable Version 8 | Latest Unstable Version 9 | Scrutinizer Code Quality 10 | Code Coverage 11 | Total Downloads 12 | License 13 |

14 | 15 | 16 | ## Requirements 17 | 18 | - PHP >= 7.0 19 | 20 | ## Installing 21 | 22 | ```shell 23 | $ composer require "overtrue/cuttle" 24 | ``` 25 | 26 | ## Usage 27 | 28 | ```php 29 | use Overtrue\Cuttle\Cuttle; 30 | 31 | $config = [ 32 | 'default' => 'foo', // default channel 33 | 34 | 'formatters' => [ 35 | 'dashed' => [ 36 | 'formatter' => \Monolog\Formatter\LineFormatter::class, // default 37 | 'format' => "%datetime% - %channel%.%level_name% - %message%\n" 38 | ], 39 | ], 40 | 'handlers' => [ 41 | 'file' => [ 42 | 'handler' => \Monolog\Handler\StreamHandler::class, // default 43 | 'formatter' => 'dashed', 44 | 'stream' => '/tmp/demo.log', 45 | 'level' => 'info', 46 | ], 47 | 'console' => [ 48 | 'formatter' => 'dashed', 49 | 'stream' => 'php://stdout', 50 | 'level' => 'debug', 51 | ], 52 | ], 53 | 'channels' => [ 54 | 'foo' => [ 55 | 'handlers' => ['console', 'file'], 56 | ], 57 | 'bar' => [ 58 | 'handlers' => ['file'], 59 | ], 60 | ], 61 | ]; 62 | 63 | $cuttle = new Cuttle($config); 64 | 65 | $cuttle->info('hello'); // channel: foo 66 | $cuttle->channel('bar')->debug('debug message.'); 67 | 68 | // aslias of channel($name) 69 | // ->of('bar') 70 | // ->from('bar') 71 | ``` 72 | 73 | ## PHP 扩展包开发 74 | 75 | > 想知道如何从零开始构建 PHP 扩展包? 76 | > 77 | > 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package) 78 | 79 | ## License 80 | 81 | MIT 82 | -------------------------------------------------------------------------------- /src/Cuttle.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Overtrue\Cuttle; 13 | 14 | use Monolog\Logger; 15 | use Monolog\Registry; 16 | 17 | /** 18 | * Class Cuttle. 19 | * 20 | * @author overtrue 21 | */ 22 | class Cuttle 23 | { 24 | /** 25 | * @var \Overtrue\Cuttle\Config 26 | */ 27 | protected $config; 28 | 29 | /** 30 | * Cuttle constructor. 31 | * 32 | * @param array $config 33 | */ 34 | public function __construct(array $config) 35 | { 36 | $this->config = new Config($config); 37 | } 38 | 39 | /** 40 | * @param string $name 41 | * 42 | * @return \Monolog\Logger 43 | */ 44 | public function channel(string $name) 45 | { 46 | return $this->getLogger($name); 47 | } 48 | 49 | /** 50 | * @param string $name 51 | * 52 | * @return \Monolog\Logger 53 | */ 54 | public function of(string $name) 55 | { 56 | return $this->channel($name); 57 | } 58 | 59 | /** 60 | * @param string $name 61 | * 62 | * @return \Monolog\Logger 63 | */ 64 | public function from(string $name) 65 | { 66 | return $this->channel($name); 67 | } 68 | 69 | /** 70 | * @return \Overtrue\Cuttle\Config 71 | */ 72 | public function getConfig() 73 | { 74 | return $this->config; 75 | } 76 | 77 | /** 78 | * @param \Overtrue\Cuttle\Config $config 79 | * 80 | * @return \Overtrue\Cuttle\Cuttle 81 | */ 82 | public function setConfig(Config $config) 83 | { 84 | $this->config = $config; 85 | 86 | return $this; 87 | } 88 | 89 | /** 90 | * @param $name 91 | * 92 | * @return \Monolog\Logger 93 | */ 94 | public function getLogger(string $name) 95 | { 96 | if (!Registry::hasLogger($name)) { 97 | $config = $this->config->getChannel($name); 98 | Registry::addLogger($this->makeLogger($name, $config['handlers'], $config['processors'])); 99 | } 100 | 101 | return Registry::getInstance($name); 102 | } 103 | 104 | /** 105 | * @return \Monolog\Logger 106 | */ 107 | public function getDefaultLogger() 108 | { 109 | return $this->getLogger($this->config->getDefaultChannel()); 110 | } 111 | 112 | /** 113 | * @param string $name 114 | * @param array $handlers 115 | * @param array $processors 116 | * 117 | * @return \Monolog\Logger 118 | */ 119 | public function makeLogger(string $name, array $handlers = [], array $processors = []) 120 | { 121 | return new Logger($name, $handlers, $processors); 122 | } 123 | 124 | /** 125 | * Magic call. 126 | * 127 | * @param string $method 128 | * @param array $args 129 | * 130 | * @return mixed 131 | */ 132 | public function __call($method, $args) 133 | { 134 | $logger = $this->getDefaultLogger(); 135 | 136 | return call_user_func_array([$logger, $method], $args); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Overtrue\Cuttle; 13 | 14 | use Closure; 15 | use InvalidArgumentException; 16 | use Monolog\Formatter\LineFormatter; 17 | use Monolog\Handler\StreamHandler; 18 | 19 | /** 20 | * Class Config. 21 | * 22 | * @author overtrue 23 | */ 24 | class Config 25 | { 26 | /** 27 | * @var array 28 | */ 29 | protected $formatters = []; 30 | 31 | /** 32 | * @var array 33 | */ 34 | protected $handlers = []; 35 | 36 | /** 37 | * @var array 38 | */ 39 | protected $processors = []; 40 | 41 | /** 42 | * @var array 43 | */ 44 | protected $channels = []; 45 | 46 | /** 47 | * @var string 48 | */ 49 | protected $defaultChannel = ''; 50 | 51 | /** 52 | * Config constructor. 53 | * 54 | * @param array $config 55 | */ 56 | public function __construct(array $config) 57 | { 58 | $this->parse($config); 59 | } 60 | 61 | /** 62 | * @param string $name 63 | * 64 | * @return array 65 | */ 66 | public function getChannel(string $name) 67 | { 68 | if (empty($this->channels[$name])) { 69 | throw new InvalidArgumentException("No channel named '{$name}' found."); 70 | } 71 | 72 | $this->channels[$name]['handlers'] = $this->getHandlers($this->channels[$name]['handlers']); 73 | $this->channels[$name]['processors'] = $this->getProcessors($this->channels[$name]['processors']); 74 | 75 | return $this->channels[$name]; 76 | } 77 | 78 | /** 79 | * @return string 80 | */ 81 | public function getDefaultChannel() 82 | { 83 | if (!$this->defaultChannel) { 84 | throw new \LogicException('No default channel configured.'); 85 | } 86 | 87 | return $this->defaultChannel; 88 | } 89 | 90 | /** 91 | * @param array $names 92 | * 93 | * @return array 94 | */ 95 | protected function getFormatters(array $names) 96 | { 97 | return array_map(function ($name) { 98 | return $this->getFormatter($name); 99 | }, $names); 100 | } 101 | 102 | /** 103 | * @param array $names 104 | * 105 | * @return array 106 | */ 107 | protected function getHandlers(array $names) 108 | { 109 | return array_map(function ($name) { 110 | return $this->getHandler($name); 111 | }, $names); 112 | } 113 | 114 | /** 115 | * @param array $names 116 | * 117 | * @return array 118 | */ 119 | protected function getProcessors(array $names) 120 | { 121 | return array_map(function ($name) { 122 | return $this->getProcessor($name); 123 | }, $names); 124 | } 125 | 126 | /** 127 | * @param string $formatterId 128 | * 129 | * @return \Monolog\Formatter\FormatterInterface 130 | */ 131 | protected function getFormatter(string $formatterId) 132 | { 133 | if ($this->formatters[$formatterId] instanceof Closure) { 134 | $this->formatters[$formatterId] = $this->formatters[$formatterId](); 135 | } 136 | 137 | return $this->formatters[$formatterId]; 138 | } 139 | 140 | /** 141 | * @param string $handlerId 142 | * 143 | * @return \Monolog\Handler\HandlerInterface 144 | */ 145 | protected function getHandler(string $handlerId) 146 | { 147 | if ($this->handlers[$handlerId] instanceof Closure) { 148 | $this->handlers[$handlerId] = $this->handlers[$handlerId](); 149 | } 150 | 151 | return $this->handlers[$handlerId]; 152 | } 153 | 154 | /** 155 | * @param string $processorId 156 | * 157 | * @return \Monolog\Handler\HandlerInterface 158 | */ 159 | protected function getProcessor(string $processorId) 160 | { 161 | if ($this->processors[$processorId] instanceof Closure) { 162 | $this->processors[$processorId] = $this->processors[$processorId](); 163 | } 164 | 165 | return $this->processors[$processorId]; 166 | } 167 | 168 | /** 169 | * @param array $config 170 | */ 171 | protected function parse(array $config) 172 | { 173 | $this->formatters = $this->formatFormatters($config['formatters'] ?? []); 174 | $this->handlers = $this->formatHandlers($config['handlers'] ?? []); 175 | $this->processors = $this->formatProcessors($config['processors'] ?? []); 176 | $this->channels = $this->formatChannels($config['channels'] ?? []); 177 | $this->defaultChannel = $config['default']; 178 | } 179 | 180 | /** 181 | * @param array $formatters 182 | * 183 | * @return array 184 | */ 185 | protected function formatFormatters(array $formatters = []) 186 | { 187 | foreach ($formatters as $id => $option) { 188 | $class = $option['formatter'] ?? LineFormatter::class; 189 | unset($option['formatter']); 190 | 191 | $formatters[$id] = function () use ($class, $option) { 192 | return (new ClassResolver($class))->resolve($option); 193 | }; 194 | } 195 | 196 | return $formatters; 197 | } 198 | 199 | /** 200 | * @param array $handlers 201 | * 202 | * @return array 203 | */ 204 | protected function formatHandlers(array $handlers = []) 205 | { 206 | foreach ($handlers as $id => $option) { 207 | if (isset($option['formatter']) && !isset($this->formatters[$option['formatter']])) { 208 | throw new InvalidArgumentException(sprintf('Formatter %s not configured.', $option['formatter'])); 209 | } 210 | 211 | foreach ($option['processors'] ?? [] as $processorId) { 212 | if (!isset($this->processors[$processorId])) { 213 | throw new InvalidArgumentException(sprintf('Processor %s not configured.', $processorId)); 214 | } 215 | } 216 | 217 | $class = $option['handler'] ?? StreamHandler::class; 218 | unset($option['handler']); 219 | 220 | $handlers[$id] = function () use ($class, $option) { 221 | $handler = (new ClassResolver($class))->resolve($option); 222 | 223 | if (!empty($option['formatter'])) { 224 | $handler->setFormatter($this->getFormatter($option['formatter'])); 225 | } 226 | 227 | if (!empty($option['processors'])) { 228 | $handler->pushProcessor($this->getProcessors($option['processors'])); 229 | } 230 | 231 | return $handler; 232 | }; 233 | } 234 | 235 | return $handlers; 236 | } 237 | 238 | /** 239 | * @param array $processors 240 | * 241 | * @return array 242 | */ 243 | protected function formatProcessors(array $processors = []) 244 | { 245 | foreach ($processors as $id => $option) { 246 | if (empty($option['processor'])) { 247 | continue; 248 | } 249 | 250 | $class = $option['processor']; 251 | unset($option['processor']); 252 | 253 | $processors[$id] = function () use ($class, $option) { 254 | return (new ClassResolver($class))->resolve($option); 255 | }; 256 | } 257 | 258 | return $processors; 259 | } 260 | 261 | /** 262 | * @param array $channels 263 | * 264 | * @return array 265 | */ 266 | protected function formatChannels(array $channels = []) 267 | { 268 | foreach ($channels as $id => $option) { 269 | foreach ($option['processors'] ?? [] as $processorId) { 270 | if (!isset($this->processors[$processorId])) { 271 | throw new InvalidArgumentException(sprintf('Processor %s not configured.', $processorId)); 272 | } 273 | } 274 | 275 | foreach ($option['handlers'] ?? [] as $handlerId) { 276 | if (!isset($this->handlers[$handlerId])) { 277 | throw new InvalidArgumentException(sprintf('Handler %s not configured.', $handlerId)); 278 | } 279 | } 280 | $channels[$id] = array_merge(['handlers' => [], 'processors' => []], $option); 281 | } 282 | 283 | return $channels; 284 | } 285 | } 286 | --------------------------------------------------------------------------------