293 lines
8.1 KiB
PHP
293 lines
8.1 KiB
PHP
<?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_* 配置项');
|
||
}
|
||
}
|
||
}
|