构建API是我们看到的无服务器架构中最常见的使用案例的一个数量级。为什么不呢?结合API Gateway和AWS Lambda来创建API端点是非常容易的,这些端点默认拥有你所需要的所有灾难恢复和负载管理基础设施。创建它们就像将其与无服务器框架结合起来。
functions:
myfunction:
handler: myhandlerfile.myhandlerfunction
events:
- http:
path: myendpoint
method: get
但我们如何去调试和排除API的故障呢?AWS内部的CloudWatch确实(某种程度上)让我们能够轻松访问我们的Lambda日志,而且我们可以打开API网关的日志。但是,如果我们的API开始出现问题,这并不能提供我们需要的所有信息。
这几乎是我们创建Serverless Framework Pro的全部原因,因为它可以帮助Serverless Framework的用户监控和调试他们的Serverless服务;API是其中最主要的。
如
如果你想知道如何将你的一个服务连接到仪表盘上,请确保你已经安装了最新的Serverless版本(npm i -g serverless ,如果你使用的是二进制版本serverless upgrade ),然后在与你的服务相同的文件夹中运行命令serverless 。你将被引导完成所有设置。
登录到CloudWatch
当你试图调试时,你需要有数据,以帮助你确定什么可能导致任何问题。最简单的方法是确保你在需要时使用你的运行时的日志方法。例如,在NodeJS Lambda中,我们可以捕捉到当我们调用其他AWS资源(例如DynamoDB)时出现的任何错误。在这种情况下,编写记录出适当的错误数据的代码可能看起来像这样。
const query = {
TableName: process.env.DYNAMODB_USER_TABLE,
KeyConditionExpression: '#id' = ':id',
ExpressionAttributeNames: {
'#id': id
},
ExpressionAttributeValues: {
':id':'someid'
}
}
let result = {}
try {
const dynamodb = new AWS.DynamoDB.DocumentClient()
result = await dynamodb.query(userQuery).promise()
} catch (queryError) {
console.log('There was an error attempting to retrieve the data')
console.log('queryError', queryError)
console.log('query', query)
return new Error('There was an error retrieving the data: ' + queryError.message)
}
有了这样的安排,就意味着如果我们对DynamoDB的查询因为某种原因出错了,看一下日志就会发现确切的原因。同样的模式可以应用于几乎所有类型的代码,这些代码在执行时有可能出错。
聚合监控
在我们能够排除任何具体的错误之前,往往很难说出是否有任何错误首先发生!尤其是当你在处理一个繁忙的问题时,你会发现有很多错误。特别是当你在处理一个繁忙的生产系统时,你很难判断你的用户是否遇到了任何错误,这就是Serverless Framework Pro通过服务概览屏幕发挥作用的地方。
只要扫一眼这里提供的图表,你就能立即看到是否有任何API请求或Lambda调用返回错误,并以某种方式影响了你的用户,即使他们自己并没有意识到这一点。

通过上面的图片,我不需要等待用户抱怨或报告错误,我可以立即看到一些错误在晚上7点左右开始发生。但这并没有结束。如果不需要我观察这些图表,而只是在有事发生时得到通知,那就更好了。
这就是Serverless Framework Pro通知的作用。通过进入我的应用设置,并在菜单中选择通知,我可以配置将通知发送到一个或几个电子邮件地址、Slack频道、调用Webhook或甚至将通知发送到SNS,这样我就可以有自己的Lambda函数,例如,按照我的要求处理这些通知。

你可以在每个服务和每个阶段配置这些通知,并有你想要的许多通知配置;也许开发阶段通过电子邮件报告,因为他们不是关键,但生产中的错误总是到整个团队的Slack频道。
检索错误细节
既然我现在能够看到错误并被提醒,我需要一些方法来帮助我找出错误是什么以及如何修复它。这在Serverless Framework Pro中又变得相对容易。

你一开始就会看到这样一个概览屏幕,我看到一些错误。让我点击它...

现在我可以看到关于该时间段内的错误的一些摘要信息。让我选择一个来进一步深入了解

在下一个视图中向下滚动,我可以看到Serverless Framework Pro为我提供了处理程序中出现错误的那行代码的堆栈跟踪,这样我就知道该去哪里找了。而且,由于我的详细console.log 行,我的CloudWatch日志显示了与错误有关的数据。(显然,我在这里故意产生了一个错误,以达到演示的目的,但这也适用于实际的错误)。
注意:CloudWatch日志是从你的AWS账户中拉过来的。它们并不存储在Serverless Framework Pro的任何地方,所以当我打开这个详细的视图时,Serverless Framework Pro会向你的AWS账户发出请求以检索日志。如果你从你的账户中删除了CloudWatch日志,它也不会在这里看到。
预防胜于治疗
到目前为止,我们一直在研究如何对错误做出反应。但我们甚至可以更进一步,留意那些可能会导致以后出现问题的问题。例如,如果我们的Lambda函数通常运行一定的时间,比如在50到100毫秒之间,而突然出现了一个高峰,我们的Lambda运行时间超过了200毫秒,这可能表明一个潜在的问题正在酝酿;也许一些下游的供应商出现了问题,如果我们能提前得到一些警告,也许就能把问题解决。同样的事情也可以适用于调用次数。也许我们的Lambda调用活动通常非常稳定,任何突然出现的调用高峰都是我们需要了解的。
Serverless Framework Pro已经为你自动创建了这些警报,你可以选择使用之前显示的通知系统向你发送这些警报的通知。
性能调整
故障排除不一定都是关于错误的。我们可能需要满足某些性能标准,而Serverless Framework Pro也为我们提供了评估的方法。
评估执行时间
每个Lambda函数都可以设置一个内存大小值。但这个设置不仅仅是针对内存的,也会以线性方式影响CPU和网络的分配;如果你把内存翻倍,就会把有效的CPU和网络翻倍。通过点击左边菜单上的功能部分,然后选择一个特定的功能,你可以看到带有虚线的部署持续时间统计。现在你可以立即看到你所做的改变是如何影响部署后调用的平均执行时间的。

你也可以对内存的使用做同样的处理...
SDK和HTTP请求
在Lambda中,我们经常需要通过AWS SDK向其他AWS服务提出请求,甚至向其他第三方服务提出HTTP请求,这些都会对我们的端点性能产生一定的影响。因此,能够衡量这种影响将是非常有用的。
同样,Serverless Framework Pro使我们有可能对此进行调查。在Lambda的详细视图中,我们可以看到跨度部分,它将向我们表明,如果我们发出的请求比它们应该的慢。还记得上面提到的第三方服务的问题吗?那么,通过跨度,我们可以看到请求可能需要多长时间,然后采取适当的行动。

在运行时推送数据
然而,并不是所有我们想看的数据都像我们到目前为止看到的那样虚无缥缈,容易捕捉。有时,我们需要能够分析只有在运行时才能获得的指标和数据。这就是为什么无服务器框架专业版SDK整合了一些功能,以帮助更容易地跟踪这些数据。默认情况下,Serverless Framework Pro会在运行时重载上下文对象,并提供一些额外的函数用于运行时数据捕获。
所有这些选项在Serverless网站上都有记录,包括Node和Python运行时的选项。
捕获错误
在某些情况下,我们可能希望了解一个潜在的错误,而不需要向提出请求的终端用户返回错误。所以我们可以使用captureError方法来代替。
if (context.hasOwnProperty('serverlessSdk')) {
context.serverlessSdk.captureError('Could not put the user')
}
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
'Access-Control-Allow-Headers': 'Authorization'
}
}
正如你在上面看到的,我们只是推送了一条错误信息,但最终返回一个200的响应。而我们的监控将显示它是一个错误。

捕获跨度
我们也可以用同样的方法来捕获任何可能需要时间执行的代码。我们可以把这些代码包在我们自己的自定义跨度中,并看到向我们提供的性能数据。
if(context.hasOwnProperty(‘serverlessSdk’)) {
await context.serverlessSdk.span('HASH', async () => {
return new Promise((resolve, reject) => {
bcrypt.hash("ARANDMOMSTRING", 13, () => {
resolve()
})
})
})
}
以上产生的跨度如下。

仅仅看一下,你就可以立即知道,你的优化重点需要放在HASH跨度上。试图优化其他东西是没有意义的。
捕获标签
最后,有一种方法可以在运行时从调用中捕获键值对,可以在资源管理器视图中过滤。也许一个例子可以让你更容易理解。
你已经建立了一个结账流程,该流程捕获用户的信用卡信息,然后将这些信息传递给第三方支付提供商。我们很多人在过去都建立过这样的功能。通常发生的情况是,在传递这些细节后,响应会显示成功或失败,实际上甚至会解释为什么会失败;缺乏资金、卡片过期、被银行拒绝等等。我们可以对这些不同的状态进行标记,使我们能够更容易地搜索这些状态。它基本上可以让你传递一个键,一个值和额外的上下文数据,如果你需要的话。
if (paymentProvider.status === ‘success’) {
context.serverlessSdk.tagEvent('checkout-status', event.body.customerId, paymentProvider.response
});
}
这样你就可以找到与特定客户ID相关的所有调用,所以如果我们需要找到处理银行卡详细信息的支付提供商的非常具体的日志,我们可以很容易地通过客户ID进行过滤。