为 Laravel Http 客户端添加详情的请求日志

有时候难免要对 Http 的请求和响应包体进行记录以方便查找问题或做什么。

Laravel 的 Http 客户端是基于 Guzzle 进行封装的,在上层进行了简化,并没有直接给我们留相关的日志配置,想要对请求的 http 进行详细的记录,则需要借助于 Guzzle 的 Handler/中间件,和 withOptions 方法。

首先们要使用 composer 安装一个第三方的 Guzzle 日志中间件。

composer require rtheunissen/guzzle-log-middleware

该中间件比较简单,仅是在请求发生时将请求和响应对象传递给我们指定的闭包,然后我们在闭包中直接调用 Log 的方法进行记录即可。

简单的演示如下:

$stack = new HandlerStack();
$stack->setHandler(new CurlHandler()); //使用 HandlerStack 后必须指定一个 Handler

//日志中间件
$logger = new Logger(function ($level, $message, array $context) {
    Log::log($level, $message);
});

$stack->push($logger);

$res = Http::withOptions([
    'handler' => $stack
])->post($url, $data);

此时请求发生时我们就会在日志里看到一条这样的简单日志:

[2021-04-28 17:00:30] local.INFO: homestead GuzzleHttp/7 - [28/Apr/2021:17:00:30 +0800] "POST /your/request/url HTTP/1.1" 200 136

不过显示这个日志太简单了,我们需要更详细的信息,比如请求和响应的头信息及主体内容,通过该中间件的主页得知可使用一个闭包来进行 message 的格式化。

于是我们对 Logger 进行一番修改,从 Request 和 Response 中取出相应的信息,并且拼装成 Http 的包体结构,结果如下:

$logger = new Logger(function ($level, $message, array $context) {
    Log::log($level, $message);
}, function ($request, $response, $reason) {
    /**
     * @var Request $request
     * @var Response $response
     */
    $requestBody = $request->getBody();
    $requestBody->rewind();
    
    //请求头
    $requestHeaders = [];
    
    foreach ($request->getHeaders() as $k => $vs) {
        foreach ($vs as $v) {
            $requestHeaders[] = "$k: $v";
        }
    }
    
    //响应头
    $responseHeaders = [];
    
    foreach ($response->getHeaders() as $k => $vs) {
        foreach ($vs as $v) {
            $responseHeaders[] = "$k: $v";
        }
    }
    
    $uri = $request->getUri();
    $path = $uri->getPath();
    
    if ($query = $uri->getQuery()) {
        $path .= '?'.$query;
    }
    
    return sprintf(
        "Request %s\n%s %s HTTP/%s\r\n%s\r\n\r\n%s\r\n--------------------\r\nHTTP/%s %s %s\r\n%s\r\n\r\n%s",
        $uri,
        $request->getMethod(),
        $path,
        $request->getProtocolVersion(),
        join("\r\n", $requestHeaders),
        $requestBody->getContents(),
        $response->getProtocolVersion(),
        $response->getStatusCode(),
        $response->getReasonPhrase(),
        join("\r\n", $responseHeaders),
        $response->getBody()->getContents()
    );
});

发送请求后,日志内容如下:

[2021-04-28 17:06:11] local.NOTICE: Request https://www.baidu.com/
POST / HTTP/1.1
User-Agent: GuzzleHttp/7
Content-Type: application/json
Host: www.baidu.com

{"hello":"I am a fake POST."}
--------------------
HTTP/1.1 302 Found
Bdpagetype: 3
Connection: keep-alive
Content-Length: 154
Content-Type: text/html
Date: Wed, 28 Apr 2021 09:06:11 GMT
Location: https://www.baidu.com/search/error.html
Server: BWS/1.1
Set-Cookie: BDSVRTM=0; path=/
Traceid: 161960077103724047468432068516915727024
X-Ua-Compatible: IE=Edge,chrome=1

<html>
<head><title>302 Found</title></head>
<body bgcolor="white">
<center><h1>302 Found</h1></center>
<hr><center>nginx</center>
</body>
</html>

怎么样,详细否?

Pader 4月28日 17:07 0

关于 PHP 最近的 RFC:Fibers

PHP 在最近收到了一个可以实现协程的扩展 RFC:Fibers(https://wiki.php.net/rfc/fibers)。

Fibers 从本质上来讲是一个加强的,有栈的生成器,通过 Fibers 可以对整个调用栈代码无侵入式的暂停和恢复执行,再配合用户层面实现 EventLoop 和异步 IO,可以做到非常通俗易懂的,很常规的代码中实现协程,说人话就是最终做到不需要用 yield,不需要第三方扩展,即可实现纯 PHP 的协程,写法上和常规的代码没有区别。

但是这样的一个 RFC,却引起了诸多的争辩,尤其是国内知名的协程扩展 Swoole 的相关人员几乎全投了反对票。当然支持的人也是非常多。

阅读全部内容

Pader 3月16日 23:35 1

纯 PHP 协程框架 Wind Framework 0.1.0 发布啦

        Wind Framework 是我一开始基于纯 PHP 协程实现开发出的一个实验性项目,目的是为了测试纯 PHP 协程应用于工作中的可行性。但经过测试发现应对绝大部分 IO 密集型的场景是完全可行的,于是便基于此不断开发出来的框架。

阅读全部内容

Pader 1月24日 12:48 0

使用 Nvidia 显卡在浏览器播放视频时出现短暂黑屏的解决方案

该问题的表现是在浏览器中打开播放视频的网页,或在视频进行全屏时会短暂的黑屏,大概几秒后会恢复正常。

该问题的核心原因根据网络上的讨论结果,应该是 Chrome 浏览器内核的 BUG,搜索一般给出的办法是关闭浏览器的硬件加速功能,但是硬件加速对浏览器渲染页面有很大的帮助,因为视频播放关闭硬件加速有些得不偿失。

目前最好的解决的办法就是禁止应用程序自己调整屏幕的颜色设置。

打开 Nvidia 控制面板后,在“调整桌面颜色设置”设置方式如下:

重点是“报告给显示器的内容类型”修改为“全屏视频”。

颜色的设置也可以从“其它应用程序控制颜色设置”改为“使用 Nvidia 设置“。


Pader 1月7日 00:40 0

PHP 的协程库

协程是当下火热的概念,尤其是 NodeJS 和 Go 语言的流行将协程彻彻底底的带入了大家的视野。然后大家这才意识到,在绝大多数面向服务端的程序中,阻塞式的 IO 才是并发最大的原罪。