本文实现了一种自动记录请求响应日志的一种方式,
在没有链路追踪系统的情况下也可以快速定位问题。
这里使用了 mongodb存储日志 laravel的mongodb包使用了jenssegers/mongodb
对于错误日志,加入请求标识符,可通过日志收集工具例如Elastic stack在kibana中检索标识符查看相关信息

创建request event

  1. namespace App\Light\RequestID;
  2. use Illuminate\Contracts\Support\Arrayable;
  3. class RequestEndEvent implements \JsonSerializable, Arrayable
  4. {
  5. public $request_id; //请求标识
  6. public $elapse_time; //消耗时间
  7. public $url; //url
  8. public $params; //请求参数
  9. public $method; //请求方法
  10. public $header; //请求
  11. public $uid; //用户id
  12. public $created_at; //请求时间
  13. public $response_content;//响应内容
  14. public $response_header; //响应头
  15. public $tag; //标签
  16. public $memory; //消耗内存
  17. /**
  18. * 实现arrayable
  19. * @return array
  20. */
  21. public function toArray()
  22. {
  23. try {
  24. $res = json_decode($this->response_content, true);
  25. } catch (\Exception $exception) {
  26. $res = $this->response_content;
  27. }
  28. return [
  29. 'request_id' => $this->request_id,
  30. 'elapse_time' => $this->elapse_time,
  31. 'url' => $this->url,
  32. 'params' => $this->params,
  33. 'method' => $this->method,
  34. 'header' => $this->header,
  35. 'uid' => $this->uid,
  36. 'created_at' => $this->created_at,
  37. 'response_content' => $res,
  38. 'response_header' => $this->response_header,
  39. 'tag' => $this->tag,
  40. 'memory' => $this->memory,
  41. ];
  42. }
  43. /**
  44. * 实现JsonSerializable
  45. * @return array
  46. */
  47. public function jsonSerialize()
  48. {
  49. return $this->toArray();
  50. }
  51. /**
  52. * 支持打印
  53. * @return false|string
  54. */
  55. public function __toString()
  56. {
  57. return json_encode($this, JSON_UNESCAPED_UNICODE);
  58. }
  59. }

创建事件监听器

  1. <?php
  2. namespace App\Light\RequestID;
  3. use Carbon\Carbon;
  4. use Illuminate\Support\Facades\DB;
  5. class RequestEndEventListener
  6. {
  7. public function handle(RequestEndEvent $event)
  8. {
  9. //持久化
  10. DB::connection('mongodb')
  11. ->collection('request_' . Carbon::now()->format('Ym'))
  12. ->insert($event->toArray());
  13. }
  14. }

创建RequestID处理类

  1. <?php
  2. namespace App\Light\RequestID;
  3. use Carbon\Carbon;
  4. use Illuminate\Support\Facades\Auth;
  5. use Ramsey\Uuid\Uuid;
  6. class RequestID
  7. {
  8. //请求ID
  9. private $ID;
  10. //请求开始时间,不严格
  11. private $startTime;
  12. /**获取请求ID
  13. * @return mixed
  14. */
  15. public function get()
  16. {
  17. return $this->ID;
  18. }
  19. /**
  20. * 生成请求ID
  21. * @throws \Exception
  22. */
  23. public function begin()
  24. {
  25. if (empty($this->ID)) {
  26. $this->ID = Uuid::uuid4()->toString();
  27. $this->startTime = microtime(true);
  28. }
  29. }
  30. /**
  31. * 结束请求
  32. * @param \Illuminate\Http\Request $request
  33. * @param $response
  34. */
  35. public function end(\Illuminate\Http\Request $request, $response)
  36. {
  37. $event = new RequestEndEvent();
  38. $event->request_id = ResID::get();
  39. $event->elapse_time = round(microtime(true) - $this->startTime, 4);
  40. $event->url = $request->fullUrl();
  41. $event->params = $request->all();
  42. $event->method = $request->method();
  43. $event->header = $request->header();
  44. $uid = Auth::id();
  45. $event->uid = $uid ? $uid : 0;
  46. $event->created_at = Carbon::now()->toDateTimeString();
  47. $event->response_content = $response->getContent();
  48. $event->response_header = $response->headers->all();
  49. // $event->tag = '';
  50. $event->tag = $request->route()->getTag();//这里可以使用获取路由自定义标签
  51. //触发事件
  52. event($event);
  53. }
  54. }

创建门面

  1. <?php
  2. namespace App\Light\RequestID;
  3. use Illuminate\Support\Facades\Facade;
  4. /**
  5. * 支持IDE语法提示
  6. * @method static \App\Light\RequestID\RequestID get()
  7. * @method static \App\Light\RequestID\RequestID begin()
  8. * @method static \App\Light\RequestID\RequestID end($request, $response)
  9. */
  10. class ResID extends Facade
  11. {
  12. /**
  13. * Get the registered name of the component.
  14. *
  15. * @return string
  16. */
  17. protected static function getFacadeAccessor()
  18. {
  19. return 'RequestID';
  20. }
  21. }

创建Terminate中间件

  1. <?php
  2. namespace App\Light\RequestID;
  3. use Closure;
  4. class RequestIDMiddlerware
  5. {
  6. public function handle($request, Closure $next)
  7. {
  8. //开始生成请求标识
  9. ResID::begin();
  10. return $next($request);
  11. }
  12. public function terminate(\Illuminate\Http\Request $request, $response)
  13. {
  14. //保存请求数据
  15. ResID::end($request, $response);
  16. }
  17. }

创建服务提供者

  1. <?php
  2. namespace App\Light\RequestID;
  3. use Illuminate\Foundation\Support\Providers\EventServiceProvider;
  4. class RequestIDServiceProvider extends EventServiceProvider
  5. {
  6. protected $listen = [
  7. RequestEndEvent::class => [
  8. RequestEndEventListener::class,//注册事件处理器
  9. ],
  10. ];
  11. public function register()
  12. {
  13. //绑定单例
  14. $this->app->singleton('RequestID', function () {
  15. return new RequestID();
  16. });
  17. //注册全局中间件
  18. $kernel = $this->app[\Illuminate\Contracts\Http\Kernel::class];
  19. // 放在所有中间件的最前面,确保其他中间件都能获取到
  20. $kernel->prependMiddleware(RequestIDMiddlerware::class);
  21. }
  22. }

注册服务提供者

config/app.php加入

  1. //request服务提供者
  2. App\Light\RequestID\RequestIDServiceProvider::class,

日志中加入请求标识

app/Exceptions/Handle.php加入

  1. /**
  2. * 增加日志记录全局请求标识
  3. *
  4. * @return array
  5. */
  6. protected function context()
  7. {
  8. return array_merge(parent::context(), [
  9. 'request_id' => ResID::get(), //让错误日志也记录请求标识
  10. ]);
  11. }

查看结果

访问任意URL,可看到Mongodb中自动记录了该请求相关信息
image.png

查看报错日志

image.png