golang から Application Insights にログを送信する

# golang から Application Insights にログを送信する

先だって、こんなアナウンスがありました。

[Making Azure the Best Place to Observe Your Apps with OpenTelemetry](https://techcommunity.microsoft.com/t5/azure-observability-blog/making-azure-the-best-place-to-observe-your-apps-with/ba-p/3995896?utm_source=pocket_saves)

> GoやRustなど、現在Distroが提供されていない言語はコミュニティのライブラリで計装して、Azure Monitor Agent経由でAzute Monitor/Application Insightsに送る、という方針

らしいのですが、このあたりあまり情報が整理されていないので、現時点で何ができているのか備忘録代わりに整理しておこうと思います。

全般的に、この辺りについては素人なので、用語や理解が間違っているかもしれませんがあしからず。あくまでも備忘録であり、モチベーションは golang から Application Insights にログを簡単に送信したいというあたりにあります。

## Application Insights SDK

現時点では2つの方法があるようで、そのうち1つは、Application Insights SDKを使う方法です。

[microsoft/ApplicationInsights-Go: Microsoft Application Insights SDK for Go](https://github.com/microsoft/ApplicationInsights-Go)

だたし、現在このSDKはサポートされていないとのことです。

> この SDK は現在、Microsoft によって保守またはサポートされていません。Azure Monitor は、サポートされている SDK を使用する場合にのみサポートを提供し、この SDK はまだその基準を満たしていません。

## Azure Monitor Exporter

もう1つは、Azure Monitor Exporterを使う方法です。Open Telemetry Collector Contribにあります。コミニィティによって提供される拡張機能の1つとして提供されているようです。先のアナウンスで言及しているのはこの機能なのでしょうか。

[opentelemetry-collector-contrib/exporter/azuremonitorexporter at main · open-telemetry/opentelemetry-collector-contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/azuremonitorexporter#readme)

Github Copilot 曰く

- OpenTelemetry Collector Contrib は、OpenTelemetry Collector のコミュニティによって提供される拡張機能の集合です。これには、さまざまなベンダーとの統合を可能にする追加のレシーバー、プロセッサー、エクスポーターなどが含まれます。

- OpenTelemetry Collector 自体は、テレメトリデータを収集、処理、エクスポートするためのツールで、サービス間でテレメトリデータを一元化する役割を果たします。これにより、開発者は一貫した方法でテレメトリデータを収集し、それを任意のバックエンドにエクスポートできます。

- OpenTelemetry Collector Contrib は、この基本的な機能を拡張し、より多くのベンダーとの統合を可能にします。たとえば、Azure Monitor Exporter は、テレメトリデータをAzure Monitor にエクスポートするためのエクスポーターです。

とのことです

## Azure Monitor Exporterを使う

直接、Azure Monitor Exporter を使う方法がよくわからない（そもそも出来るのか）ので、OpenTelemetry Collector というものを経由して使ってみることにします。Azure Monitor Exporter を含むカスタムの OpenTelemetry Collector を作る方法は、こちらの記事を参考にさせていただきました。

[OpenTelemetry Collector Builder (ocb) を使ってお好みの OTel Collector を作る](https://zenn.dev/k6s4i53rx/articles/df59cb65b34ef8)

### カスタムコレクターの作成

#### builder をインストールする

カスタムコレクターを作るために、`builder` をインストールします。

```shell
go install go.opentelemetry.io/collector/cmd/builder@latest
```

#### 設定ファイルを用意する

カスタムコレクター用の設定ファイルを用意します。組み込むレシーバーとエクスポーターを指定するようです。最後の  `azuremonitorexporter` が、Azure Monitor Exporter です。

```yaml
receivers:
  - gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.90.0
exporters:
  - gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.90.0
  - gomod: go.opentelemetry.io/collector/exporter/loggingexporter v0.90.0
  - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/exporter/azuremonitorexporter v0.90.0
```

#### カスタムコレクターを作成する

`builder` を実行して、カスタムコレクターを作成します。`otelcol-custom`という実行ファイルができます。

```shell
builder --config=otelcol-recipe.yaml --output-path=./
```

#### カスタムコレクターを実行する

構成ファイルを用意します。`azuremonitor` の `connection_string` には、Application Insights の接続文字をコピーします。
受信は、grpc で、ポートは 4317 で待ち受けるようにしています。

```yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: "0.0.0.0:4317"

exporters:
  logging:
    verbosity: detailed
    sampling_initial: 5
    sampling_thereafter: 200
  file:
    path: ./log.json
    format: json
  azuremonitor:
    connection_string: InstrumentationKey=...

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [logging, file, azuremonitor]
    logs:
      receivers: [otlp]
      exporters: [logging, file, azuremonitor]
```

#### 実行する

カスタムコレクターを実行します。テレメトリを受信する状態になります。

```shell
./otelcol-custom --config=./otel-config.yaml
2023-12-06T09:58:13.811+0900    info    service@v0.90.0/telemetry.go:86 Setting up own telemetry...
2023-12-06T09:58:13.811+0900    info    service@v0.90.0/telemetry.go:203        Serving Prometheus metrics      {"address": ":8888", "level": "Basic"}
2023-12-06T09:58:13.811+0900    info    exporter@v0.90.0/exporter.go:275        Deprecated component. Will be removed in future releases.       {"kind": "exporter", "data_type": "traces", "name": "logging"}
2023-12-06T09:58:13.813+0900    info    exporter@v0.90.0/exporter.go:275        Deprecated component. Will be removed in future releases.       {"kind": "exporter", "data_type": "logs", "name": "logging"}
2023-12-06T09:58:13.815+0900    info    service@v0.90.0/service.go:148  Starting otelcol-custom...      {"Version": "1.0.0", "NumCPU": 16}
2023-12-06T09:58:13.815+0900    info    extensions/extensions.go:34     Starting extensions...
2023-12-06T09:58:13.815+0900    warn    internal@v0.90.0/warning.go:40  Using the 0.0.0.0 address exposes this server to every network interface, which may facilitate Denial of Service attacks        {"kind": "receiver", "name": "otlp", "data_type": "traces", "documentation": "https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/security-best-practices.md#safeguards-against-denial-of-service-attacks"}
2023-12-06T09:58:13.815+0900    info    otlpreceiver@v0.90.0/otlp.go:83 Starting GRPC server    {"kind": "receiver", "name": "otlp", "data_type": "traces", "endpoint": "0.0.0.0:4317"}
2023-12-06T09:58:13.815+0900    info    service@v0.90.0/service.go:174  Everything is ready. Begin running and processing data.
```

### HTTP サーバーのテレメトリを送信する

いろいろとググっていたら、こんなコードを見つけました。多少修正して実行します。

[opentelemetry-examples/go-example/auto-instrumentation/main.go at master · wavefrontHQ/opentelemetry-examples](https://github.com/wavefrontHQ/opentelemetry-examples/blob/master/go-example/auto-instrumentation/main.go)

そして、`curl http://localhost:8080` でアクセスしてみます。

カスタムコレクターの stdout にログが表示されるます。これは、`loggingexporter` によるものです。

```
[2023-12-06T10:07:43.473+0900      info    TracesExporter  {"kind": "exporter", "data_type": "traces", "name": "logging", "resource spans": 1, "spans": 1}
2023-12-06T10:07:43.474+0900    info    ResourceSpans #0
Resource SchemaURL: 
Resource attributes:
     -> application: Str(moris-app)
     -> service.name: Str(moris-sample-service)
ScopeSpans #0
ScopeSpans SchemaURL: 
InstrumentationScope go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin 0.46.1
Span #0
    Trace ID       : 9ebd15b66d7511d01edd152b9c4e027f
    Parent ID      : 
    ID             : 6fe08f157260ecb1
    Name           : /
    Kind           : Server
    Start time     : 2023-12-06 01:07:43.47331266 +0000 UTC
    End time       : 2023-12-06 01:07:43.473330084 +0000 UTC
    Status code    : Unset
    Status message : 
Attributes:
     -> http.method: Str(GET)
     -> http.scheme: Str(http)
     -> http.flavor: Str(1.1)
     -> net.host.name: Str(hoge)
     -> net.host.port: Int(8080)
     -> net.sock.peer.addr: Str(::1)
     -> net.sock.peer.port: Int(41280)
     -> http.user_agent: Str(Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36)
     -> http.route: Str(/)
     -> http.status_code: Int(200)
        {"kind": "exporter", "data_type": "traces", "name": "logging"}
2023-12-06T10:07:47.436+0900    info    TracesExporter  {"kind": "exporter", "data_type": "traces", "name": "logging", "resource spans": 1, "spans": 1}
2023-12-06T10:07:47.436+0900    info    ResourceSpans #0
Resource SchemaURL: 
Resource attributes:
     -> application: Str(moris-app)
     -> service.name: Str(moris-sample-service)
ScopeSpans #0
ScopeSpans SchemaURL: 
InstrumentationScope go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin 0.46.1
Span #0
    Trace ID       : 9ebd15b66d7511d01edd152b9c4e027f
    Parent ID      : 
    ID             : 6fe08f157260ecb1
    Name           : /
    Kind           : Server
    Start time     : 2023-12-06 01:07:43.47331266 +0000 UTC
    End time       : 2023-12-06 01:07:43.473330084 +0000 UTC
    Status code    : Unset
    Status message : 
Attributes:
     -> http.method: Str(GET)
     -> http.scheme: Str(http)
     -> http.flavor: Str(1.1)
     -> net.host.name: Str(hoge)
     -> net.host.port: Int(8080)
     -> net.sock.peer.addr: Str(::1)
     -> net.sock.peer.port: Int(41280)
     -> http.user_agent: Str(Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36)
     -> http.route: Str(/)
     -> http.status_code: Int(200)
        {"kind": "exporter", "data_type": "traces", "name": "logging"}
```

次に Application Insights にログが送られているか確認します。添付画像のように、リクエストテレメトリにログが送信されていることが確認できます。

サーバーリクエストに反応していることがグラフからわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34017/63fcca04-8bbf-579e-50c0-2083da1a4a1d.png)

ログからリクエストを抽出してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34017/800e47f5-a479-0151-ef5e-5071e6a0b803.png)

ログも構造化されていて、多々メタ情報が記録されていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34017/2e7adcfb-79f4-0926-2c89-81a4665c595c.png)

## 雑感

- とりあえず、HTTPサーバーのリクエストを記録してみましたが、できれば grpc サーバーのログとか、通常のzapのログとかも収集してみたいです。

- Java や .NET からの利用になれていると色々と手間がかかる感じです。Open Telemetry についてもう少し知見を得ないと、実際の利用では苦労しそうですし。

- Application Insights SDK は サポートされないとのことですが、Azure Monitor Exporter からは、このSDKに依存があります。このあたり、どうなるのかちょっと気になります。

以上