更新日志

  • 2023-04-23 13:00: 增加性能和问题分配的实践
  • 2023-04-23 16:41: 增加问题过滤实践
  • 2023-05-06 16:50: 增加自定义上下文和实践

1. 实践一: 性能优化

最近的实践发现服务加上 Sentry 会性能降低很多, 做了一些对比测试即有 Sentry 和无 Sentry 的相同 API 访问测试, 发现有 Sentry 很影响性能, 推断影响性能的原因:

  • Sentry 默认会开启性能监控, 性能监控会进行采样, 采样会影响性能
  • 服务本身除了集成 Sentry 还开启了 Source Map, 该特性也影响性能

省略测试过程, 测试结果如下:

  • Source Map 和 Sentry 都会影响性能
  • 影响性能因素:
    • Sentry Trace
    • Sentry Senssion
    • Source Map
  • Sentry Trace 影响效果 > Source Map
  • 减少 Sentry Trance Sample Rate 可以提高性能

根据以上测试和实验, 最佳实践是: 保留 Source Map, 禁用性能监控. 具体做法如下:

1
2
3
4
5
6
7
Sentry.AWSLambda.init({
dsn: process.env.SENTRY_DSN,
environment,
enableTracing: false,
autoSessionTracking: false,
enabled: environment !== "local",
});

2. 实践二: 问题自动分配

Sentry 的 Issue 默认不会分配到某个人, 但是通过配置归属规则可以指定哪些 Issue 分配到哪个人负责解决问题, 这个分配的行为和 Github Issue Assign 的行为一样. 具体设置分配规则可以参考文档: ownership-rules, Sentry 的规则写法是: type:pattern owners

  • type: 匹配类型, 可以选择: path, module, url, tag
  • pattern: 是默认匹配, 匹配规则: https://en.wikipedia.org/wiki/Glob_(programming)
  • owners: 是分配的人或者团队, 分配到人可以写邮箱, 分配到团队可以写团队名称(带#)

假设我们是用使用 Lambda, user-service 有问题分配给 a(a@mail.com), report-service 有问题分配给 b(b@mail.com)则规则如下:

1
2
tags.transaction:user-service* a@mail.com
tags.transaction:report-service* b@mail.com

其中tags.transaction是 Sentry SDK 集成后默认会有的 Tag, 根据分析, 这个值是和 Lambda 的名字有关, 而*是为了默认匹配, 因为这个服务被部署到多个环境, 所以后缀是不一样的

3. 实践三: 异常过滤

为了避免非必要的异常淹没重要的异常, 也为了开发者可以更关注, 过滤异常可以有以下两个方法:

3.1 捕获时过滤

因为捕获是通过 SDK 的captureException方法实现的, 所以可以在执行captureException之前进行条件判断, 代码如下:

1
2
3
4
5
6
7
// 自定义错误
class ValidationError extends Error {}

// 捕获前过滤
if (exception.constructor.name !== "ValidationError") {
Sentry.captureException(exception);
}

3.2 通知时过滤

创建 Alert 时可以增加过滤, 这个方式与上面的方式不同之处是: 通知时过滤 Event 是已经被捕获和发送了, 只是在通知的时候不关注. 继续使用3.1中的案例, 通知时过滤的过滤条件可以如下设置:

The event’s exception.type value does not equal ValidationError

当然通知过滤支持更丰富的过滤条件, 具体可以从这里查看.

4. 实践四: 自定义上下文

4.1 自定义用户

在捕获异常的中间中获取用户上下文, 组装成 Sentry 的用户对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Injectable()
export class SentryInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
tap(null, (exception) => {
const cxt = context.switchToHttp();
const req = cxt.getRequest<GuardedRequest>();
const captureContext: CaptureContext = {
extra: {
method: req.method,
url: req.url,
params: req.params,
},
tags: {
funcName: context.getHandler().name,
className: context.getClass().name,
},
};
if (req.userInfo) {
captureContext.user = {
id: req.userInfo.sub,
email: req.userInfo.email + "",
username: req.userInfo.name + "",
};
}
Sentry.captureException(exception, captureContext);
})
);
}
}

注意:
上面代码中的userInfo是 JWTToken 解析后放置到 req 对象上的, express/nest 等框架中间件均可以这样实现

4.2 自定义上下文

在使用 AWS Lambda 的 Sentry 监控中, 通常使用Sentry.AWSLambda.wrapHandler包装 handler 函数, 但是这个wrapHandler中的 CloudWatch 地址在国内是错误的, 这里定义一个帮助函数, 执行这个函数增加正确的 CloudWatch 上下文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Context } from "aws-lambda";

const configCnCloudWatchContext = (context: Context) => {
const hub = Sentry.getCurrentHub();
const scope = hub.getScope();
scope.setContext("aws.cloudwatch.logs.cn", {
log_group: context.logGroupName,
log_stream: context.logStreamName,
url: `https://${
process.env.AWS_REGION.startsWith("cn-")
? `${process.env.AWS_REGION}.console.amazonaws.cn`
: "console.aws.amazon.com"
}/cloudwatch/home?region=${
process.env.AWS_REGION
}#logsV2:log-groups/log-group/${encodeURIComponent(
context.logGroupName
)}/log-events/${encodeURIComponent(context.logStreamName)}?filterPattern="${
context.awsRequestId
}"`,
});
};