此页面需要javascript支持,请在浏览器中启用javascript

sentry监控for node(1)

性能
Performance
node
express
sentry
共1231个字,阅读时间 6 分钟
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://icebreaker.top/articles/2021/1/23-sentry-node-1

sentry for node.js

看完 Vue , 其中有很大一块在 @sentry/types , @sentry/browser@sentry/utils

这一块到时候每个都会发一些文章,来详细介绍下

今天我们来看看 @sentry/node, 这个包和上一篇文章介绍的 @sentry/vue 有很大的不同,
它是面向服务端的,而且是 @sentry/serverless 的基石
虽然 @sentry/serverless 目前支持的都是国外云的公司,但是我们完全有能力,为它添加 2 个定制的 provider (阿里云和腾讯云)

Framework

看了一下源码,从写代码的思路风格来说,明显和 @sentry/vue 不是同一个人写的
git 记录也佐证了我的猜测

init

初始化,调整完 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

Express / Connect

从使用指南中,我们看到了 Sentry.Handlers.requestHandler()Sentry.Handlers.errorHandler()

同样照理说 errorHandler 应该放在所有需要被捕获的中间件的后面,而 requestHandler 应该放在所有需要被监控的中间的前面,来形成一套洋葱结构

Express / Connect 处理上非常相似,所以就一起写了

handlers 源码的角度看

requestHandlererrorHandler 都只是返回处理中间的闭包而已

看了一下处理默认处理逻辑:

  • error 默认处理 http code >= 500 (可自定义)
  • request 去劫持 node 原生 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 (第四声) , 所以作者用这种方式告诉我们这些使用者们

程序要死啦,程序要死啦!赶紧看看处理一下吧(笑~)

Koa

先直接上代码

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 ,scopethis.pushScope()

pushScopepopScope 做了啥事呢,为什么看命名这么像一个 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

Scope.clone

一个 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;

上面是一些字段的解释,算是很原子化的东西了

getScope

不过就是把栈顶的东西的scope返回出来

其他 api 就一下子知道了

withScope 就是一个作用域下,执行完 callback (scope) 后立马销毁呗

addEventProcessor

_eventProcessors 里加个事件,可链式调用

parseRequest

本质就是根据传入的 eventreq , options,去提取 req , options上的信息,赋给 event 上,并且把 event 返回出来,嗯,不是纯函数,js 的魅力之一,但是要先禁用 no-param-reassign

captureException

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 这个对象值得好好看看