126 lines
4.0 KiB
PHP
126 lines
4.0 KiB
PHP
<?php
|
||
// +----------------------------------------------------------------------
|
||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||
// +----------------------------------------------------------------------
|
||
// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
|
||
// +----------------------------------------------------------------------
|
||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||
// +----------------------------------------------------------------------
|
||
// | Author: liu21st <liu21st@gmail.com>
|
||
// +----------------------------------------------------------------------
|
||
declare (strict_types = 1);
|
||
|
||
namespace app\log\driver;
|
||
|
||
use think\App;
|
||
use think\contract\LogHandlerInterface;
|
||
use think\facade\Config;
|
||
|
||
/**
|
||
* 本地化调试输出到文件
|
||
*/
|
||
class Monolog implements LogHandlerInterface
|
||
{
|
||
/**
|
||
* 配置参数
|
||
* @var array
|
||
*/
|
||
protected $config = [
|
||
'time_format' => 'c',
|
||
'json_options' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
|
||
|
||
];
|
||
|
||
|
||
/**
|
||
* 日志写入接口
|
||
* @access public
|
||
* @param array $log 日志信息
|
||
* @return bool
|
||
*/
|
||
public function save(array $log): bool
|
||
{
|
||
$info = [];
|
||
// 日志信息封装
|
||
$time = \DateTime::createFromFormat('0.u00 U', microtime())->setTimezone(new \DateTimeZone(date_default_timezone_get()))->format($this->config['time_format']);
|
||
foreach ($log as $type => $val) {
|
||
$message = [];
|
||
foreach ($val as $msg) {
|
||
|
||
$message[] = json_encode(['time' => $time, 'type' => $type, 'msg' => $msg], $this->config['json_options']) ;
|
||
}
|
||
$info[$type] = $message;
|
||
}
|
||
if ($info) {
|
||
return $this->write($info);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
/**
|
||
* 日志写入
|
||
* @access protected
|
||
* @param array $message 日志信息
|
||
* @param string $destination 日志文件
|
||
* @return bool
|
||
*/
|
||
protected function write(array $message): bool
|
||
{
|
||
$tcp_log_url = env('TCP_LOG_URL');
|
||
$tcp_log_port = (int) env('TCP_LOG_PORT');
|
||
$project = env('PROJECT_NAME');
|
||
|
||
// 快速失败:配置缺失
|
||
if (!$tcp_log_url || !$tcp_log_port || !$project) {
|
||
return true; // 或 error_log("Log config missing");
|
||
}
|
||
|
||
// 1. 安全序列化并限制大小(例如最大 64KB)
|
||
$maxSize = 64 * 1024; // 64KB
|
||
$json = json_encode([
|
||
'message' => $message,
|
||
'project' => $project
|
||
], $this->config['json_options'] ?? 0);
|
||
|
||
if ($json === false) {
|
||
error_log("Failed to encode log message: " . json_last_error_msg());
|
||
return true;
|
||
}
|
||
|
||
// 截断过大的日志(保留结构完整性)
|
||
if (strlen($json) > $maxSize) {
|
||
// 尝试只截断 message 内容(更智能)
|
||
$fallbackMessage = [
|
||
'message' => '[TRUNCATED] Log too large (' . strlen($json) . ' bytes)',
|
||
'project' => $project,
|
||
'original_size_bytes' => strlen($json)
|
||
];
|
||
$json = json_encode($fallbackMessage, $this->config['json_options'] ?? 0);
|
||
}
|
||
|
||
// 2. 添加换行符(Logstash tcp + json_lines 必需)
|
||
$payload = $json . "\n";
|
||
|
||
// 3. 建立连接(适当延长超时)
|
||
$socket = @fsockopen($tcp_log_url, $tcp_log_port, $errno, $errstr, 2); // 2秒超时
|
||
|
||
if (!$socket) {
|
||
error_log("Log socket connect failed to {$tcp_log_url}:{$tcp_log_port} - {$errstr} ({$errno})");
|
||
return true; // 不影响主流程
|
||
}
|
||
|
||
// 4. 发送数据(抑制 fwrite 警告,但可选记录)
|
||
$bytesWritten = @fwrite($socket, $payload);
|
||
$expected = strlen($payload);
|
||
|
||
if ($bytesWritten === false || $bytesWritten !== $expected) {
|
||
error_log("Partial or failed log send to {$tcp_log_url}:{$tcp_log_port} (sent {$bytesWritten}/{$expected} bytes)");
|
||
}
|
||
|
||
// 5. 确保关闭 socket
|
||
fclose($socket);
|
||
|
||
return true;
|
||
}
|
||
}
|