Skip to content

Commit

Permalink
feat: support log and metric command
Browse files Browse the repository at this point in the history
  • Loading branch information
yugasun committed Apr 26, 2021
1 parent e724ef8 commit 8e5419d
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 19 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,6 @@ serverless remove
> **说明**:配置详情请参考 [全量配置文档](https://github.com/serverless-components/tencent-scf/blob/master/docs/configure.md)
```
#scf组件配置样例
#全量配置参考https://github.com/serverless-components/tencent-scf/blob/master/docs/configure.md
#组件信息
component: scf # (必填) 引用 component 的名称,当前用到的是 tencent-scf 组件
name: scfdemo # (必填) 创建的实例名称,请修改成您的实例名称
Expand Down Expand Up @@ -125,6 +121,10 @@ Serverless 部署一个组件实例实质是部署了一个单组件实例的应

在应用项目开发过程中,一个应用下可能会存在多个组件实例,如何管理组件实例进行应用项目开发,请参考 [应用管理](https://cloud.tencent.com/document/product/1154/48261)

## 其他命令

[其他命令](./docs/command.md)

## License

MIT License
Expand Down
84 changes: 84 additions & 0 deletions docs/command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# 支持自定义命令

如果当前项目已经部署成功,所有命令可以省略 `function` 参数。

### invoke

**调用函数**,本地调用云函数,如下:

```bash
$ sls invoke --inputs function=scfdemo event='{"hello":{"msg":"world"}}'
```

### log

**查看日志**,查看近一个小时的函数日志,如下:

```bash
$ sls log --inputs function=scfdemo
```

> 注意:函数默认命名空间为 `default`,如果不是,需要带上 namespace=xxx 参数,来指定函数命名空间。
### metric

**查看监控指标**,如下:

```bash
$ sls metric --inputs function=scfdemo
```

> 注意:函数默认命名空间为 `default`,如果不是,需要带上 namespace=xxx 参数,来指定函数命名空间。
默认查看时间粒度为 `1分钟`,目前支持时间粒度为:1 分钟、5 分钟、1 小时,可以通过 `period` 参数指定,单位为 ``,比如:

```bash
# 指定时间粒度为 1分钟
$ sls metric --inputs function=scfdemo period=60
```

默认查看时间范围为 `近 15 分钟`,可以通过制定 `interval` 来指定最近时间段,单位为 ``,比如近 1 个小时:

```
$ sls metric --inputs function=scfdemo period=60 interval=3600
```

### publish-ver

**发布版本**,不部署直接给函数 `scfdemo` 发版本:

```plaintext
sls publish-ver --inputs function=scfdemo
```

### create-alias

**创建别名**,给云函数 `scfdemo` 创建别名 `routing-alias`,路由规则为:版本 1 流量为 50%,版本 2 流量为 50%,如下:

```plaintext
sls create-alias --inputs name=routing-alias function=scfdemo version=1 config='{"weights":{"2":0.5}}'
```

### update-alias

**更新别名**,更新云函数 `scfdemo` 别名 `routing-alias` 的流量规则为版本 1 流量为 10%,版本 2 流量为 90%:

```plaintext
sls --inputs name=routing-alias function=scfdemo version=1 config='{"weights":{"2":0.9}}'
```

### list-alias

**查看别名**,列举云函数 `scfdemo` 别名 `routing-alias`

```plaintext
sls list-alias --inputs function=scfdemo
```

### delete-alias

**删除别名**,删除云函数 `scfdemo` 的别名 `routing-alias`

```plaintext
sls delete-alias --inputs name=routing-alias function=scfdemo
```
14 changes: 8 additions & 6 deletions docs/configure.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,17 @@ inputs:
- ckafka: # ckafka触发器
parameters:
qualifier: $DEFAULT # 别名配置
name: ckafka-2o10hua5
name: ckafka-xxx
topic: test
maxMsgNum: 999
retry: 10000
offset: latest
timeout: 60
enable: true
retry: 10000
- cls: # cls 触发器
parameters:
qualifier: '$DEFAULT' # 别名配置
topicId: '31d3ce01-228b-42f5-aab5-7f740cc2fb11' # 日志主题 ID
topicId: 'xxx-228b-42f5-aab5-7f740cc2fb11' # 日志主题 ID
maxWait: 60 # 最长等待时间,单位秒
enable: true
- mps: # mps 触发器
Expand Down Expand Up @@ -356,9 +357,10 @@ clb - CLB 触发器
| qualifier | 否 | string | `$DEFAULT` | 触发版本,默认为 `$DEFAULT`,即 `默认流量` |
| name | 是 | string | | 配置连接的 CKafka 实例,仅支持选择同地域下的实例。 |
| topic | 是 | string | | 支持在 CKafka 实例中已经创建的 Topic。 |
| maxMsgNum | 是 | number | | 5 秒内每汇聚 maxMsgNum 条 Ckafka 消息,则触发一次函数调用 |
| offset | 是 | string | | offset 为开始消费 Ckafka 消息的位置,目前只能填写 `latest` |
| retry | 是 | number | | 重试次数,函数调用失败时的最大重试次数。 |
| maxMsgNum | 是 | number | `100` | 5 秒内每汇聚 maxMsgNum 条 Ckafka 消息,则触发一次函数调用 |
| offset | 是 | string | `latest` | offset 为开始消费 Ckafka 消息的位置,目前只能填写 `latest` |
| retry | 是 | number | `10000` | 重试次数,函数调用失败时的最大重试次数。 |
| timeout | 是 | number | `60` | 单次触发的最长等待时间,最大 60 秒 |
| enable | 否 | boolean | `false` | 触发器是否启用 |
> 注意:添加 CKafka 触发器,需要给 `SLS_QcsRole` 添加 `QcloudCKafkaFullAccess` 策略。
Expand Down
6 changes: 5 additions & 1 deletion serverless.component.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: scf
version: 0.5.4
version: 0.6.0
author: Tencent Cloud, Inc.
org: Tencent Cloud, Inc.
description: Deploy a serverless cloud function on Tencent Cloud.
Expand Down Expand Up @@ -335,3 +335,7 @@ actions:
definition: Publish a new version of function
invoke:
definition: Invoke function synchronously
log:
definition: Get function logs in 1hour
metric:
definition: Get function metrics
2 changes: 1 addition & 1 deletion src/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"dependencies": {
"download": "^8.0.0",
"tencent-component-toolkit": "^2.5.13",
"tencent-component-toolkit": "^2.7.0",
"type": "^2.0.0"
}
}
75 changes: 69 additions & 6 deletions src/serverless.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
const { Component } = require('@serverless/core')
const { Scf } = require('tencent-component-toolkit')
const { Scf, Monitor } = require('tencent-component-toolkit')
const { ApiTypeError } = require('tencent-component-toolkit/lib/utils/error')
const { prepareInputs, prepareAliasInputs, getType, getDefaultProtocol } = require('./utils')
const {
prepareInputs,
prepareAliasInputs,
getType,
getDefaultProtocol,
formatMetricData
} = require('./utils')
const CONFIGS = require('./config')

class ServerlessComponent extends Component {
Expand Down Expand Up @@ -311,16 +317,18 @@ class ServerlessComponent extends Component {
const region = inputs.region || CONFIGS.region

const invoke_params = {}
invoke_params.functionName = inputs.function
invoke_params.namespace = inputs.namespace
invoke_params.invocationType = 'RequestResponse'
invoke_params.clientContext = inputs.clientContext || {}
invoke_params.clientContext = inputs.event || inputs.clientContext || {}

const functionInfo = this.state.function
const functionName = inputs.function || (functionInfo && functionInfo.FunctionName)
invoke_params.functionName = functionName

console.log(`invoke for function ${inputs.function}...`)
const scf = new Scf(credentials, region)

console.log(`Invoke for function ${inputs.function}`)
const scfOutput = await scf.invoke(invoke_params)
console.log(`invoke for function ${inputs.function}...`)
return scfOutput
} catch (e) {
return {
Expand All @@ -329,6 +337,61 @@ class ServerlessComponent extends Component {
}
}
}

async log(inputs) {
const credentials = this.getCredentials()
const region = inputs.region || CONFIGS.region
const functionInfo = this.state.function
const functionName = inputs.function || (functionInfo && functionInfo.FunctionName)

if (!functionName) {
throw new ApiTypeError(`SCF_method_log`, `[SCF] 参数 function 必须`)
}

console.log(`Get logs for function ${functionName}`)
const scf = new Scf(credentials, region)

const scfOutput = await scf.logs({
functionName: functionName,
namespace: inputs.namespace,
qualifier: inputs.qualifier,
reqId: inputs.reqid
})

return scfOutput
}

async metric(inputs) {
const credentials = this.getCredentials()
const region = inputs.region || CONFIGS.region

const functionInfo = this.state.function
const functionName = inputs.function || (functionInfo && functionInfo.FunctionName)

if (!functionName) {
throw new ApiTypeError(`SCF_method_metric`, `[SCF] 参数 function 必须`)
}

if (!inputs.metric) {
throw new ApiTypeError(`SCF_method_metric`, `[SCF] 参数 metric 必须`)
}

console.log(`Get metric ${inputs.metric} for function ${functionName}`)
const monitor = new Monitor(credentials, region)

const res = await monitor.get({
functionName: functionName,
namespace: inputs.namespace,
alias: inputs.alias,
metric: inputs.metric,
interval: inputs.interval,
period: inputs.period,
startTime: inputs.startTime,
endTime: inputs.endTime
})

return formatMetricData(res)
}
}

module.exports = ServerlessComponent
39 changes: 38 additions & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,11 +346,48 @@ const prepareAliasInputs = (inputs) => {
return outputs
}

function formatNumber(num) {
return num > 9 ? num : `0${num}`
}

function formatDate(timestamp) {
const dStr = `${timestamp}`.length === 10 ? timestamp * 1000 : timestamp
const d = new Date(dStr)
const year = d.getFullYear()
const month = d.getMonth() + 1
const day = d.getDate()
const hour = d.getHours()
const minute = d.getMinutes()
const second = d.getSeconds()

return `${year}-${formatNumber(month)}-${formatNumber(day)} ${formatNumber(hour)}:${formatNumber(
minute
)}:${formatNumber(second)}`
}

function formatMetricData(data) {
const { DataPoints = [] } = data
const list = []
DataPoints.forEach((point) => {
const { Timestamps = [], Values = [] } = point
Timestamps.forEach((time, i) => {
list.push({
time: formatDate(time),
value: Values[i],
timestamp: time
})
})
})

return list
}

module.exports = {
getType,
getDefaultProtocol,
generateId,
prepareInputs,
prepareAliasInputs,
getDefaultZipPath
getDefaultZipPath,
formatMetricData
}

0 comments on commit 8e5419d

Please sign in to comment.