行動すれば次の現実

ほどよくモダンなシステム開発を目指しています。メインテーマは生産性、Ruby、Javascriptです。

ScoutAPMのトレース情報にユーザーIDを付加する方法

ScoutAPMを使ってモニタリングしているとトランザクションとログインユーザー情報を紐付けたいと思ったことはありませんか?

そのような場合は、ScoutAPMのCustom Contextという機能を使うことでトランザクションに対して付加情報を付けることが出来ます。

Custom Contextとは

ScoutAPMでモニタリングされるトランザクションに対して、任意の付加情報を付ける機能です。 アプリケーション側にシンプルなコードを埋め込むだけで実現することが出来ます。

どのような情報を付加するかはアイデア次第ですので、例えば以下のようなニーズに応えることが可能です。

  • 遅いリクエストによって影響を受けるユーザーは誰か?何人くらい該当するか?
  • 毎週木曜の深夜2時にメモリ負荷が上がる原因は「どのユーザー」の「どの操作」によるものか?

Custom Contextの使用方法

ユーザー情報を付加したい場合

ユーザー情報を付加したい場合は下記のメソッドを使用します。

ScoutApm::Context.add_user({})

例えばIDを付加したい場合は下記のような実装になります。

ScoutApm::Context.add_user(id: @user.id)

それ以外の情報以外を付加したい場合

ユーザー情報ではない一般的な情報を付加したい場合は下記のメソッドを使用します。

ScoutApm::Context.add({})

下記のように任意の情報を付加することが出来ます。

ScoutApm::Context.add(database_shard: @db.shard_id)

ユーザーIDをContextに付加する実装例

各アクション単位でユーザーIDをContextに付加することで実現可能です。 ApplicationController.rbに下記を定義します

before_filter :set_scout_context

def set_scout_context
  ScoutApm::Context.add_user(id: current_user.id) if current_user.is_a?(User)
end

このような実装を加えるだけで簡単にContextを追加することが出来ます。

下記は実際のScoutAPMのContextタブの画面イメージです。

f:id:furu07yu:20220112140327j:plain
ScoutAPMのContextタブ

user idが新しく追加されていることが確認できます。

設定自体はとても簡単なので運用のトレーサビリティを上げたい場合は設定しておくことをおすすめします。

参考

Advanced Features

RailsのLogにuser idを出力する方法

Ruby on Rails5でリクエストログにユーザーIDを追加する方法を説明します。 ミドルウェアの修正は不要ですので、気軽に取り入れることができると思います。

実装内容

config/initializers/logging.rb

Rails.configuration.log_tags = [
  :request_id,
  lambda do |req|
    session_key = (Rails.application.config.session_options || {})[:key]
    session_data = req.cookie_jar.encrypted[session_key] || {}
    user_key = session_data['warden.user.user.key']
    user_id = user_key.present? ? user_key[0][0] : 'guest'
    "user: #{user_id}"
  end,
]

実装解説

config.log_tagsというメソッドを使用して出力したいタグを指定します。

https://railsguides.jp/configuring.html#config-log-tags

Rails5の場合、デフォルトでproduction.rbにconfig.log_tags = [ :request_id ] が指定されていますが、今回は環境問わずlogging.rbの内容で上書きします。

user_idを出力したいので、:request_idの後ろにrequestオブジェクトを引数に取るlambdaメソッドを定義します。

Rails.application.config.session_optionsからsession_key取得して、requestのcookie情報からsession_keyに対応するsession_dataを取得します。

session_dataは以下のような内容になっています。

{
  "session_id" => "c8aead6bfbdfsdfc2dsafasccdda2", 
  "warden.user.user.key" => [
    [12],
    "$2A$1d$Qd775IfaSsdfRfasdfdfmu"
  ],
  "_csrf_token" => "vffdcdfasd-t3rWY8mfDFADSFACEFDAfeccdsfffSA"
}

warden.user.user.keyの[0][0]がuser_idですので、それをログに出力します。

Railsでよくbuildエラーになるlibv8、therubyracer、mini_racerあたりをまとめてみた

Railsアプリを構築しようとするときにbuildエラーになるライブラリの代表にlibv8、mini_racerなどが上げられると思います。(個人的感覚)

毎回gemを最新化することでなんとなく解決していたのですが、これではその場凌ぎしかなく応用が効きません。 しっかり理解しておく必要があると思ったので、libv8周りのことを調べてみました。

そもそもv8とは

  • GoogleのオープンソースハイパフォーマンスJavaScriptエンジンです

v8.dev

libv8とは

  • v8ランタイムのソースとバイナリが格納されたgemです
  • linux,os x, windowsプラットフォームにおける様々なCPUにサポートしています
  • libv8のバージョンによってサポートされているCPUが異なるので、適切なバージョンを使用する必要があります
    • そのためよくbuild時にエラーが起こりがち

github.com

therubyracerとは

  • RubyでJavascriptを実行できるようにするgemです
  • libv8に依存しており、libv8を使用してv8エンジンをインストールしています
  • もともとはRails本体の依存ライブラリでしたが、2017年より開発がストップしてRails本体からも切り離されました

github.com

mini_racerとは

  • RubyでJavascriptを実行できるようにするgemです
  • 2017年にtherubyracerに代わってRails本体に組み込まれました
  • 0.3.1まではlibv8を使用しており、0.4.0からlibv8-nodeを使用するように変更されました
    • GitHub - rubyjs/libv8-node: Package libv8 from Node
    • 2022/01/05 現在まだリリースタグが打たれていないかつ、build失敗のissueも上がっているので不安要素がある
    • 元に筆者のci環境(docker)ではbuildエラーになってしまう

github.com

execjs

  • RubyでJavascriptを実行できるようにするgemです
  • Javascriptを実行するために最適なランタイムを自動で選択します(下記ランタイムをから選択される)
    • therubyrhino
    • Duktape.rb
    • Node.js
    • Apple JavaScriptCore
    • Microsoft Windows Script Host
    • Google V8
    • mini_racer
  • 大抵の環境にはNode.jsがインストールされていると思うので、それが選択されると思います
    • そのためmini_racerのlibv8、libv8-nodeあたりのbuildエラーから開放される

github.com

yarnコマンドで発生したgyp ERR!の原因究明と対処方法

アプリケーションのnode.jsのバージョンを上げて、yarn installコマンドを実行したら以下のようなエラーが発生しました。

1 error generated.
make: *** [Release/obj.target/binding/src/binding.o] Error 1
gyp ERR! build error
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/Users/my_name/my_app/node_modules/node-gyp/lib/build.js:262:23)
gyp ERR! stack     at ChildProcess.emit (node:events:390:28)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (node:internal/child_process:290:12)
gyp ERR! System Darwin 20.6.0
gyp ERR! command "/usr/local/var/nodebrew/node/v16.13.1/bin/node" "/Users/my_name/my_app/node_modules/node-gyp/bin/node-gyp.js" "rebuild" "--verbose" "--libsass_ext=" "--libsass_cflags=" "--libsass_ldflags=" "--libsass_library="
gyp ERR! cwd /Users/my_name/my_app/node_modules/node-sass
gyp ERR! node -v v16.13.1

エラー内容を確認するとnode-sassという記載があります。

node-sassはpackage.jsonには記載のないライブラリでした。そのため、アプリケーションで使用している何かライブラリが依存しているということがわかります。

原因究明の方法

yarn why [モジュール名]というコマンドで、間接的に依存しているライブラリを特定できます。

$ yarn why node-sass

[1/4] 🤔  Why do we have the module "node-sass"...?
[2/4] 🚚  Initialising dependency graph...
[3/4] 🔍  Finding dependency...
[4/4] 🚡  Calculating file sizes...
=> Found "node-sass@4.14.1"
info Reasons this module exists
   - "@rails#webpacker" depends on it
   - Hoisted from "@rails#webpacker#node-sass"
info Disk size without dependencies: "2.97MB"
info Disk size with unique dependencies: "11.13MB"
info Disk size with transitive dependencies: "23.48MB"
info Number of sha

"@rails#webpacker" depends on itという記載がありますので、@rails#webpackerで使用されているnode-sassのバージョンに問題があることがわかります。

対処方法

@rails#webpackerで使用しているnode-sassのバージョンに問題があることがわかりましたので、本体となる@rails#webpackerのバージョンを上げてみようと思います。

package.jsonの記載を以下のように変更しました。

    "@rails/webpacker": "^5.4.3",

その後、yarn installを実行したら正常にインストールが完了しました。

めでたしめでたし。

エンジニアは普段身につけるモノにもっと投資するべき

YOUTUBEやTwitterを見ているとリモートワークの機会が増えたこともあり、自宅の作業環境をアップグレードする人が多いように感じます。

PC、デスク、椅子、照明などに投資していくのが一般的だと思うのですが、実はそれと同じくらい普段身につけているアイテムにも投資したほうが良いと筆者は考えます。

身につけるモノをアップグレードしたことで生産性が上がったと実感しています。

リモートワーク歴4年の私が、オススメしたい生活用品を4種類紹介したいと思います。

メガネ

目が悪い人は一日中メガネを付けているのではないでしょうか。 特にリモートワークだと一日の中でメガネを付ける時間がかなり長くなるので、メガネは真っ先に投資するアイテムの一つです。

  • 身に付けていて疲れないか
  • ブルーライトカット仕様であるか
  • 軽量なフレームか
  • デザインが古くないか

という観点で選定すると良いでしょう。

ちなみに筆者は眼鏡市場で購入したこちらのメガネにブルーライトカット加工オプションを付けて使用しています。 流行りのボストン型で、しかも軽量、価格も絶妙ですのでオススメです。

www.meganeichiba.jp

部屋着

部屋着もかなり重要です。寝間着のままスウェットやパジャマで作業するのも良いのですが、服を着替えて仕事モードに心を切り替えてることが大事だと考えます。

  • 作業するのにストレスが感じないか
  • 着脱がしやすいか
  • 温度調整が可能か
  • そのままの格好で外に出ても平気か

という観点で選定すると良いでしょう。

特に「そのままの格好で外に出ても平気か」というのは地味に重要です。気軽に外出できることで時間短縮になりますし、突然の配達などで人に合う場合も恥ずかしくないというメリットがあります。

筆者のオススメ部屋着は、冬場であればユニクロの「ファーリーフリースフルジップジャケット」は欠かせません。

インナーには無印の「脇に縫い目がない 天竺編みTシャツ」などのオーガニックコットン素材のモノがオススメです。

スボンは動きやすさや通気性を重視すると良いと思います。 「ウルトラストレッチドライスウェットパンツ」などがオススメです。

ルームシューズ(スリッパ)

スリッパは履かないという人もいるかもしれませんが、冬場であればスリッパの使用をおすすめします。 座って作業するのが中心になると足への血流が悪くなり、足裏が冷えてしまいます。 スリッパを履くと足が冷えることを防げますので、集中力が向上します。

素材によって好き嫌いがあると思うので、自分の好みで選ぶと良いでしょう。

ちなみに筆者はニトリで購入した「あったかスリッパ」を使用しています。

タンブラー

アイスコーヒーやホットコーヒーを飲んでいてすぐにぬるくなってしまうのは非常に残念です。 いつでも適温な状態で飲み物を口にできるとリッラクス効果が高まり、生産性も上がるのではないでしょうか。

  • 保温性が高い
  • 倒れてもこぼれない
  • デザインがシンプル

という観点で選定すると良いでしょう。

例えばサーモスのタンブラーなどがオススメです。

www.amazon.co.jp

その際、タンブラーの蓋も同時に購入するのは必須です。 保温性が高まりますし、転倒した場合の被害もある程度防げますので買って損はありません。

タンブラー用フタ(S)/JDA Lid(S) | マグカップ・タンブラー・ジョッキ・保冷缶ホルダー・食器 | サーモス 魔法びんのパイオニア

良いエンドポイントとは何か

良いエンドポイントを意識すると使用者にとって理解しやすく、使いやすいAPIを提供できます。

また、実装者にとってもメンテナンスしやすく、不具合の温床を防ぐことにも繋がります。 そのようなAPIを実装するためには、「どのようなエンドポイントが良いのか」を理解する必要があります。

短くて入力しやすい

短くて入力しやすいということはシンプルで覚えやすいことに繋がります。使用者にとって使いやすいAPIと言えるでしょう。

x https://api.example.com/service/api/search
o https://api.example.com/search

人間が読んで理解できる

そのURLを見ただけで、それ以外の情報がなくてもそれが何を目的としたものなのかがある程度わかるようにしておくことが良いでしょう。 そのためには単語を無闇矢鱈に省略したりしないことです。

x https://api.example.com/p/12345
o https://api.example.com/products/12345

大文字小文字が混在していない

大文字小文字が混在するとAPIがわかりづらくなり、間違いのもとに繋がります。デファクトスタンダードである小文字に統一することが良いでしょう。

x https://api.example.com/USERS/12345
o https://api.example.com/users/12345

サーバー側のアーキテクチャが反映されていない

サーバー側がどのようなアーキテクチャを使用してアプリケーションを実装しているかの情報は使用者には全く関係のない情報ですのでURIには反映させないほうが良いでしょう。

また、アーキテクチャが変更された際にURIも変更することになり、使用者・実装者ともに手間が増えてしまいます。

x https://api.example.com/cgi-bin/get_user.php?user=100

ルールが統一されている

URIの構造などのルールが統一されていないと、使用者にとっては誤解を招きやすくトラブルの温床となってしまいます。

例えばid情報をクエリパラメータに載せるのか、URIに載せるのか、単数形なのか、複数形なのかなどのルールを統一させておく必要があります。

# ルールが統一されていない例
https://api.example.com/products?id=100
https://api.example.com/products/100/reservation

ActionMailerでOpenSSL::SSL::SSLErrorが発生してメールが送れない

ActionMailerで別ドメインのSMTPサーバー経由でメール送信しようとしたらOpenSSL::SSL::SSLErrorが発生してエラーになりました。

※接続情報等は架空の値になっております。

ERROR -- : OpenSSL::SSL::SSLError
ERROR -- : hostname "smtp.example.com" does not match the server certificate

ActionMailer側の設定は以下の通りです。

config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  user_name: 'test@example.com',
  password: 'hogehoge',
  address: 'smtp.example.com',
  domain: 'smtp.example.com',
  authentication: :plain,
  port: 587,
}

エラーの内容は「証明書と送信元ドメインが一致しない」ということです。

別ドメインからのメール送信ですので、本来はDKIM認証/SPF設定をするのが望ましいのですが、 今回はシステム要件の都合上、別の方法を試したいと思います。

証明書チェックを省いて強引にメールを送信する

今回は強引に送りたいので、STARTTLSを有効にしつつ不正な証明書を受けるけるように設定する方法を取ります。

config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  user_name: 'test@example.com',
  password: 'hogehoge',
  address: 'smtp.example.com',
  domain: 'smtp.example.com',
  authentication: :plain,
  enable_starttls_auto: true, # STARTTLSに自動接続する
  openssl_verify_mode: 'none', # 証明書の妥当性はチェックしない
  port: 587
}

authenticationがloginかplainはSMTPサーバーによって違いますので、それぞれで試して使用できる方を採用します。

それ以外の接続エラー

上記の設定をしてもサーバーによっては別のエラーで送信できないことがありました。

No route to host - connect(2) for "smtp.example.com" port 587

「宛先ホストへの送信経路が存在しない」という内容ですので、ファイアウォールで弾かれている可能性が高いです。

$ telnet smtp.example.com 587
telnet: Unable to connect to remote host: No route to host

telnet等で疎通確認をして、接続出来ない場合は宛先ホストのファイアウォール設定を見直すことで解決するかと思います。

国内レンタルサーバーやVPSを使用している場合、海外IPからの接続が拒否されているケースがありますので、全て許可するようにするか、ホワイトリストで対応するなどの考慮が必要になります。

参考

rails/configuring.md at 481343ed91a66a112f7d929a73214f42ffb38dfc · rails/rails · GitHub

ruby - rails email error - 530-5.5.1 Authentication Required. - Stack Overflow