LogManager.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. <?php
  2. /**
  3. * 重庆赤晓店信息科技有限公司
  4. * https://www.chixiaodian.com
  5. * Copyright (c) 2023 赤店商城 All rights reserved.
  6. */
  7. /*
  8. * This file is part of the overtrue/wechat.
  9. *
  10. * (c) overtrue <i@overtrue.me>
  11. *
  12. * This source file is subject to the MIT license that is bundled
  13. * with this source code in the file LICENSE.
  14. */
  15. namespace ByteDance\Kernel\Log;
  16. use Monolog\Formatter\LineFormatter;
  17. use Monolog\Handler\ErrorLogHandler;
  18. use Monolog\Handler\HandlerInterface;
  19. use Monolog\Handler\RotatingFileHandler;
  20. use Monolog\Handler\SlackWebhookHandler;
  21. use Monolog\Handler\StreamHandler;
  22. use Monolog\Handler\SyslogHandler;
  23. use Monolog\Logger as Monolog;
  24. use ByteDance\Kernel\ServiceContainer;
  25. use Psr\Log\LoggerInterface;
  26. /**
  27. * Class LogManager.
  28. *
  29. * @author overtrue <i@overtrue.me>
  30. */
  31. class LogManager implements LoggerInterface
  32. {
  33. /**
  34. * @var \ByteDance\Kernel\ServiceContainer
  35. */
  36. protected $app;
  37. /**
  38. * The array of resolved channels.
  39. *
  40. * @var array
  41. */
  42. protected $channels = [];
  43. /**
  44. * The registered custom driver creators.
  45. *
  46. * @var array
  47. */
  48. protected $customCreators = [];
  49. /**
  50. * The Log levels.
  51. *
  52. * @var array
  53. */
  54. protected $levels = [
  55. 'debug' => Monolog::DEBUG,
  56. 'info' => Monolog::INFO,
  57. 'notice' => Monolog::NOTICE,
  58. 'warning' => Monolog::WARNING,
  59. 'error' => Monolog::ERROR,
  60. 'critical' => Monolog::CRITICAL,
  61. 'alert' => Monolog::ALERT,
  62. 'emergency' => Monolog::EMERGENCY,
  63. ];
  64. /**
  65. * LogManager constructor.
  66. *
  67. * @param \ByteDance\Kernel\ServiceContainer $app
  68. */
  69. public function __construct(ServiceContainer $app)
  70. {
  71. $this->app = $app;
  72. }
  73. /**
  74. * Create a new, on-demand aggregate logger instance.
  75. *
  76. * @param array $channels
  77. * @param string|null $channel
  78. *
  79. * @return \Psr\Log\LoggerInterface
  80. */
  81. public function stack(array $channels, $channel = null)
  82. {
  83. return $this->createStackDriver(compact('channels', 'channel'));
  84. }
  85. /**
  86. * Get a log channel instance.
  87. *
  88. * @param string|null $channel
  89. *
  90. * @return mixed
  91. */
  92. public function channel($channel = null)
  93. {
  94. return $this->get($channel);
  95. }
  96. /**
  97. * Get a log driver instance.
  98. *
  99. * @param string|null $driver
  100. *
  101. * @return mixed
  102. */
  103. public function driver($driver = null)
  104. {
  105. return $this->get($driver ?? $this->getDefaultDriver());
  106. }
  107. /**
  108. * Attempt to get the log from the local cache.
  109. *
  110. * @param string $name
  111. *
  112. * @return \Psr\Log\LoggerInterface
  113. */
  114. protected function get($name)
  115. {
  116. try {
  117. return $this->channels[$name] ?? ($this->channels[$name] = $this->resolve($name));
  118. } catch (\Throwable $e) {
  119. $logger = $this->createEmergencyLogger();
  120. $logger->emergency('Unable to create configured logger. Using emergency logger.', [
  121. 'exception' => $e,
  122. ]);
  123. return $logger;
  124. }
  125. }
  126. /**
  127. * Resolve the given log instance by name.
  128. *
  129. * @param string $name
  130. *
  131. * @throws \InvalidArgumentException
  132. *
  133. * @return \Psr\Log\LoggerInterface
  134. */
  135. protected function resolve($name)
  136. {
  137. $config = $this->app['config']->get(\sprintf('log.channels.%s', $name));
  138. if (is_null($config)) {
  139. throw new \InvalidArgumentException(\sprintf('Log [%s] is not defined.', $name));
  140. }
  141. if (isset($this->customCreators[$config['driver']])) {
  142. return $this->callCustomCreator($config);
  143. }
  144. $driverMethod = 'create'.ucfirst($config['driver']).'Driver';
  145. if (method_exists($this, $driverMethod)) {
  146. return $this->{$driverMethod}($config);
  147. }
  148. throw new \InvalidArgumentException(\sprintf('Driver [%s] is not supported.', $config['driver']));
  149. }
  150. /**
  151. * Create an emergency log handler to avoid white screens of death.
  152. *
  153. * @return \Monolog\Logger
  154. */
  155. protected function createEmergencyLogger()
  156. {
  157. return new Monolog('EasyWeChat', $this->prepareHandlers([new StreamHandler(
  158. \sys_get_temp_dir().'/easywechat/easywechat.log', $this->level(['level' => 'debug'])
  159. )]));
  160. }
  161. /**
  162. * Call a custom driver creator.
  163. *
  164. * @param array $config
  165. *
  166. * @return mixed
  167. */
  168. protected function callCustomCreator(array $config)
  169. {
  170. return $this->customCreators[$config['driver']]($this->app, $config);
  171. }
  172. /**
  173. * Create an aggregate log driver instance.
  174. *
  175. * @param array $config
  176. *
  177. * @return \Monolog\Logger
  178. */
  179. protected function createStackDriver(array $config)
  180. {
  181. $handlers = [];
  182. foreach ($config['channels'] ?? [] as $channel) {
  183. $handlers = \array_merge($handlers, $this->channel($channel)->getHandlers());
  184. }
  185. return new Monolog($this->parseChannel($config), $handlers);
  186. }
  187. /**
  188. * Create an instance of the single file log driver.
  189. *
  190. * @param array $config
  191. *
  192. * @return \Psr\Log\LoggerInterface
  193. */
  194. protected function createSingleDriver(array $config)
  195. {
  196. return new Monolog($this->parseChannel($config), [
  197. $this->prepareHandler(
  198. new StreamHandler($config['path'], $this->level($config))
  199. ),
  200. ]);
  201. }
  202. /**
  203. * Create an instance of the daily file log driver.
  204. *
  205. * @param array $config
  206. *
  207. * @return \Psr\Log\LoggerInterface
  208. */
  209. protected function createDailyDriver(array $config)
  210. {
  211. return new Monolog($this->parseChannel($config), [
  212. $this->prepareHandler(new RotatingFileHandler(
  213. $config['path'], $config['days'] ?? 7, $this->level($config)
  214. )),
  215. ]);
  216. }
  217. /**
  218. * Create an instance of the Slack log driver.
  219. *
  220. * @param array $config
  221. *
  222. * @return \Psr\Log\LoggerInterface
  223. */
  224. protected function createSlackDriver(array $config)
  225. {
  226. return new Monolog($this->parseChannel($config), [
  227. $this->prepareHandler(new SlackWebhookHandler(
  228. $config['url'],
  229. $config['channel'] ?? null,
  230. $config['username'] ?? 'EasyWeChat',
  231. $config['attachment'] ?? true,
  232. $config['emoji'] ?? ':boom:',
  233. $config['short'] ?? false,
  234. $config['context'] ?? true,
  235. $this->level($config)
  236. )),
  237. ]);
  238. }
  239. /**
  240. * Create an instance of the syslog log driver.
  241. *
  242. * @param array $config
  243. *
  244. * @return \Psr\Log\LoggerInterface
  245. */
  246. protected function createSyslogDriver(array $config)
  247. {
  248. return new Monolog($this->parseChannel($config), [
  249. $this->prepareHandler(new SyslogHandler(
  250. 'EasyWeChat', $config['facility'] ?? LOG_USER, $this->level($config))
  251. ),
  252. ]);
  253. }
  254. /**
  255. * Create an instance of the "error log" log driver.
  256. *
  257. * @param array $config
  258. *
  259. * @return \Psr\Log\LoggerInterface
  260. */
  261. protected function createErrorlogDriver(array $config)
  262. {
  263. return new Monolog($this->parseChannel($config), [
  264. $this->prepareHandler(new ErrorLogHandler(
  265. $config['type'] ?? ErrorLogHandler::OPERATING_SYSTEM, $this->level($config))
  266. ),
  267. ]);
  268. }
  269. /**
  270. * Prepare the handlers for usage by Monolog.
  271. *
  272. * @param array $handlers
  273. *
  274. * @return array
  275. */
  276. protected function prepareHandlers(array $handlers)
  277. {
  278. foreach ($handlers as $key => $handler) {
  279. $handlers[$key] = $this->prepareHandler($handler);
  280. }
  281. return $handlers;
  282. }
  283. /**
  284. * Prepare the handler for usage by Monolog.
  285. *
  286. * @param \Monolog\Handler\HandlerInterface $handler
  287. *
  288. * @return \Monolog\Handler\HandlerInterface
  289. */
  290. protected function prepareHandler(HandlerInterface $handler)
  291. {
  292. return $handler->setFormatter($this->formatter());
  293. }
  294. /**
  295. * Get a Monolog formatter instance.
  296. *
  297. * @return \Monolog\Formatter\FormatterInterface
  298. */
  299. protected function formatter()
  300. {
  301. $formatter = new LineFormatter(null, null, true, true);
  302. $formatter->includeStacktraces();
  303. return $formatter;
  304. }
  305. /**
  306. * Extract the log channel from the given configuration.
  307. *
  308. * @param array $config
  309. *
  310. * @return string
  311. */
  312. protected function parseChannel(array $config)
  313. {
  314. return $config['name'] ?? null;
  315. }
  316. /**
  317. * Parse the string level into a Monolog constant.
  318. *
  319. * @param array $config
  320. *
  321. * @throws \InvalidArgumentException
  322. *
  323. * @return int
  324. */
  325. protected function level(array $config)
  326. {
  327. $level = $config['level'] ?? 'debug';
  328. if (isset($this->levels[$level])) {
  329. return $this->levels[$level];
  330. }
  331. throw new \InvalidArgumentException('Invalid log level.');
  332. }
  333. /**
  334. * Get the default log driver name.
  335. *
  336. * @return string
  337. */
  338. public function getDefaultDriver()
  339. {
  340. return $this->app['config']['log.default'];
  341. }
  342. /**
  343. * Set the default log driver name.
  344. *
  345. * @param string $name
  346. */
  347. public function setDefaultDriver($name)
  348. {
  349. $this->app['config']['log.default'] = $name;
  350. }
  351. /**
  352. * Register a custom driver creator Closure.
  353. *
  354. * @param string $driver
  355. * @param \Closure $callback
  356. *
  357. * @return $this
  358. */
  359. public function extend($driver, \Closure $callback)
  360. {
  361. $this->customCreators[$driver] = $callback->bindTo($this, $this);
  362. return $this;
  363. }
  364. /**
  365. * System is unusable.
  366. *
  367. * @param string $message
  368. * @param array $context
  369. *
  370. * @return mixed
  371. */
  372. public function emergency($message, array $context = [])
  373. {
  374. return $this->driver()->emergency($message, $context);
  375. }
  376. /**
  377. * Action must be taken immediately.
  378. *
  379. * Example: Entire website down, database unavailable, etc. This should
  380. * trigger the SMS alerts and wake you up.
  381. *
  382. * @param string $message
  383. * @param array $context
  384. *
  385. * @return mixed
  386. */
  387. public function alert($message, array $context = [])
  388. {
  389. return $this->driver()->alert($message, $context);
  390. }
  391. /**
  392. * Critical conditions.
  393. *
  394. * Example: Application component unavailable, unexpected exception.
  395. *
  396. * @param string $message
  397. * @param array $context
  398. *
  399. * @return mixed
  400. */
  401. public function critical($message, array $context = [])
  402. {
  403. return $this->driver()->critical($message, $context);
  404. }
  405. /**
  406. * Runtime errors that do not require immediate action but should typically
  407. * be logged and monitored.
  408. *
  409. * @param string $message
  410. * @param array $context
  411. *
  412. * @return mixed
  413. */
  414. public function error($message, array $context = [])
  415. {
  416. return $this->driver()->error($message, $context);
  417. }
  418. /**
  419. * Exceptional occurrences that are not errors.
  420. *
  421. * Example: Use of deprecated APIs, poor use of an API, undesirable things
  422. * that are not necessarily wrong.
  423. *
  424. * @param string $message
  425. * @param array $context
  426. *
  427. * @return mixed
  428. */
  429. public function warning($message, array $context = [])
  430. {
  431. return $this->driver()->warning($message, $context);
  432. }
  433. /**
  434. * Normal but significant events.
  435. *
  436. * @param string $message
  437. * @param array $context
  438. *
  439. * @return mixed
  440. */
  441. public function notice($message, array $context = [])
  442. {
  443. return $this->driver()->notice($message, $context);
  444. }
  445. /**
  446. * Interesting events.
  447. *
  448. * Example: User logs in, SQL logs.
  449. *
  450. * @param string $message
  451. * @param array $context
  452. *
  453. * @return mixed
  454. */
  455. public function info($message, array $context = [])
  456. {
  457. return $this->driver()->info($message, $context);
  458. }
  459. /**
  460. * Detailed debug information.
  461. *
  462. * @param string $message
  463. * @param array $context
  464. *
  465. * @return mixed
  466. */
  467. public function debug($message, array $context = [])
  468. {
  469. return $this->driver()->debug($message, $context);
  470. }
  471. /**
  472. * Logs with an arbitrary level.
  473. *
  474. * @param mixed $level
  475. * @param string $message
  476. * @param array $context
  477. *
  478. * @return mixed
  479. */
  480. public function log($level, $message, array $context = [])
  481. {
  482. return $this->driver()->log($level, $message, $context);
  483. }
  484. /**
  485. * Dynamically call the default driver instance.
  486. *
  487. * @param string $method
  488. * @param array $parameters
  489. *
  490. * @return mixed
  491. */
  492. public function __call($method, $parameters)
  493. {
  494. return $this->driver()->$method(...$parameters);
  495. }
  496. }