🎍

New Relic の Node.js agent API でデコレータを使って宣言的に Segment を作成する

2021/08/24に公開

Segment とは

New Relic の Node.js agent API では Transaction の中をさらに細分化する方法として、newrelic.startSegment(name, record, handler, callback) 関数が用意されています。
https://docs.newrelic.com/jp/docs/agents/nodejs-agent/api-guides/nodejs-agent-api/#startSegment

この関数は第三引数の handler に関数を渡すことである程度の処理のまとまりについて名前をつけて、New Relic のダッシュボード上で閲覧できるようになります。

以下のコードは https://docs.newrelic.com/jp/docs/agents/nodejs-agent/extend-your-instrumentation/nodejs-custom-instrumentation/#expanding-instrumentation にあるサンプルコードです。

try {
  const client = await nr.startSegment('pg:connect', true, async () => {
    // Async functions simply return promises, so this example is
    // very similar to the promise one.
    return await pg.connect(config.db_string)
  })

  // The transaction context is propagated into the code following `await`.
  try {
    const result = await nr.startSegment('pg:query', true, async () => {
      return await client.query('SELECT count(*) FROM test_count'))
    })

    res.write(result.rows[0].count)
    res.write('\n')
    res.end()
  } catch(err) {
    // Error from querying.
    util.handleError(err, '500', res)
  } finally {
    await client.release()
  }
} catch(err) {
  // Error from connecting.
  util.handleError(err, '500', res)
}

handler に関数を渡す方法は

  • 別途関数を作成するためネストが1つ深くなる
  • 実装の本質ではないコードが処理の上下に追加される

などの性質があり、可読性が低下します。

そこでこの記事ではデコレータを用いて Segment を作成する方法を紹介します。

デコレータを用いて宣言的に Segment を作成する

具体的には以下のようなデコレータを作成します[1]

function Segment(): MethodDecorator {
  return (
    _: any,
    methodName: string | symbol,
    descriptor: TypedPropertyDescriptor<any>
  ) => {
    const originalMethod = descriptor.value;

    descriptor.value = (...args: unknown[]) => {
      return newrelic.startSegment(methodName, true, () => {
        return originalMethod(...args);
      });
    };
  };
}

このデコレータをクラスのメソッドに付与するだけで、Segment を作成することができます。

class Test {
  @Segment()
  async getCount() {
    return new Promise((resolve, reject) => {
      connection.query("SELECT COUNT(*) FROM item", (err, result) => {
        if (err) {
          reject(err);
          return;
        }
        resolve(result);
      });
    });
  }
}

app.get("/count-with-segment", (_, res) => {
  new Test().getCount().then((count) => {
    res.send({ count });
  });
});

New Relic のダッシュボードでは次の画像のように表示されます。CategoryCustom の名前が #getCountSegment が追加されていることがわかります。


脚注
  1. このデコレータは十分に動作確認していないためバグが存在する可能性があります ↩︎

Discussion