Windows版のFirefoxでは、メニューから「Firefoxについて」を選択すると、最新のバージョンのチェックを行えます。 この際、更新があればアップデートを適用できるようになっています。*1
今回は、Firefoxのアップデートのしくみがどのようになっているのかを以下の技術資料を参考に紹介します。
Firefoxの更新がある場合、以下の流れで適用されます。(手動で明示的に更新する場合)
ここでポイントとなるのがバージョンチェックとアップデートファイルのダウンロードの部分です。 それぞれもう少し詳しく説明します。
バージョンチェックでは、実際にどのようなリクエストを更新サーバに出しているのでしょうか。実際に確認してみましょう。
これはURL入力欄に about:config
とタイプして設定画面を表示させ、 app.update.url
を検索してみるとわかります。
app.update.url
はバージョンチェックするためにアクセスするURLです。
45.6.0ESRの場合は以下のとおりです。*2
https://aus5.mozilla.org/update/6/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%(nowebsense)/%SYSTEM_CAPABILITIES%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml
いくつか %XXX%
というパラメータがあります。これはリクエストするときに実際の値に置換されます。それぞれの意味は次の通りです。
%PRODUCT%
プロダクト名です。「Firefox」となります。%VERSION%
45.6.0ESRの場合「45.6.0」です。%BUILD_ID%
45.6.0ESRの場合は「20161209150850」です。%BUILD_TARGET%
32bit版のFirefoxでは「WINNT_x86-msvc-x64」です。64bit版だと「WINNT_x86_64-msvc-x64」です。%LOCALE%
日本語版なので「ja」です。%CHANNEL%
どのエディションなのかを示す値です。ESR版なので「esr」です。%OS_VERSION%
使用しているOSを示す値です。Windows 7 64bit版だと「Windows_NT%206.1.1.0%20(x64)」です。%SYSTEM_CAPABILITIES%
環境によっておそらく異なる値となります。手元の環境では「SSE3」でした。%DISTRIBUTION%
Firefox公式なので「default」です。%DISTRIBUTION_VERSION%
Firefox公式なので「default」です。したがって、実際のリクエストは以下のようになります。
https://aus5.mozilla.org/update/6/Firefox/45.6.0/20161209150850/WINNT_x86-msvc-x64/ja/esr/Windows_NT%206.1.1.0%20(x64)(nowebsense)/SSE3/default/default/update.xml
このとき実際に更新サーバから返ってくるレスポンスは以下のとおりです。
<?xml version="1.0"?>
<updates>
<update type="minor" displayVersion="45.7.0esr" appVersion="45.7.0" platformVersion="45.7.0" buildID="20170118123525" detailsURL="https://www.mozilla.org/ja/firefox/45.7.0/releasenotes/">
<patch type="complete" URL="http://download.mozilla.org/?product=firefox-45.7.0esr-complete&os=win&lang=ja" hashFunction="sha512" hashValue="6168bcaa9424fe1d789c80cf54fe55c8b4e70f2d5468ebbeaef1f2776a3a840806b1ac270306852684a45ff9ad050de3f46a149dc2747d4a6b9440e0c27bf8a5" size="52388819"/>
<patch type="partial" URL="http://download.mozilla.org/?product=firefox-45.7.0esr-partial-45.6.0esr&os=win&lang=ja" hashFunction="sha512" hashValue="885218494c4b2b8ecd9c1c696b84f5850e78898cd289e28aa6cd8b6f727fa4dbd363de3edb6da861456ab01bb2c38639fbdc93f01746e89e7de4c3d836fceab5" size="6470506"/>
</update>
</updates>
更新対象として、45.7.0ESRが存在していることがわかります。*3
次のようにコマンドラインからパラメータを明示してバージョンチェックのリクエストを投げることもできます。*4
curl "https://aus5.mozilla.org/update/6/Firefox/45.6.0/20161209150850/WINNT_x86-msvc-x64/ja/esr/Windows_NT%206.1.1.0%20(x64)(nowebsense)/SSE3/default/default/update.xml"
レスポンスの例で示したように、更新サーバはアップデートファイルへのリンクを返します。アップデートがなければ以下のように空のタグを返します。
<?xml version="1.0"?>
<updates>
</updates>
アップデートがあるときには差分版もしくは完全版がダウンロードされますが、これらはどこから入手したらよいのでしょうか。
これには、CDN経由でダウンロードするやりかたがあります。以下のような所定のディレクトリ階層でアップデートファイルが提供されています。
http://download.cdn.mozilla.net/pub/firefox/releases/(ESRのバージョン)/update/(32bit or 64bit)/(言語)/
したがって45.7.0ESRの場合、45.6.0ESRから45.7.0ESRへの差分版と45.7.0ESRへの完全版のアップデートファイルが以下から入手できます。
http://download.cdn.mozilla.net/pub/firefox/releases/45.7.0esr/update/win32/ja/
このディレクトリには以下の3つのファイルがあります。
このうち、アップデートファイルは拡張子が.marのものです。marとは Mozilla ARchive
に由来しています。
marファイルのフォーマットに興味がある方は、Software_Update:MARを参照するとよいでしょう。
firefox-45.6.0esr-45.7.0esr.partial.mar
は45.6.0ESRから45.7.0ESRへ差分更新する際に適用されるファイルで、 firefox-45.7.0esr.complete.mar
は45.7.0ESRへ完全版でアップデートするときに適用されるファイルです。
今回は、Firefoxのアップデートのしくみについて紹介しました。 このしくみを利用すれば、企業内でFirefoxの更新サーバを独自に立てることも可能です。 そのあたりの話は別の記事にて紹介したいと考えています。
2017年4月12日頃のApache Arrowの様子を紹介します。
arrow::Tensor
の追加0.2.0頃のApache Arrowはarrow::Array
で1次元のデータ(配列)、arrow::Table
で2次元のデータ(表)を表現していました。最近のApache Arrowはこれらに加えてarrow::Tensor
を追加しました。これはN次元のデータを表現します。
arrow::Tensor
は以下と同じようなデータを表現します。
ndarray
Tensor
(TourchはFacebookやTwitterなどが利用している科学計算フレームワークで機械学習もできる。Luaを使う。)Tensor
Apache Arrowはシステム間でのデータ交換のコストを下げることを重視しています。つまり、最近のApache Arrowはarrow::Tensor
で表現するようなデータのデータ交換コストも下げる取り組みを始めた、ということです。
現時点のarrow::Tensor
はゼロコピーでのデシリアライズに対応しています。Rayという分散タスク実行エンジンはNumPyのデータをシリアライズするためにApache Arrowを使うようにしました。
今後はarrow::Tensor
のデータに対して数学関数を使えるようにする予定です。要素毎(element-wise)の演算だけでなく行列演算もサポートするかどうかはまだわかりません。
0.2.0までのApache Arrowはlibarrowとlibarrow_io(入出力用)とlibarrow_ipc(シリアライズ・デシリアライズ用)というライブラリーに分かれていましたが、libarrowに統合されました。Apache Arrowを使う場合は全部使うことが多いので、これでシンプルに使えるようになりました。
2017年4月12日頃のApache Arrowの様子を紹介しました。そろそろ0.3.0がでそうなのですが、arrow::Tensor
は0.3.0の目玉になりそうです。
fluent-plugin-geoipというIPアドレスから国や州・県などの情報を取得してレコードを加工するFluentdのプラグインがあります。
以前はGeoIP Legacyにあるデータベースを使ってIPアドレスから情報を取得していましたが、GeoIP2がリリースされてしばらく経過したのでGeoIP2に対応した話を書きます。
経緯はSupport GeoLite2 format · Issue #39 · y-ken/fluent-plugin-geoipに書いてありますが、少し抜粋します。
GeoIPについて調べているとGeoIP2を見つけ、さらにGeoIP2に対応したfluent-plugin-filter-geoipを見つけました。 そこで、fluent-plugin-filter-geoipの内部を調べ、どのようにしてGeoIP2に対応させているかを確認したところmaxminddbというピュアRuby実装のライブラリを使っていました。 他にGeoIP2を利用できるライブラリがあるかどうかを調査したところいくつか既存の実装がありました。
新たにfluent-plugin-geoipにGeoIP2対応を追加するにあたって、GeoIPを使用していたときと同等の性能を維持できるかどうかを確認するためにベンチマークをとりました。
ベンチマークによると、geoip2_compatであれば性能に問題はなさそうなことがわかりましたが、geoip2_compatだとGeoIP Legacyと同等のデータしか取得することができません。GeoIP2にはそれ以外のデータも多数追加されているので、できれば全ての機能を使えるようにしたいと考えていました。maxminddbとhive_geoip2はGeoIPよりも遅くなってしまうので使えません。maxmind_geoip2は速度的には問題なさそうでしたがAPIが独特で使い辛い感じでした。
それぞれの拡張ライブラリのコードを読んでみたところ、遅くなっていた原因は全ての属性値を取得していたことでした。速くするためには、必要な属性値のみ取得するようにすればよいはずです。 この仮説を検証するためにgeoip2_cを開発しました。
先程のベンチマークにgeoip2_cを追加したベンチマークによるとgeoip2_cが最速です。
Rehearsal ---------------------------------------------------------
geoip 0.140000 0.000000 0.140000 ( 0.147379)
geoip2_compat 0.110000 0.010000 0.120000 ( 0.108135)
maxminddb (pure ruby) 4.310000 0.000000 4.310000 ( 4.320897)
hive 0.320000 0.000000 0.320000 ( 0.321934)
maxmind_geoip2 1.240000 0.320000 1.560000 ( 1.561630)
geoip2_c 0.070000 0.000000 0.070000 ( 0.067715)
------------------------------------------------ total: 6.520000sec
user system total real
geoip 0.140000 0.000000 0.140000 ( 0.142973)
geoip2_compat 0.160000 0.000000 0.160000 ( 0.162996)
maxminddb (pure ruby) 4.650000 0.000000 4.650000 ( 4.654088)
hive 0.310000 0.000000 0.310000 ( 0.308363)
maxmind_geoip2 1.350000 0.430000 1.780000 ( 1.780049)
geoip2_c 0.080000 0.010000 0.090000 ( 0.078209)
bundle exec ruby bench.rb 13.26s user 0.83s system 99% cpu 14.134 total
geoip2_cはIPアドレスでlookup
を実行しただけでは、実際の値を取得しません。他のライブラリはlookup
の時点で値を取得しています。GeoIP2のライブラリでは取得する値が多ければ多いほど処理に時間がかかります。geoip2_cでもGeoIP2で利用できる値を全て取得すると処理に時間がかかるようになります。
利用可能な属性数は、表の通りです。
ライブラリ | 利用可能な属性数 |
---|---|
geoip | 9 |
geoip2_compat | 8 |
geoip2_c | 7+17+(4*7)=52 |
hive_geoip2 | 7+17+(4*7)=52 |
geoip2_cでは例えば、以下のような属性を取得することができますが、実際のアプリケーションでは全ての属性を必要とすることは少ないでしょう。よって必要な属性を必要なときに取得するようにした方が効率がよいです。なお、GeoIP2ではIPアドレスによって取得できる属性に違いがあります。
{"city"=>{"geoname_id"=>10300919, "names"=>{"en"=>"Fort Huachuaca"}},
"continent"=>
{"code"=>"NA",
"geoname_id"=>6255149,
"names"=>
{"de"=>"Nordamerika",
"en"=>"North America",
"es"=>"Norteamérica",
"fr"=>"Amérique du Nord",
"ja"=>"北アメリカ",
"pt-BR"=>"América do Norte",
"ru"=>"Северная Америка",
"zh-CN"=>"北美洲"}},
"country"=>
{"geoname_id"=>6252001,
"iso_code"=>"US",
"names"=>
{"de"=>"USA",
"en"=>"United States",
"es"=>"Estados Unidos",
"fr"=>"États-Unis",
"ja"=>"アメリカ合衆国",
"pt-BR"=>"Estados Unidos",
"ru"=>"США",
"zh-CN"=>"美国"}},
"location"=>
{"accuracy_radius"=>1000,
"latitude"=>31.5273,
"longitude"=>-110.3607,
"metro_code"=>789,
"time_zone"=>"America/Phoenix"},
"postal"=>{"code"=>"85613"},
"registered_country"=>
{"geoname_id"=>6252001,
"iso_code"=>"US",
"names"=>
{"de"=>"USA",
"en"=>"United States",
"es"=>"Estados Unidos",
"fr"=>"États-Unis",
"ja"=>"アメリカ合衆国",
"pt-BR"=>"Estados Unidos",
"ru"=>"США",
"zh-CN"=>"美国"}},
"subdivisions"=>
[{"geoname_id"=>5551752,
"iso_code"=>"AZ",
"names"=>
{"de"=>"Arizona",
"en"=>"Arizona",
"es"=>"Arizona",
"fr"=>"Arizona",
"ja"=>"アリゾナ州",
"pt-BR"=>"Arizona",
"ru"=>"Аризона"}}]}
GeoIP2サポートする際、なるべくGeoIP Legacyと互換性を保つためにgeoip2_compatを利用し、GeoIP2で利用できる属性を全て使用するためにgeoip2_cを使用することにしました。 設定によってGeoIP Legacyも利用できるようにしました。
それぞれで利用できる属性は以下の通りです。
GeoIP Legacy:
placeholder attributes | output example | type | note |
---|---|---|---|
${city[lookup_field]} | "Ithaca" | varchar(255) | - |
${latitude[lookup_field]} | 42.4277992248535 | decimal | - |
${longitude[lookup_field]} | -76.4981994628906 | decimal | - |
${country_code3[lookup_field]} | "USA" | varchar(3) | - |
${country_code[lookup_field]} | "US" | varchar(2) | A two-character ISO 3166-1 country code |
${country_name[lookup_field]} | "United States" | varchar(50) | - |
${dma_code[lookup_field]} | 555 | unsigned int | only for US |
${area_code[lookup_field]} | 607 | char(3) | only for US |
${region[lookup_field]} | "NY" | char(2) | A two character ISO-3166-2 or FIPS 10-4 code |
geoip2_c backend:
placeholder attributes | output example | note |
---|---|---|
${city.names.en[lookup_field]} | "Mountain View" | - |
${location.latitude[lookup_field]} | 37.419200000000004 | - |
${location.longitude[lookup_field]} | -122.0574 | - |
${country.iso_code[lookup_field]} | "US" | - |
${country.names.en[lookup_field]} | "United States" | - |
${postal.code[lookup_field]} | "94043" | - |
${subdivisions.0.iso_code[lookup_field]} | "CA" | - |
${subdivisions.0.names.en[lookup_field]} | "California" | - |
geoip2_cバックエンドでは、上記の属性だけでなくGeoIP2のデータベースに含まれる全ての属性を使用可能です。
geoip2_compat backend:
placeholder attributes | output example | note |
---|---|---|
${city[lookup_field]} | "Mountain View" | - |
${latitude[lookup_field]} | 37.419200000000004 | - |
${longitude[lookup_field]} | -122.0574 | - |
${country_code[lookup_field]} | "US" | - |
${country_name[lookup_field]} | "United States" | - |
${postal_code[lookup_field]} | "94043" | |
${region[lookup_field]} | "CA" | - |
${region_name[lookup_field]} | "California" | - |
geoip2_compatバックエンドでは、上記の属性のみ使用可能です。
geoip2_c/geoip2_compatを利用するにはlibmaxminddbを事前にインストールする必要があります*1。
libmaxminddb*2は多くのLinuxディスリビューションでパッケージ化されていてそれぞれのパッケージマネージャで簡単にインストールすることができます。
GeoIP2を利用できるfluent-plugin-geoip 0.7.0がリリース済みです。互換性のためにgeoip2_cとgeoip2_compatはdevelopment dependenciesになっているので、GeoIP2を利用したい場合は、利用したいバックエンドに対応したGemを事前にgem install
するかGemfileに記載してbundle install
するかしてください。
なおFluentd v0.14 APIへの対応はこのプルリクエストで進行中です。
調査過程で見つけたGeoIP Legacy/GeoIP2に対応したfluent-pluginの比較を載せておきます。おすすめはもちろんfluent-plugin-geoipです。
GeoIP Legacy | GeoIP2 | 速度 | 特徴 | |
---|---|---|---|---|
fluent-plugin-geoip | ○ | ○ | 速い | |
fluent-plugin-filter-geoip | × | ○ | 遅い | データベースを自動ダウンロードできる |
fluent-plugin-geoip-filter | ○ | × | 速い | LRUキャッシュ搭載 |
fluent-plugin-filter-geo | × | ○ | 遅い | fluent-plugin-filter-geoipのfork |
fluent-plugin-geoipのGeoIP2対応を進めたときの流れをまとめてみました。
Rubyの拡張ライブラリは簡単に書ける*3ので今後も機会があればどんどん書きたいです。
Fluentd v0.14では新たにstorageプラグインという新しいタイプのプラグインが導入されました。
FluentdをインストールしただけではJSON形式により保存される storage_local
プラグインしかありませんが、このstorageプラグインはFluentdのプラグインのインスタンスが保持する値をKVSに集約することにも使用することができます。
Fluentd v0.14ではさらにプラグインヘルパーという概念も追加されました。storageプラグインにおいてもstorageプラグインを直接使うのではなく、storageプラグインヘルパーを通じて使うことが推奨されます。
storageプラグインは以下のAPIを持ちます。これはKVSから値を取り出したり、保存したり、また取り出した値をキャッシュしておくのに合うAPIとなっています。
# basically, interact with KVS
def load
end
# basically, interact with KVS
def save
end
# Normally, the following methods work as `cache`.
def get(key)
end
def fetch(key, defval)
end
def put(key, value)
end
def delete(key)
end
def update(key, &block) # transactional get-and-update
end
このうち、 #load
と #save
については実際のKVSに対して値を読み込んできたり、保存したりする役割を担います。
一方、 #get
、#fetch
、#put
、#delete
、#update
についてはstorageプラグインだけではキャッシュとして振る舞うことが求められます。
storageプラグインヘルパーはstorageプラグインを直接使わずにstorageプラグインの性質を変化させるように作成されています。
storageプラグインヘルパーの #wrap_instance
メソッドにより、storageプラグインのインスタンスをそのまま使用するか、storageプラグインの値を永続化して同期を取るか、単に同期を取るかが決定されます。
Fluentdの実際のコードでは以下のようになっています。
def wrap_instance(storage)
if storage.persistent && storage.persistent_always?
storage
elsif storage.persistent
PersistentWrapper.new(storage)
elsif !storage.synchronized?
SynchronizeWrapper.new(storage)
else
storage
end
end
<storage>
セクションに persistent true
が設定されていることや、 #persistent_always?
の返す値、#synchronized?
が返す値が振る舞いを変えることがわかります。
これらを踏まえて、筆者はMongo、Redis、Memchachedについてのstorageプラグインをそれぞれ作成しました。
これらを用いると上記3つのKVSに対してstorageプラグインによりownerプラグインであるinput, output, filterプラグインの情報をKVSへ集約することができます。
Fluentd v0.14で導入されたstorageプラグインの概要とstorageプラグインヘルパーを通した場合の振る舞いの変化について解説しました。 storageプラグインをうまく活用するとstorageプラグイン対応が入っているfluent-plugin-systemdやfluent-plugin-windows-eventlogのようにどこまで読んだかの位置の記録をKVSに集約することができるようになります。
Firefoxのアドオンを個人でインストールする場合、Firefoxのアドオンマネージャを経由するか、Add-onsサイトからインストールすることでしょう。
個人利用の場合にはそれでよいのですが、Firefoxを企業内に導入する場合には自由にアドオンをインストールさせないというポリシーを適用することがあります。 その場合、事前に許可したアドオンだけは使えるようにしたいという要件が必須となることがあります。
今回はFirefoxの企業内利用で事前に許可したアドオンだけは使えるようにしたいという要件を満たそうとしたものの、アドオンが認識されなくて困った場合の確認方法について紹介します。
そもそも、アドオンはどこに配置することになっているのでしょうか。 アドオンがインストールされた状態としてFirefoxに認識されるパターンはいくつかあります。 Windowsの場合だと例えば以下です。
C:\Program Files (x86)\Mozilla Firefox\browser\extensions
C:\Program Files (x86)\Mozilla Firefox\distribution\extensions
%AppData%\Mozilla\Profiles\(プロファイル)\extensions
(個人利用の場合はたいていこのケース)基本は上記の3箇所です。企業利用において、 browser\extensions
に配置するか distribution\extensions
に配置するかはポリシーによって変わります。
通常は browser\extensions
です。Firefox起動時にユーザープロファイルへとインストールさせたい場合にのみ distribution\extensions
に配置します。
例えば、Add-onsのサイトからアドオンファイルのみダウンロードして、企業内利用のために配置する場合を考えてみましょう。 最新のアドオンはAdd-onsのサイトの「Firefoxへ追加」ボタンのリンクをたどることで個別にダウンロードすることが可能です。
一定期間後に履歴を削除するためのアドオン「Expire history by days」なら以下のリンクから入手可能です。
https://addons.mozilla.org/firefox/downloads/latest/expire-history-by-days/addon-331631-latest.xpi
しかし、xpiをそのまま browser\extensions
へと配置してもFirefoxは認識しません。
実は、アドオンがFirefoxに認識されるかはある特定のファイル名になっているかによって決まります。
この決まりとは、アドオンのファイル名は em:id
と呼ばれる値にならって命名するというものです。
では em:id
は何を確認すればいいかというと、アドオンに含まれる install.rdf
の中身をみるとわかります。
「Expire history by days」の場合、em:id
は em:id="expire-history-by-days@bonardo.net"
となっています。
そのため、ダウンロードした addon-331631-latest.xpi
を expire-history-by-days@bonardo.net
にリネームして browser\extensions
以下に配置するとFirefoxにアドオンとして認識されるようになります。
今回は、企業内利用で事前に許可したアドオンだけを使えるようにしようとして、アドオンがうまく認識されずに困った場合の確認方法を紹介しました。もしアドオンを所定の場所へと配置したのに認識されない場合には、ファイル名がアドオンの em:id
と一致しているか確認してみてください。
プログラマーは基本的にプレーンテキスト形式が好きな生き物で、ドキュメントならMarkdown、表形式のデータならCSVが定番です。プレーンテキスト形式だとシェルのコマンドや簡単なスクリプトで容易に加工できますし、Gitリポジトリなどに格納した状態でも変更点を追いやすいです。
しかし、たまにどうしても、もうちょっとリッチな形式のバイナリファイルをマスターデータとして持っておかないといけないことがあります。Microsoft ExcelやLibreOffice(OpenOffice.org)Calcなのスプレッドシートもそのひとつです。
Microsoft Excelの場合、悩みを抱える人が多いためか、すでに色々な方が解決策を公開されています。例えばGitで管理しているExcelファイルの差分を見るという記事では、Go言語製のツールを併用する手順が紹介されています。
一方、ODF(OpenDocument Format)のスプレッドシート形式(ods)についてはあまりそのような情報が出回っていないようです。odt2txtというツールを使ってドキュメント形式(odt)の差分を表示する方法の解説はあり、その一環でodsも差分を表示できるようになるのですが、元がodt用なのでodsについてはいまいち微妙な結果になってしまいます。
そこでこの記事では、「odsの変更点を差分表示する」という事に焦点を当てて解説することにします。
バイナリファイルの変更点の差分をgit diff
で見られるようにするために必要なのは、要するに、diff
で比較できる形式にファイルを変換するツールです。前述のExcel形式やodtの差分を見る方法も、それらのファイルに対応した*「引数で指定されたファイルをプレーンテキスト形式に変換して標準出力に出力する」というツールをいかに用意するか*がキモになっています。
odsをプレーンテキスト(CSV)に変換して標準出力に出力するコマンドが~/local/bin/ods2csv
の位置に置かれていたとすると、Linux環境では
コマンドをフィルタとして登録するため、~/.gitconfig
に以下の行を追加する。
# odfspreadheetというフィルタの実体として、ods2csvを登録する。
[diff "spreadsheet"]
textconv = ~/local/bin/ods2csv
拡張子とフィルタを対応付けるため、~/.gitattributes
に以下の行を追加する。
*.ods diff=spreadsheet
以下のコマンド列を実行し、~/.gitattributes
をグローバルな設定ファイルとして登録する。
$ git config --global core.attributesFile ~/.gitattributes
これで、手元のGitリポジトリ内で拡張子が.ods
であるファイルに変更が加わっていた場合に、git diff
を実行すると自動的にods2csv
が実行され、プレーンテキストに変換した後の内容の差分が表示されるようになります。
ということで、あとはこのような振る舞いをするods2csv
をどのように用意するかという話になります。
実は、LibreOffice(OpenOffice.org)はコマンドライン引数を使ってある程度の自動操作ができ、--convert-to csv
と指定すれば、odsの中でアクティブなワークシートをCSVとしてエクスポートさせられます。以下は、この機能を使って~/local/bin/ods2csv
をシェルスクリプトとして記述した例です。
#!/bin/bash
tempdir="$(mktemp -d)"
csvfile="$(basename "$1" .ods).csv"
# $HOMEを上書きしておかないと、すでにLibreOfficeのプロセスが起動している場合に
# ここでのsofficeの実行に失敗してしまう。
export HOME="$tempdir"
soffice --nofirststartwizard --headless --convert-to csv --infilter=CSV:44,34,76,1 --outdir "$tempdir" "$1"
cat "$tempdir/$csvfile"
rm -rf "$tempdir"
ただ、これだとあくまでアクティブなワークシート1つだけが変換されて他のワークシートは無視されてしまいます。複数ワークシートがあるファイルだと期待したような結果を得られません。
ssconvert
を使うより実用的な方法として、Gnumeric(Ubuntuであればsudo apt install gnumeric
でインストール可能)の一部として提供されているコマンドラインツールのssconvert
を使う方法があります。
ssconvert
はGnumericで取り扱える形式のファイルを変換する機能を提供していますが、出力形式をCSVにして、-S
(--export-file-per-sheet
)オプションを指定すれば、すべてのワークシートを別々のCSVファイルに分割して出力させる事もできます。以下は、それを応用して~/local/bin/ods2csv
をシェルスクリプトとして記述した例です。
#!/bin/bash
sscat() {
ssconvert -S "$(basename "$1")" "$(basename "$1").%s.csv" 1>/dev/null 2>&1
for csv in *.csv
do
echo "$csv:"
cat "$csv"
done
}
tempdir="$(mktemp -d)"
cp "$1" "$tempdir/"
(cd "$tempdir" &&
sscat "$(basename "$1")")
rm -rf "$tempdir"
実際のところは、ssconvert
は入出力ファイルの形式をファイル名から自動判別するようになっているため、このスクリプトは.xlsでも.xlsxでもGnumericが取り扱える形式なら何にでも使えます。よって、~/.gitattributes
は
*.ods diff=spreadsheet
*.xls diff=spreadsheet
*.xlsx diff=spreadsheet
と書いてしまって問題ありません。
Gitリポジトリに格納された.odsの変更点を差分表示する方法として、ssconvert
をベースにしたシェルスクリプトで.ods(およびその他のスプレッドシート形式のファイル)の全内容をCSVに変換して出力する方法をご紹介しました。
Gitでのバイナリファイルの取り扱いは何かと面倒ですが、差分が見やすくなるだけでも使い勝手はずいぶん向上するはずです。また、直接はGitのテキスト化フィルタに使えないコマンドでも、これらの例のようにシェルスクリプトを作成するだけでテキスト化フィルタにすることができます。お手元のリポジトリでスプレッドシート形式のファイルの取り扱いにお悩みの方は、ぜひ一度挑戦してみて下さい。
(なお、この方法はあくまでgit diff
に対してのみ有効で、git log
やgit show
に対しては機能しません。あしからずご了承ください。)
(なお、ここではgit diff
についてのみ触れましたが、実際にはこの設定がなされていれば、git log -p
やgit show
でもodsがCSVに変換された結果の差分が表示されるようになります。)
クリアコードのフリーソフトウェア・OSSの法人向けサポートサービスでは、お客様向けの納品物としてExcel形式のワークシートや目視確認用の検証手順書を提供する場合が度々あります。この記事では、そういった納品ドキュメントの作成と更新の手間を軽減するために行っている工夫の一部をご紹介します。
自由な気風を好むITエンジニアは、資料にはMarkdownやCSVのようにプレーンテキスト形式を好む場合が多いです。プレーンテキスト形式の資料には、
diff
コマンドで簡単に差分を見られる。といった具合に、何かと取り扱いが容易であるという大きなメリットがあります。
しかし、それらのプレーンテキストベースの資料は、一般寄りの方にとってはあまりいい印象を持たれない場合があります。弊社の法人向けサポート事業においても、直接やりとりする情報システム部門の担当者の方はまだしも、その向こう側で決済等の意志決定を下される方々は、ITの専門家というわけではない事がままあります。そのため納品物としては、文章であればきちんと整形されたドキュメントが、表形式であればセルの色付けや結合などのレイアウト調整が施された物が望まれる傾向にあります。
その一方で、前述したようなメリットを享受しにくい事から、OSS開発者は整形されたドキュメントをできるかぎり一時ソースにはしたがらない印象もあります。
普段の作業効率を高める事と、納品物としての体裁を整える事。この両者をどうやって両立するかが、法人向けサポートでの地味に重要な点となります。
作業効率と納品物の品質の両立のための方法としてまず考えつくのは、ソースをプレーンテキスト形式で管理し、納品物を自動生成するというやり方です。
実際に、Pandocというツールを使うと、Markdown形式のソースからPDFやODF(odt)、Word形式(doc, docx)を自動生成したり、その際に目次を自動的に埋め込んだりといった事が容易にできます。これについての詳しい話は、Markdownで書いたテキストをPDFに変換して納品用ドキュメントを作成するという過去の記事をご覧下さい。
その一方で、表形式のドキュメントについて同じような事をやるのは大変です。表形式のデータのプレーンテキスト形式といえばCSVですが、CSVには行・列・セルに内容以外の属性情報を持たせることができません。また、セル内で改行するような複雑なデータや、セルの結合を含むような複雑な構成のデータだと、CSVのまま管理するのは骨が折れます。HTMLのtable要素やXML形式を使うとすると、そのような情報も問題なく保持できるようにはなりますが、今度は編集・閲覧が大変になります。このように、表形式のデータとは基本的にプレーンテキストの表現力では手に余るものだと言えます。
正攻法としては、必要なすべてのデータをRDBに格納しておき、Railsアプリケーションのようなインターフェースを介して編集・閲覧する方法が考えられます。この場合、入力・編集用のビューで入力しておき、何らかの方法で納品用のスプレッドシート形式のファイル(ods、xls、xlsxなど)を自動生成するということになります。この方法は柔軟性が高いですが、そのようなシステムの開発と維持にはそれなりのコストがかかるため、案件の性質によっては赤字になってしまいそうです。
実際に弊社で手がけているFirefoxの法人向け導入支援や技術支援の場合、ほとんどのお客様は1年間は大きな仕様変更がないESR版を使われています。そのため資料の更新のための作業量も人力での作業で収まる規模です。変更点の調査やカスタマイズ結果の目視での検証など、システム開発では軽減できない部分のコストの方がはるかに大きいため、資料の作成部分だけをシステム化する動機は薄いというのが実情です。
とはいえ、資料の作成・更新を手作業だけで行うのにも手間はかかりますし、ヒューマンエラーによるミスも起こり得ます。容易に自動化できる部分だけでも自動化できればそれに越した事はありません。
システムを改めて開発するほどのコストはかけたくないが、ミスをしやすい部分は自動化したい。このような欲張りなニーズに対する答えとしては、スプレッドシート形式のファイルをマスターデータとして使うという方法が有効かもしれません。ods、xls、xlsxなどのスプレッドシート形式のファイルは、書式設定やセル結合などの高い表現力と、数式や関数による自動処理の機能を併せ持っています。また、これらの形式であればCSV形式への変換スクリプトを使って容易にプレーンテキスト化できるため、変更時の差分も確認しやすいです。
さて、ここからがこの記事の本題です。
前述のような事情から、クリアコードではFirefoxの法人向けカスタマイズ案件用の資料の一般化したバージョンについて、マスターデータをOpenDocument Formatのスプレッドシート(ods)として保持しており、各顧客向けに微調整したりExcel形式で保存し直したりして実際の業務に使用しています。
この資料は「カスタマイズ項目一覧表(カスタマイズメニュー)」と「検証手順書」が対になっており、それぞれ以下のようになっています。
configurations/customization-items.ods
)
verification_manual/verification_manual.md
)
この時、
という事が問題になります。このドキュメントではこれらをどうやって解決しているのかを、順を追って説明していきます。
カスタマイズ項目一覧表は、
という要領で「変更可能な箇所」と「取り得る選択肢」(およびその実現方法)が列挙されています。
この資料ですが、当初の運用方法は「静的なデータだけが記入された表を用意しておき、お客様向けに選択・推奨した設定をセルの色を変える事で示す」というものでした。つまり、スプレッドシートとは言いつつも、実際の所は「セルの結合と色付けができる静的な表」としてしか使っていませんでした。
ここから「どの設定が選択されたのか」という情報をそのまま出力するのは難しいです。というのも、odsやxlsxで使える関数には「あるセルの色を参照する」というような物が存在しないからです。仮にそれをダイレクトにやろうと思うと、Basicなどでマクロを書く必要があり、この方向に進むのは面倒ごとが増えるばかりになってしまいます。
こういう場面では、「セルの色という書式情報を、項目の選択状態を示すマスターデータにする」という発想を捨てれば話が単純になります。
ExcelもLibreOfficeも「書式情報を参照する」ことは不得意ですが、「条件にマッチする行に自動的に書式設定を反映する」ことは得意です。それが「条件付き書式」という機能です。本題から外れるためここでは詳しくは述べませんが、実際のodsファイルをダウンロードしてLibreOfficeで開き、「書式」→「条件付き書式」→「管理」を選択すると、そのワークシートでどのような条件付き書式が設定されているかを見られますので、参考にしてみて下さい。
選択状態を表す情報と書式情報とを切り離せれば、「選択された設定のIDを示す文字列を別のシートに出力する」という事も容易にできます。1番目のcustomization-items
というシートを例に取ると、
という事をした上で、verify-targets
という名前のシートにVLOOKUP()
関数を使った計算式を記入しておくことで、「customization-items
のA列に何か記入されている選択項目について、そのIDを自動的に出力する」ということを実現しています(数式の詳細は、実際のファイルをご覧下さい)。
他のシートも同様の方法で選択項目のIDをverify-targets
に出力するようにしてあり、後はodsをCSVに変換する方法などを併用すれば、「どの設定が選択されたのか」という情報を簡単に取り出せます。また、この程度の数式であればODFでもExcelワークシートでも互換性があるため、odsをLibreOfficeで開いてxlsx形式で保存し直せば、そのままExcelワークシートとして管理し続ける事もできます。
検証手順書は、以下のような要領で検証手順が記載されたドキュメントです(実際にはPDFになります)。
4.4 攻撃サイトに対する警告
4.4.1 確認する項目
• Security-5-*
• Security-6-*
• Hide-1
4.4.2 準備
1. 前項に引き続き検証するか、または以下の状態を整えておく。
1. カスタマイズ済み Firefox のインストールが完了した状態にする。
4.4.3検証
1. Firefox のロケーションバーに「 https://itisatrap.org/firefox/its-an-attack.html 」
と入力し、 Enterを押下する。
• 確認項目
1. 攻撃サイトとしてブロックされない。 (Security-5-2)
2. Firefox のロケーションバーに「 http://itisatrap.org/firefox/unwanted.html 」と
入力し、 Enter を押下する。
• 確認項目
1. 望ましくないソフトウェアの提供サイトとしてブロックされない。 (Security-5-2)
3. Firefox のロケーションバーに「 http://itisatrap.org/firefox/its-a-trap.html 」と
入力し、 Enter を押下する。
• 確認項目
1. 詐欺サイトとしてブロックされない。 (Security-6-2)
4. 「ツール」→「オプション」を開く。
• 確認項目
1. 「セキュリティ」タブが表示されていない。 (Hide-1)
納品後にお客様側でも検証できるようするため、あるいは納品前の検証をこのように実施しましたというエビデンスにするためという目的の物なので、人が手作業で実施する前提の検証手順が記載されています。
上記の例は
が選択された場合を想定した内容ですが、例えば「Security-5-1 攻撃サイトに対する警告を行う」が選択された場合には当然ながら期待される結果が変わりますし、「Hide-1」が選択されなかった場合には「4.」以下の手順は不要となります。
このように検証手順書の内容を細かく切り替えるための材料になるのが、前述のverify-targets
ワークシートをCSV出力した結果です。
この検証手順書のソースはMarkdown形式ですが、mustacheという軽量テンプレートエンジンを使って以下のような書き方をしています。
## 攻撃サイトに対する警告
### 確認する項目
{{#Security-5}} - Security-5-\* {{/Security-5}}
{{#Security-6}} - Security-6-\* {{/Security-6}}
{{#Hide-1}} - Hide-1 {{/Hide-1}}
...
### 検証
{{#Security-5}}
1. Firefoxのロケーションバーに「 https://itisatrap.org/firefox/its-an-attack.html 」と入力し、Enterを押下する。
- 確認項目
1. 攻撃サイトとしてブロック{{#Security-5-1}}される。(Security-5-1){{/Security-5-1}}{{#Security-5-2}}されない。(Security-5-2){{/Security-5-2}}
1. Firefoxのロケーションバーに「 http://itisatrap.org/firefox/unwanted.html 」と入力し、Enterを押下する。
- 確認項目
1. 望ましくないソフトウェアの提供サイトとしてブロック{{#Security-5-1}}される。(Security-5-1){{/Security-5-1}}{{#Security-5-2}}されない。(Security-5-2){{/Security-5-2}}
{{/Security-5}}
...
mustacheでは{{#Security-5-2}}〜{{/Security-5-2}}
のようなタグ風の書き方によって、その名前のパラメータ(ここではSecurity-5-2
)の値が何か設定されている時だけそのタグで囲われた内容を出力する、という形で出力を切り替えることができます。これをMarkdownからPDFへの変換処理の中で前処理として行い、その際にverify-targets
ワークシートをCSV出力した結果の内容をパラメータとして組み合わせることによって、「スプレッドシートの設定内容に基づいて検証手順書の内容を細かく切り替えた結果」をPDFに変換しています。
(ただ、実際にはmustacheの表現力はそれほど高くないです。そのため、mustacheによる処理を終えたコンテンツをさらに加工して、出力する必要がなくなった検証手順や節・章などを丸ごと削除してから、それをPDFに変換するようにしています。)
これらの事を実際にはどのようにやっているのか、という事についてはRakefile
をご覧下さい。
ここまでの処理を終えた段階で、手元には「設定が選択されたカスタマイズ項目表」と「選択されたカスタマイズ内容に対応する検証手順書」の2つができています。
しかしながら、これらのドキュメントの参照関係はあくまで一方通行です。検証手順書を眺めていて「この検証手順を実施すれば、カスタマイズ項目一覧のこの項目についての検証ができる」という事は読み取れるのですが、逆に、カスタマイズ項目一覧表を見ていて「この設定を検証する手順は、検証手順書のどこを見れば記載されているか」という事は読み取れないままです。
検証手順書の見出し番号が静的であれば、あらかじめカスタマイズ項目一覧に記入しておけるのですが、前述の通り検証手順書の章や節の数は選択された設定によって変動します。また、通しでの検証のしやすさを考慮した位置に検証項目を追加した場合にも、以降の章や節の番号がずれてしまいます。このような理由から、検証手順書の見出し番号は、最初からは記入しておけません。
そこで、先の検証手順書の生成時の前処理の一環として、「カスタマイズ項目のID」と「それに対応する検証手順書中の見出し番号」を列挙したCSVを出力するようにしています。カスタマイズ項目一覧表にはあらかじめこのCSVを入力として受け付けるための場所としてverify-targets-to-chapters
シートが用意されており、CSVファイルの内容を当該ワークシートに貼り付けると、他の各シートの対応する行に検証手順書の章番号・節番号が自動で流し込まれるようになっています。(CSVを自動的に参照するようにスプレッドシート内に数式を書いておくこともできますが、そうするとスプレッドシート単体での取り回しが悪くなってしまうので、今のところはCSVの内容を静的に貼り付けるという仕様にとどめています)
以上の手順によって、納品用のスプレッドシートと検証手順書のPDFが手に入る事になります。スプレッドシートの体裁を多少変える程度の事はここまでで説明した自動化のプロセスには何ら影響を与えませんので、後は実際のお客様ごとの要件に合わせて、スプレッドシートをExcel形式に改めたり、表紙を足したりといった微調整を行うだけです。
以上、スプレッドシートとMarkdown形式のファイルの2つをマスターデータとして、納品用のスプレッドシートと検証手順書のPDFを半自動生成する事例についてご紹介しました。
フリーソフトウェアやOSSの開発者にとっては、Excelなどのスプレッドシートは「非ITエンジニアの人達とやりとりするためだけの物」という感覚が強いかもしれません。しかし、数式や関数を活用すれば、大仰なシステム開発無しに十分な品質の納品物を少ない労力で作るための素材としても使えます。
皆さんもスプレッドシートの持つポテンシャルを引き出して、ぜひ業務を効率化しましょう!
日本では東京オリンピックに向けてサイバーセキュリティ強化の機運が高まっていますが、ソフトウェアの脆弱性を利用した攻撃は増加し続けています。 最近では、Apache Struts2やWordPressの脆弱性を利用した攻撃により、Webサイトの改ざんや情報漏えいなどが発生し、社会的に大きな影響を及ぼしています。
ソフトウェアの脆弱性は、(例えばバッファオーバーフローのように)バグとして見つかることもありますが、通常のユースケースでは問題にならない実装になっている場合もあり、コードレビューやテストをすり抜けてしまうことがあります。 脆弱性を発見するためには、どのような攻撃手法が存在するかを知る必要があり、発見にはそれなりの知見が必要となりますが、いくつかのチェックポイントがありますので、ここではそれを紹介してみようと思います。
ただ、あまり多くの脆弱性について言及すると、話題が発散してしまいますので、ここでは、OSコマンドインジェクションを防ぐためにコードレビューでチェックすべき点に絞って紹介します。
OSコマンドインジェクションとは、あるプログラムからOSのコマンドを実行する際に、ソフトウェアの作成者が意図しないコマンドをプログラムの利用者が実行できてしまう脆弱性のことです。これを利用すると例えば、作成者がls
(Windowsの場合はdir
)コマンドを呼び出してカレントディレクトリのファイルリストを取得することを想定した処理で、ls
ではなく、rm -rf *
のような別のコマンドを実行できてしまいます。
この脆弱性は利用されると、OSで使用できる任意のコマンドをプログラム利用者が実行できてしまいますので、リモートからアクセス可能なプログラムにOSコマンドインジェクションの脆弱性が存在すると、不特定多数の利用者から、任意のコマンドを実行されてしまう状態に陥ることになります。
上記の通り、利用されると非常に危険な脆弱性ですが、幸いにしてコードレビューで注意すべき点はそれほど多くありません。 OSコマンドインジェクションの脆弱性を防ぐためにコードレビューでチェックすべきポイントは以下の通りです。
OSのコマンドを実行する関数が使用されていなければ、OSコマンドインジェクションは引き起こせないため、これらの関数を使用しているか どうかが最初のチェックポイントとなります。 一例をあげると、PHPでは「system()」や「exec()」等、Perlでは、「eval()」や「open()」、「system()」等が該当します。 ここで挙げた例以外にも、OSコマンドインジェクションを引き起こす可能性のある関数がありますが、具体例の紹介は、別記事にて取り扱う予定です。
使用していない場合 -> OSコマンドインジェクションの危険性はありませんので、以下のチェックは必要ありません。
使用している場合 -> チェックポイント2へ進んでください。
OSのコマンドを実行する関数は、実行するコマンドを引数として渡します。この引数を組み立てる際にプログラム外部からの入力を使っている場合は、OSコマンドインジェクションが発生する可能性が高まります。
OSのコマンドを実行する関数に与える引数をプログラム外部からの入力を使わないで構成している場合 -> 以下のチェックは必要ありません。ただし、OSのコマンドの中には、xargs
やfind
等の任意のコマンドを引数に指定出来るコマンドが存在するので、そのようなコマンドをOSのコマンドを実行する関数の引数にしている場合は修正が必要です。
OSのコマンドを実行する関数に与える引数がプログラム外部からの入力を使って構成している場合 -> チェックポイント3へ進んでください。
あるプログラムからOSのコマンドを実行する関数には、シェルを用いてOSのコマンドを実行する関数と、シェルを用いないで OSのコマンドを実行する関数があります。 OSコマンドインジェクションは、シェルが解釈可能な特殊文字等を利用し、作成者が意図しないコマンドも実行させるものですので、 問題となるのは、シェルを用いてOSのコマンドを実行する関数となります。
ただし、シェルを用いないでOSのコマンドを実行する関数の中には引数の指定方法によって、シェルを用いる、用いないを切り替える関数がありますので、このチェックポイントでは、各関数のドキュメントをよく読むことをおすすめします。主要な言語での具体例については、改めて別記事にて紹介させていただきます。
また、以下のようにOSのコマンドを実行する関数に与える引数に直接外部からの入力を使っている場合は、外部から自由にOSのコマンドが実行できる状態ですので、シェルを用いる関数か用いない関数かにかかわらず外部からの入力を直接引数に渡さないように修正が必要です。
import sys
import subprocess
args = sys.argv
subprocess.call(args[1])
前述の通りOSコマンドインジェクションは、シェルが解釈可能な特殊文字等を利用し、作成者が意図しないコマンドも実行させるものです。 主要な言語では、OSコマンドインジェクションを防ぐためにシェルの特殊文字をエスケープする方法が存在します。 例えば、PHPでは「escapeshellarg()」という関数が文字列のエスケープのために用意されています。具体的なエスケープ方法に ついても、後日、別記事にて扱う予定です。 それらの方法を使用して、外部からの入力をエスケープしてから、OSのコマンドを実行する関数を呼び出しているかチェックします。
外部からの入力をエスケープしてから、OSのコマンドを実行する関数を呼び出している
可能であればOSのコマンドを実行する関数の使用をやめるか、OSのコマンドを実行する関数に渡す引数を 外部からの入力によって変更させないように修正するか、シェルを用いない関数に変更したほうが安全です。
シェルの文字列解釈の仕様は複雑なため、各言語で用意しているエスケープの方法にもバグが存在することがあり、 正しくエスケープされない可能性があります。
外部からの入力をエスケープせずに、OSのコマンドを実行する関数を呼び出している
大まかに4つのチェックポイントを紹介しました。あまり難易度の高いチェック方法はなかったのではないかと思います。
本記事では、紹介できませんでしたが、OSコマンドインジェクションを引き起こす可能性のある具体的な関数や各言語で用意されている エスケープの具体的な方法については、また後日、改めて別記事として記載する予定です。
Fluentdにログを送る方法として、Fluent Loggerを使う方法があります。 RubyやJavaにはそれぞれfluent-logger-rubyやfluent-logger-javaなどのFluent Loggerがあり、よくメンテナンスされています。 この記事ではFluent Loggerを使ってFluentd v0.12またはv0.14にログを送信する時にどのようにするとより確実にログ転送ができるようになるかを解説します。
最小構成のFluent_Loggerを作成するには では最小構成のFluent Loggerはどのような仕様に基づき実装されるべきかを解説しました。この記事はその続編です。
確実にログを送るにはエラーが起きた時にそのエラーを回復する手段を提供されていることが必要です。
より確実にログを送信したことをLogger側で確認するにはOptionやResponse節にあるようにoptionを使う事が重要になります。 optionの中に128bitユニークなIDのbase64を取ったものをchunkをキーとしたペアに入れ、ackで返って来たbase64の値と比較してやる事で、Logger側で確実に送信されたものと判定出来ます。 *1 確実に送信されたログに関してはバッファから削除してしまって問題ありません。
また、ネットワークの状況によっては、一回でTCPの接続を確立するのが難しく、何回か再試行する必要があることがあります。 このときに、接続を複数回繰り返す方法としては、一定期間ごとに試行する方法(periodic)、試行間隔を指数関数的に増やして行く方法(exponential back-off)のどちらかが取られます。
アプリケーションが予期しない理由により停止してしまった場合に備えて、送信していないログをメモリ上のバッファに溜めておくだけでなく、ファイルに書き込む必要がある場合もあります。 このとき、ファイルにバッファを書き込む際にはFluentdのプラグインで扱いやすい形にしておくほうがよいです。 例えば、msgpackのバイナリ列のバッファをそのまま吐き出したり、TSV形式にすることで、in_tailにより送信ができなかったファイルを後から送信する、という回復処理が行えるようになっているとFluentdに長期間繋がらなかった際にログの消失を抑える有効な手立てとなります。
ここからはFluent Loggerのよくある実装を踏まえて解説を行います。 これら3つの仕様をFluent Loggerに入れることができればより強固にFluentdへログを転送することが可能になるでしょう。
確実にログを送ったことをFluent Logger側で検出するのに実装するべきことは次の通りです。 Fluent protocolでは最後のoptionというフィールドに12byteのbase64エンコードされた値をchunkをキーとするKey-Valueを持たせることができます。*2 このオプションを用いることで、Fluentdへのログ送信が完了したことがFluent Logger側で確認できるようになります。
このオプションが実装されているFluent LoggerにはFluencyやfluent-logger-nodeがあります。
TCP接続のエラーから回復して何度か再試行する、ということを実現するには再試行の戦略を決める必要があります。 大きく分けてTCP接続に失敗した時に等間隔で再試行するか、それとも再試行の間隔を指数関数的に増やしていくかの二つの方法が取れます。 等間隔で再試行を行う場合は再試行の時間まであとどれくらいかを予測しやすくなりますが、一方で送信先のノードが落ちている場合は再試行回数が極端に増える結果となります。 そのため、最初は再試行間隔が最初は徐々に増やされ、だんだんと間隔が開いていく指数関数的に再試行時間を決める方法を筆者はとることが多いです。
送信の再試行回数を超えてエラーとなった時のログ消失を防ぐにはどうしてもログが送信できない場合に、ローカルのストレージへファイルとして出力する方法が取れます。in_tailではmsgpackやTSV形式などでログファイルをパースすることが可能です。 どうしてもエラーの回復ができない場合には最終的にはFluent Loggerに渡したログをファイルに出力し、後日改めてFluentdのin_tailなどで送信エラーの起きたログを回収するようにすると良いでしょう。
Fluent Loggerは単純にログを転送するだけではなく、Logger側でログがFluentdへ転送できたことを検知する仕組みを入れることができたり、TCP接続を確実に確立するための再試行の機構を取り入れたり、再試行回数の上限を超えてしまった時はファイルに転送しようとしたログをダンプする戦略が取れることを解説しました。 なお、この記事では解説できませんでしたが、この記事は筆者が作成したRustのFluent Loggerのfruentlyを作成するにあたって得られた知見を元にしています。