Skip to content

Commit 40ede8d

Browse files
committed
feat(cls): support alarm and notice
1 parent 96d78f1 commit 40ede8d

File tree

13 files changed

+659
-17
lines changed

13 files changed

+659
-17
lines changed

.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,7 @@ CFS_VPC_ID=vpc-xxx
2222
CFS_SUBNET_ID=subnet-xxx
2323

2424
# apigw OAUTH PUBLIC_KEY
25-
API_PUBLIC_KEY=
25+
API_PUBLIC_KEY=
26+
27+
# CLS 通知用户 ID
28+
NOTICE_UIN=

__tests__/cls/alarm.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { CreateAlarmOptions, CreateAlarmResult } from '../../src/modules/cls/interface';
2+
import { ClsAlarm } from '../../src';
3+
4+
describe('Cls Alarm', () => {
5+
const credentials = {
6+
SecretId: process.env.TENCENT_SECRET_ID,
7+
SecretKey: process.env.TENCENT_SECRET_KEY,
8+
};
9+
const client = new ClsAlarm(credentials, process.env.REGION);
10+
11+
let detail: CreateAlarmResult;
12+
13+
const options: CreateAlarmOptions = {
14+
name: 'serverless-unit-test',
15+
logsetId: '5e822560-4cae-4037-9ec0-a02f8774446f',
16+
topicId: '6e60b6c7-a98e-4fc8-8ba8-bdfe4ab9c245',
17+
targets: [
18+
{
19+
period: 15,
20+
query: 'level:error | select count(*) as errCount',
21+
},
22+
{
23+
period: 10,
24+
query: 'level:error | select count(*) as errCount',
25+
},
26+
],
27+
monitor: {
28+
type: 'Period',
29+
time: 1,
30+
},
31+
trigger: {
32+
condition: '$1.count > 1',
33+
count: 2,
34+
period: 15,
35+
},
36+
noticeId: 'notice-4271ef11-1b09-459f-8dd1-b0a411757663',
37+
};
38+
39+
test('create', async () => {
40+
const res = await client.create(options);
41+
expect(res).toEqual({
42+
...options,
43+
id: expect.stringContaining('alarm-'),
44+
});
45+
46+
detail = res;
47+
});
48+
49+
test('update', async () => {
50+
const res = await client.create(detail);
51+
expect(res).toEqual({
52+
...options,
53+
id: expect.stringContaining('alarm-'),
54+
});
55+
});
56+
57+
test('delete', async () => {
58+
await client.delete({ id: detail.id! });
59+
const res = await client.get({ id: detail.id });
60+
expect(res).toBeNull();
61+
});
62+
});

__tests__/cls/cls.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ClsDeployInputs, ClsDeployOutputs } from '../../src/modules/cls/interface';
1+
import { DeployInputs, DeployOutputs } from '../../src/modules/cls/interface';
22
import { Scf } from '../../src';
33
import { Cls } from '../../src';
44
import { sleep } from '@ygkit/request';
@@ -11,9 +11,9 @@ describe('Cls', () => {
1111
const scf = new Scf(credentials, process.env.REGION);
1212
const client = new Cls(credentials, process.env.REGION);
1313

14-
let outputs: ClsDeployOutputs;
14+
let outputs: DeployOutputs;
1515

16-
const inputs: ClsDeployInputs = {
16+
const inputs: DeployInputs = {
1717
region: 'ap-guangzhou',
1818
name: 'cls-test',
1919
topic: 'cls-topic-test',

__tests__/cls/notice.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { CreateNoticeOptions, CreateNoticeResult } from '../../src/modules/cls/interface';
2+
import { ClsNotice } from '../../src';
3+
4+
describe('Cls Alarm', () => {
5+
const credentials = {
6+
SecretId: process.env.TENCENT_SECRET_ID,
7+
SecretKey: process.env.TENCENT_SECRET_KEY,
8+
};
9+
const client = new ClsNotice(credentials, process.env.REGION);
10+
11+
let detail: CreateNoticeResult;
12+
13+
const options: CreateNoticeOptions = {
14+
name: 'serverless-unit-test',
15+
type: 'All',
16+
receivers: [
17+
{
18+
start: '00:00:00',
19+
end: '23:59:59',
20+
type: 'Uin',
21+
ids: [Number(process.env.NOTICE_UIN)],
22+
channels: ['Email', 'Sms', 'WeChat', 'Phone'],
23+
},
24+
],
25+
webCallbacks: [
26+
{
27+
type: 'WeCom',
28+
url: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx',
29+
body: '【腾讯云】日志服务CLS监控告警\n您好,您账号(账号UIN:{{.UIN}},昵称:{{.User}})下的日志服务告警策略(策略ID:{{.AlarmId}},策略名:{{.AlarmName}})触发告警:\n监控对象:{{.TopicName}}\n触发条件:持续满足条件{{.Condition}}达{{.ConsecutiveAlertNums}}次\n触发时间:最近于{{.TriggerTime}}发现异常\n您可以登录腾讯云日志服务控制台查看。',
30+
},
31+
{
32+
type: 'Http',
33+
headers: ['Content-Type: application/json'],
34+
method: 'POST',
35+
url: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx',
36+
body: '{\n "UIN":"{{.UIN}}",\n "User":"{{.User}}}",\n "AlarmID":"{{.AlarmID}}",\n "AlarmName":"{{.AlarmName}}",\n "TopicName":"{{.TopicName}}",\n "Condition":"{{.Condition}}",\n "TriggerTime":"{{.TriggerTime}}"\n}',
37+
},
38+
],
39+
};
40+
41+
test('create', async () => {
42+
const res = await client.create(options);
43+
expect(res).toEqual({
44+
...options,
45+
id: expect.stringContaining('notice-'),
46+
});
47+
48+
detail = res;
49+
});
50+
51+
test('update', async () => {
52+
const res = await client.create(detail);
53+
expect(res).toEqual({
54+
...options,
55+
id: expect.stringContaining('notice-'),
56+
});
57+
});
58+
59+
test('delete', async () => {
60+
await client.delete({ id: detail.id! });
61+
const res = await client.get({ id: detail.id });
62+
expect(res).toBeNull();
63+
});
64+
});

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
"@types/jest": "^26.0.20",
8888
"@types/node": "^14.14.31",
8989
"@ygkit/request": "^0.1.8",
90+
"camelcase": "^6.2.0",
9091
"cos-nodejs-sdk-v5": "^2.9.20",
9192
"dayjs": "^1.10.4",
9293
"moment": "^2.29.1",

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export { default as Layer } from './modules/layer';
1313
export { default as Cfs } from './modules/cfs';
1414
export { default as Cynosdb } from './modules/cynosdb';
1515
export { default as Cls } from './modules/cls';
16+
export { default as ClsAlarm } from './modules/cls/alarm';
17+
export { default as ClsNotice } from './modules/cls/notice';
1618
export { default as Clb } from './modules/clb';
1719
export { default as Monitor } from './modules/monitor';
1820
export { default as Account } from './modules/account';

src/modules/cls/alarm.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { Capi } from '@tencent-sdk/capi';
2+
import { CreateAlarmOptions, AlarmInfo, AlarmDetail } from './interface';
3+
import APIS, { ActionType } from './apis';
4+
import { pascalCaseProps, camelCaseProps } from '../../utils';
5+
import { ApiError } from '../../utils/error';
6+
import { ApiServiceType, CapiCredentials, RegionType } from '../interface';
7+
import { formatAlarmOptions } from './utils';
8+
9+
export default class Alarm {
10+
credentials: CapiCredentials;
11+
capi: Capi;
12+
region: RegionType;
13+
14+
constructor(credentials: CapiCredentials, region: RegionType = 'ap-guangzhou') {
15+
this.credentials = credentials;
16+
this.region = region;
17+
18+
this.capi = new Capi({
19+
Region: region,
20+
ServiceType: ApiServiceType.cls,
21+
SecretId: this.credentials.SecretId!,
22+
SecretKey: this.credentials.SecretKey!,
23+
Token: this.credentials.Token,
24+
});
25+
}
26+
27+
/**
28+
* 获取告警详情
29+
* @param options 告警 id 或者 name
30+
* @returns 告警详情
31+
*/
32+
async get({ id, name }: { id?: string; name?: string }): Promise<AlarmDetail | null> {
33+
if (!id && !name) {
34+
throw new ApiError({
35+
type: 'PARAMETER_ERROR',
36+
message: `Alarm id or name is required`,
37+
});
38+
}
39+
let filter = {
40+
Key: 'name',
41+
Values: [name],
42+
};
43+
if (id) {
44+
filter = {
45+
Key: 'alarmId',
46+
Values: [id],
47+
};
48+
}
49+
const { Alarms = [] }: { Alarms: AlarmInfo[] } = await this.request({
50+
Action: 'DescribeAlarms',
51+
Filters: [filter],
52+
Offset: 0,
53+
Limit: 100,
54+
});
55+
const detail = Alarms.find((alarm) => alarm.Name === name || alarm.AlarmId === id);
56+
if (detail) {
57+
return camelCaseProps(detail as AlarmInfo);
58+
}
59+
return null;
60+
}
61+
62+
async create(options: CreateAlarmOptions): Promise<CreateAlarmOptions & { id: string }> {
63+
const detail = await this.get({ name: options.name });
64+
const alarmOptions = formatAlarmOptions(options);
65+
let id = '';
66+
if (detail) {
67+
id = detail.alarmId;
68+
await this.request({
69+
Action: 'ModifyAlarm',
70+
AlarmId: id,
71+
...alarmOptions,
72+
});
73+
} else {
74+
const { AlarmId } = await this.request({
75+
Action: 'CreateAlarm',
76+
...alarmOptions,
77+
});
78+
id = AlarmId;
79+
}
80+
81+
return {
82+
...options,
83+
id,
84+
};
85+
}
86+
87+
async delete({ id, name }: { id?: string; name?: string }) {
88+
if (!id && !name) {
89+
throw new ApiError({
90+
type: 'PARAMETER_ERROR',
91+
message: `Alarm id or name is required`,
92+
});
93+
}
94+
if (id) {
95+
const detail = await this.get({ id });
96+
if (detail) {
97+
await this.request({
98+
Action: 'DeleteAlarm',
99+
AlarmId: id,
100+
});
101+
} else {
102+
console.log(`Alarm ${id} not exist`);
103+
}
104+
}
105+
if (name) {
106+
const detail = await this.get({ name });
107+
if (detail) {
108+
await this.request({
109+
Action: 'DeleteAlarm',
110+
AlarmId: detail.alarmId,
111+
});
112+
} else {
113+
console.log(`Alarm ${name} not exist`);
114+
}
115+
}
116+
return true;
117+
}
118+
119+
async request({ Action, ...data }: { Action: ActionType; [key: string]: any }) {
120+
const result = await APIS[Action](this.capi, pascalCaseProps(data));
121+
return result;
122+
}
123+
}

src/modules/cls/apis.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { ApiFactory } from '../../utils/api';
2+
import { ApiServiceType } from '../interface';
3+
4+
const ACTIONS = [
5+
'CreateAlarm',
6+
'ModifyAlarm',
7+
'DescribeAlarms',
8+
'DeleteAlarm',
9+
'CreateAlarmNotice',
10+
'ModifyAlarmNotice',
11+
'DeleteAlarmNotice',
12+
'DescribeAlarmNotices',
13+
] as const;
14+
15+
export type ActionType = typeof ACTIONS[number];
16+
17+
const APIS = ApiFactory({
18+
debug: false,
19+
isV3: true,
20+
serviceType: ApiServiceType.cls,
21+
version: '2020-10-16',
22+
actions: ACTIONS,
23+
});
24+
25+
export default APIS;

0 commit comments

Comments
 (0)