OpenTelemetryのSpanでBody内容をTraceしてみた

# OpenTelemetryのSpanでBody内容をTraceしてみた


## 概要

NestJSを使っている皆さん、アプリケーションのトレースを行う際に、HTTPリクエストのボディ内容を詳細に追跡したいと思ったことはありませんか？この記事では、OpenTelemetryを使用して、NestJSのHTTPリクエストボディをトレースする方法について解説します。


### 目的
この記事では、NestJSとOpenTelemetryを使って、HTTPリクエストボディをトレースする方法を説明します。これにより、デバッグやパフォーマンス監視が容易になります。

### 使用ツール/技術
- NestJS
- OpenTelemetry

## 具体例と詳細説明

### デコレーターの作成

まず、トレース可能なボディデータを持つ関数をデコレーターで定義します。

```typescript
import { TraceableBodyDto } from '@/ui/shared/TraceableBodyDto'
import { trace } from '@opentelemetry/api'

export function TraceBody<T extends TraceableBodyDto>() {
  return (target: unknown, methodName: string, descriptor: TypedPropertyDescriptor<TraceableUIFunction<T>>) => {
    if (process.env.NODE_ENV === 'development') {
      return
    }
    const addFunc = descriptor.value
    descriptor.value = function (body, ...args: Array<unknown>) {
      const span = trace.getTracer('default').startSpan('span')
      span.setAttribute('body', JSON.stringify(body)) // ここでbodyを設定する
      span.end()

      const result = addFunc.apply(this, [body, ...args])
      return result
    } as TraceableUIFunction<T>
  }
}
```
### コード解説: TraceBodyデコレーター

このセクションでは、上記の `TraceBody` デコレーターのコードについて詳細に解説します。

#### デコレーター定義

```typescript
export function TraceBody<T extends TraceableBodyDto>() {
```

- `TraceBody` はジェネリックデコレーター関数で、型パラメータ `T` を受け取ります。`T` は `TraceableBodyDto` を拡張する型でなければなりません。

#### デコレーターの戻り値

```typescript
return (target: unknown, methodName: string, descriptor: TypedPropertyDescriptor<TraceableUIFunction<T>>) => {
```

- このデコレーターは、ターゲットクラス (`target`)、メソッド名 (`methodName`)、およびメソッドのプロパティディスクリプタ (`descriptor`) を受け取る関数を返します。

#### 開発環境のチェック

```typescript
if (process.env.NODE_ENV === 'development') {
  return
}
```

- 現在の環境が `development` である場合、トレースを行わないようにします。この条件が満たされる場合、何もせずに戻ります。

#### メソッドのオーバーライド

```typescript
const addFunc = descriptor.value
descriptor.value = function (body, ...args: Array<unknown>) {
```

- `descriptor.value` に元のメソッド (`addFunc`) を保存します。
- 次に、元のメソッドをオーバーライドして、新しいメソッドを定義します。このメソッドは `body` とその他の引数を受け取ります。

#### スパンの開始と終了

```typescript
const span = trace.getTracer('default').startSpan('span')
span.setAttribute('body', JSON.stringify(body)) // ここでbodyを設定する
span.end()
```

- `trace.getTracer('default').startSpan('span')` を使用して、新しいスパンを開始します。スパンはトレースの一部であり、特定の操作を追跡するための単位です。
- `span.setAttribute('body', JSON.stringify(body))` で、リクエストボディをスパンの属性として設定します。ボディをJSON文字列に変換して保存します。
- `span.end()` でスパンを終了します。

#### 元のメソッドの呼び出し

```typescript
const result = addFunc.apply(this, [body, ...args])
return result
```

- 元のメソッド (`addFunc`) を呼び出し、引数として `body` とその他の引数を渡します。
- 最後に、元のメソッドの結果を返します。


### 呼び出し側

次に、このデコレーターを使用して、特定のメソッドに適用します。
(今回はUI層、bodyを引数に受け取るところで呼び出すような設計にしてます)
```typescript
@TraceBody()
async func(
  @Body() bodyDto: bodyDto,//このdtoはTraceableBodyDtoをextendsしていないと型エラーが出るようになってます
): Promise<ReturnDto> {
 ~~~
}
```
## 総括と結論

OpenTelemetryを使用することで、NestJSアプリケーションにおけるHTTPリクエストのボディ内容を詳細にトレースすることが可能です。この手法を活用することで、デバッグやパフォーマンスの監視が容易になり、アプリケーションの信頼性が向上します。ぜひ、皆さんのプロジェクトで試してみてください。

## 参考記事

- [OpenTelemetry Documentation](https://opentelemetry.io/docs/)
- [NestJS Documentation](https://docs.nestjs.com/)


このガイドを通じて、NestJSとOpenTelemetryを組み合わせてHTTPリクエストのボディ内容をトレースする方法を理解していただけたかと思います。今後の開発において、この記事が皆さんの役に立つことを願っています。