React Developer Toolsが動作しないときの対処法

背景

React.jsの概要を知りたいと思いReact.js & Next.js超入門 第2版を読みながらたまにサンプルコードを動作させたりしていました。

その本の中で、React Developer Toolsが紹介されていて、Chromeの拡張機能をインストールしました。しかし、利用環境によってはそのままで動作しなかったので、その点について記載します。

React.jsの動作環境

React.jsの動作環境はnpmを用いず、CDN上のreact.development.jsreact-dom.development.jsを読み込むことで実現しました。

VSCodeでhtmlファイルを作成し、React.jsが動作するサンプルコードを記載して、そのファイルをブラウザに読み込ませて動作させていました。なのでURLはfile://で始まっています。

React Developer Toolsが動作しない

サンプルコードをブラウザで表示後、拡張機能のアイコンをクリックしようとしたら、以下のメッセージが表示されました。

1
2
This is a restricted browser page.
React devtools cannot access this page.

ん〜なんでしょうか、ちょっとわからないのでググってみると、stackoverflowに答えが見つかりました。

https://stackoverflow.com/questions/26347489/react-dev-tools-not-loading-in-chrome-browser

1
If you opened a local html file in your browser (file://...) then you must first open chrome extensions and check off "Allow access to file URLs".

設定が必要のようです。

拡張機能の設定

stackoverflowに書いてある通り設定してみます。

  1. Chromeの拡張機能の一覧からReact Developer Toolsをクリック
  2. 設定画面の下の方、ファイルの URL へのアクセスを許可するがオフになっているのでONにする
  3. Chrome Developer Toolsを再起動する

以上になります。

Chrome Developer ToolsのElementsConsoleなどが表示されているところに、ComponentsProfilerが表示されていることが確認できました。Reactのロゴが表示されているのでわかりやすいですね。

まとめ

ファイルのURLはあまり使わないと思いますが、最初に試すときはファイルを読み込ませるのが楽でいいですね。

React Developer Toolsは正しく使うことでReact.jsの理解が捗ると思うので、使い方をちゃんと身に付けたいと思います。

目次マトリクス JavaScript編

背景

今までJavaScriptをしっかり学びたいとずっと思い続けてなかなか行動することができませんでした。

独学大全の目次マトリクスという手法を見て、JavaScriptで試してみようと思いました。

目次マトリクスとは

目次マトリクスとは多くの文献(書籍)を一望することで、共通点などを洗い出す方法です。

わたしが思うに、複数の書籍でしっかりと説明されていることは重要なのかなと思います。そういった点を洗い出すことで、JavaScriptを学ぶ上で重要な箇所を抑えていきたいと思います。

手順

目次マトリクスの作成手順は以下のようになります。

  1. 独学のテーマごとに、マトリクスを作る
  2. 文献のタイトル、著者などを表の左端のマスへ入力する
  3. 目次から「見出し」を拾い出し、マスへ入力する
  4. 必要なら各章の概略を追記する
  5. 同じ/似た内容をマーキングしたり囲んでつないだりする
  6. 文献を横断読みしながら気づいたことを抽出し、整理する

今回は1〜3まで行ってみます。

対象の文献(書籍)

JavaScriptの書籍はたくさんありますが、手元にある中で古すぎず基礎からしっかりと書いてある本と思われるものを3つ挙げてみました。その他の書籍は今後追加するとして、一旦この3冊で始めてみたいと思います。

JavaScript Primer

2020年に読んだ本のなかで、とても良い部類にはいる本でした。Webで参照できるのも魅力的です。

ハンズオンJavaScript

日本人の著者でオライリー本、しかも2020年11月出版ということで、情報も新しいと思い選びました。ボリュームが多く、どこまで情報を拾っていいかわかりませんが、可能であればまるっと理解しておくのが良さそうです。

JavaScriptモダンプログラミング完全ガイド

モダンプログラミングという言葉に惹かれて選びました。2020年12月出版ということで情報も新しいのかなと思います。

JavaScriptの目次マトリクス

では目次マトリクスを作っていきます。横1列に並べると長くなるので5章ごとに区切っています。

第1章 第2章 第3章 第4章 第5章
JavaScript Primer JavaScriptとは コメント 変数と宣言 値の評価と表示 データ型とリテラル
ハンズオンJavaScript 学び始める前に データを学ぶ 処理を学ぶ オブジェクトを学ぶ モジュールを学ぶ
JavaScriptモダンプログラミング完全ガイド 値と変数 制御構造 関数と関数型プログラミング オブジェクト指向プログラミング 数と日付/時刻
第6章 第7章 第8章 第9章 第10章
JavaScript Primer 演算子 暗黙的な型変換 関数と宣言 文と式 条件分岐
ハンズオンJavaScript 基本的な標準オブジェクトを学ぶ コレクションを学ぶ 複雑なデータの扱いを学ぶ 国際化を学ぶ 非同期処理を学ぶ
JavaScriptモダンプログラミング完全ガイド 文字列と正規表現 配列とコレクション 国際化 非同期プログラミング モジュール
第11章 第12章 第13章 第14章 第15章
JavaScript Primer ループと反復処理 オブジェクト プロトタイプオブジェクト 配列 文字列
ハンズオンJavaScript メタプログラミングを学ぶ Webを学ぶ ネットワークを学ぶ ストレージを学ぶ マルチメディアを学ぶ
JavaScriptモダンプログラミング完全ガイド メタプログラミング イテレータとジェネレータ TypeScript入門
第16章 第17章 第18章 第19章 第20章
JavaScript Primer 文字列とUnicode ラッパーオブジェクト 関数とスコープ 関数とthis クラス
ハンズオンJavaScript センサーとデバイスを学ぶ PWAを学ぶ セキュリティを学ぶ パフォーマンスを学ぶ 学び続けるために
JavaScriptモダンプログラミング完全ガイド
第21章 第22章 第23章 第24章 第25章
JavaScript Primer 例外処理 非同期処理: コールバック /Promise/Async Function Map/Set JSON Date
ハンズオンJavaScript
JavaScriptモダンプログラミング完全ガイド
第26章 第27章 第28章 第29章 第30章
JavaScript Primer Math ECMAScriptモジュール ECMAScript アプリケーション開発の準備 ユースケース: Ajax通信
ハンズオンJavaScript
JavaScriptモダンプログラミング完全ガイド
第31章 第32章
JavaScript Primer ユースケース: Node.jsでCLIアプリケーション ユースケース: Todoアプリケーション
ハンズオンJavaScript
JavaScriptモダンプログラミング完全ガイド

まとめ

今回、目次マトリクスを使って、JavaScriptを学ぶ上で重要な箇所を絞り込もうと思いました。

現状ですと、以下のキーワードが共通して出てきていると思います。

  • オブジェクト
  • モジュール
  • 非同期プログラミング
  • メタプログラミング
  • 国際化
  • 配列(コレクション)

今後も書籍を追加していく予定です。

Docker環境でCould not find 'bundler'のエラーが出た時の対処法

背景

久しぶりにあるRailsアプリケーションのローカル開発環境を動作させようとしました。git pullした後にdocker-compose upとすると、

1
2
3
4
5
6
7
Traceback (most recent call last):
2: from /usr/local/bin/bundle:23:in `<main>'
1: from /usr/local/lib/ruby/2.6.0/rubygems.rb:302:in `activate_bin_path'
/usr/local/lib/ruby/2.6.0/rubygems.rb:283:in `find_spec_for_exe': Could not find 'bundler' (2.2.15) required by your /app/Gemfile.lock. (Gem::GemNotFoundException)
To update to the latest version installed on your system, run `bundle update --bundler`.
To install the missing version, run `gem install bundler:2.2.15`
!! exit status 1

というエラーが発生してしまいました。その時の対応を記載します。

bundlerのバージョンアップを試す

gem install bundler

バージョン変更があったのか、とりあえず言われた通りgem install bundlerを実行してみます

1
2
3
4
5
6
7
8
9
10
11
12
$ docker-compose run --rm web gem install bundler
Creating app_web_run ... done
Traceback (most recent call last):
2: from /usr/local/bin/bundle:23:in `<main>'
1: from /usr/local/lib/ruby/2.6.0/rubygems.rb:302:in `activate_bin_path'
/usr/local/lib/ruby/2.6.0/rubygems.rb:283:in `find_spec_for_exe': Could not find 'bundler' (2.2.15) required by your /app/Gemfile.lock. (Gem::GemNotFoundException)
To update to the latest version installed on your system, run `bundle update --bundler`.
...
(中略)
...
To install the missing version, run `gem install bundler:2.2.15`
!! exit status 1

メッセージは変わりません…

bundle update –bundler

もう1つの方法、bundle update —bundlerを行なってみましょう

1
2
3
4
5
6
7
8
9
$ docker-compose run --rm web bundle update --bundler
Creating app_web_run ... done
Traceback (most recent call last):
2: from /usr/local/bin/bundle:23:in `<main>'
1: from /usr/local/lib/ruby/2.6.0/rubygems.rb:302:in `activate_bin_path'
/usr/local/lib/ruby/2.6.0/rubygems.rb:283:in `find_spec_for_exe': Could not find 'bundler' (2.2.15) required by your /app/Gemfile.lock. (Gem::GemNotFoundException)
To update to the latest version installed on your system, run `bundle update --bundler`.
To install the missing version, run `gem install bundler:2.2.15`
!! exit status 1

変わりません…

ローカルのRubyの確認

DockerではなくローカルのRubyのバージョンを確認してみます

1
2
3
4
$ ruby -v
ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin17]
$ cat .ruby-version
2.7.2

コンテナ内のバージョンとはあってないみたいですね…
Bundlerのバージョンも確認します

1
2
$ bundler --version
Bundler version 2.1.4

Gemfileに記載のバージョンとは異なるようです。updateできるか試してみます。

1
2
$ bundle update --bundler
Warning: the running version of Bundler (2.1.4) is older than the version that created the lockfile (2.2.15). We suggest you to upgrade to the version that created the lockfile by running `gem install bundler:2.2.15`.

ローカルもできないようです

コンテナのbundlerのバージョンを更新する
一旦すべてのイメージとボリュームを削除することにしました

1
2
3
4
5
6
7
8
$ docker-compose down --rmi all --volumes
Removing app_web_1 ... done
Removing app_db_1 ... done
Removing network app_default
Removing volume app_mysql-data
Removing volume app_bundle
Removing image mysql:5.7
Removing image app_web

削除ができたのでdocker-compose upします

1
2
3
4
5
6
7
8
9
10
11
12
13
docker-compose up
Creating network "app_default" with the default driver
Creating volume "app_mysql-data" with default driver
Creating volume "app_bundle" with default driver
Pulling db (mysql:5.7)...
5.7: Pulling from library/mysql
...
(中略)
...
Step 1/14 : FROM ruby:2.7.2
2.7.2: Pulling from library/ruby
004f1eed87df: Pull complete
5d6f1e8117db: Pull complete

ruby:2.7.2のベースイメージからコンテナをビルドしていることがわかります
この後bundle installも終わって無事起動しました

まとめ

コンテナでバージョンの不一致が発生したら、以下のコマンドで一旦コンテナを削除する

1
docker-compose down --rmi all --volumes

MySQLのデータ(データベース、テーブルなど)も全てなくなるので注意が必要です

ローカルのRubyバージョンとコンテナのバージョンの問題を混同しないように気をつけないといけません
この切り分けがはっきりしていないと間違った方向に進んでしまいます

あとから思ったのですが、コンテナを再ビルドすれば大丈夫だったかもしれません

1
docker-compose build

Dockerfileが変更されているはずなのでこちらでも必要なコンテナイメージの再ビルドが行われたと思います

assets precompileで発生するエラー

背景

久々にコーダーが実装したcssをRailsに組み込んだら、エラーが発生してあまりうまくいかなかったのでメモしておきます。

css内の画像指定にimage-urlを指定した場合

エラー内容

cssのbackgroundで指定されている画像はimage-urlで指定します。

1
background: image-url("test/test.png") no-repeat;

引数のパスはapp/assets/images/の下から指定します。

このcssをapplication.scssで読み込むようにします。

1
@import 'test';

すると、以下のようなエラーが発生しました。

1
NoMethodError: undefined method `[]' for nil:NilClass

原因

ファイルの拡張子をcssのままで読み込んでいたため。scssとして読み込む必要があります。

参考 : https://stackoverflow.com/questions/12313612/rake-assetsprecompile-undefined-method-for-nilnilclass

cssをimportする場合

エラー内容

上記エラーが解消されたので、これでOKと思っていたら以下のエラーが発生しました。

1
stack level too deep

原因

importするファイル名はアンダースコアから開始する必要があります。

1
$ mv test.scss _test.scss

これでエラーは解消します。

まとめ

久々にcssの組み込みを行うと忘れていることが多くて戸惑ってしまいます。知識を蓄積するために書き残していくことを続けていきたいと思います。

BigQueryで時間単位の列でのパーティション分割テーブルを作成する

背景

BigQueryでパーティショニングテーブルを作成することになりました。以前も作ったのですが、2度目作るにあたって情報がまとまってないことに気づいたのでこちらに記載していきます。

BigQueryのパーティショニング

BigQueryのパーティショニングの種類は以下のページにまとまっています。

https://cloud.google.com/bigquery/docs/partitioned-tables

今回のデータは日単位で集計したいと思っています。かつ、BigQueryでは標準時間になってしまうので、こちらで指定したカラムにdate型のデータを保持して、そのカラムの値を基準にパーティショニングしたいと思います。

分割テーブルの作成

では実際に作成していきます

スキーマの作成

スキーマはjsonファイルにまとめます

1
2
3
4
5
6
7
8
9
10
11
12
[
{
"name": "id",
"type": "STRING",
"mode": "REQUIRED"
},
{
"name": "created_date",
"type": "DATE",
"mode": "REQUIRED"
}
]

created_dateというカラムを基準にパーティショニングします。test.jsonというファイルで保存します。

テーブルの作成

bqコマンドでテーブルを作成します。コマンドのオプションについてはこちらに記載があります。

オプションにはいろんな種類がありますが、利用するのは--schema--time_partitioning_fieldだけです。(--time_partitioning_typeはデフォルトがDAYのため)

1
2
$ bq mk --table --schema test.json --time_partitioning_field created_date project:dataset.test
Table 'project:dataset.test' successfully created.

テーブルの作成が終わったので内容を確認します。GCPのコンソールから作成したテーブルを選択、右の詳細タブを選択します。

表の情報の中にパーティションに関する情報が表示されます

1
2
3
4
テーブルタイプ         分割
パーティショニングの基準 Day
フィールドで分割     created_date
パーティション フィルタ 不要

正しく作成できました。

まとめ

BigQueryでパーティションテーブルを作成する方法をまとめました。次回からはスムーズに行えると思います。

ChromeDriverのバージョン自動判別

Dockerコンテナビルド後にRSpecでエラー

Dockerコンテナを久々にビルドしてRSpecを実行したらエラーが発生しました。

1
2
Selenium::WebDriver::Error::SessionNotCreatedError:
session not created: This version of ChromeDriver only supports Chrome version 84

原因はChromeとChromeDriverのバージョンがあっていないからです。

以前は動作していましたが、今回コンテナを再ビルドしたことによって問題が発生したような形になっています。原因は何でしょうか。

DockerでChromeDriverのバージョンを固定で指定することの問題点

Dockerfileを見てみましょう。

1
2
3
4
5
6
7
8
RUN yum install -y unzip \
https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm && \
sed -i 's|HERE/chrome"|HERE/chrome" --disable-dev-shm-usage --disable-setuid-sandbox --no-sandbox|g' \
"/opt/google/chrome/google-chrome" && \
cd /usr/local/src/ && \
wget https://chromedriver.storage.googleapis.com/84.0.4147.30/chromedriver_linux64.zip && \
unzip chromedriver_linux64.zip && \
install chromedriver /usr/local/bin/

この記述を見ると、google-chrome-stable_current_x86_64.rpmがバージョン84であるときは正常に動作するのですが、バージョンが更新されてしまうと、バージョン固定でインストールしているChromeDriverとバージョンが合わなくなってしまいます。

ということはDockerコンテナをビルドするタイミングでうまくいったり行かなかったりしてしまうということです。これはよくないですね。

ChromeDriverのバージョン自動判別

そこでインストールされるGoogle ChromeのバージョンにあったChromeDriverをインストールするように変更してみます。

先ほどのDockerfileのRUNコマンドを以下のように変更してみます。

1
2
3
4
5
6
7
8
9
10
RUN yum install -y unzip \
https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm && \
sed -i 's|HERE/chrome"|HERE/chrome" --disable-dev-shm-usage --disable-setuid-sandbox --no-sandbox|g' \
"/opt/google/chrome/google-chrome" && \
cd /usr/local/src/ && \
wget -O LATEST_RELEASE_ "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$(rpm -q --queryformat='%{Version}' google-chrome-stable | sed -e 's/[.][0-9]*$//')" && \
wget "https://chromedriver.storage.googleapis.com/$(cat LATEST_RELEASE_)/chromedriver_linux64.zip" && \
rm LATEST_RELEASE_ && \
unzip chromedriver_linux64.zip && \
install chromedriver /usr/local/bin/

変更があったのは以下の3行です

1
2
3
wget -O LATEST_RELEASE_ "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$(rpm -q --queryformat='%{Version}' google-chrome-stable | sed -e 's/[.][0-9]*$//')" && \
wget "https://chromedriver.storage.googleapis.com/$(cat LATEST_RELEASE_)/chromedriver_linux64.zip" && \
rm LATEST_RELEASE_ && \

なにを行なっているのか一つずつみていきましょう。

インストールされているChromeのバージョン取得

まずは1行目のコマンド置換している箇所を見ていきます。

1
rpm -q --queryformat='%{Version}' google-chrome-stable

インストールされているgoogle-chromeをrpmコマンドを使って問い合わせています。--queryformatというオプションはあまり見かけませんが、manページによると以下のようなオプションです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
検索オプション
rpm 検索の一般的な形式は:

rpm {-q|--query} [select-options] [query-options]

表示されるパッケージ情報の書式を指定することができる。 そうするために
は、

--qf|--queryformat QUERYFMT

オプションを使う。 QUERYFMT が書式文字列である。検索の書式は、標準的な
printf(3) の書式の修正である。書式は静的な文字列 (改行・タブ・その他の
特殊文字の、C 言語の標準的な文字エスケープを含む)と、 printf(3) 型の書
式文字列からなる。 しかし rpm は表示する型を既に知っているので、型指定
は省略されるべきである。 代わりに {} で囲まれた、表示されるへッダーのタ
グ名が使用される。 タグ名に大文字/小文字の区別はなく、タグ名の先頭の
RPMTAG_ 部分も省略することができる。

利用できるタグはrpm --querytagsを実行することで全て表示することができます。AmazonLinux2で実行したら、205ありました。

それではrpmコマンドを実際に実行してみます。

1
2
# rpm -q --queryformat='%{Version}' google-chrome-stable
86.0.4240.75

バージョン番号のみ表示されました。

ちなみに通常の出力は以下です。

1
2
# rpm -q google-chrome-stable
google-chrome-stable-86.0.4240.75-1.x86_64

Chromeのバージョン番号の整形

バージョン番号をパイプで渡してsedに入力として渡しています。sedの正規表現を見てみましょう。

1
's/[.][0-9]*$//'

[.]\.でも良さそうですね。数字が0個以上繰り返して、$は行末にマッチします。バージョン番号の最後のオクテットを''に置換しているということになります。

実際に試してみましょう。

1
2
$ echo '86.0.4240.75' | sed -e 's/[.][0-9]*$//'
86.0.4240

これでコマンド置換している箇所の文字列が取得できました。次は実際にwgetをしてみます。

ChromeDriverのバージョンを保存

先ほどあったコマンド

1
wget -O LATEST_RELEASE_ "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$(rpm -q --queryformat='%{Version}' google-chrome-stable | sed -e 's/[.][0-9]*$//')"

1
wget -O LATEST_RELEASE_ "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_86.0.4240"

であることがわかりました。wgetの-Oオプションは、取得したコンテンツを出力するファイル名を指定します。

実際に実行してしましょう。

1
2
3
4
5
6
7
8
9
$ wget -O LATEST_RELEASE_ "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_86.0.4240"
--2021-02-22 21:01:33-- https://chromedriver.storage.googleapis.com/LATEST_RELEASE_86.0.4240
接続要求を送信しました、応答を待っています... 200 OK
長さ: 12 [text/plain]
`LATEST_RELEASE_' に保存中

LATEST_RELEASE_ 100%[=========================================================================================================================>] 12 --.-KB/s 時間 0s

2021-02-22 21:01:33 (186 KB/s) - `LATEST_RELEASE_' へ保存完了 [12/12]

LATEST_RELEASE_に保存できました。中身を確認します。

1
2
$ cat LATEST_RELEASE_ 
86.0.4240.22

これでインストールすべきChromeDriverのバージョンが

ChromeDriverのダウンロード

実際にChromeDriverがダウンロードできるかどうか確認します。

1
2
3
4
5
6
7
8
9
$ wget "https://chromedriver.storage.googleapis.com/$(cat LATEST_RELEASE_)/chromedriver_linux64.zip"
--2021-02-22 21:14:24-- https://chromedriver.storage.googleapis.com/86.0.4240.22/chromedriver_linux64.zip
接続要求を送信しました、応答を待っています... 200 OK
長さ: 5456932 (5.2M) [application/zip]
`chromedriver_linux64.zip' に保存中

chromedriver_linux64.zip 100%[=========================================================================================================================>] 5.20M 3.17MB/s 時間 1.6s

2021-02-22 21:14:26 (3.17 MB/s) - `chromedriver_linux64.zip' へ保存完了 [5456932/5456932]

無事ダウロードできました。これで、いつでもChromeのバージョンにあったChromeDriverがダウンロード、インストールできるようになりました。

まとめ

  • ChromeDriverの最新バージョンはChromeのバージョンの一部で構成されるURLで取得できる
  • そのURLはhttps://chromedriver.storage.googleapis.com/LATEST_RELEASE_バージョン番号である
  • Chromeのバージョンは少し加工する必要がある

nginxのログを解析する

nginxのログ分析

業務でnginxのログを分析する必要が出てきたので、調査してみました。同じようなことは誰かやっていそうなのですが、あまり情報が出てきません…なのでRubyで簡単に扱えるようなものを作ろうと思いました。

nginxのログフォーマットの確認

まずはnginxのログフォーマットを確認します。nginx.confを確認します。

1
2
3
log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$upstream_response_time" "$request_time"';

この形式はデフォルトなんですかね…

CSVクラスの利用

どのようにして処理をしようかと考えて、CSVクラスを利用することにしました。CSVクラスはセパレータをcol_sepで指定できますし、ダブルクォートで囲まれたセパレータは無視してくれます。

時刻のセパレータを変換

1点困った点がありました。時刻のカラムの処理です。時刻のカラムは

1
[12/Feb/2021:03:20:08 +0900]

と言ったように、[]で囲まれ、+0900の前には空白があります。これをうまく一つのカラムとして処理しなければなりません。log_formatを適切に設定しておけばよかったかもしれませんが、現状こうなってしまっているのでなんとかしなければいけません。

そこで [] "" に変換すれば時刻もダブルクォートで囲うことができます。そのように変換するようにします。

クラスの作成

今回のメインの目的はremote_addrやx_fowarded_forの取得だったので、以下のようなクラスを作成しました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
require 'csv'

class NginxLog
def initialize(raw)
tmp = raw.gsub(' [', ' "').gsub('] ', '" ')
@log = CSV.parse(tmp, col_sep: ' ', liberal_parsing: true).flatten
end

def remote_addr
@log[0]
end

def x_forwarded_for_ips
ips = @log[9].split(',').map &:strip
ips.reject { |ip| ['-', 'unknown'].include? ip }
end

def remote_ips
x_forwarded_for_ips << remote_addr
end
end

コンストラクタの引数に生のログを1行いれてもらえれば、必要な情報を取得できるようにします。

1
2
3
4
log = '1.1.1.1 - - [12/Feb/2021:23:48:48 +0900] "GET / HTTP/1.1" 200 38485 "https://book-reviews.blog/" "Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Mobile/15E148 Safari/604.1" "2.2.2.2" "0.403" "0.404"'
nginx_log = NginxLog(log)
puts nginx_log.remote_ips
=> ["1.1.1.1", "2.2.2.2"]

まとめ

今回はCSVクラスを使ってnginxのログを解析してみました。CSVクラスはCSVだけでなく、いろんな形式のファイルに利用できそうです。

本来であればログ解析基盤にログを集約し、いつでも分析できるような状況を整えておくのがよいかと思います。

GitHub Actionsで The `set-env` command is disabled. のエラーが出た時の対応

GitHub Actionsのworkflowでエラー発生

あるRailsプロジェクトのテストをGitHub Actionsで実行していますが、突然即終了になるようになってしまいました。

見てみると、Rubyのセットアップのところで以下のエラーが出ていました

1
Error: The `set-env` command is disabled. Please upgrade to using Environment Files or opt into unsecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_COMMANDS` environment variable to `true`. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/

Rubyのセットアップ自体は

1
2
- name: Set up Ruby
uses: ruby/setup-ruby@ec106b438a1ff6ff109590de34ddc62c540232e0

という感じのコードになっていました。

エラーの原因調査

特になにも変更していないのになと思ったのですが、まずはエラーにあるリンクのドキュメントを読んでみました。

2020年10月なので、結構前からのようですね。ざっくりいうとセキュリティの問題からset-envadd-pathというコマンドは使えなくなりましたということのようです。どうしても使いたい場合はACTIONS_ALLOW_UNSECURE_COMMANDSという環境変数をtrueに設定すればよいようですが、のぞましくはないんでしょうね。

しかし、見たところset-envadd-pathも使っていません。どこかで呼び出されているのでしょうか。

考えられるのはruby/setup-rubyというActionの中で利用しているということです。

githubをみてみましょう。
https://github.com/ruby/setup-ruby

このREADMEをみると、ruby/setup-ruby@v1となっています。こちらで現在設定しているのはruby/setup-ruby@ec106b438a1ff6ff109590de34ddc62c540232e0です。このcommit idのようなバージョンはGitHub Actionsのテンプレートで指定されていたバージョンです。

試しに今テンプレートを見てみました。

1
2
3
4
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
# change this to (see https://github.com/ruby/setup-ruby#versioning):
# uses: ruby/setup-ruby@v1
uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e

というコードになっており、workflowを作成するタイミングでテンプレートが指定するコミットも変わるようです。コメントにあるように、@v1にしておくのが良さそうです。

というわけで、ruby/setup-ruby@v1に変更することでdeprecationエラーも表示されなくなりました。

まとめ

deprecationエラーの場合は、基本的にはバージョンアップで対応する。コミット指定だと対応が自動的に対応はできないので、バージョン指定が望ましい。自分がソフトウェアを公開する時にも参考になりますね。

RailsのRestClientが利用するTLSのバージョンを確認する

TLSv1.2への移行

利用しているAPIがTLSv1.2へ移行するとのことで、クライアント側がTLSv1.2で通信可能かどうか確認してくださいと連絡をもらいました。

APIにアクセスしているのはrest-client(2.1.0)でした。なのでrest-clientがアクセスする際に利用するTLSのバージョンを確認すればよいことになります。

TLSのバージョン取得方法

ググってみるとopensslのドキュメントが見つかりました

https://www.openssl.org/docs/man1.1.1/man3/SSL_get_version.html

いろんな種類があるようです。

rest-clientの場合

rest-clientはリクエストを作成する場合にNet::HTTPを利用します。

https://github.com/rest-client/rest-client/blob/master/lib/restclient/request.rb#L163
https://github.com/rest-client/rest-client/blob/master/lib/restclient/request.rb#L464

Net::HTTPが利用するTLSのバージョンを知るためにgithubでコードを確認してみます。

https://github.com/ruby/net-http/blob/master/lib/net/http.rb#L975

https://github.com/ruby/net-http/blob/master/lib/net/http.rb#L1030

ここでsocketの生成を行なっています

https://github.com/ruby/net-http/blob/master/lib/net/http.rb#L1044

@socketを作成しています。Net::BufferedIO.newの引数で先ほど作成したsocketを指定しています。

BufferedIOを確認すると、#ioというメソッドがあり、オブジェクトを返してくれるということです。

Module#prependで試してみる

Module#prependを使って実際に出力してみます。

処理を入れるところはsocketをクローズする手前のdo_finishメソッドにします。

https://github.com/ruby/net-http/blob/master/lib/net/http.rb#L1069

まずは@socketを確認してみます。

1
2
3
4
5
6
7
$ rails c
[1] pry(main)> Net::HTTP.prepend Module.new { def do_finish; pp @socket; super; end }
=> Net::HTTP
[2] pry(main)> RestClient.get('https://google.co.jp')
#<Net::BufferedIO io=#<OpenSSL::SSL::SSLSocket:0x00007f81b4001558>>
#<Net::BufferedIO io=#<OpenSSL::SSL::SSLSocket:0x00007f81aedd4be0>>
=> <RestClient::Response 200 "<!doctype h...">

ioというOpenSSL::SSL::SSLSocketクラスのインスタンスがあります。
ioの中身を確認します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[1] pry(main)> Net::HTTP.prepend Module.new { def do_finish; pp @socket.io; super; end }
=> Net::HTTP
[2] pry(main)> RestClient.get('https://google.co.jp')
#<OpenSSL::SSL::SSLSocket:0x00007f8a3a8f9b18
@context=
#<OpenSSL::SSL::SSLContext:0x00007f8a3a8fac48
@cert_store=
#<OpenSSL::X509::Store:0x00007f8a369b2ba8
@chain=nil,
@error=nil,
@error_string=nil,
@time=nil,
@verify_callback=nil>,
@max_proto_version=nil,
@min_proto_version=769,
@session_new_cb=
#<Proc:0x00007f8a3a8f9cf8@/Users/s1180997/.anyenv/envs/rbenv/versions/2.6.6/lib/ruby/2.6.0/net/http.rb:986>,
@verify_hostname=true,
@verify_mode=1>,
@eof=false,
@hostname="www.google.co.jp",
@io=#<TCPSocket:fd 26, AF_INET, 10.81.20.99, 55387>,
@rbuffer="",
@sync=true,
@sync_close=true,
@wbuffer="">

OpenSSL::SSL::SSLContextのインスタンスの@contextから取得できそうです。

1
2
3
4
5
6
7
8
[1] pry(main)> Net::HTTP.prepend Module.new { def do_finish; pp @socket.io.context.ssl_version; super; end }
=> Net::HTTP
[2] pry(main)> RestClient.get('https://google.co.jp')
NoMethodError: undefined method `ssl_version' for #<OpenSSL::SSL::SSLContext:0x00007ff13076a998>
from (pry):1:in `do_finish'
Caused by NoMethodError: undefined method `ssl_version' for #<OpenSSL::SSL::SSLContext:0x00007ff1310a2808>
Did you mean? ssl_version=
from (pry):1:in `do_finish'

ん〜ダメでした。setterのみのようです。

ためしにmethodメソッドを利用してみます。

1
2
3
4
5
6
[1] pry(main)> Net::HTTP.prepend Module.new { def do_finish; pp @socket.io.method(:ssl_version); super; end }
=> Net::HTTP
[2] pry(main)> RestClient.get('https://google.co.jp')
#<Method: OpenSSL::SSL::SSLSocket#ssl_version>
#<Method: OpenSSL::SSL::SSLSocket#ssl_version>
=> <RestClient::Response 200 "<!doctype h...">

ssl_versionがかえってきています。ということは呼び出せるということですね。

1
2
3
4
5
6
[1] pry(main)> Net::HTTP.prepend Module.new { def do_finish; pp @socket.io.ssl_version; super; end }
=> Net::HTTP
[2] pry(main)> RestClient.get('https://google.co.jp')
"TLSv1.3"
"TLSv1.3"
=> <RestClient::Response 200 "<!doctype h...">

無事バージョンが取得できました!

まとめ

RailsのRestClientで利用しているTLSのバージョンを確認するにはrails consoleで

1
Net::HTTP.prepend Module.new { def do_finish; pp @socket.io.ssl_version; super; end }

を実行してからRestClient.getでリクエストを送る。

fluentd-plugin-concatのtimeout_labelについて

前回までのまとめ

前回、分割されたdockerのログをどのようにして結合したかを記載しました。

今回はfluentd-plugin-concatの設定で用いたtimeout_labelについて記載します。

timeoutエラーが大量に発生する

最初はtimeout_labelを設定していませんでした。それでも正しく動作したのですが、CloudWatchのログを確認すると、大量のERRORが発生していました。

1
2
Throughput limits for the delivery stream may have been exceeded.
294 records failed to be delivered. Will retry.

結合されたログが正しくkinesisに出力されていなかったようです。

timeout_labelを追加する

timeout_labelを設定するとログを再ルーティングできると書いてあったので、ログを結合する箇所に timeout_labelを設定しました。そのlabelの先では、通常(タスク定義ファイルで設定した)通り、kinesisへデータを送るように設定します。

設定した箇所のconfを掲載します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<filter firelens**>
@type concat
key log
stream_identity_key partial_id
multiline_start_regexp /{"JSONの先頭":/
multiline_end_regexp /"JSONの終点"}$/
separator ""
timeout_label @TIMEOUT
</filter>

<label @TIMEOUT>
<match **>
@type kinesis_firehose
delivery_stream_name kinesis-data-stream
region ap-northeast-1
</match>
</label>

timeout_labelの設定をしなければ、filterディレクティブだけで設定は完了です。実際に転送するkinesisの設定は、タスク定義ファイルで行うからです。

しかし、今回timeout_labelの設定を行なって、その転送先として、タスク定義ファイルと同じ設定をしています。

これでtimeout_labelに引っかかったログは、正しい転送先へ送られます。

timeout_labelに引っかかったということはCloudWatchを見ると確認できます。

1
adding match in @TIMEOUT pattern="**" type="kinesis_firehose"

まとめ

Dockerのログが16kで切れてしまう問題について、記載してきました。fluentd-plugin-concatの設定が実際のログデータに依存してしまっているので、その辺りをDockerのログであるという普遍的なデータ(partial_idやpartial_lastなど)を用いて結合できるように設定できるとよりスマートかなと思います。今後はその辺りも調査していきたいと思います。