失业两个月找存在感系列——几行代码轻松实现laravel链式追踪,一分钟快速排查系统问题

YY理解

所谓的链式追踪,并没有多高大尚,简单的可以理解为在一次请求中,生成一个链路请求trace_id,在整个请求过程中记录的日志都包含这个trace_id,对于拆分系统的微服务而言,可以在调用其他服务的时候把这个trace_id传入调用系统,这样整个请求生命周期中一直都有这个标识,可以顺藤摸瓜,问题有迹可循

优点

能快速的通过trace_id 查找完整的请求链路日志,通过命令或者elk方式只需要这个ID就能搜索出所有线索日志,进而快速排查问题

laravel代码实现

思路

laravel通常是运行在php-fpm模式下,请求都是在一个进程中完成的,所以本身就是一条从开始到结束的直线。只需要我们在请求最初时候产生这个trace_id ,然后在后续的代码处理记录日志的时候只要把这个ID加上即可。

laravel实现思路

对于laravel而言,可以使用前置中间件和后置中间件以及重新log记录日志方式便可以实现

laravel代码实现

  • 首先kernel.php 注册中间件

    /**
    * The application's route middleware groups. * * @var array
    */protected $middlewareGroups = [
    'gptapp' => [
    \App\Http\Middleware\RequestBefore::class,
    \App\Http\Middleware\ResponseAfter::class,
    ],
    ];
  • 编写请求之前前置中间件 RequestBefore,在请求到业务逻辑之前进行生成trace_id 保存到request 对象上

    class RequestBefore
    {
      public function handle(Request $request, Closure $next)
      {
          //没有则生成唯一请求ID
          if (! $request->header('Request-Id')) {
              $requestId = (string)md5( uniqid() . time());
              $request->headers->set('Request-Id', $requestId);
          }
          return $next($request);
      }
    }
  • 封装带有trace_id的日志记录方法 Log, laravel8以后有自带上下文的日志记录方法(此步骤可以省略)

    class extends Log {
      static function info ($arr) {
         //加上唯一ID 
         $arr['request_id'] = request()->headers->get('Request-Id', $requestId);
         //调用laravel log门面写日志
          SysLog::info(json_encode($arr));
      }
    }
  • 编写请求响应之后的后置中间件 ResponseAfter,使用带有trace_id 的日志记录方法进行日志保存

    class ResponseAfter
    {
      public function handle(Request $request, Closure $next)
      {
          $response = $next($request);
          // 执行操作
          Log::info([
              'request' => [
                  'client_ip' => $request->getClientIp(),
                  'method' => $request->getMethod(),
                  'route' => $request->getRequestUri(),
                  'content_type' => $request->getContentType(),
                  'content' => $request->all(),
                  'headers' => $request->headers->all(),
              ],
              'response' => [
                  'headers' => $response->headers->all(),
                  'contents' => $response->getContent(),
              ],
          ]);
          return $response;
      }
    }

总结

php是世界上最好的编程语言,其他编程语言能做的,php也能!!!

本作品采用《CC 协议》,转载必须注明作者和本文链接
PHP是世界上最好的编程语言,它能快速的进行技术变现,让代码多一份价值。
本帖由系统于 1年前 自动加精
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 19

有点麻烦,直接withContext或者shareContext

class RequestBefore
{
    public function handle(Request $request, Closure $next)
    {
        $requestId = (string) Str::uuid();
        //withContext,未指定channel的每条日志都会记录request-id
        \Illuminate\Support\Facades\Log::withContext([
            'request-id' => $requestId
        ]);

        //shareContext,每条日志都会记录request-id
        \Illuminate\Support\Facades\Log::shareContext([
            'request-id' => $requestId
        ]);

        return $next($request)->header('Request-Id', $requestId);
    }
}
1年前 评论
PHP之父一只码 (楼主) 1年前
DonnyLiu

赞,不错的方案 :+1:

1年前 评论
PHP之父一只码 (楼主) 1年前

欢迎大佬们给出更好的方案

1年前 评论

如果是fpm的话一个静态变量就可以搞定。

class TraceIdMaker
{
    protected static $inc;

    public static function getInstance(): TraceIdMaker
    {
        if (self::$inc === null) {
            self::$inc = new self();
        }
        return self::$inc;
    }

    public static function make(): string
    {
        return self::getInstance()->getNowCompleteTraceId();
    }


    protected $commonTraceId;

    protected $stop = 0;

    protected function __construct()
    {
        $this->commonTraceId = $this->createTraceId();
    }

    protected function createTraceId(): string
    {
        return md5(uniqid(mt_rand(), true));
    }

    public function getNowCompleteTraceId(): string
    {
        $this->stop += 1;
        return $this->commonTraceId . ':traceNumber:' . str_pad($this->stop, 3, '0', STR_PAD_LEFT);
    }
}

laravel 的 这个 AppServiceProvider 里面加上这个就行。

 /* @var $monolog Logger */
        $monolog = \Log::getMonolog();
        $monolog->pushProcessor(function ($item) {
            if (array_key_exists('message', $item)) {
                $item['message'] = TraceIdMaker::make() . ':' . $item['message'];
            }
            return $item;
        });

其实最早的时候用其他的也是类似。fpm不通请求都是隔离的。这个方案就可以快速实现。

1年前 评论
PHP之父一只码 (楼主) 1年前

有点麻烦,直接withContext或者shareContext

class RequestBefore
{
    public function handle(Request $request, Closure $next)
    {
        $requestId = (string) Str::uuid();
        //withContext,未指定channel的每条日志都会记录request-id
        \Illuminate\Support\Facades\Log::withContext([
            'request-id' => $requestId
        ]);

        //shareContext,每条日志都会记录request-id
        \Illuminate\Support\Facades\Log::shareContext([
            'request-id' => $requestId
        ]);

        return $next($request)->header('Request-Id', $requestId);
    }
}
1年前 评论
PHP之父一只码 (楼主) 1年前

看起来还不错的样子。

11个月前 评论

大佬目前找到工作了吗

11个月前 评论
PHP之父一只码 (楼主) 11个月前

请问这种追踪是只能用于微服务架构对吗?

我这边想处理的链追踪是分布式架构的,就是后端有多个服务器

用户第一个请求在 IP1,第二个请求在 IP2 .....

期望在查询日志的时候,可以把某一个用户的请求全部查询出来,用这个方式是不是不适用?

11个月前 评论
罐装仙人掌CuratorC 8个月前

我之前也写过一个 github.com/kphcdr/laravel-trace-id 公司的项目一直在用

10个月前 评论

队列能用吗?

6个月前 评论
July993 6个月前
caonima888 5个月前

这种只是追日志吧 真链路追踪还得是这种

file

3个月前 评论
rooy 3个月前
kolin (作者) 3个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
UFO @ 一只码科技
文章
9
粉丝
37
喜欢
80
收藏
178
排名:508
访问:1.6 万
私信
所有博文
社区赞助商