alibabacloud/aliyun-log-php-sdk
Official PHP SDK for Alibaba Cloud Log Service (SLS). Write, query, and manage logs from PHP with ready-made clients for logstores, indexes, and machines. Composer-installable, simple to wire into existing apps, and best suited to projects already running on Alibaba Cloud.
This is the Darabonba/Tea-generated PHP client for Alibaba Cloud Log Service (SLS), pinned to API version V20201230. It is a thin, machine-generated wrapper over the SLS OpenAPI — not the hand-written aliyun/aliyun-log-php-sdk you'll find in most blog posts. Knowing that up front saves you an hour.
Minimal setup:
composer require alibabacloud/aliyun-log-php-sdk
use AlibabaCloud\SDK\Sls\V20201230\Sls;
use AlibabaCloud\SDK\Sls\V20201230\Models\GetLogsRequest;
use Darabonba\OpenApi\Models\Config;
use Darabonba\OpenApi\Models\RuntimeOptions;
$config = new Config([
'accessKeyId' => config('services.sls.key'),
'accessKeySecret' => config('services.sls.secret'),
// optionally 'securityToken' => $stsToken,
'endpoint' => 'cn-hangzhou.log.aliyuncs.com', // NOT sls.cn-hangzhou.aliyuncs.com
'connectTimeout' => 5000,
'readTimeout' => 60000,
]);
$client = new Sls($config);
$resp = $client->getLogs('my-project', 'my-logstore', new GetLogsRequest([
'from' => time() - 900, // seconds, not milliseconds
'to' => time(),
'query' => 'level: ERROR and service: checkout',
'line' => 100,
'offset'=> 0,
]), new RuntimeOptions(['autoretry' => true, 'maxAttempts' => 3]));
Where to look first: vendor/alibabacloud/aliyun-log-php-sdk/src/Sls.php. The generated method list is your API reference — the public docs describe HTTP endpoints, not this SDK's method signatures. Then src/Models/*Request.php for the accepted parameters of each operation. Every operation has a xxxWithOptions($a, $b, $request, $runtime) variant; the non-WithOptions form just passes null runtime.
First real use case: an ops/error-triage endpoint in your app — listLogStores() to discover stores, getLogs() to fetch matching entries, getHistograms() to render the count-over-time sparkline next to them. That loop (histogram → drill-down → detail) is 80% of what anyone actually does with SLS.
Wrap it, don't sprinkle it. Bind a single client as a singleton and put queries behind a small service class or repository interface. This is doubly important here because the generated classes are un-mockable in practice (Tea's HTTP layer has no first-class fake), so your test suite should depend on your interface:
$this->app->singleton(SlsService::class, fn () => new SlsService(new Sls($config)));
In Laravel, keep credentials in config/services.php reading env('ALIYUN_SLS_*'), and never construct the client inside a controller or job handle() — a long-lived queue worker will otherwise hold a client with an expired STS token.
Search vs. SQL is the same call. getLogs takes one query string. A plain search is level: ERROR; a SQL query is level: ERROR | select date_trunc('minute', __time__) as t, count(*) as c group by t order by t. Branch in your wrapper on str_contains($query, '|') only if you need different line/offset limits or timeouts — SQL needs a larger readTimeout.
Pagination. Loop offset += line with line = 100. SLS caps offset + line at 1,000,000 per query; treat that as a hard boundary and page by time window instead of by offset once you're past a few thousand rows. Always surface a total-scan guard in your wrapper so a dashboard doesn't accidentally pull 500k rows.
Management as code. Creating projects/logstores/indexes from an idempotent deploy command is far more reliable than clicking in the console. Check-then-create, and catch the "already exists" codes rather than if (!exists) alone (races happen in CI). Remember that a logstore with no index is effectively unqueryable — creating the logstore is only half the job; createIndex with correctly typed fields is the other half.
Ingestion. Prefer Logtail, the SLS web-tracking endpoint, or an async job that batches writes. If you must putLogs from PHP, be aware the body is a protobuf-encoded LogGroup, not JSON — the generated SDK typically exposes only a raw body stream, so you need generated protobuf classes (or the legacy hand-written SDK) to build it. Do not ship logs synchronously in the request path; dispatch after the response or use terminate().
Cache the expensive reads. SLS bills by data scanned, and getHistograms/aggregate SQL are the expensive calls. A 30–60s cache on dashboard tiles is free money. Also log your own query metadata (query, from/to, elapsed, x-log-processed-rows from the response headers) — it's the only way to explain next month's bill.
aliyun/aliyun-log-php-sdk is what nearly every production integration uses and includes protobuf support. A pragmatic split: use this package for reads and management APIs, the legacy one for writes — or standardize on legacy entirely and skip this repo.2026-07-10 release date reflects regeneration, not semantic versioning. Pin the exact version in composer.json and read the diff on upgrade; method signatures can shift between regenerations.from/to are Unix seconds. Passing milliseconds silently returns an empty set — no error, just an empty result that looks like "we have no logs." Normalize once, in your wrapper.[from, to). An off-by-one on to loses the log line you're debugging right now. Add a small tail buffer when querying "up to now."max_text_len truncation. The index default is 32768 bytes; long JSON blobs get silently cut. The classic bug report is "my message field is missing the stack trace." Bump max_text_len or index the field as JSON.text vs long/double vs json changes both query syntax (payload.user_id: 42 for JSON) and sort/aggregate behaviour. Querying a number as text sorts lexicographically.readTimeout. SLS may return partial results with x-log-progress: Incomplete — check the response headers, otherwise you'll present a truncated aggregate as fact.SignatureNotMatch usually means the endpoint's region doesn't match the project's region, or the system clock has drifted. ProjectNotExist on a project you can see in the console means you're hitting the wrong endpoint (or the intranet one from outside the VPC).Tea\Exception\TeaUnableRetryError wrapping a TeaError. Read $e->lastException->code / ->data, not just the message — codes like Unauthorized, LogStoreNotExist, ParameterInvalid, WriteQuotaExceed, ShardReadQuotaExceed need different handling (retry vs. surface vs. alert). autoretry retries all failures by default; restrict it, or you'll retry quota errors pointlessly.<region>-intranet.log.aliyuncs.com. From ECS, using the public endpoint routes through NAT and costs money for a hop that the intranet endpoint makes free.Sls.php (newer GetLogsV2/ScanLogs-style APIs, some Logtail config endpoints), don't patch vendor — either upgrade and check, or write a signed Guzzle request in your wrapper using the same credentials. Keep that escape hatch one class deep so you can delete it later.How can I help you explore Laravel packages today?