#feature: add AI log analysis & some bugfix
This commit is contained in:
292
app/Clients/SlsClient.php
Normal file
292
app/Clients/SlsClient.php
Normal file
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
namespace App\Clients;
|
||||
|
||||
use Aliyun_Log_Client;
|
||||
use Aliyun_Log_Models_GetLogsRequest;
|
||||
use Aliyun_Log_Models_GetHistogramsRequest;
|
||||
use Aliyun_Log_Exception;
|
||||
use RuntimeException;
|
||||
|
||||
class SlsClient
|
||||
{
|
||||
private ?Aliyun_Log_Client $client = null;
|
||||
private string $project;
|
||||
private array $logstores;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$config = config('services.sls');
|
||||
|
||||
if (empty($config['endpoint']) || empty($config['access_key_id']) || empty($config['access_key_secret'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->project = $config['project'] ?? '';
|
||||
|
||||
// 解析 logstore 配置,支持逗号分隔的多个 logstore
|
||||
$logstoreConfig = $config['logstore'] ?? '';
|
||||
if (!empty($logstoreConfig)) {
|
||||
// 如果包含逗号,说明是多个 logstore
|
||||
if (str_contains($logstoreConfig, ',')) {
|
||||
$this->logstores = array_map('trim', explode(',', $logstoreConfig));
|
||||
} else {
|
||||
$this->logstores = [$logstoreConfig];
|
||||
}
|
||||
} else {
|
||||
$this->logstores = [];
|
||||
}
|
||||
|
||||
$this->client = new Aliyun_Log_Client(
|
||||
$config['endpoint'],
|
||||
$config['access_key_id'],
|
||||
$config['access_key_secret'],
|
||||
$config['security_token'] ?: ''
|
||||
);
|
||||
}
|
||||
|
||||
public function isConfigured(): bool
|
||||
{
|
||||
// 只要有 client、project,并且至少有一个 logstore 就算配置完成
|
||||
return $this->client !== null
|
||||
&& !empty($this->project)
|
||||
&& !empty($this->logstores);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置的所有 logstore
|
||||
*/
|
||||
public function getLogstores(): array
|
||||
{
|
||||
return $this->logstores;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认的 logstore(第一个)
|
||||
*/
|
||||
private function getDefaultLogstore(): string
|
||||
{
|
||||
if (empty($this->logstores)) {
|
||||
throw new RuntimeException('没有配置可用的 logstore');
|
||||
}
|
||||
|
||||
return $this->logstores[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询日志
|
||||
*
|
||||
* @param int $from 开始时间戳
|
||||
* @param int $to 结束时间戳
|
||||
* @param string|null $query SLS 查询语句
|
||||
* @param int $offset 偏移量
|
||||
* @param int $limit 返回数量
|
||||
* @param string|null $logstore 可选的 logstore,不传则使用默认
|
||||
* @return array{logs: array, count: int, complete: bool}
|
||||
*/
|
||||
public function getLogs(
|
||||
int $from,
|
||||
int $to,
|
||||
?string $query = null,
|
||||
int $offset = 0,
|
||||
int $limit = 100,
|
||||
?string $logstore = null
|
||||
): array {
|
||||
$this->ensureConfigured();
|
||||
|
||||
$request = new Aliyun_Log_Models_GetLogsRequest(
|
||||
$this->project,
|
||||
$logstore ?? $this->getDefaultLogstore(),
|
||||
$from,
|
||||
$to,
|
||||
'',
|
||||
$query ?? '*',
|
||||
$limit,
|
||||
$offset,
|
||||
false
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $this->client->getLogs($request);
|
||||
|
||||
$logs = [];
|
||||
foreach ($response->getLogs() as $log) {
|
||||
$logs[] = $log->getContents();
|
||||
}
|
||||
|
||||
return [
|
||||
'logs' => $logs,
|
||||
'count' => $response->getCount(),
|
||||
'complete' => $response->isCompleted(),
|
||||
];
|
||||
} catch (Aliyun_Log_Exception $e) {
|
||||
throw new RuntimeException(
|
||||
"SLS 查询失败: [{$e->getErrorCode()}] {$e->getErrorMessage()}",
|
||||
0,
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日志分布直方图
|
||||
*
|
||||
* @param int $from 开始时间戳
|
||||
* @param int $to 结束时间戳
|
||||
* @param string|null $query SLS 查询语句
|
||||
* @param string|null $logstore 可选的 logstore
|
||||
* @return array{histograms: array, count: int, complete: bool}
|
||||
*/
|
||||
public function getHistograms(
|
||||
int $from,
|
||||
int $to,
|
||||
?string $query = null,
|
||||
?string $logstore = null
|
||||
): array {
|
||||
$this->ensureConfigured();
|
||||
|
||||
$request = new Aliyun_Log_Models_GetHistogramsRequest(
|
||||
$this->project,
|
||||
$logstore ?? $this->getDefaultLogstore(),
|
||||
$from,
|
||||
$to,
|
||||
'',
|
||||
$query ?? '*'
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $this->client->getHistograms($request);
|
||||
|
||||
$histograms = [];
|
||||
foreach ($response->getHistograms() as $histogram) {
|
||||
$histograms[] = [
|
||||
'from' => $histogram->getFrom(),
|
||||
'to' => $histogram->getTo(),
|
||||
'count' => $histogram->getCount(),
|
||||
'complete' => $histogram->isCompleted(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'histograms' => $histograms,
|
||||
'count' => $response->getTotalCount(),
|
||||
'complete' => $response->isCompleted(),
|
||||
];
|
||||
} catch (Aliyun_Log_Exception $e) {
|
||||
throw new RuntimeException(
|
||||
"SLS 直方图查询失败: [{$e->getErrorCode()}] {$e->getErrorMessage()}",
|
||||
0,
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页获取所有日志
|
||||
*
|
||||
* @param int $from 开始时间戳
|
||||
* @param int $to 结束时间戳
|
||||
* @param string|null $query SLS 查询语句
|
||||
* @param int $maxLogs 最大返回日志数
|
||||
* @param string|null $logstore 可选的 logstore
|
||||
* @return array
|
||||
*/
|
||||
public function getAllLogs(
|
||||
int $from,
|
||||
int $to,
|
||||
?string $query = null,
|
||||
int $maxLogs = 1000,
|
||||
?string $logstore = null
|
||||
): array {
|
||||
$allLogs = [];
|
||||
$offset = 0;
|
||||
$batchSize = 100;
|
||||
|
||||
while (count($allLogs) < $maxLogs) {
|
||||
$result = $this->getLogs($from, $to, $query, $offset, $batchSize, $logstore);
|
||||
|
||||
$allLogs = array_merge($allLogs, $result['logs']);
|
||||
|
||||
if ($result['complete'] || count($result['logs']) < $batchSize) {
|
||||
break;
|
||||
}
|
||||
|
||||
$offset += $batchSize;
|
||||
}
|
||||
|
||||
return array_slice($allLogs, 0, $maxLogs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试连接
|
||||
*/
|
||||
public function testConnection(): bool
|
||||
{
|
||||
if (!$this->isConfigured()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$now = time();
|
||||
$this->getLogs($now - 60, $now, '*', 0, 1);
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从多个 logstore 获取日志
|
||||
*
|
||||
* @param int $from 开始时间戳
|
||||
* @param int $to 结束时间戳
|
||||
* @param string|null $query SLS 查询语句
|
||||
* @param int $maxLogs 最大返回日志数
|
||||
* @param array|null $logstores 要查询的 logstore 列表,不传则使用配置的所有 logstore
|
||||
* @return array 按 logstore 分组的日志数据 ['logstore_name' => ['logs' => [...], 'count' => 100]]
|
||||
*/
|
||||
public function getAllLogsFromMultipleStores(
|
||||
int $from,
|
||||
int $to,
|
||||
?string $query = null,
|
||||
int $maxLogs = 1000,
|
||||
?array $logstores = null
|
||||
): array {
|
||||
$this->ensureConfigured();
|
||||
|
||||
$targetLogstores = $logstores ?? $this->getLogstores();
|
||||
|
||||
if (empty($targetLogstores)) {
|
||||
throw new RuntimeException('没有配置可用的 logstore');
|
||||
}
|
||||
|
||||
$results = [];
|
||||
|
||||
foreach ($targetLogstores as $logstore) {
|
||||
try {
|
||||
$logs = $this->getAllLogs($from, $to, $query, $maxLogs, $logstore);
|
||||
$results[$logstore] = [
|
||||
'logs' => $logs,
|
||||
'count' => count($logs),
|
||||
'success' => true,
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
$results[$logstore] = [
|
||||
'logs' => [],
|
||||
'count' => 0,
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function ensureConfigured(): void
|
||||
{
|
||||
if (!$this->isConfigured()) {
|
||||
throw new RuntimeException('SLS 客户端未配置,请检查 .env 中的 SLS_* 配置项');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user