@acedatacloud/skills 2026.425.1 → 2026.504.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/_shared/tencentcloud.md +84 -0
- package/skills/tencentcloud-cls/SKILL.md +214 -0
- package/skills/tencentcloud-cls/scripts/cls.py +186 -0
- package/skills/tencentcloud-cls-alarm/SKILL.md +248 -0
- package/skills/tencentcloud-cls-alarm/scripts/cls_alarm.py +654 -0
- package/skills/tencentcloud-cos/SKILL.md +221 -0
- package/skills/tencentcloud-cos/scripts/cos.py +410 -0
- package/skills/tencentcloud-dns/SKILL.md +201 -0
- package/skills/tencentcloud-dns/scripts/dns.py +248 -0
- package/skills/tencentcloud-edgeone/SKILL.md +257 -0
- package/skills/tencentcloud-edgeone/scripts/edgeone.py +439 -0
- package/skills/tencentcloud-tke/SKILL.md +195 -0
- package/skills/tencentcloud-tke/scripts/tke.py +392 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acedatacloud/skills",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.504.1",
|
|
4
4
|
"description": "Agent Skills for AceDataCloud AI services — music, image, video generation, LLM chat, web search. Compatible with Claude Code, GitHub Copilot, Gemini CLI, OpenAI Codex, and 30+ AI coding agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Tencent Cloud Authentication
|
|
2
|
+
|
|
3
|
+
All `tencentcloud-*` skills authenticate with the same SecretId / SecretKey pair via the official Tencent Cloud Python SDK.
|
|
4
|
+
|
|
5
|
+
## Get Your Credentials
|
|
6
|
+
|
|
7
|
+
1. Sign in to [Tencent Cloud Console](https://console.cloud.tencent.com/cam/capi)
|
|
8
|
+
2. Navigate to **Access Management → API Keys → Create Key**
|
|
9
|
+
3. You'll get a **SecretId** (starts with `AKID...`) and a **SecretKey** that's displayed exactly once — save it before closing the page.
|
|
10
|
+
|
|
11
|
+
> ⚠️ **Use a sub-account, not your main account.** Create a CAM sub-user with only the policies the skill needs (e.g. `QcloudCOSReadOnlyAccess` for read-only COS, `QcloudCLSReadOnlyAccess` for log queries) and generate the keys for that sub-account. Limits the blast radius if the credentials leak.
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
The Tencent Cloud SDK reads credentials from these environment variables:
|
|
16
|
+
|
|
17
|
+
| Variable | Description | Example |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `TENCENTCLOUD_SECRET_ID` | API SecretId | `AKIDxxxxxxxxxxxx` |
|
|
20
|
+
| `TENCENTCLOUD_SECRET_KEY` | API SecretKey (sensitive) | `xxxxxxxxxxxxxxxx` |
|
|
21
|
+
| `TENCENTCLOUD_REGION` | Default region | `ap-hongkong`, `ap-guangzhou`, ... |
|
|
22
|
+
|
|
23
|
+
**Local dev — `.env` file**:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
TENCENTCLOUD_SECRET_ID=AKID...
|
|
27
|
+
TENCENTCLOUD_SECRET_KEY=...
|
|
28
|
+
TENCENTCLOUD_REGION=ap-hongkong
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Load it before running any tool:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
set -a; source .env; set +a
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> ⚠️ **Important:** Add `.env` to your `.gitignore` — never commit credentials to git.
|
|
38
|
+
|
|
39
|
+
**Agent usage** (Claude / Studio / etc.): If you've installed the [腾讯云 connector](https://auth.acedata.cloud/user/connections) on AceDataCloud, your encrypted credentials are auto-injected as the env vars above when the agent runs any `tencentcloud-*` skill — no manual `.env` setup needed.
|
|
40
|
+
|
|
41
|
+
## Common regions
|
|
42
|
+
|
|
43
|
+
| Region code | Location |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `ap-hongkong` | Hong Kong, China |
|
|
46
|
+
| `ap-guangzhou` | Guangzhou |
|
|
47
|
+
| `ap-beijing` | Beijing |
|
|
48
|
+
| `ap-shanghai` | Shanghai |
|
|
49
|
+
| `ap-singapore` | Singapore |
|
|
50
|
+
| `ap-tokyo` | Tokyo |
|
|
51
|
+
| `eu-frankfurt` | Frankfurt |
|
|
52
|
+
| `na-siliconvalley` | Silicon Valley |
|
|
53
|
+
| `na-ashburn` | Ashburn (Virginia) |
|
|
54
|
+
|
|
55
|
+
Full list: [Tencent Cloud regions and AZs](https://www.tencentcloud.com/document/product/213/6091).
|
|
56
|
+
|
|
57
|
+
## Verifying credentials
|
|
58
|
+
|
|
59
|
+
A quick sanity check that doesn't cost anything:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from tencentcloud.common import credential
|
|
63
|
+
from tencentcloud.cam.v20190116 import cam_client, models
|
|
64
|
+
|
|
65
|
+
cred = credential.EnvironmentVariableCredential().get_credential()
|
|
66
|
+
client = cam_client.CamClient(cred, "ap-guangzhou")
|
|
67
|
+
resp = client.GetUserAppId(models.GetUserAppIdRequest())
|
|
68
|
+
print("OK, AppId =", resp.AppId)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Returns your Tencent Cloud `AppId` (a numeric account identifier). Any 4xx tells you the keys are wrong / expired / lack `QcloudCamReadOnlyAccess`.
|
|
72
|
+
|
|
73
|
+
## Dependencies
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install tencentcloud-sdk-python
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Service-specific add-ons used by individual skills:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install cos-python-sdk-v5 # tencentcloud-cos
|
|
83
|
+
pip install kubernetes # tencentcloud-tke (kubeconfig handling)
|
|
84
|
+
```
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tencentcloud-cls
|
|
3
|
+
description: |
|
|
4
|
+
Search and analyze Tencent Cloud CLS (Cloud Log Service) logs. Use
|
|
5
|
+
whenever the user asks to: search logs, debug API errors, trace
|
|
6
|
+
requests by trace ID, find 5xx errors, run CQL/SQL analytics over
|
|
7
|
+
log topics, extract structured fields. Backed by the official
|
|
8
|
+
tencentcloud-sdk-python CLS client.
|
|
9
|
+
license: Apache-2.0
|
|
10
|
+
metadata:
|
|
11
|
+
author: acedatacloud
|
|
12
|
+
version: "1.0"
|
|
13
|
+
connections: [tencentcloud]
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Tencent Cloud CLS (Log Service) — Search & Analysis
|
|
17
|
+
|
|
18
|
+
Search and run CQL / SQL analytics over Tencent Cloud CLS log topics.
|
|
19
|
+
|
|
20
|
+
> **Setup:** See [tencentcloud authentication](../_shared/tencentcloud.md). The SDK reads `TENCENTCLOUD_SECRET_ID` / `TENCENTCLOUD_SECRET_KEY` / `TENCENTCLOUD_REGION` from the environment.
|
|
21
|
+
>
|
|
22
|
+
> **Companion skill:** Use `tencentcloud-cls-alarm` for alarm policy / notice group / shield management. This skill is only about searching log content.
|
|
23
|
+
|
|
24
|
+
## CLI (preferred)
|
|
25
|
+
|
|
26
|
+
The skill ships [`scripts/cls.py`](scripts/cls.py) — a self-contained CLI for the most common operations.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
CLS=$SKILL_DIR/scripts/cls.py
|
|
30
|
+
|
|
31
|
+
python3 $CLS topics # list topics
|
|
32
|
+
python3 $CLS search --topic <topic-id> --query 'level:ERROR' --time 1h
|
|
33
|
+
python3 $CLS search --topic <topic-id> --trace-id <uuid>
|
|
34
|
+
python3 $CLS search --topic <topic-id> --time 1d \
|
|
35
|
+
--query '* | SELECT api_name, count(*) AS cnt GROUP BY api_name ORDER BY cnt DESC LIMIT 20' \
|
|
36
|
+
--format json
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`--time` accepts `30m / 1h / 6h / 1d / 7d`. `--query` is CQL by default; pass `--lucene` to switch dialect. Append `| SELECT ... GROUP BY ...` to a query for SQL analytics.
|
|
40
|
+
|
|
41
|
+
For anything beyond what the CLI exposes (custom field projections, raw paginated walks, `Context`-based tailing) drop down to the SDK calls below.
|
|
42
|
+
|
|
43
|
+
## When to Use
|
|
44
|
+
|
|
45
|
+
- Find recent errors / 5xx responses for a service
|
|
46
|
+
- Look up a single request by trace ID across multiple topics
|
|
47
|
+
- Run CQL filters (`status_code:>=500 AND service:"openai"`)
|
|
48
|
+
- Run SQL analytics (`SELECT api_name, COUNT(*) GROUP BY api_name`)
|
|
49
|
+
- Chain trace logs to reconstruct a request lifecycle
|
|
50
|
+
- Extract specific fields for billing / audit reports
|
|
51
|
+
|
|
52
|
+
## Dependencies
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install tencentcloud-sdk-python
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import os
|
|
62
|
+
import json
|
|
63
|
+
from tencentcloud.common import credential
|
|
64
|
+
from tencentcloud.cls.v20201016 import cls_client, models
|
|
65
|
+
|
|
66
|
+
cred = credential.EnvironmentVariableCredential().get_credential()
|
|
67
|
+
client = cls_client.ClsClient(cred, os.environ["TENCENTCLOUD_REGION"])
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Workflows
|
|
71
|
+
|
|
72
|
+
### Discover topics
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
req = models.DescribeTopicsRequest()
|
|
76
|
+
resp = client.DescribeTopics(req)
|
|
77
|
+
for t in resp.Topics:
|
|
78
|
+
print(t.TopicId, t.TopicName, t.LogsetId)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> Tip: ask the user for the topic ID up front. Topic IDs look like `751a7350-dc5d-41c3-a6ca-c178bae05807` and aren't guessable from a service name.
|
|
82
|
+
|
|
83
|
+
### Search logs (CQL)
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import time
|
|
87
|
+
|
|
88
|
+
req = models.SearchLogRequest()
|
|
89
|
+
req.TopicId = "<topic-id>"
|
|
90
|
+
req.From = int((time.time() - 3600) * 1000) # 1 hour ago, ms epoch
|
|
91
|
+
req.To = int(time.time() * 1000)
|
|
92
|
+
req.Query = 'status_code:>=500 AND service:"openai"'
|
|
93
|
+
req.Limit = 100
|
|
94
|
+
req.Sort = "desc"
|
|
95
|
+
req.SyntaxRule = 1 # 1 = CQL, 0 = Lucene
|
|
96
|
+
|
|
97
|
+
resp = client.SearchLog(req)
|
|
98
|
+
for line in resp.Results:
|
|
99
|
+
fields = {f.Key: f.Value for f in line.LogJson and []} # see below
|
|
100
|
+
print(line.Time, line.PkgLogId, fields.get("status_code"), fields.get("api_name"))
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
> CLS hands you `Results` with `LogJson` as a JSON string per record. Decode with `json.loads(line.LogJson)` to get a flat dict of all the indexed fields the topic stores.
|
|
104
|
+
|
|
105
|
+
### Look up a trace ID across multiple topics
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
TRACE_ID = "34341776-0835-422a-956d-ac8d5b404db1"
|
|
109
|
+
TOPICS = {
|
|
110
|
+
"trace": "<trace-topic-id>",
|
|
111
|
+
"api-usages": "<api-usages-topic-id>",
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for label, topic_id in TOPICS.items():
|
|
115
|
+
req = models.SearchLogRequest()
|
|
116
|
+
req.TopicId = topic_id
|
|
117
|
+
req.From = int((time.time() - 86400) * 1000)
|
|
118
|
+
req.To = int(time.time() * 1000)
|
|
119
|
+
req.Query = f'trace_id:"{TRACE_ID}"'
|
|
120
|
+
req.Limit = 100
|
|
121
|
+
req.SyntaxRule = 1
|
|
122
|
+
resp = client.SearchLog(req)
|
|
123
|
+
print(f"--- {label}: {len(resp.Results)} records ---")
|
|
124
|
+
for line in resp.Results:
|
|
125
|
+
print(line.Time, line.LogJson)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### SQL analytics
|
|
129
|
+
|
|
130
|
+
CLS supports a SQL-on-logs subset for aggregation. Append `| <SQL>` to a CQL filter:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
req.Query = '* | SELECT api_name, count(*) AS cnt GROUP BY api_name ORDER BY cnt DESC LIMIT 20'
|
|
134
|
+
req.SyntaxRule = 1
|
|
135
|
+
resp = client.SearchLog(req)
|
|
136
|
+
# When the query has a `|`, results land in resp.Analysis (rows of column→value)
|
|
137
|
+
for row in resp.AnalysisResults or []:
|
|
138
|
+
print(row)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Tail recent logs
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
# Poll every 5s for new lines after the last cursor
|
|
145
|
+
last_cursor = None
|
|
146
|
+
while True:
|
|
147
|
+
req = models.SearchLogRequest()
|
|
148
|
+
req.TopicId = topic_id
|
|
149
|
+
req.From = int((time.time() - 30) * 1000)
|
|
150
|
+
req.To = int(time.time() * 1000)
|
|
151
|
+
req.Query = 'level:ERROR'
|
|
152
|
+
req.Limit = 100
|
|
153
|
+
req.Sort = "asc"
|
|
154
|
+
req.SyntaxRule = 1
|
|
155
|
+
if last_cursor:
|
|
156
|
+
req.Context = last_cursor
|
|
157
|
+
resp = client.SearchLog(req)
|
|
158
|
+
for line in resp.Results:
|
|
159
|
+
print(line.Time, line.LogJson)
|
|
160
|
+
last_cursor = resp.Context
|
|
161
|
+
time.sleep(5)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## CQL cheatsheet
|
|
165
|
+
|
|
166
|
+
| Pattern | Meaning |
|
|
167
|
+
|---|---|
|
|
168
|
+
| `status_code:500` | exact match |
|
|
169
|
+
| `status_code:>=500` | range |
|
|
170
|
+
| `status_code:[500 TO 599]` | range with bounds |
|
|
171
|
+
| `service:"openai"` | quoted phrase (use for values containing spaces) |
|
|
172
|
+
| `NOT level:DEBUG` | negation |
|
|
173
|
+
| `service:openai AND status_code:>=400` | conjunction |
|
|
174
|
+
| `(api:foo OR api:bar)` | grouping |
|
|
175
|
+
| `* | SELECT ... GROUP BY ...` | switch into SQL analytics |
|
|
176
|
+
|
|
177
|
+
## Pagination
|
|
178
|
+
|
|
179
|
+
CLS returns up to 1000 records per call. For larger result sets, iterate with `Context`:
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
all_records = []
|
|
183
|
+
context = None
|
|
184
|
+
while True:
|
|
185
|
+
req = models.SearchLogRequest()
|
|
186
|
+
req.TopicId = topic_id
|
|
187
|
+
req.From = ...
|
|
188
|
+
req.To = ...
|
|
189
|
+
req.Query = '...'
|
|
190
|
+
req.Limit = 1000
|
|
191
|
+
req.SyntaxRule = 1
|
|
192
|
+
if context:
|
|
193
|
+
req.Context = context
|
|
194
|
+
resp = client.SearchLog(req)
|
|
195
|
+
all_records.extend(resp.Results)
|
|
196
|
+
context = resp.Context
|
|
197
|
+
if not context or len(resp.Results) < 1000:
|
|
198
|
+
break
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Error patterns
|
|
202
|
+
|
|
203
|
+
| Symptom | Likely cause |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `InvalidParameter.QueryError` | Quote phrases that contain `:` or spaces; check `SyntaxRule` matches the query |
|
|
206
|
+
| `LimitExceeded` | Concurrent `SearchLog` calls exceed the 30-QPS quota — back off & retry |
|
|
207
|
+
| `OperationDenied.AccountIsolated` | CLS service is suspended for billing — check the console |
|
|
208
|
+
| Empty `Results` but logs visible in console | Time range is wrong (`From` / `To` are ms epoch, not seconds), or topic has different field names than the query expects |
|
|
209
|
+
|
|
210
|
+
## Console links
|
|
211
|
+
|
|
212
|
+
- CLS console: <https://console.cloud.tencent.com/cls/topic>
|
|
213
|
+
- CQL syntax: <https://www.tencentcloud.com/document/product/614/47044>
|
|
214
|
+
- SQL analytics syntax: <https://www.tencentcloud.com/document/product/614/58978>
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Tencent Cloud CLS (Cloud Log Service) — search & analytics CLI.
|
|
4
|
+
|
|
5
|
+
Search log topics with CQL filters or run SQL analytics over them.
|
|
6
|
+
|
|
7
|
+
Quick examples:
|
|
8
|
+
python3 $SKILL_DIR/scripts/cls.py topics
|
|
9
|
+
python3 $SKILL_DIR/scripts/cls.py search --topic <topic-id> --query 'level:ERROR' --time 1h
|
|
10
|
+
python3 $SKILL_DIR/scripts/cls.py search --topic <topic-id> --trace-id <uuid>
|
|
11
|
+
python3 $SKILL_DIR/scripts/cls.py search --topic <topic-id> \\
|
|
12
|
+
--query '* | SELECT api_name, count(*) AS cnt GROUP BY api_name ORDER BY cnt DESC LIMIT 20' \\
|
|
13
|
+
--time 1d
|
|
14
|
+
|
|
15
|
+
Environment:
|
|
16
|
+
TENCENTCLOUD_SECRET_ID — required
|
|
17
|
+
TENCENTCLOUD_SECRET_KEY — required
|
|
18
|
+
TENCENTCLOUD_REGION — optional, default ap-hongkong
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import re
|
|
27
|
+
import sys
|
|
28
|
+
import time
|
|
29
|
+
|
|
30
|
+
DEFAULT_REGION = os.environ.get("TENCENTCLOUD_REGION", "ap-hongkong")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_client(region: str = DEFAULT_REGION):
|
|
34
|
+
try:
|
|
35
|
+
from tencentcloud.cls.v20201016 import cls_client
|
|
36
|
+
from tencentcloud.common import credential
|
|
37
|
+
except ImportError:
|
|
38
|
+
print(
|
|
39
|
+
"ERROR: tencentcloud-sdk-python not installed.\nRun: pip3 install tencentcloud-sdk-python",
|
|
40
|
+
file=sys.stderr,
|
|
41
|
+
)
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
secret_id = os.environ.get("TENCENTCLOUD_SECRET_ID")
|
|
44
|
+
secret_key = os.environ.get("TENCENTCLOUD_SECRET_KEY")
|
|
45
|
+
if not secret_id or not secret_key:
|
|
46
|
+
print(
|
|
47
|
+
"ERROR: TENCENTCLOUD_SECRET_ID and TENCENTCLOUD_SECRET_KEY must be set in env",
|
|
48
|
+
file=sys.stderr,
|
|
49
|
+
)
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
cred = credential.Credential(secret_id, secret_key)
|
|
52
|
+
return cls_client.ClsClient(cred, region)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# Time parsing — accepts `30m`, `1h`, `6h`, `1d`, `7d`.
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
_TIME_RE = re.compile(r"^(\d+)([smhd])$")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def parse_relative(spec: str) -> int:
|
|
63
|
+
"""Return milliseconds for a string like `1h` / `30m` / `7d`."""
|
|
64
|
+
m = _TIME_RE.match(spec)
|
|
65
|
+
if not m:
|
|
66
|
+
raise ValueError(f"bad --time: {spec!r}; use e.g. 30m, 1h, 6h, 1d, 7d")
|
|
67
|
+
n, unit = int(m.group(1)), m.group(2)
|
|
68
|
+
return n * {"s": 1000, "m": 60_000, "h": 3_600_000, "d": 86_400_000}[unit]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# Commands
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def cmd_topics(args):
|
|
77
|
+
"""List all log topics in the region."""
|
|
78
|
+
from tencentcloud.cls.v20201016 import models
|
|
79
|
+
|
|
80
|
+
client = get_client(args.region)
|
|
81
|
+
req = models.DescribeTopicsRequest()
|
|
82
|
+
if args.logset_id:
|
|
83
|
+
req.Filters = [{"Key": "logsetId", "Values": [args.logset_id]}]
|
|
84
|
+
resp = client.DescribeTopics(req)
|
|
85
|
+
if args.format == "json":
|
|
86
|
+
print(json.dumps([t._serialize() for t in (resp.Topics or [])], indent=2, ensure_ascii=False))
|
|
87
|
+
return
|
|
88
|
+
print(f"{'TopicId':40s} {'TopicName':32s} {'LogsetId'}")
|
|
89
|
+
for t in resp.Topics or []:
|
|
90
|
+
print(f"{t.TopicId:40s} {(t.TopicName or '')[:32]:32s} {t.LogsetId}")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def cmd_search(args):
|
|
94
|
+
"""Search a topic with a CQL / SQL query."""
|
|
95
|
+
from tencentcloud.cls.v20201016 import models
|
|
96
|
+
|
|
97
|
+
client = get_client(args.region)
|
|
98
|
+
req = models.SearchLogRequest()
|
|
99
|
+
req.TopicId = args.topic
|
|
100
|
+
now_ms = int(time.time() * 1000)
|
|
101
|
+
req.From = now_ms - parse_relative(args.time)
|
|
102
|
+
req.To = now_ms
|
|
103
|
+
|
|
104
|
+
if args.trace_id:
|
|
105
|
+
query = f'trace_id:"{args.trace_id}"'
|
|
106
|
+
else:
|
|
107
|
+
query = args.query
|
|
108
|
+
req.Query = query
|
|
109
|
+
req.Limit = args.limit
|
|
110
|
+
req.Sort = args.sort
|
|
111
|
+
req.SyntaxRule = 0 if args.lucene else 1
|
|
112
|
+
|
|
113
|
+
resp = client.SearchLog(req)
|
|
114
|
+
|
|
115
|
+
# Analytics queries (have a `|`) land in AnalysisResults.
|
|
116
|
+
if "|" in query and (resp.AnalysisResults or []):
|
|
117
|
+
rows = []
|
|
118
|
+
for row in resp.AnalysisResults or []:
|
|
119
|
+
rows.append({c.Name: c.Value for c in row.Data})
|
|
120
|
+
if args.format == "json":
|
|
121
|
+
print(json.dumps(rows, indent=2, ensure_ascii=False))
|
|
122
|
+
else:
|
|
123
|
+
for r in rows:
|
|
124
|
+
print(json.dumps(r, ensure_ascii=False))
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
out = []
|
|
128
|
+
for line in resp.Results or []:
|
|
129
|
+
try:
|
|
130
|
+
fields = json.loads(line.LogJson) if line.LogJson else {}
|
|
131
|
+
except Exception:
|
|
132
|
+
fields = {"_raw": line.LogJson}
|
|
133
|
+
out.append({"time_ms": line.Time, "fields": fields})
|
|
134
|
+
if args.format == "json":
|
|
135
|
+
print(json.dumps(out, indent=2, ensure_ascii=False))
|
|
136
|
+
else:
|
|
137
|
+
for entry in out:
|
|
138
|
+
ts = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(entry["time_ms"] / 1000))
|
|
139
|
+
f = entry["fields"]
|
|
140
|
+
short = " ".join(f"{k}={v}" for k, v in list(f.items())[:6])
|
|
141
|
+
print(f"{ts} {short}")
|
|
142
|
+
if resp.Context:
|
|
143
|
+
print(f"\n# more results: --context {resp.Context}", file=sys.stderr)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ---------------------------------------------------------------------------
|
|
147
|
+
# Argparse
|
|
148
|
+
# ---------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def main() -> int:
|
|
152
|
+
parser = argparse.ArgumentParser(
|
|
153
|
+
description="Search Tencent Cloud CLS log topics.",
|
|
154
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
155
|
+
epilog=__doc__,
|
|
156
|
+
)
|
|
157
|
+
parser.add_argument(
|
|
158
|
+
"--region",
|
|
159
|
+
default=DEFAULT_REGION,
|
|
160
|
+
help=f"Tencent Cloud region (default: {DEFAULT_REGION})",
|
|
161
|
+
)
|
|
162
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
163
|
+
|
|
164
|
+
p_topics = sub.add_parser("topics", help="List log topics")
|
|
165
|
+
p_topics.add_argument("--logset-id", help="Filter to one logset")
|
|
166
|
+
p_topics.add_argument("--format", choices=["text", "json"], default="text")
|
|
167
|
+
p_topics.set_defaults(func=cmd_topics)
|
|
168
|
+
|
|
169
|
+
p_search = sub.add_parser("search", help="Search a topic")
|
|
170
|
+
p_search.add_argument("--topic", required=True, help="TopicId (UUID)")
|
|
171
|
+
g = p_search.add_mutually_exclusive_group(required=True)
|
|
172
|
+
g.add_argument("--query", "-q", help="CQL or SQL query (use `|` for SQL analytics)")
|
|
173
|
+
g.add_argument("--trace-id", help="Shortcut for trace_id:\"<id>\"")
|
|
174
|
+
p_search.add_argument("--time", default="1h", help="Time range (30m, 1h, 6h, 1d, 7d). Default 1h.")
|
|
175
|
+
p_search.add_argument("--limit", type=int, default=100, help="Max results (default 100)")
|
|
176
|
+
p_search.add_argument("--sort", choices=["asc", "desc"], default="desc")
|
|
177
|
+
p_search.add_argument("--lucene", action="store_true", help="Treat --query as Lucene (default CQL)")
|
|
178
|
+
p_search.add_argument("--format", choices=["text", "json"], default="text")
|
|
179
|
+
p_search.set_defaults(func=cmd_search)
|
|
180
|
+
|
|
181
|
+
args = parser.parse_args()
|
|
182
|
+
return args.func(args) or 0
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if __name__ == "__main__":
|
|
186
|
+
raise SystemExit(main())
|