├── .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 | 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 | --------------------------------------------------------------------------------