Request Idについて調べた

Request id について

herokuで動いているRailsアプリケーションのログを眺めていたときに出てくる、uuidって何?となったのでメモる。わからないところがわかったら、あとで加筆する。

つまり

  • RailsアプリケーションにはX-Request-Id というHTTPヘッダーを見てログに出す仕組みがある。
  • herokuのWebサーバーが X-Request-Id を付与してRailsアプリケーションにリクエストを流している
  • X-Request-Id の仕組みはherokuに限らず、Webサーバー全体で使われてそう。
sequenceDiagram
    participant ブラウザ
    participant heroku/router
    participant app/web(Rails)
    
    ブラウザ->>heroku/router: APIリクエスト
    heroku/router->>heroku/router: X-Request-IDヘッダーをログ出力
    heroku/router->>app/web(Rails): X-Request-IDヘッダーを付加してリクエスト
    activate app/web(Rails)
    app/web(Rails)->>app/web(Rails): 処理
    app/web(Rails)->>app/web(Rails): X-Request-IDの内容を含めてアプリケーショのログ出力
    deactivate app/web(Rails)
    app/web(Rails)->>heroku/router: レスポンスを返す
    heroku/router->>ブラウザ: レスポンスを返す

Railsのロギング

先にRailsアプリケーションがどうやってログ出しているの?というところから。

Railsアプリケーションでログ出力の設定としてRails::Application::Configuration クラスの log_tags プロパティが存在する。 Rails アプリケーションを設定する - Railsガイドには以下の記述がある。

config.log_tags: 次のリストを引数に取ります(requestオブジェクトが応答するメソッド、requestオブジェクトを受け取るProc、またはto_sに応答できるオブジェクト)。これは、ログの行にデバッグ情報をタグ付けする場合に便利です。たとえばサブドメインやリクエストidを指定することができ、これらはマルチユーザーのproductionアプリケーションをデバッグするのに便利です。

色々付加してログに出せるっぽいですね。

Rails 6.0.3.2でrails new した時点で、config/environments/production.rb にはconfig.log_tags = [ :request_id ]が設定されている。この :request_idrequestオブジェクトが応答するメソッド に合致する。この先はRackかと思ったのだけども迷子になってしまったのでまた別の機会の追う。

production.rb にある設定を config/environments/development.rb で同じように設定したうえでリクエストをすると、以下のような結果になる。

X-Request-Id ヘッダーなし

$ curl -I http://localhost:3000
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: text/html; charset=utf-8
ETag: W/"6e6be3ab17e553298ac68f8a42a50540"
Cache-Control: max-age=0, private, must-revalidate
Content-Security-Policy: script-src 'unsafe-inline'; style-src 'unsafe-inline'
X-Request-Id: d662620f-f16e-4b0f-b0df-34a45247f195
X-Runtime: 0.005475
# $ curl -I http://localhost:3000 の結果
[d6e516d6-8a13-4bb4-be3a-c0a0636b4b0e] Started GET "/" for ::1 at 2020-07-24 19:41:13 +0900
[d6e516d6-8a13-4bb4-be3a-c0a0636b4b0e]    (1.6ms)  SELECT sqlite_version(*)
[d6e516d6-8a13-4bb4-be3a-c0a0636b4b0e] Processing by Rails::WelcomeController#index as HTML
[d6e516d6-8a13-4bb4-be3a-c0a0636b4b0e]   Rendering /Users/ikaruga/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/railties-6.0.3.2/lib/rails/templates/rails/welcome/index.html.erb
[d6e516d6-8a13-4bb4-be3a-c0a0636b4b0e]   Rendered /Users/ikaruga/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/railties-6.0.3.2/lib/rails/templates/rails/welcome/index.html.erb (Duration: 10.1ms | Allocations: 295)
[d6e516d6-8a13-4bb4-be3a-c0a0636b4b0e] Completed 200 OK in 22ms (Views: 15.4ms | ActiveRecord: 0.0ms | Allocations: 1643)

X-Request-Id ヘッダーあり

$ curl -I -H "X-Request-Id: 1" http://localhost:3000
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: text/html; charset=utf-8
ETag: W/"6e6be3ab17e553298ac68f8a42a50540"
Cache-Control: max-age=0, private, must-revalidate
Content-Security-Policy: script-src 'unsafe-inline'; style-src 'unsafe-inline'
X-Request-Id: 1
X-Runtime: 0.018485
[1] Started HEAD "/" for ::1 at 2020-07-24 19:52:49 +0900
[1] Processing by Rails::WelcomeController#index as */*
[1]   Rendering /Users/ikaruga/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/railties-6.0.3.2/lib/rails/templates/rails/welcome/index.html.erb
[1]   Rendered /Users/ikaruga/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/railties-6.0.3.2/lib/rails/templates/rails/welcome/index.html.erb (Duration: 3.3ms | Allocations: 8)
[1] Completed 200 OK in 12ms (Views: 5.6ms | ActiveRecord: 0.0ms | Allocations: 303)

レスポンスヘッダーに付加して返すのはActionDispatch::RequestIdのしごとかな。 rails/request_id.rb at v6.0.3.2 · rails/rails

herokuのRequest id

では、herokuではどうしているかというと、 HTTP Request IDs | Heroku Dev Center に大体の事が書いてあるのでそれを読む。

リクエストを受けると、herokuのrouterがリクエストID(UUID)を自動生成し、X-Request-Id ヘッダーに付加してアプリケーションに流すっぽい。外から X-Request-Id を受けるときは、20~200文字の/[a-zA-Z0-9+=/-]/ が使えるみたい。これを満たさないヘッダーが来たときは、herokuがRequest idを自動生成して、アプリケーションに流すと。

各Webサーバーの Request Id の対応

Nginxは1.11.0から $request_id 変数が実装されて、HTTPリクエストが来るごとに16進16桁の文字列を生成するみたい。 これをnginx.conf X-Request-Id ヘッダーに付与してアプリケーションにリクエストを流すと良さそう。 Application Performance Management with NGINX Variables

Apacheにも mod_unique_id というモジュールがあって、 UNIQUE_ID 環境変数を取得することでリクエストごとのユニークなIDを取れそう。 mod_unique_id - Apache HTTP サーバ バージョン 2.4

UNIQUE_ID 環境変数は 112 ビット (32 ビット IP アドレス、32 ビット pid, 32 ビットタイムスタンプ、16 ビットカウンタの四つの組) をアルファベット [A-Za-z0-9@-] を用いて MIMEbase64 符号化と同様の方法により符号化し、19 の文字を生成することにより作成されます。MIMEbase64 のアルファベットは実際は [A-Za-z0-9+/] ですが、 + と / とは URL では特別な符号化が必要なので、あまり望ましくありません。 全ての値はネットワークバイトオーダで符号化されますので、 符号は違ったバイトオーダのアーキテクチャ間で比較可能です。 実際の符号化の順番は: タイムスタンプ、IP アドレス、pid, カウンタです。この順には目的がありますが、 アプリケーションは符号を解析するべきではないことを強調しておきます。 アプリケーションは符号化された UNIQUE_ID 全体を透過的なトークンとして扱うべきです。 UNIQUE_ID は他の UNIQUE_ID との等価性を調べるためだけにのみ使用できます。


Reqeust Idがうまく渡せると、色々なアプリケーションを経由するリクエストで横断的にログを見ることができてよいですね。

tigで1行だけStage / Unstage する

help には全部書いてある。 stage viewで 1 を押すんだ。

いつもgitのコミットや差分確認にtigを使っているんだけど、この行だけStageに上げたいなーとかその逆をやりたいというときが出てくる。VSCodeのGit管理ではDiffを見ながら右クリックメニューから「選択した範囲をステージ」みたいなことができるので、tigでもできるでしょう。と探したら見つけた。デフォルトのキーバインディングで設定されているはず。

[-] stage bindings
View-specific actions
                           u status-update       Stage/unstage chunk or file changes
                           ! status-revert       Revert chunk or file changes
                           1 stage-update-line   Stage/unstage single line
                           \ stage-split-chunk   Split current diff chunk

stage-update-line というのがそれ。 これで stage-split-chunk で切り分けられなかった差分も拾ってStageにあげれて便利〜。

jq で shuffle を実装する

諸事情で jq で Array shuffle を実装したので、 実装や実装に必要となった初歩的なアルゴリズムについて記録する。

とりあえずできたものはこちら。 jq -nrcf usage.jq でシャッフルされた配列が返ってくるはず。 gist.github.com

jqとは

jq には

jq is a lightweight and flexible command-line JSON processor.

と書かれている。 JSONを入力として、それに対するフィルターを書くことでJSONの加工が行えるくん。なのだけれども、JSONを入力とせずともフィルターのみでコーディングすればおおよそなんでもできる。

jqが用意してくれているもの

条件分岐

jq は if 文が使える。

2 | if . == 0 then "zero" elif . == 1 then "one" else "many" end
# => "many"

関数

jq は関数を作ることができる。作った関数はパイプを通すことができる。引数も渡せる。

def increment: . + 1;

1 | increment
# => 2

変数

jq は変数を定義できる。

["Amy", "Basil", "Clara"] as $children

と書いておけばパイプの後からでも $children を呼べる。

これら以外にもたくさん jq はできることがあって、それらは jq Manual (development version) に記載されている。変数宣言が "Advanced features" の項目にいるのは、本来の使い方を考えるとまあそうだよね。といった感じ。

モジュール

jq は別のjqファイルを読み込むことができる。 上の usage.jq でやっているように import PATH as Name でPATHを読み込む。読み込んだファイルの関数は Name::function のように呼べる。

デバッグ出力

jq は debug 関数を使うことで計算途中でも関数実行時点での値を出力することができる。実装中長いパイプを通す必要が出てきたときに重宝した。

["A", "B", "C", "D", "E", "F", "G"] | { "key": .[] }
# {"key":"A"}
# {"key":"B"}
# {"key":"C"}
# {"key":"D"}
# {"key":"E"}
# {"key":"F"}
# {"key":"G"}
["A", "B", "C", "D", "E", "F", "G"] | { "key": (.[] | debug) }
# ["DEBUG:","A"]
# {"key":"A"}
# ["DEBUG:","B"]
# {"key":"B"}
# ["DEBUG:","C"]
# {"key":"C"}
# ["DEBUG:","D"]
# {"key":"D"}
# ["DEBUG:","E"]
# {"key":"E"}
# ["DEBUG:","F"]
# {"key":"F"}
# ["DEBUG:","G"]
# {"key":"G"}

jq が用意してくれていないもの

乱数

jq には乱数が用意されていない。精度はまったく気にしていなかったので、擬似乱数について調べた。線形合同法を用いた疑似乱数が古典的でシンプルというのを見たので雑に書いた。定数はC言語のライブラリにある Park and Millerによって提案された「最小標準」の実装を参考にした。 ちょっと散らすために何回か繰り返しかけるようにしているが、個人的なおまじないレベル。

refs: Question 13.15

シャッフル

jq には配列のシャッフル関数が用意されていない。乱数が用意されていないのでそれはそう。どういったアルゴリズムが適しているのか調べると、 フィッシャー-イェーツのシャッフルというアルゴリズムがあることを知った。配列の要素ごとに無作為に選んだ要素と場所を入れ替える、というのを要素数分繰り返すことでシャッフルを実現している。畳み込みでも実現できそうだったので、今回は reduce を使って畳み込むことにした。配列の要素を指定してSwapした結果を別の配列として返す、というのが意外とめんどくさくて、上の実装では Array Slice を使って無理やり配列を組み立てている。もう少しどうにかなりそう。

refs: Fisher–Yates Shuffle

懸念していること

乱数の偏りについてなにも考えられていないので検証が必要。カルドセプト サーガを思い出した。

学び

  • 乱数、シャッフルの初歩的なアルゴリズムの復習ができた
  • jq 意外と何でもできることがわかった
    • 上記のような変なこと意外にも関数一通り眺めたので、どんなことができるかは把握できた。
  • ついでに配列、ハッシュの操作を行うコードも別件でちゃんと書いたので、大体jqわかった。

なぜこのようなことを

知り合いがjqでProject Eulerを解いているという話を聞いて興味が出たので。

参考

Raspberry Pi 4 + DAC + Volumio で音楽を聴けるようになるまで

仕事用のPC以外から家のNASに入ってる音楽を聴きたい〜って考えたとき、タブレットから再生するのが速そうと思ったのだけれども、Android / iOS だといい塩梅のアプリがなくてじゃあRaspiだとなったのであれこれ買って試した話。

用意したもの

Raspberry Pi 4

家に元々あったやつ。詳しくは後述。

Raspberry Pi 4のヒートシンク

Amazon | Raspberry Pi 4(Model B)用アルミヒートシンクケース (レッド) | Side3 | PCケース 通販

DAC

Kuman PIFI Digital DAC+ Amazonで買った。Raspberry Pi のソケットに刺さるタイプのDAC。チップとかそこまでこだわっていなかったので、適当に良さそうなのを選んだ。これがあるとRaspberry Pi の本体からよりは相対的に音が良くなる。

とても背の高いピンソケット

とても背の高いピンソケット2×20(40P) - スイッチサイエンス

スイッチサイエンスで買った。これも後述

Volumioの入ったmicro SD

Volumioという音楽再生に特化したディストリビューションがあって、それを焼いたもの。

組む

実はRaspi 3も家にあって、それを流用するつもりだったのだけれども、いざDACが届いたときに通電確認したらpow LEDが点灯すらせず文鎮と化したので予備で用意していたRaspi 4を渋々使った、という経緯がある。Raspi 4は熱がヤバいと聞いていたので、ヒートシンクも慌てて買った形となっている…

そして予想はしてたけれど、素の状態だとヒートシンクDACに干渉してしまったので、とても背の高いピンソケットを間に挟むことで解決した。スイッチサイエンス最高。

Volumioの設定

ハマったと言うか、調べないとわからなかったこと

NAS接続

使っているNASの問題なのか、 smb のバージョン設定を入れないと接続できなかった。 オプションに vers=1.0 を入れると接続できた。 f:id:uvb_76:20200616222136p:plain

コンピレーション問題

アルバムリストを表示したときにアーティストごとにアルバムが増殖するよくあるやつ。よく聴く音楽ジャンルの都合上そこそこの数のコンピレーションアルバムを管理している。iTunesだと何も考えずにコンピレーションとして認識させればよかったのだけれども、そんな親切なものは無いので、別の手段を使って、コンピレーションで有ることを伝える必要がある。今回はアルバムアーティストのタグを Compilation Metadata Fields として認識っせることで対処した。ただ、 mp3タグが不完全なアルバムもあったので、これを期に設定し直した。 f:id:uvb_76:20200616221505p:plain

使ってみて

同じネットワークからブラウザで volumio.local にアクセスするとVolumioの再生画面を表示できる。ブラウザで一通りの操作が完結できるのは強いと思う。スマホからでも同様にアクセスできる。レスポンシブデザイン対応しているのは親切だなーと感じた。ただ、Rapi4で音楽再生するだけなのはオーバースペックっぽいと思うので、別用途でもなにか動かせないか模索しているところ。また、Volumioで提供されている Spotify Plugin のログインが未だにうまく行っていないのでどうにかしたい。うまく行けばNexus 7から脱却できるんじゃ…

参考

Raspberry Piで部屋のオーディオ環境を改善した話 - しろうまの小屋

Volumioでコンピレーション・アルバムの再生ができない場合の対処方法 - Qiita

静的ページ生成のブログにVssueを使ってコメント欄を置く

VssueというVueのライブラリがある。これを使うとページに簡単にコメント欄を置くことができる。VuePressで作ってた日記に導入してみたので、ライブラリの紹介も兼ねて書く。

github.com

しくみ、やったこと

コメントを残す、ということはどこかにコメントデータを保持しておかなくてはいけない。静的ページでどうやって? Vssueはその問題をGit ホスティングサービスのIssueにAPI経由で読み書きすることで解決している。コメントの編集、削除はもちろん、 絵文字のリアクションもつけることができる。 もちろん Issueの emoji reaction を利用している。そりゃそうだよねと言った感じだけど。

対応しているGitホスティングサービスは以下のとおり。今回はGitHub(V3)を採用した。

詳細は Introduction | Vssue が詳しい。

GitHubAPIを叩いてコメントを読み書きするので、OAuth2のGitHubアプリを作る必要がある。ClientIDとClientSecretを払い出したら環境変数に隠して置く必要はある。この日記はNetrifyにデプロイしているので、環境変数はSettings→Build & deploy → Environment variablesで設定すれば良い。

現物

実際のもの、作業記録は以下から確認することができる。

1つの記事に対して1つのIssueでコメントを保存している。 誰もコメントしてないときはコメント欄が disabled になっているが、Click to create issue を選択することでVssueがリポジトリにIssueを新規作成してくれる。この状態でコメントを書いて投稿すると、Issueのコメントとして連なる、という仕組み。

f:id:uvb_76:20200527030950p:plain
コメントする前

コメントを書くのにGitHubのアカウントが必要だったり、たまーに表示されなくなって更新するとまた出てくるなどちょっと不安なところはあるけれど、なかなかおもしろいライブラリだと思った。

MacでCanonのカメラをビデオチャットのカメラとして使う

在宅勤務で外に出なくなり一眼レフの出番が消え失せたのでなにか使い所がないか考えていたところ、MacWebカメラとして使えるのではと思い至り、調べてた。

lowreal.net 5年前の情報だけど、ここの記事の内容がそのまま使えたよという記録。

用意したもの

設定も単純で、カメラの電源を入れたあと Camera Live とCam Twist を起動して以下のように設定するだけ。

f:id:uvb_76:20200412142349p:plain
アプリケーションの設定

EOS Utility を一緒にインストールしていると、 カメラの電源 ON 時に EOS Utility が起動し Camera Live の認識とぶつかって両方ともフリーズしてしまうので、自動起動の設定を切った。 f:id:uvb_76:20200412141910p:plain

これで一眼カメラを入力としてWebカメラの内容として出力できる。

仮想カメラを入力ソースとして使えるか

ただ、そもそも Cam Twist が各種ビデオチャットのカメラソースとして使えるかという問題が存在する。この記事を書いた時点で

  • 使用可能(Cam Twist が選択できる)
  • 使用不可能 (Cam Twist が Video Settings の選択肢として表示されない)
    • Slack Call
    • Zoom
    • Discord

という状態を確認した。Zoomは仮想カメラを無効にした話を聞いたが、Slack Call も認識しなかったのは想定していなかった。


というわけで、手元で余しているものを転用する話でした。社内で受肉しながらMeet通話している人たちがCam Twist使っている話はちらっと聞いたけど、話が繋がって学びになった。Syphon初めて知ったけど、間に編集挟めば楽しそうなことができそうなこともわかった。バッテリー問題や置き場所問題があるのでこれを実用するかどうかは別だけど… OSMO PocketあたりをWebカメラとして使えるようにならないかな、というメーカーへの期待は別に持っていたりする。

GitHub Actions 実践入門を読んだ

booth.pm

技術書典8が中止になったので、興味ある本は買っていこうと思い買った本のうちの一つ。この前読みながら写経してたので記録に残す。

どんな本か

GitHub Actions ができること、yamlファイルの記載方法、GitHub Actionsから実行できるアクションの作成方法、公開方法についてがコンパクトに纏められている。

あまり、CIに触れてきていなかったので、マトリックステストの項目ではそんなことできるの〜と感心してしまった。また、GitHub Actionsを公開できることで、CIに必要な機能をサクッと拾ってこれるのもとても便利だと感じた。

GitHub Actions で感心したこと、メモ

仮想環境内に入っているツール、言語類がまとまっている記載

ワークフローのトリガーとなるイベント

秘匿情報

リポジトリのSettings Secrets から設定し、 secrets.FOO の形で参照できる。Actionsの実行ログはちゃんと *** でマスクされる。

f:id:uvb_76:20200403030726p:plain

組み合わせテスト

storategy を設定することによって、複数の環境での組み合わせテストも簡単に実行できる。便利。

成果物

actions/upload-artifact というActionsを使うと、CIを実行中に生成した成果物をGitHub上にアップロードできる。ダウンロードも同様にActionsが提供されている。setup-ruby もそうなんだけど、 https://github.com/actions/ の一覧を見れば大体のことはできそう。


ymlの書き方から、実際に手を動かして自動テストをCIで実現するまでを体験できるので、GitHub Actions からCIを触る人にもおすすめ。

次はOpenAPI 3を完全に理解できる本か やりかけの RubyでつくるRubyのどちらかを読んで行こうかな。