SigNozでRailsのAPMを取得&ログを無理やり転送する

## 結論

- SigNozでAPMを取得するのはわりかし簡単
- 2024/02現在でログを転送するは若干面倒

## 前提条件

- Ruby on Rails7.1の環境をDockerとdocker-compose.ymlで構築済み


## 手順

### SigNozを構築する

https://signoz.io/docs/install/docker/

を手順通りに実行

```sh
git clone -b main https://github.com/SigNoz/signoz.git && cd signoz/deploy/
docker compose -f docker/clickhouse-setup/docker-compose.yaml up -d
```

http://localhost:3301

にアクセス

### SigNozでログの転送準備

https://signoz.io/docs/userguide/send-logs-http/
の手順通りに

- deploy/docker/clickhouse-setup/docker-compose.yamlの8082ポートを解放する

```deploy/docker/clickhouse-setup/docker-compose.yaml
...
otel-collector:
    image: signoz/signoz-otel-collector:0.88.11
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "8082:8082" ## ここのコメントアウトを外す
...
```


- deploy/docker/clickhouse-setup/otel-collector-config.yamlにhttplogreceiverを追加

```deploy/docker/clickhouse-setup/otel-collector-config.yaml
receivers: 
  httplogreceiver/json: #追加
    endpoint: 0.0.0.0:8082 #追加
    source: json #追加
...

service:
    ....
    logs:
        receivers: [otlp, httplogreceiver/json]　 #httplogreceiver/jsonを追加
        processors: [batch]
        exporters: [clickhouselogsexporter]
```

```sh
docker compose -f docker/clickhouse-setup/docker-compose.yaml up -d
もしくは
docker compose -f docker/clickhouse-setup/docker-compose.yaml restart
でSigNozを再起動する
```

試しにログを送ってみる

```sh
curl --location 'http://localhost:8082' \
--header 'Content-Type: application/json' \
--data '[
  {
      "trace_id": "000000000000000018c51935df0b93b9",
      "span_id": "18c51935df0b93b9",
      "trace_flags": 0,
      "severity_text": "info",
      "severity_number": 4,
      "attributes": {
          "method": "GET",
          "path": "/api/users"
      },
      "resources": {
          "host": "myhost",
          "namespace": "prod"
      },
      "message": "This is a log line"
  }
]'
```

`This is a log line` というログがSigNozで出たら成功！

### Railsの設定

https://signoz.io/blog/opentelemetry-ruby/
の手順通りに

Gemfileに以下を追加して


```Gemfile
gem 'opentelemetry-sdk'
gem 'opentelemetry-exporter-otlp'
gem 'opentelemetry-instrumentation-all'
```

```sh
bundle install
```
を実行

- config/environment.rbを編集


```rb
require 'opentelemetry/sdk'
require_relative 'application'

OpenTelemetry::SDK.configure do |c|
  c.use_all
end

Rails.application.initialize!
```

docker-compose.ymlを編集

```docker-compose.yml
  version: "3"
  services:
    rails
.
.
.

      environment:
        OTEL_EXPORTER: otlp
        OTEL_SERVICE_NAME: sample
        OTEL_EXPORTER_OTLP_ENDPOINT: http://localhost:4318
        OTEL_EXPORTER_OTLP_ENDPOINT_LOG: http://localhost:8082

```

環境変数にOTEL_EXPORTER/OTEL_SERVICE_NAME/OTEL_EXPORTER_OTLP_ENDPOINT/OTEL_EXPORTER_OTLP_ENDPOINT_LOGを追加する

### Logを転送するJobを作成する

app/jobs/send_log_data_job.rbを作成する


```app/jobs/send_log_data_job.rb
class SendLogDataJob < ApplicationJob
  queue_as :default

  def perform(log_data)
    uri = URI((ENV['OTEL_EXPORTER_OTLP_ENDPOINT_LOG']).to_s)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == 'https'
    request = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
    request.body = log_data.is_a?(String) ? log_data : log_data.to_json
    response = http.request(request)

    Rails.logger.info "SendLogDataJob: HTTP Response Status: #{response.code}, Body: #{response.body}"
  rescue StandardError => e
    Rails.logger.error "SendLogDataJob: Failed to send log data: #{e.message}"
  end
end

```

app/controllers/application_controller.rbを編集しJobを呼び出す

```app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  around_action :capture_action_log

  private

  def capture_action_log
    start_time = Time.current
    yield
    end_time = Time.current

    trace_id = OpenTelemetry::Trace.current_span.context.hex_trace_id
    span_id = OpenTelemetry::Trace.current_span.context.hex_span_id
    duration = (end_time - start_time) * 1000.0
    body_content = {
       ## 飛ばしたいメッセージなどをテキトーに
      test: 'test',
    }

    log_data = [{
      app: ENV['OTEL_SERVICE_NAME'],
      host: request.host,
      status: response.status,
      duration:,
      trace_id:,
      span_id:,
      body: body_content.to_json
    }]

    SendLogDataJob.perform_later(log_data.to_json)
  end
end

```


`docker-compose up`などでRailsを起動して、アプリにアクセスしてみる

すると


![スクリーンショット 2024-02-10 16.04.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133718/b0b62383-2e08-d224-d586-90fb4da28338.png)

OpenTelemetry::Instrumentation関連でめちゃくちゃログが出る
（主に警告が）

ログの中にこのようなログがでていれば、ログの転送に成功している
```
{"time":"2024-02-09T14:33:19.340+09:00","level":"INFO","message":"SendLogDataJob: HTTP Response Status: 200, Body: ","trace_id":"fe47f03f1b032cbb4055a94596fbef25","span_id":"995b4d6abe8cfb38","operation":"default process"}
```

http://localhost:3301

でログを検索すると画像のようなログが見れるはず

![スクリーンショット 2024-02-10 16.13.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133718/84744a47-7d8e-4c3e-242d-2f5738449c35.png)


## 参考

https://signoz.io/docs/userguide/send-logs-http/
https://signoz.io/blog/opentelemetry-ruby/