看完 Vue , 其中有很大一块在 @sentry/types
, @sentry/browser
和 @sentry/utils
这一块到时候每个都会发一些文章,来详细介绍下
今天我们来看看 @sentry/node
, 这个包和上一篇文章介绍的 @sentry/vue
有很大的不同,
它是面向服务端的,而且是 @sentry/serverless
的基石
虽然 @sentry/serverless
目前支持的都是国外云的公司,但是我们完全有能力,为它添加 2 个定制的 provider
(阿里云和腾讯云)
看了一下源码,从写代码的思路风格来说,明显和 @sentry/vue
不是同一个人写的
git 记录也佐证了我的猜测
初始化,调整完 options
if ((domain as any).active) {
setHubOnCarrier(getMainCarrier(), getCurrentHub());
}
initAndBind(NodeClient, options);
我们先不看 domain.active
那一块,那一块牵扯到了 @sentry/hub
和 @sentry/core
initAndBind
也是 @sentry/core
中的一个方法,传入 2 个参数,一个 是个 NodeClient class
一个是 options
从使用指南中,我们看到了 Sentry.Handlers.requestHandler()
和 Sentry.Handlers.errorHandler()
同样照理说 errorHandler
应该放在所有需要被捕获的中间件的后面,而 requestHandler
应该放在所有需要被监控的中间的前面,来形成一套洋葱结构
Express / Connect
处理上非常相似,所以就一起写了
从 handlers
源码的角度看
requestHandler
和 errorHandler
都只是返回处理中间的闭包而已
看了一下处理默认处理逻辑:
end
方法,通过 promise , 先 flush
然后 res.end
之后有错误就 logger.error(e)
它这里的,居然没有用 .catch(e=>{})
而是 .then(null,e=>{})
, 虽然效果一样,但是我不理解已经用 typescript
的情况下,为什么要这么写?
node domain 这块我也不懂,得去学习一下,看了一下 node lts api
发现domain
这个包已经被废弃了
用来 简化异步代码的异常处理 ,难道现在都用 await + try/catch
const local = domain.create();
local.add(req);
local.add(res);
local.on('error', next);
local.run(() => {
getCurrentHub().configureScope(scope =>
scope.addEventProcessor((event: Event) => parseRequest(event, req, options)),
);
next();
});
理解不能....
ps: 写到这突然想吐槽一下某 route
> layer
的那种写法
// express
if (fn.length > 3) {
return next();
}
if (fn.length !== 4) {
return next(error);
}
// connect
if (hasError && arity === 4) {
// error-handling middleware
handle(err, req, res, next);
return;
} else if (!hasError && arity < 4) {
// request-handling middleware
handle(req, res, next);
return;
}
上面这段代码,从侧面说明了 express / connect
作者受中华文化影响之深刻啊!
大家都知道,4 通 si (第四声) , 所以作者用这种方式告诉我们这些使用者们
程序要死啦,程序要死啦!赶紧看看处理一下吧(笑~)
先直接上代码
app.on("error", (err, ctx) => {
Sentry.withScope(function(scope) {
scope.addEventProcessor(function(event) {
return Sentry.Handlers.parseRequest(event, ctx.request);
});
Sentry.captureException(err);
});
});
koa 这种方式就很舒服,EventEmitter
的腔调
Sentry.withScope
从 @sentry/hub
来看介绍
Sentry hub which handles global state managment.
处理全局状态管理的
public withScope(callback: (scope: Scope) => void): void {
const scope = this.pushScope();
try {
callback(scope);
} finally {
this.popScope();
}
}
withScope
接受一个 callback
, callback
传入 scope
,scope
从 this.pushScope()
来
那 pushScope
和 popScope
做了啥事呢,为什么看命名这么像一个 Stack
// pushScope
public pushScope(): Scope {
// We want to clone the content of prev scope
const scope = Scope.clone(this.getScope());
this.getStack().push({
client: this.getClient(),
scope,
});
return scope;
}
// popScope
public popScope(): boolean {
if (this.getStack().length <= 1) return false;
return !!this.getStack().pop();
}
看上去还是不懂,得继续往上递归看 getStack
, Scope.clone(this.getScope())
, getClient
等等 api
一个 static clone
方法,重新赋值制造一个 Scope
对象
// class Scope
/** Flag if notifiying is happening. */
protected _notifyingListeners: boolean = false;
/** Callback for client to receive scope changes. */
protected _scopeListeners: Array<(scope: Scope) => void> = [];
/** Callback list that will be called after {@link applyToEvent}. */
protected _eventProcessors: EventProcessor[] = [];
/** Array of breadcrumbs. */
protected _breadcrumbs: Breadcrumb[] = [];
/** User */
protected _user: User = {};
/** Tags */
protected _tags: { [key: string]: Primitive } = {};
/** Extra */
protected _extra: Extras = {};
/** Contexts */
protected _contexts: Contexts = {};
/** Fingerprint */
protected _fingerprint?: string[];
/** Severity */
protected _level?: Severity;
/** Transaction Name */
protected _transactionName?: string;
/** Span */
protected _span?: Span;
/** Session */
protected _session?: Session;
上面是一些字段的解释,算是很原子化的东西了
不过就是把栈顶的东西的scope
返回出来
其他 api 就一下子知道了
withScope 就是一个作用域下,执行完 callback (scope) 后立马销毁呗
_eventProcessors
里加个事件,可链式调用
本质就是根据传入的 event
和 req
, options
,去提取 req
, options
上的信息,赋给 event
上,并且把 event
返回出来,嗯,不是纯函数,js 的魅力之一,但是要先禁用 no-param-reassign
form core
=> minimal
没理解为什么要自己 throw 再赋值
export function captureException(exception: any, captureContext?: CaptureContext): string {
let syntheticException: Error;
try {
throw new Error('Sentry syntheticException');
} catch (exception) {
syntheticException = exception as Error;
}
return callOnHub('captureException', exception, {
captureContext,
originalException: exception,
syntheticException,
});
}
不过也是通过 hub 这个 func 去处理所有的参数的
hub 这个对象值得好好看看