ThinkPHP 对接外部接口进行详细日志记录

接口的对接,已经是常规的工作了,但很多接口提供商并不会提供我们所使用语言的 SDK,也没有我们熟悉语言的 Demo,那这时我们只能通过文档提供的流程来进行编码。联调期间相关信息的提供、线上环境信息的记录都非常重要,我真的遇到有人在文档里撒谎的。

因为又要搞一个安装包太麻烦了,所以这里我直接把源码放出来,目前我的设计是规划成几个添加,在类被销毁的时候再追加到日志中,也可以直接提取信息然后入 Redis 或者 MySQL。其中 add_count 用来添加常量,比如阿里云 AK 这些固定值;add_param 来添加处理过程中的变量;add_log 来添加过程中的一些信息;add_remark 添加字符串的文本内容;add_http 来添加 HTTP 信息,做到这里我才发现太局限了,可能是我这段时间一直写 Golang,目前这里可以添加 GuzzleHttp 的返回(GuzzleHttp\Psr7\Response)和异常(GuzzleHttp\Exception\ClientException);最后就是 add_exception 来添加异常信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
<?php

namespace app\utils;

use Exception;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Exception\ClientException;

class API
{
protected $output = true;

protected $list = [];

const TYPE_COUNT = 0x0;

const TYPE_PARAM = 0x1;

const TYPE_INFO = 0x2;

const TYPE_REMARK = 0X3;

const TYPE_HTTP = 0X4;

const TYPE_EXCEPTION = 0X5;

/**
* 添加常量
*
* @param string $key 常量名称
* @param string $value 常量值
*/
public function add_count (string $key, string $value)
{
$this->list[] = [
'type' => self::TYPE_COUNT,
'key' => $key,
'value' => $value,
];
}

/**
* 添加变量
*
* @param string $key 变量名称
* @param mixed $value 变量值
*/
public function add_param (string $key, $value)
{
$this->list[] = [
'type' => self::TYPE_PARAM,
'key' => $key,
'value' => $value,
];
}

/**
* 添加过程日志
*
* @param string $key 名称
* @param mixed $value 值
*/
public function add_log (string $key, $value)
{
$this->list[] = [
'type' => self::TYPE_INFO,
'key' => $key,
'value' => $value,
];
}

/**
* 添加备注
*
* @param string $content 内容
*/
public function add_remark (string $content)
{
$this->list[] = [
'type' => self::TYPE_REMARK,
'value' => $content,
];
}

/**
* 添加 HTTP
*
* @param Response|ClientException $http 内容
*/
public function add_http ($http)
{
$this->list[] = [
'type' => self::TYPE_HTTP,
'http' => $http,
];
}

/**
* 添加异常
*
* @param Exception $e 异常
*/
public function add_exception (Exception $e)
{
$this->list[] = [
'type' => self::TYPE_EXCEPTION,
'exception' => [
'file' => $e->getFile(),
'code' => $e->getCode(),
'line' => $e->getLine(),
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
],
];
}

/**
* 获取是否输出
*
* @return bool
*/
public function get_output (): bool
{
return (bool) $this->output;
}

/**
* 设置是否删除
*
* @param bool $value
*/
public function set_output (bool $value)
{
$this->output = $value;
}

public function get_data (): array
{
return $this->list;
}

public function __destruct ()
{
if (!$this->output) {
return;
}

trace(str_pad(date('Y-m-d H:i:s'), 80, '-', STR_PAD_BOTH));

foreach ($this->list as $item) {
if ($item['type'] === self::TYPE_COUNT) {
trace(sprintf('[常量] %s=%s', $item['key'], $item['value']));
} else if ($item['type'] === self::TYPE_PARAM) {
trace(sprintf('[变量] %s', $item['key']));
trace($item['value']);
} else if ($item['type'] === self::TYPE_INFO) {
trace(sprintf('%s %s', $item['key'], $item['value']));
} else if ($item['type'] === self::TYPE_REMARK) {
trace($item['value']);
} else if ($item['type'] === self::TYPE_HTTP) {
if ($item['http'] instanceof Response) {
trace(sprintf('接口请求完成 %d-%s', $item['http']->getStatusCode(), $item['http']->getReasonPhrase()));

trace('返回 Header:');

foreach ($item['http']->getHeaders() as $name => $values) {
trace(sprintf('%s=%s', $name, implode(' ', $values)));
}

trace(sprintf('返回内容:%s', $item['http']->getBody()->getContents()));
}
} else if ($item['type'] === self::TYPE_EXCEPTION) {
$e = $item['exception'];

trace(sprintf('%s:%s|%d %s', '异常', $e['file'], $e['line'], $e['message'] . time()));
trace($e['trace']);
}
}

trace(str_pad('', 80, '-'));
}
}

目前以阿里云对象存储 OSS 的上传文件来作为 Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
<?php

namespace app\controller;

use app\BaseController;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use think\App;

class Index extends BaseController
{
const ACCESS_KEY_ID = 'LTAI5tN2UwSUNpxaLodnDd8g';

const ACCESS_KEY_SERECT = '5Rx17mer3lfxYfvgjPzy0eYW68ycVI';

const BUCKET = 'hongfs-test';

protected $log = null;

public function __construct (App $app)
{
$this->log = new \app\utils\API();

parent::__construct($app);
}

public function index ()
{
$this->upload_content(((string) time()) . '.txt', 'hongfs');

return '';
}

public function upload_content (string $path, string $content)
{
$this->log->add_remark('进入 upload_content');
$this->log->add_param('上传路径', $path);
$this->log->add_param('上传内容', $content);

$this->log->add_count('ACCESS_KEY_ID', self::ACCESS_KEY_ID);
$this->log->add_count('ACCESS_KEY_SERECT', self::ACCESS_KEY_SERECT);
$this->log->add_count('BUCKET', self::BUCKET);

$content_md5 = base64_encode(md5($content, true));

$this->log->add_log('计算上传内容 MD5+BASE64', $content_md5);

$date = gmdate('D, d M Y H:i:s \G\M\T');

$this->log->add_log('date', $date);

$sign_content = sprintf("%s\n%s\n%s\n%s\n/%s/%s", 'PUT', $content_md5, 'application/octet-stream', $date, self::BUCKET, $path);

$this->log->add_log('拼接签名字符串', $sign_content);

$sign_content = hash_hmac('sha1', $sign_content, self::ACCESS_KEY_SERECT, true);

$this->log->add_log('SHA1 签名', $sign_content);

$sign_content = base64_encode($sign_content);

$this->log->add_log('BASE64 签名', $sign_content);

$authorization = sprintf('OSS %s:%s', self::ACCESS_KEY_ID, $sign_content);

$this->log->add_log('Authorization', $authorization);

$url = sprintf('https://%s.oss-cn-hongkong.aliyuncs.com/%s', self::BUCKET, $path);

$this->log->add_param('请求 URL', $url);

$headers = [
'Content-MD5' => $content_md5,
'Content-Type' => 'application/octet-stream',
'Authorization' => $authorization,
'Date' => $date,
'Host' => parse_url($url, PHP_URL_HOST),
];

$this->log->add_param('请求 Header', $headers);

try {
$request = (new Client())->requestAsync('PUT', $url, [
'headers' => $headers,
'body' => $content,
]);

$response = $request->wait();
}
catch (ClientException $e) {
$this->log->add_http($e->getResponse());

throw $e;
}
catch (\Exception $e) {
$this->log->add_exception($e);

throw $e;
}

$this->log->add_http($response);

return true;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[2023-02-22T09:33:37+08:00][log] ------------------------------2023-02-22 09:33:37-------------------------------
[2023-02-22T09:33:37+08:00][log] 进入 upload_content
[2023-02-22T09:33:37+08:00][log] [变量] 上传路径
[2023-02-22T09:33:37+08:00][log] 1677029617.txt
[2023-02-22T09:33:37+08:00][log] [变量] 上传内容
[2023-02-22T09:33:37+08:00][log] hongfs
[2023-02-22T09:33:37+08:00][log] [常量] ACCESS_KEY_ID=LTAI5tN2UwSUNpxaLodnDd8g
[2023-02-22T09:33:37+08:00][log] [常量] ACCESS_KEY_SERECT=5Rx17mer3lfxYfvgjPzy0eYW68ycVI
[2023-02-22T09:33:37+08:00][log] [常量] BUCKET=hongfs-test
[2023-02-22T09:33:37+08:00][log] 计算上传内容 MD5+BASE64 VYXnex0kujaON1zxhc4iVw==
[2023-02-22T09:33:37+08:00][log] date Wed, 22 Feb 2023 01:33:37 GMT
[2023-02-22T09:33:37+08:00][log] 拼接签名字符串 PUT
VYXnex0kujaON1zxhc4iVw==
application/octet-stream
Wed, 22 Feb 2023 01:33:37 GMT
/hongfs-test/1677029617.txt
[2023-02-22T09:33:37+08:00][log] SHA1 签名 �*

+�l��9>��pps�
[2023-02-22T09:33:37+08:00][log] BASE64 签名 jSoKvgoTK8Bs6eG3OT6qx3Bwc5A=
[2023-02-22T09:33:37+08:00][log] Authorization OSS LTAI5tN2UwSUNpxaLodnDd8g:jSoKvgoTK8Bs6eG3OT6qx3Bwc5A=
[2023-02-22T09:33:37+08:00][log] [变量] 请求 URL
[2023-02-22T09:33:37+08:00][log] https://hongfs-test.oss-cn-hongkong.aliyuncs.com/1677029617.txt
[2023-02-22T09:33:37+08:00][log] [变量] 请求 Header
[2023-02-22T09:33:37+08:00][log] array (
'Content-MD5' => 'VYXnex0kujaON1zxhc4iVw==',
'Content-Type' => 'application/octet-stream',
'Authorization' => 'OSS LTAI5tN2UwSUNpxaLodnDd8g:jSoKvgoTK8Bs6eG3OT6qx3Bwc5A=',
'Date' => 'Wed, 22 Feb 2023 01:33:37 GMT',
'Host' => 'hongfs-test.oss-cn-hongkong.aliyuncs.com',
)
[2023-02-22T09:33:37+08:00][log] 接口请求完成 200-OK
[2023-02-22T09:33:37+08:00][log] 返回 Header:
[2023-02-22T09:33:37+08:00][log] Server=AliyunOSS
[2023-02-22T09:33:37+08:00][log] Date=Wed, 22 Feb 2023 01:33:39 GMT
[2023-02-22T09:33:37+08:00][log] Content-Length=0
[2023-02-22T09:33:37+08:00][log] Connection=keep-alive
[2023-02-22T09:33:37+08:00][log] x-oss-request-id=63F570F38A23F731366466D2
[2023-02-22T09:33:37+08:00][log] ETag="5585E77B1D24BA368E375CF185CE2257"
[2023-02-22T09:33:37+08:00][log] x-oss-hash-crc64ecma=18211861437049737415
[2023-02-22T09:33:37+08:00][log] Content-MD5=VYXnex0kujaON1zxhc4iVw==
[2023-02-22T09:33:37+08:00][log] x-oss-server-time=19
[2023-02-22T09:33:37+08:00][log] 返回内容:
[2023-02-22T09:33:37+08:00][log] --------------------------------------------------------------------------------
往上