LogService.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. <?php
  2. namespace services\common;
  3. use common\helpers\DebrisHelper;
  4. use common\helpers\StringHelper;
  5. use Yii;
  6. use common\helpers\EchantsHelper;
  7. use common\helpers\ArrayHelper;
  8. use common\components\Service;
  9. use common\queues\LogJob;
  10. use common\models\common\Log;
  11. use common\models\api\AccessToken;
  12. use common\enums\AppEnum;
  13. use common\enums\StatusEnum;
  14. use common\enums\SubscriptionActionEnum;
  15. use common\enums\SubscriptionReasonEnum;
  16. use common\enums\MessageLevelEnum;
  17. /**
  18. * Class LogService
  19. * @package services\common
  20. * @author qimall
  21. */
  22. class LogService extends Service
  23. {
  24. /**
  25. * 丢进队列
  26. *
  27. * @var bool
  28. */
  29. public $queueSwitch = false;
  30. /**
  31. * 状态码
  32. *
  33. * @var int
  34. */
  35. private $statusCode;
  36. /**
  37. * 状态内容
  38. *
  39. * @var string
  40. */
  41. private $statusText;
  42. /**
  43. * 报错详细数据
  44. *
  45. * @var array
  46. */
  47. private $errData = [];
  48. /**
  49. * 不记录的状态码
  50. *
  51. * @var array
  52. */
  53. public $exceptCode = [];
  54. /**
  55. * 日志记录
  56. *
  57. *
  58. * @param $response
  59. * @param bool $showReqId
  60. * @return array
  61. * @throws \yii\base\InvalidConfigException
  62. * @throws \Exception
  63. */
  64. public function record($response, $showReqId = false)
  65. {
  66. // 判断是否记录日志
  67. if (in_array($this->getLevel($response->statusCode), Yii::$app->params['user.log.level'])) {
  68. // 检查是否报错
  69. if ($response->statusCode >= 300 && $exception = Yii::$app->getErrorHandler()->exception) {
  70. $this->errData = [
  71. 'type' => get_class($exception),
  72. 'file' => method_exists($exception, 'getFile') ? $exception->getFile() : '',
  73. 'errorMessage' => $exception->getMessage(),
  74. 'line' => $exception->getLine(),
  75. 'stack-trace' => explode("\n", $exception->getTraceAsString()),
  76. ];
  77. $showReqId && $response->data['req_id'] = Yii::$app->params['uuid'];
  78. }
  79. $this->statusCode = $response->statusCode;
  80. $this->statusText = $response->statusText;
  81. // 排除状态码
  82. if (Yii::$app->params['user.log'] && !in_array($this->statusCode,
  83. ArrayHelper::merge($this->exceptCode, Yii::$app->params['user.log.except.code']))) {
  84. $this->push();
  85. }
  86. }
  87. return $this->errData;
  88. }
  89. /**
  90. * 推入日志
  91. *
  92. * 判断进入队列或直接写入数据库
  93. */
  94. public function push()
  95. {
  96. try {
  97. // 判断是否开启队列
  98. if ($this->queueSwitch == true) {
  99. $message_id = Yii::$app->queue->push(new LogJob([
  100. 'data' => $this->getData(),
  101. ]));
  102. } else {
  103. $this->realCreate($this->getData());
  104. }
  105. } catch (\Exception $e) {
  106. }
  107. }
  108. /**
  109. * 真实写入
  110. *
  111. * @param $data
  112. */
  113. public function realCreate($data)
  114. {
  115. $log = new Log();
  116. $log->attributes = $data;
  117. $log->save();
  118. // 记录风控日志
  119. Yii::$app->services->reportLog->create($log);
  120. // 创建订阅消息
  121. $action = $this->getLevel($log['error_code']);
  122. $actions = [
  123. MessageLevelEnum::SUCCESS => SubscriptionActionEnum::LOG_SUCCESS,
  124. MessageLevelEnum::INFO => SubscriptionActionEnum::LOG_INFO,
  125. MessageLevelEnum::WARNING => SubscriptionActionEnum::LOG_WARNING,
  126. MessageLevelEnum::ERROR => SubscriptionActionEnum::LOG_ERROR,
  127. ];
  128. // 加入提醒池
  129. Yii::$app->services->backendNotify->createRemind(
  130. $log->id,
  131. SubscriptionReasonEnum::LOG_CREATE,
  132. $actions[$action],
  133. $log['user_id'],
  134. MessageLevelEnum::getValue($action) . "请求:" . $log->error_msg
  135. );
  136. }
  137. /**
  138. * @param int $code
  139. * @param string $text
  140. * @param $error_data
  141. */
  142. public function setErrorStatus(int $code, string $text, $error_data)
  143. {
  144. $this->statusCode = $code;
  145. $this->statusText = $text;
  146. $this->errData = $error_data;
  147. }
  148. /**
  149. * 状态报表统计
  150. *
  151. * @param $type
  152. * @return array
  153. */
  154. public function stat($type)
  155. {
  156. $fields = [];
  157. $codes = [400, 401, 403, 404, 405, 422, 429, 500];
  158. foreach ($codes as $code) {
  159. $fields[$code] = $code . '错误';
  160. }
  161. // 获取时间和格式化
  162. list($time, $format) = EchantsHelper::getFormatTime($type);
  163. // 获取数据
  164. return EchantsHelper::lineOrBarInTime(function ($start_time, $end_time, $formatting) use ($codes) {
  165. $statData = Log::find()
  166. ->select(["from_unixtime(created_at, '$formatting') as time", 'count(id) as count', 'error_code'])
  167. ->andWhere(['between', 'created_at', $start_time, $end_time])
  168. ->andWhere(['status' => StatusEnum::ENABLED])
  169. ->andWhere(['in', 'error_code', $codes])
  170. ->andFilterWhere(['merchant_id' => Yii::$app->services->merchant->getId()])
  171. ->groupBy(['time', 'error_code'])
  172. ->asArray()
  173. ->all();
  174. return EchantsHelper::regroupTimeData($statData, 'error_code');
  175. }, $fields, $time, $format);
  176. }
  177. /**
  178. * 流量报表统计
  179. *
  180. * @param $type
  181. * @return array
  182. */
  183. public function flowStat($type)
  184. {
  185. $fields = [
  186. 'count' => '访问量(PV)',
  187. 'user_id' => '访问人数(UV)',
  188. 'ip' => '访问IP',
  189. ];
  190. // 获取时间和格式化
  191. list($time, $format) = EchantsHelper::getFormatTime($type);
  192. // 获取数据
  193. return EchantsHelper::lineOrBarInTime(function ($start_time, $end_time, $formatting) {
  194. return Log::find()
  195. ->select(["from_unixtime(created_at, '$formatting') as time", 'count(id) as count', 'count(distinct(ip)) as ip', 'count(distinct(user_id)) as user_id'])
  196. ->andWhere(['between', 'created_at', $start_time, $end_time])
  197. ->andWhere(['status' => StatusEnum::ENABLED])
  198. ->andFilterWhere(['merchant_id' => Yii::$app->services->merchant->getId()])
  199. ->groupBy(['time'])
  200. ->asArray()
  201. ->all();
  202. }, $fields, $time, $format);
  203. }
  204. /**
  205. * @return array
  206. * @throws \yii\base\InvalidConfigException
  207. */
  208. private function getData()
  209. {
  210. $data = $this->initData();
  211. $data['req_id'] = Yii::$app->params['uuid'];
  212. $data['error_code'] = $this->statusCode;
  213. $data['error_data'] = $this->errData;
  214. $data['error_msg'] = isset($this->errData['errorMessage']) ? $this->errData['errorMessage'] : $this->statusText;
  215. return $data;
  216. }
  217. /**
  218. * 初始化默认日志数据
  219. *
  220. * @return array
  221. * @throws \yii\base\InvalidConfigException
  222. */
  223. private function initData()
  224. {
  225. $user_id = Yii::$app->user->id;
  226. if (Yii::$app->id == AppEnum::API && !Yii::$app->user->isGuest) {
  227. /** @var AccessToken $identity */
  228. $identity = Yii::$app->user->identity;
  229. $user_id = $identity->member_id ?? 0;
  230. }
  231. $url = explode('?', Yii::$app->request->getUrl());
  232. $data = [];
  233. $data['user_id'] = $user_id ?? 0;
  234. $data['app_id'] = Yii::$app->id;
  235. $data['merchant_id'] = Yii::$app->services->merchant->getId();
  236. $data['url'] = $url[0];
  237. $data['get_data'] = Yii::$app->request->get();
  238. $data['header_data'] = ArrayHelper::toArray(Yii::$app->request->headers);
  239. // 过滤敏感字段
  240. $post_data = Yii::$app->request->post();
  241. $noPostData = Yii::$app->params['user.log.noPostData'];
  242. foreach ($noPostData as $noPostDatum) {
  243. isset($post_data[$noPostDatum]) && $post_data[$noPostDatum] = '';
  244. }
  245. $data['post_data'] = $post_data;
  246. $data['user_agent'] = Yii::$app->debris->detectVersion();
  247. $data['method'] = Yii::$app->request->method;
  248. $data['module'] = Yii::$app->controller->module->id ?? '';
  249. $data['controller'] = Yii::$app->controller->id ?? '';
  250. $data['action'] = Yii::$app->controller->action->id ?? '';
  251. $data['ip'] = (int)ip2long(Yii::$app->request->userIP);
  252. $data['created_at'] = time();
  253. return $data;
  254. }
  255. /**
  256. * 获取报错级别
  257. *
  258. * @param $statusCode
  259. * @return bool|string
  260. */
  261. private function getLevel($statusCode)
  262. {
  263. if ($statusCode < 300) {
  264. return MessageLevelEnum::SUCCESS;
  265. } elseif ($statusCode >= 300 && $statusCode < 400) {
  266. return MessageLevelEnum::INFO;
  267. } elseif ($statusCode >= 400 && $statusCode < 500) {
  268. return MessageLevelEnum::WARNING;
  269. } elseif ($statusCode >= 500) {
  270. return MessageLevelEnum::ERROR;
  271. }
  272. return MessageLevelEnum::ERROR;
  273. }
  274. }