株式会社クリアコード > ククログ

ククログ


MariaDBへ不具合をフィードバックするには

はじめに

MySQL/MariaDB用のストレージエンジンのひとつにMroongaがあり、MySQL/MariaDBユーザーに高速な日本語全文検索機能を提供しています。
先日リリースされたMroonga 9.04では、MySQL 8.0向けのパッケージの提供(CentOS 6とCentOS 7)もはじまりました。

クリアコードでは、Mroonga関連プロダクトのサポートサービスを提供しています。
今回は、サポートサービスをきっかけにMariaDBへ不具合を報告し、修正されたケースがあったのでその事例を紹介します。

不具合の発覚のきっかけ

Mroongaでは、ユーザーの利便性のためにMariaDBにMroongaをバンドルしたWindows版のパッケージを配布しています。
不具合発覚のきっかけは、Windowsでこのパッケージを利用中のお客様からの「レプリケーション中にスレーブがとまる」という報告でした。

実際にお客様から提供された問題発生時のログを確認すると、次のようなメッセージが記録されていました。

[ERROR] Semisync slave socket fd is 580. select() cannot handle if the socket fd is greater than 64 (FD_SETSIZE).
[Note] Stopping ack receiver thread

このエラーメッセージに対応するのはMariaDBの以下の実装箇所でした。

該当するソースコードを抜粋すると次のようになっていました。

#ifndef WINDOWS
      if (socket_id > FD_SETSIZE)
      {
        sql_print_error("Semisync slave socket fd is %u. "
                        "select() cannot handle if the socket fd is "
                        "greater than %u (FD_SETSIZE).", socket_id, FD_SETSIZE);
        return 0;
      }
#endif //WINDOWS

ソケットのファイルディスクリプターの値が大きいせいで、スレーブからのackを受け取るスレッドが止まっていました。
「Windows以外で」上記のチェックが有効なはずなのに、実際には「Windowsで」エラーが発生していました。
これは、#ifndef WINDOWSWINDOWS が定義されていないことによるものでした。
通常こういう場合は #ifndef _WIN32 が使われるので、正しくありません。

実際、他の実装としてMySQLを確認したところ、Bug#23581389 として一年半以上前にまったく同じ問題の修正がなされていました。

不具合を報告するには

MariaDBの場合、MariaDB bugs databaseに不具合を報告します。

遭遇した問題は既知の問題として報告されていなかったので、新規課題の「作成」をクリックして問題を報告しました。
(課題の報告にはあらかじめアカウントを作成してログインが必要です。)

対象コンポーネントは「Replication」を選択し、環境には「Windows」であることを明記しました。
問題の説明では、エラー発生時のログや、実装のどこが問題であるかに言及しました。

実際に報告した内容は次のとおりです。

MDEV-19643の不具合報告

この問題については、報告から1ヶ月しないうちに修正されました。
修正版はMariaDB 10.3.17や10.4.7としてリリースされる見込みです。

まとめ

今回、サポートサービスをきっかけにMariaDBへ不具合を報告し、問題が修正されたケースがあったのでその事例を紹介しました。
Mroongaに限らず関連プロダクトのサポートが必要な方は問い合わせフォームからご相談ください。

2019-07-01

Firefoxのトラブルシューティング:Firefox 67以降のバージョンを起動すると意図せず別プロファイルが作られてしまう

2019年8月29日追記:この問題はFirefox 69以降およびFirefox ESR68.1.0以降では修正済みです。本稿はFirefox 67~68.0.x(ESR含む)に特有の現象についての解説となります。

Firefox 67以降のバージョンではFirefoxのインストール先ごとにプロファイルが作られるようになったという事をご紹介しました。しかし、起動の仕方によっては意図せず新しいプロファイルが作られてしまうという場面があります。

例えば、

  • デスクトップ上のショートカットからFirefoxを起動した時
  • コマンドプロンプト(cmd.exe)からパスを明示してFirefoxを起動した時
  • メールクライアントの本文内のURLをクリックして、URLを開く既定のアプリケーションとしてFirefoxを起動した時
  • テキストエディタから登録済みの外部アプリケーションとしてFirefoxを起動した時
  • Google Chrome用拡張機能を使ってリンク先をFirefoxで開いた時

のような場面で、同じ位置にインストールされているFirefoxなのに、それぞれ別々のプロファイルが作られてしまうという事が起こり得ます。これは何故でしょうか、そしてどうすれば回避できるでしょうか。

原因

この問題は、プロファイルを紐付ける対象のパスが正規化されず、大文字小文字が厳密に区別される事が原因で発生します。

通常、Windowsではファイルパスの大文字小文字は区別されません*1。また、ファイルには古いアプリケーション向けに、MS-DOS互換の短い名前が付いている場合があります。これらの理由から、一般的にWindows上では以下のパス表記はすべて等価に扱われます。

  • C:\Program Files\Mozilla Firefox(正式)
  • c:\Program Files\Mozilla Firefox(ドライブレターのみ小文字)
  • c:\program files\mozilla firefox(すべて小文字)
  • C:\PROGRAM FILES\MOZILLA FIREFOX(すべて大文字)
  • C:\PROGRA~1\MOZILL~1(MS-DOS互換の8.3形式)

ところが、Firefoxがインストール先パスとプロファイルとを紐付けるために使う情報は、当該フォルダのNTFSでのオブジェクトIDでもなければ正規化されたパス文字列でもなく、これらを単純な文字列として計算したCity Hash値です。City Hash値*2はパスの表記の仕方ごとに

  • C:\Program Files\Mozilla Firefox308046B0AF4A39CB
  • c:\Program Files\Mozilla Firefox4FD4C3CBEC5FE500
  • c:\program files\mozilla firefox4D589497A5E2C2E9
  • C:\PROGRAM FILES\MOZILLA FIREFOXB86E4C730D96F15B
  • C:\PROGRA~1\MOZILL~1A86124024953D658

と変化し、FirefoxはこのCity Hash値に対してプロファイルを作成し紐付けるため、「起動する時のパスの指定の仕方によってプロファイルが変わってしまう」という事が起こる訳です。

Firefoxを起動する手段が関連付けのみに依っているのであれば、この問題には遭遇しにくいです。問題が起こりやすいのは、ユーザーが明示的に文字列としてパスを指定する場合です。上記の例の「テキストエディタの登録済みの外部アプリケーション」や「Google Chrome用拡張機能で起動する外部アプリケーション」としてFirefoxを指定する場面では、正式な表記とは大文字小文字が異なるパスを自分で入力してしまいがちだからです。

対処

この問題はBug 1555319 - The case insensitive file system on Windows makes it possible to run Firefox with seemingly different installation pathsとして既にトラックされており、Firefox 69では修正済みとなっています*3。ただ、Firefox 68向けには修正がバックポートされていない*4ため、Firefox 67から68までのバージョンを使っている場合は手動での対策が必要です。

原因は上記の通りなので、対処としては、Firefoxを起動するためにパスを指定している箇所すべてについて、パス表記を正式な表記に揃えるという事になります。

正式なパス表記は、ファイルのプロパティを開いて「全般」タブの「場所」欄に表示される物です。firefox.exeをデスクトップなどにドラッグしてショートカットを作成し、そのプロパティを開いて「リンク先」欄の内容を参照しても同じパスを得られます。これらの方法で正式なパス表記を調べたら、Firefoxのパスを指定していた箇所を探して地道に設定し直していきましょう。

まとめ

Firefox 67以降のバージョンについて、起動の方法によって違うプロファイルが使われてしまう場合があるという問題の原因と回避策を解説しました。

当社ではFirefoxの法人での使用上で起こる様々な問題について、原因や回避方法の調査を有償にて承っています。社内でのFirefoxの運用でお困りのシステム運用担当者の方がいらっしゃいましたら、ぜひメールフォームよりお問い合わせ下さい。

*1 NTFSであればfsutilコマンドを使って特別な属性を与えることでファイル名の大文字小文字を区別するようにする事もできますが、伝統的なWindowsアプリケーションはファイルパスの大文字小文字が区別されない前提で設計されているため、一般的にはこの機能は使われていません。

*2 ファイル名部分を除外した、フォルダのパスのハッシュ値が使われます。

*3 Firefox側でパスを正規化してからCity Hash値を求めるようになりました。

*4 Firefox 69のブロッカーバグとして修正されたものの、Firefox 68に修正をバックポートするには遅すぎたとの事です。Firefox ESR68については、Firefox ESR68.1.0の時点で修正がバックポートされる見込みです。

タグ: Mozilla
2019-07-05

OSSへのフィードバックをしてみたいけど、何をフィードバックしたらいいのか分からない

結城です。

これまで、「OSSを使用していてトラブルに遭遇しているか、改善の提案があり、その情報を開発元のイシュートラッカーに伝えようとしている」という人向けの情報として、「どういう場所に報告するのが適切か」「どんな内容を書くのが適切か」という話をしてきました。

しかしOSS Gateワークショップにビギナーとして参加された方の中には、そういった「どうやるか」の前の段階の話として、「フィードバックしたい情報」をまだ持っていないという人もいます。というか、「別に今の段階で何か具体的にOSSの使用上で困っているわけではないが、とにかくOSS開発に関わりたい、OSS開発に関われる人になりたい」という人の方がまだ数としては多く、OSS Gateワークショップはまさにそのような人を想定した内容になっています。

なので、OSS Gateワークショップでは「フィードバックするネタ*1の見つけ方」からやるようにしています。この記事ではワークショップで実際に伝えているやり方も含めて、「フィードバックするネタ」の見つけ方を紹介します。

どのOSSにフィードバックするか

ネタを探すには、当然ですが、ネタを探す先のOSSが必要です。

ワークショップに来られた方に「このOSSにフィードバックしたいなあ、というプロジェクトは何かありますか?」と聞くと、「Linuxカーネル」や「Docker」といった有名なプロジェクトの名前を挙げられる事が結構あります。

ワークショップでは「なるべく普段から使っているOSSを題材にしましょう」と案内しているので、その意味ではこれらの名前を挙げても間違いではないのですが、単に「OSS」と言われてそれらの有名なプロジェクトくらいしか思い浮かばなかったと見受けられるケースでは、筆者はなるべく、もっと身近な・小規模のプロジェクトをお薦めするようにしています。というのも、有名だったり大規模だったり歴史が長かったりするOSSは、「別に今の段階で何か具体的に使用上で困っているわけではない」という人がフィードバック先にするには色々とハードルが高い事が多いから*2です。

「普段使っているOSS」を意識してみよう

「身近なOSS」なんて思いつかない、という人でも、普段の仕事で使うアプリケーションやコマンドを作業の順番に従って思い浮かべてみると恐らくOSSがいくつも見つかるはずです。例えば以下の要領です。

  • インターネット上の情報を調べるためにChromeを起動した。
    • ChromeのベースとなっているChromiumはOSSです。
    • Chrome/Chromiumが依存しているcrc32cdom-distiller-jsenum34などの多数のライブラリ( chrome://credits/ で確認できます)もOSSです。
    • Chrome/Chromiumで使える拡張機能にもOSSが多くあります。
  • 作業を始めるために、ターミナルを起動して、Bashの上でgit cloneを実行した。
    • GitはOSSです。
    • BashもOSSです。
  • Ruby on Railsで構築されたサービスのユニットテストを走らせるために、bundle instalLしてrake testした。
    • Ruby on RailsはOSSです。
    • RubyもOSSです。
    • RailsやRailsアプリケーションのGemfileに従ってbundle installでインストールされた多くのGemもOSSです。
    • bundleコマンドを提供するBundlerもOSSです。
    • rakeコマンドを提供するRakeもOSSです。
  • ソースコードを編集するためにVisual Studio Codeを起動した。
    • VSCodeはOSSです。
    • VSCodeのベースになっているElectronもOSSです。
    • ElectronのベースになっているChromiumもOSSです。
    • VSCodeで使えるプラグインにもOSSが多くあります。
  • Vue.jsで構築されたフロントエンドにファイルを追加し、ESLintでエラーがない事を確かめて、Babelでトランスパイルした。
    • Vue.jsはOSSです。
    • ESLintもOSSです。
    • BabelもOSSです。
    • ESLintやBabelのpackage.jsonに従ってインストールされた多くのNPMモジュールもOSSです。
  • 編集したソースコードをコミットする時、コミットメッセージの編集にVimを使った。
    • VimもOSSです。
    • VimのプラグインにもOSSが多くあります。

こうして見てみると、開発に使用しているフレームワーク自体ツールがOSSであったり、また、有名なOSSも名前を聞いた事のないような無数のOSSを利用して作られていたり、という状況である事が分かります。現代のソフトウェア開発の現場では、OSSを全く使わずにいるということはあまりないでしょう。

「OSS」の見つけ方

ところで、いきなりたくさんOSSの例を挙げましたが、そもそも「どうだったらOSSなのか?」を説明していませんでした。

OSS(Open Source Software)とは、OSIがOSSライセンスと認めているライセンス一覧に載っているいずれかのライセンスの元で頒布されているソフトウェアのことです*3。例えばVSCodeでは、「Help」→「View License」で開かれるページの冒頭に「Source Code for Visual Studio Code is available at https://github.com/Microsoft/vscode under the MIT license agreement at https://github.com/Microsoft/vscode/blob/master/LICENSE.txt.(VSCodeのソースコードはMITライセンスの元で入手可能です)」と書かれています。MITライセンスは上記の一覧にあるOSSライセンスなので、VSCodeはOSSであると言える訳です。

他にも、ソフトウェアのライセンスを調べる方法はいくつかあります*4

  • GUIアプリであれば「(ソフトウェア名)について」「ライセンス」のようなメニューからこのような情報を確認できる事があります。
  • 配布ページやプロジェクトのトップページにライセンスが書かれている事もあります。
  • 公開されているソースコードのリポジトリ上にCOPYING(著作権情報)やLICENSE(ライセンス情報)といった名前のファイルがあり、そこに詳細が書かれている場合もあります。
  • READMEに端的に「このソフトウェアは何々ライセンスです」と書かれている事もあります。

「OSSに何かフィードバックしてみたいが、具体的にはビッグネーム以外すぐに思いつけない」という場合には、まずはこのようにして、自分が直接的・間接的に使用しているOSSを掘り下げてみて下さい。LinuxカーネルやDockerよりは、ずっと身近な所にOSSを見つけられるはずです。

些細な「つまずき」に敏感になろう

題材にするOSSを決めたら、次はそのOSSを普通に使います。そうして 普通に使っている途中で遭遇した些細な「つまずき」 が、フィードバックする内容となります。

しかし、そう言われても、何が「つまずき」なのかピンと来ないという人も多いでしょう。「普段から自分もOSSを使っていた事は分かったけど、べつにつまずいた事なんて無いんだけどな……」そう思うなら、それはあなたの運がよかったか、あなたより前につまずいた他の人が道を整備してくれていたか、あるいは、あなたが「自分がつまずいたという事」自体に気付いていないかのいずれかです。

最後の「気付いていない」というのは実はよくあります。ITに詳しくない人から「エラーって言われるんだけど!」と泣き付かれて、「何かした?」と聞いても「何もしてないって!」と本人は言うばかり、というのはよく聞かれる話ですが、それと同じ事を自分でも知らず知らずのうちにしている可能性があるのです。

「そんなバカな!」と思いますか? では、以下の事に身に覚えはないでしょうか?

  • 公式に情報が無いので、「(機能の名前) 使い方」でWeb検索してQiitaやStack Overflowや個人のブログの解説を探した。
  • 動かしてみたらいきなりエラーメッセージが出た。
  • よく分からないけどWebを検索したら対処法が出てきたので、書いてある通りにやったらエラーが出なくなった。

どうでしょう。「いつもの事だから、特別つまずいたとは思っていなかった」「そんな事でいちいち立ち止まってたら仕事にならないし」と思う人もいるのではないでしょうか。ですが、これらはすべて立派な「つまずき」なのです。

つまずくのは自分のレベルが低いからではなく、説明が足りないから
  • インストール説明の手順の中に、必要な前提条件が書かれていなかったので、インストールに失敗した。ググったら、◯◯というパッケージを先に入れておかないといけないと書いてあるブログが見つかった。
  • コマンドを実行しようとしたら、初期設定をして下さいというメッセージが出てエラーになった。
  • 機能を実行しても、ちゃんと動いたかどうかよく分からなかった。

実は、これらは実際にOSS Gateワークショップでもよく登場するつまずきです。こういうつまずきに遭遇した時、「つまずいたのは自分の勉強が足りなかったからだ」「こんな所でつまずくのは初心者だけに違いない、こんな所でつまずいたと人に知られるのは恥ずかしい」と思う人は少なくないでしょう。

しかし、謙虚に学ぶ姿勢は確かに大事なのですが、ここは一旦謙虚さを忘れて、自分の気持ちに素直になってみませんか?

  • インストール説明の手順の中に前提条件が書かれていなかったのでつまずいた。→必要なパッケージがあるなら、最初からそれも手順に書いておいたり、自動的にインストールするようになってくれていたりすればいいのに……
  • コマンドを実行しようとしたら、初期設定をして下さいと言われてエラーになった。→初期設定が必須なら、インストール手順の中に書いてあればいいのに……
  • 機能を実行しても、ちゃんと動いたかどうかよく分からなかった。→確認方法も書いてあればいいのに……

ほら、改善できそうな点が見えてきましたね。

多くの小規模なOSSプロジェクトでは、インストール手順や使い方の説明などのドキュメントの整備には手が回らなくなりがちです。この手の情報は、1回使い方を習得した後は2回3回と同じ人が見返す訳ではないため、今現在関わっている人にとっては「かかる手間のわりには自分自身が恩恵を受けられない」という性質があるからです。

しかしそのような状況が長く続きすぎると、「既にわかっている人」以外にとってそのOSSはどんどん使いにくく・関わりにくくなってしまいます。そうすると、新規のユーザーや開発に関わる人が増えない一方で、関わっていた人達はライフステージの変化などにより離れていき、最終的には新機能の追加も無ければ脆弱性の修正も無い、誰にもメンテナンスされない見捨てられたプロジェクトになってしまいます。そうなって一番困るのは、一般のユーザーです。「不親切な案内を丁寧な内容に直す・補完する」のは、プロジェクト自体の長期的な継続のために重要なフィードバックと言えます。

ですので、「つまずいたのは自分の勉強不足のせい」「こんな所でつまずいたと人に知られたら恥ずかしい」という考え方は一旦封印しましょう。むしろ、 「つまずいた自分じゃなく、こんな最初の方につまずくような部分が残ってる事の方が悪い」 くらいに考えておいてちょうどいいくらいです。

「注意しなくても使える」のが一番いい

また、「使う上で注意が必要」という箇所も、フィードバックのきっかけにしやすい部分です。

例えば、筆者はFirefoxの法人利用においてポリシー設定でのカスタマイズをよく行っていますが、この機能自体は導入されてまだ歴史が浅いために、色々とこなれていない部分があります。つい最近も、「検索候補を表示するかどうか」を制御するポリシー設定がロケーションバーに対してしか作用せず、単独のWeb検索バーでは設定に関わらず常に検索候補が表示される、という制限事項に遭遇しました。

そういう制限事項を見つけた時に、「この機能にはこういう制限事項があるので、使う時は注意が必要だ」というノウハウを社内で共有したりブログやQiitaに書いたりするのは、もちろん有用な事です。ただ、そういった情報ばかりが増えていくと、新しく使い始めようとした人には「なんだか、使うのに色々勉強が必要なんだな……」「気軽には使えないんだな……」という印象を持たれ、いずれは「敷居が高そうだから、触るのやめておいた方がいいかな……」と思われるまでにもなってしまいかねません。

OSSに関わりたいと考えている人は、ぜひそこから一歩先に考えを進めてみて下さい。「使う時に特別な注意が必要ないのが一番いい」、これはどんな場合にも言える事です。 その視点で考えれば、「検索候補を無効化する設定はロケーションバーだけでなく当然Web検索バーにも反映されるべきで、そうなっていないのは不具合だ」という見方ができるようになり、「注意点」に見えていた物は「フィードバックした方がよい点」として浮かび上がってきます

実際に、先のポリシー設定の制限事項は筆者が不具合として報告した結果、改善されるべき点と認識してもらう事ができました。そして、提出したパッチが取り込まれた結果、「ポリシー設定で検索候補を表示しないように設定したら、ロケーションバーでも単独のWeb検索バーでも検索候補が表示されなくなる」という、使う上で特別な注意が必要ない状態になりました。

ドキュメントの例にしても機能の例にしても、つまずきに対して「無いならしょうがない」「駄目ならしょうがない」「変えられないんだからしょうがない」と考えていると、どうにかして自分のフィールドの中だけで物事を解決しようという、いわゆる「運用で回避」の方向に行ってしまいがちです。しかし考え方を少し変えれば、そういった「面倒事」は「解決した方がよい問題」と言い換えられます。「OSSにフィードバックするって、何か特別な事だ」「そんな事ができるのは別の世界の人だ」と思っていた人でも、そう考えてみれば、OSSへのフィードバックとは決して特別な事ではなく、日常の延長線上にある物だ、という事を実感できるのではないでしょうか。

まとめ

現代は「問題は自分で解決できる」という実感を得にくい時代かも知れません。ITの分野だけ見ても、今はスマートフォンのように分解修理が不可能な機械は多いですし、Webサービスにしても、クラウドで提供されている物に何かトラブルがあってもユーザーはただベンダーによる復旧を待つ事しかできません。「駄目なら、しょうがない」と早々に諦めなくてはならない場面は非常に多いです。

また、「未解決の問題の解決に取り組む」という事は、まさしく挑戦です。成功が確約された挑戦というものは無く、挑戦するからには失敗のリスクも伴います。「これ以上の失敗はしたくない、失敗は許されない」という空気が強いと、そういう点でも挑戦するのは憚られるものでしょう。前述したFirefoxのポリシー設定の改善提案も、Firefox開発チームの理解や協力を得ることに失敗していたら、制限がそのまま残り続けることになっていたかもしれません。

しかし、「できることは何も無い」「絶対に解決できると分かっている問題以外、取り組みたくない」と最初から諦めていると、それ以上知識も深まらなければ経験も増えません。問題に遭遇した時、「もしかしたら何か解決策があるかもしれない」「解決できるかどうか分からないけど、もし解決できるなら解決したい」と考えて試しに掘り進めてみる、それこそが多くのOSS開発者が日常的にやっている事です。「OSS開発者という人になりたい」という思いは一旦脇に置いて、まずは「OSS開発者がしているような考え方をして、OSS開発者がしているような行動を取る」という事を実践してみて下さい。そうして行動している人、それが「OSS開発者」の正体です。

「OSS開発者」は、プロのスポーツ選手や士業のように試験に合格すればなれる物でも、名誉として手に入れる肩書きでもありません。行動しているという状態こそが「OSS開発者」なのだと言えます。行動すればすぐになれる「OSS開発者」に、皆さんも是非なってみて下さい。

そのような行動をいきなり一人で始められる自信が無いという方は、OSS Gateワークショップがいいきっかけになるかもしれません。東京以外の地域でもワークショップが開催されていますので、一度足を運んでみてはいかがでしょうか。

*1 OSS Gateワークショップでは「フィードバックポイント」と呼んでいます。

*2 例えば、Issue Trackerではなくメーリングリストが主な情報のやり取りの場だったり、独自の高機能なイシュートラッカーを使用していたり、プルリクエスト形式ではなく伝統的なパッチの形で修正を送る必要があったりという事があります。

*3 他の意味・定義でOSSという言葉を使う人もいますが、OSSという言葉を生み出し広めたOSIはそのように定義しています。

*4 多くのOSSライセンスは「何らかの形でライセンス名を明記する事」を利用条件に含めているため、根気よく調べると何かしらの情報を見つけられるようになっているはずです。OSSではない物をOSSと誤認して二次利用すると、もしかしたら訴訟を起こされたり賠償を求められたりするかもしれませんので、ライセンスに関する情報を見つけられない場合は安全側に倒して、OSSではない物と判断する事をお勧めします。

2019-07-08

OSSへフィードバックしてみたいけど、英語でどう書けばいいのか分からない

結城です。

ここまで、OSSへのフィードバックをやってみようとした時に躓きがちなポイントについて、フィードバックするトピックの見つけ方報告に盛り込むとよい内容その情報の送り届け先の選び方の知見をそれぞれ述べてきました。

ところで、それらの技術的な内容以前のハードルとして、言語の壁という物もあります。実際にOSS Gateワークショップでも、フィードバック内容をまとめた後、英語でそれを書き直すという段階で手こずっておられる方がかなり多い印象があります。

ITエンジニア向けに「こういう英語表現を覚えよう」という情報を紹介する記事は時々見かけます。ですが、ワークショップでビギナー参加者の方が英語を書くのに苦労している様子を実際に見ている印象では、必要なのはそういった記事で紹介される「実際の現場でよく使われる単語や熟語の情報」ではなく、「実際の現場で英文を書く時に行われる考え方の解説」の方であるように筆者には感じられました。

そこで今度は、OSSへのフィードバックを英語でやる時の考え方に焦点を当てて解説してみます。対象読者は、英語に苦手意識のある方、中学高校の英語の授業で英作文はした事があるけれども自分でゼロから何かを説明する英文を書いた事はない方、といったレベル感を想定しています。

「日本語の文章を英語に変換する」のではなく、「同じ事を説明する英語の文章を作る」という考え方をしよう

英作文に不慣れな人は、まず「日本語の文章」を考えて、それを「対応する内容の英語の文章」に変換するものだ、と考えがちなのではないでしょうか。

いきなり言ってしまうのですが、それは無理なので、潔く諦めてしまいましょう

例えば有名な話ですが、外出先から帰宅した人が在宅していた人に言う「ただいま」や、それに応えての「おかえりなさい」は、英語に翻訳しようがないと言われています。そもそも欧米には「帰宅した時に挨拶を交わす」文化が無いため、そういう言葉どころか概念が無いのだそうです。この例を一つとってみても、「日本語で書いた文章をきっちり英語に変換しきる」という事は根本的には不可能だという事が分かるのではないでしょうか。

ではどうするのかといえば、「伝えたい内容、対象」そのものの方に立ち返って、それを表現する英語の文章を考える、という事になります。

例えば、ここに1本のペンがあるとします。その状態を英語で表すには、どんな文章が考えられるでしょうか?

  • This is a pen.(これは一体何であるか?という点に着目)
  • There is a pen.(どこにあるか?という点に着目)
  • I have a pen. / This is my pen.(誰の物か?という点に着目)

「これはペンです」という日本語の文章を先に与えられていれば、「This is~」という文しか思いつかないかもしれません。しかし事実の方に焦点を当てると、色々な角度から表現できるという事が分かるのではないでしょうか。

「すでに書いてしまった日本語の文章」に囚われないで、「目の前にある物」や「目の前で起こっている事実」の方を意識するという事を、常に心がけるようにしてみて下さい。まずはそれが最初のステップです。

平易に言い換えてみよう

この事を考える上で参考になる物として、「やさしい日本語」(※リンク先はPDF)があります。

やさしい日本語とは、

  • 海外からの旅行者がよく訪れる観光施設や、移民者が手続きに来る市区町村の役所などにおいて、日本語が不得手な人が読む事を想定して
  • 複雑な表現や難しい単語をなるべく使わずに、平易な単語や単純な文の書き方を心がけて

書かれた日本語の文章の事を言います。日本語ネイティブスピーカーではない人だけでなく、子供や高齢者、障害者など幅広い層にとってもメリットがあるという事で、近年注目されてきているそうです。

「やさしい日本語」を書くためには、同じ物事をより平易な表現で言い換える必要があります。例えば、

訪日観光客で洛中・洛外は連日溢れかえっており、史跡や寺社仏閣が観光資源として有効に機能していて、誇らしい。

という文章は、

  • 多少ニュアンスは変わったとしても各単語を平易な表現に言い換える。
    • 訪日観光客→他の国から来た人達
    • 洛中・洛外→京都の町
    • 連日溢れかえり→毎日たくさんいる
    • 史跡や寺社仏閣→古い建物、お寺、神社など
    • 観光資源として機能→見どころになる
  • 接続された文を短い文に切り分ける。
  • リズム感や格好良さを優先していたトリッキーな語順を整理する。
  • 省略された主語(行為の主体)を補う。

といった加工をする事で、

京都の町には毎日、たくさんの人達が他の国から来る。
古い建物、お寺、神社などが見どころになっている。
私はとてもうれしい。

のように言い換えられるでしょう。

英語が不得意な人は、英語の文法にも単語力にも自信が無い事が多いと思います。しかしその一方で、日本語はそれなりに使えるという自信が(無意識にでも)あるはずです。そのため、自分の持てる限りの日本語力を駆使して考えた日本語の文章には、難しい単語や言い回しが無意識のうちに入ってしまいがちです。日本語力と英語力の間にギャップがあるために、日本語の方で選んだ単語や表現に対応する英語の言い方が分からなくて、手が止まってしまう……これこそが「英語になると途端になにも書けなくなる」事の正体です。

なので、日本語の文章を英語に訳せる自信が無い場合、その前段階のワンクッションとして、まずは自分が書いた文章を「やさしい日本語」に書き直すようにしてみて下さい。そうすればきっと、次のステップにもスムーズに進めるようになるでしょう。

「SVOの平叙文」で考えよう

英語の話に戻ります。

中学高校までの英語の授業では様々な文法や単語を教わりますが、こと「OSSへのフィードバック」という場面で言うと、実際に使う知識はその中のごく一部です。特に文章の形は、基本的にS(subject:主語)-V(verb:動詞)-O(object:目的語)の形式で書くと考えていいです。これは「私は○○をした」「あなたは××である」のような単純な文章、いわゆる平叙文という形式です。

日本人が英文を書くと受動態(受け身の形、主語が何々されるという文)が多くなりがちだ、とよく言われます。それは、こなれた日本語の表現では主語が省略されがちで、主語が省略された文章をそのまま直訳すると、必然として受動態にせざるを得ないからです。

ですが、「誰が」何をしたのか、「誰が」どうなったのか、という事を省略しないようにすれば、文章は自然とSVOの文型になります

「誰が」を書いたら主語が「I(私は)」「You(あなたは)」ばかりになってしまう? それは、「主語は人間だ」という無意識の思い込みがあるからです。英語では、物でも現象でも概念でも普通に主語になります。主語をどう書くかに迷ったら、一旦あらゆる物事を擬人化してみて下さい

例えば、

Firefoxの法人向けポリシー設定で、検索候補の表示を無効化するSearchSuggestEnabledfalseに設定しても、単独のWeb検索バーでは検索候補が表示され続けてしまう。

という不具合を報告したいとします*1。これはどう言い換えられるでしょうか。

日本語での省略された主語は「私が(~を設定した)」ですね。しかし、ここには「Firefox」や「SearchSuggestEnabledという設定項目」、「その設定の値がfalseである状態」、「単独のWeb検索バーというUI部品」「検索候補」といった登場人物達がいます。ということでこれらを主語にしてみると、例えば以下のような表現ができます。

  • 「設定の値がfalseである状態」を擬人化して主語にする:

    SearchSuggestEnabled=false does not hide search suggestions on the search bar.
    SearchSuggestEnabled=falseという状態が、検索バーの検索候補を隠してくれない)

  • 「検索候補」を擬人化して主語にする:

    Search suggestions on the search bar are visible with SearchSuggestEnabled=false.
    SearchSuggestEnabled=falseという設定の時に、検索バーで検索候補が見える)

どうでしょう。このくらいの英文なら、自分にも書けるような気がしてきませんか? これに

  • There is an enterprise policy SearchSuggestEnabled.
    (~という法人向けポリシー設定があります。)

  • The location bar shows search suggestions. The web search bar also.
    (ロケーションバーは検索候補を表示します。Web検索バーも同様。)

などの文を添えれば、上述の例で報告したかった内容は言い表せたと言っていいでしょう。

いかがでしょうか。長く複雑な構造の文章で表されていた内容であっても、一文一文を細かくぶつ切りにして、それぞれに主語を与えてSVOの形で書き直してみれば、こんな要領の平易な英語の文章で表現できるのです。

ソフトウェア開発の場面であれば、「モジュール名」「メソッド名」「変数名」「ソースコード中の行番号で示した行」などなど、他にも色々な物が「登場人物」になり得ます。皆さんも、名前が付いている物は片っ端から主語にしてみて下さい。

ありふれた動詞や形容詞を使おう

文章をぶつ切りにしてSVOの文型に揃える事には、同時に、使う動詞や形容詞を辞書から見つけやすくなるという効果もあります。というのも、和英辞書で動詞や形容詞を調べると例文は大抵「誰々が何々をする」のようなSVOの文の形になっているからです。辞書で見つけた例文をそのまま使える、これは大きな利点です。

また、先の例で使った動詞が

  • show(表示する)
  • hide(隠す)
  • are visible(~が見える状態である)

という、教科書に出てきたりプログラムやCSSの中で見かけたりするような、特に難しくない・ありふれた単語だったという点に気が付いたでしょうか? 「やさしい日本語」において、難しい単語を使わず平易な単語を使うように言い換える、多少の細かいニュアンスをそぎ落としてでも端的に意味が通じる言葉を選ぶという事を説明しましたが、英語でも同じ事が言えます。言いたい事に厳密に当てはまる難しい英単語は、おおむね意味合いが通じる平易な単語で代用できるのです。平易な動詞や形容詞でも、主語を変えると意外と広い意味で使える、というのもSVOの文型を使う事の利点です。

筆者はこういう場面で使われやすい英単語をもう少し知っているので、実際にはこのような場面では状況に応じて

  • appear(出現する、表れる)
  • append / add(追加する)
  • disappear(消える)
  • deactivate / disable(無効化する)

のような単語を使うと思います。ですが、こういった単語を知らなくても、先の平易な単語でも言いたいことは通じます。より厳密な単語を覚えていくのは追々で問題ありません。他の人が書いている文章で「この単語、なんていう意味だろう?」と思ったらその場で調べて、自分が似たような場面に遭遇したら次から使ってみる、そんな感じで少しずつ語彙は増えていくので安心して下さい。

とはいえ、ソフトウェア開発の場面で特有の頻出単語という物は確かにあります。例えば以下のようなまとめの記事もありますので、暗記が得意なら、頻出単語をあらかじめ頭に入れておいてもいいでしょう。

困った時は箇条書きにしよう、文章以外の表現手段も使おう

報告に盛り込むべき内容の話において、不具合や要望を伝える時は

  • steps to reproduce(再現手順)
  • expected result(期待される結果)
  • actual result(実際の結果)

を軸にすると上手く言いたい事を整理できる、という事を述べました。実際に報告を書く段階においては、このそれぞれを前述のようなぶつ切りのSVOの文型で書いていけばよいという事になります。

しかし、そうすると今度は文同士の繋がりをどうするかで悩む事になりがちです。確かに、ぶつ切りの文章は少々不格好なのは否めません。せめて順接*2か逆接*3かくらいは示しておきたい……いやそれだけじゃなくて因果関係も示したい……という風に考え始めて、またしても「日本語での表現の仕方は分かるが、英語での表現の仕方が分からないので、手が止まる」というドツボにはまり込んでしまうのは、よくある事です。

筆者は、そこで悩むくらいなら箇条書きで書いてしまう事をお勧めします。例えば対等な物を並べるなら、この項の冒頭のように序列無しの箇条書きにすればいいですし、順番や優先順位に意味がある場面では、項目の頭に数字を付けた序列付きの箇条書きにすればいいです。例えば「再現手順」は以下の要領で書けるでしょう。

  1. ◯◯をする。
  2. ××をする。
  3. △△が◇◇になる。

これなら、受け取り手が順番を読み違える事は無いですし、「Repeat steps from 1 to 4.(1から4を繰り返す)」のように手順そのものを指し示す抽象的な書き方も容易にできます。また、箇条書きを階層化すると、

  1. ◯◯をする。
    • ◎◎が出る。
  2. ××をする。
    • ◎◎が消える。
  3. △△が◇◇になるかどうか。
    • ◇◇になっていたら、4に進む。
    • ◇◇になっていなかったら、1に戻る。

というように、各項目に付随する補助的な情報だったり、条件ごとに変わる操作だったりと、各項目の因果関係や依存関係を含んだ複雑な情報もスッキリ表せます。

あるいは、ここまでやるならむしろフローチャートの図で情報を示してもいいかもしれません。そう、説明する方法は「英語の文章」でなくてもいいのです。スクリーンショットで示した方が早ければスクリーンショットに丸や矢印を描き込んだ物を添付してもいいですし、静止画では説明が難しければスクリーンキャストをYouTubeあたりにアップロードして伝えてもいいです。あるいは、プログラムやデータを実際に作れるのであれば、それを使って実行すれば必ず現象が再現するというテストケースを添付するのが一番いいです。

思い出してみて下さい。皆さんがしたい事は、「英語の文章を書く事」ではなかったはずです。本当にしたい事は「不具合を伝える事」や「要望を伝える事」の方ですよね。その手段として日本語が通じないから、別の手段として英語で説明しようとしてるだけです。「英語という手段で説明する事」自体に囚われてしまうと、本来の目的を見失ってしまいがちです。そんな時は画面から目を離して、落ち着いて深呼吸しましょう。そうすると、別のやり方を思いつけるかもしれません。とにかく自分に今使えるあらゆる手段を柔軟に使い分けて、どんな形ででも伝えたい事を伝える事が一番大切です。

もっと言うと、「OSSプロジェクトのイシュートラッカー」は美しい英文を披露する発表の場ではありません。OSSを公開して実際に障害報告を受ける側になっている筆者の実感としては、ぶっちゃけ、流暢な英語で芸術的に見事な文章をたらたらと書かれても、英語が不得手な筆者はかえって読むのに苦労するばかりで、全然嬉しくないです。それだったら箇条書きで書いてくれた方が、ずっと読みやすく・分かりやすくて助かります。

そう、これも忘れてしまいがちなのですが、OSSプロジェクトで既に英語で文章を書いている人だって必ずしも英語が得意とは限らないのです。OSSの開発者には中国人もいれば韓国人もいるし、ロシア人やインド人もいます。皆さんもどうかその事を心に留め置いて、「英語」にばかり固執しないようにして貰えると筆者は嬉しいです。

自信が無ければ英和翻訳にかけてみよう

話題がまた少し逸れました、英語の話に戻りましょう。

前述したような考え方に則って英語の文章を書いてみたけれど、ちゃんと書けているのか、本当に通じるのか、自信が無い……そんな時は、書いてみた英文を「英語→日本語」の機械翻訳にかけてみる事をお勧めします。

機械翻訳は、「日本語に熟達した人が書いたこなれた日本語の文章を、そのニュアンスを捉えた上で、こなれた英文に翻訳する」というような高度な翻訳は(まだ)こなせませんが、「平易な英文を、平易な日本語の文章に翻訳する」のには充分すぎるくらいに使えます。英和翻訳した結果の日本語の文章を読んでみて、言いたかった事がちゃんと言い表されていると思える結果になっているなら、その英文は人前に出して全然大丈夫です。

筆者も実際に、書いてみた英文の正しさに自信が無い時はGoogle翻訳で英和翻訳してみています。その結果を見ながら、単語の意味や前置詞の使い方を間違えている事に気が付いて手直しするという事も多々あります。

平易な文章の英和翻訳が実用になるなら、平易な文章の和英翻訳の結果も使えるのでは? と思うかもしれませんが、筆者はそれは以下の3つの理由からお勧めしません。

  • 前述しましたが、日本語に熟達した人が日本語で文章を書くと、どんなに気をつけていても、無意識のうちに主語や目的語の省略などを行ってしまいがちです。機械翻訳ではそのような「文章の中には含まれていない外部の情報」を推測する事が難しいために、誤訳が発生しやすいです。
  • 和英翻訳された後の英文が自分自身の英語力を大きく超える物だった場合、その意味を自分で読み取る事すらできず、翻訳結果が正しいかどうかを自分では判断できないという事が起こります。
  • サービス・ツールによっては、機械翻訳の結果の文章の使い道を利用規約で制限している場合があります。例えば、OSSは商用利用を禁止しませんが、翻訳結果の商用利用を禁止しているサービスで翻訳したテキストをOSSに含めてしまうと、利用規約に違反してしまいます。実際に、Web翻訳の結果をOSSに不用意に入れた人がいたために、その人が過去関わった全ての翻訳済みリソースについて権利面での妥当性を再確認せざるを得なくなったという事例があります。

近年、機械学習の発展のお陰か、みらい翻訳のように「こなれた日本語を入力しても、それなりにこなれた英語の文章に翻訳できる」*4翻訳サービスが登場してきています。しかし、どれだけ翻訳精度が向上しても上に挙げた2つ目や3つ目の懸念点は解消されません。機械翻訳の結果はあくまで、最終的にOSSに含めるテキストや投稿する文章そのものではなく、それらを考えるための判断材料として使うに留める事を、筆者は強くお勧めします。

まとめ

以上、OSSへのフィードバックで避けて通れない「英語で文章を書く」という事について、テクニックよりは原理原則に焦点を当てて知見を紹介してみました。

実は、ここに書いたようなことは海外旅行などでも使える一般的な考え方です。実際に、悪魔英語 喋れる人だけが知っている禁断の法則という漫画にもおおむね似たような事が書かれていました。「学校である程度の英語教育を受けたにも関わらず、自分からは英語を話せない・書けない」という人にとっての処方箋は、だいたい似たような感じの所に落ち着くのかもしれません。

ここで述べた事を実践して書かれる英語は、はっきり言えば「たどたどしくてつたなくて不格好な、ヘタクソな英語」です。でも、それでいいんです。ネイティブスピーカーとリアルタイムで舌戦を繰り広げるとか、格式高い場でスピーチをするとか、そういう場面でもない限りは 「だいたい通じる下手な英語」で大抵は間に合います

確かに、海外に移住して「生活」するとか、「相手に隙があれば目ざとくそれを突いて、とにかく自分の利益を最大化しよう、という考え方をしている相手」との交渉といった場面になってくると、英語が下手だと相手からナメられたり足下を見られたりといった不利益を被る事もあるようです。ですがOSS開発の場では、筆者が知る限りは別にそんな事はありません。むしろ、インド人や中国人など英語ネイティブでない人が、めちゃくちゃな英語で普通にコミュニケーションしているという場面の方をよく目にします。世界中で見れば「英語のネイティブスピーカー」より「外国語として英語を使う非ネイティブスピーカー」の方が多いわけで、そういう意味では我々の方が多数派なのです。特別に自分が劣っているというわけではないので、安心して下さい*5

このあたりの事について、英語話者の視点から書かれた「酷い英語をもっとお願いします」という記事(※リンク先はその日本語訳)があります。英語話者の人にとっても、英語が壁となって有用な意見やフィードバックを得られない事は損失なので、英語の上手い下手なんて気にしないでガンガン伝えて欲しい、という感覚があるのです*6。ですから皆さん、どうか物怖じしないで英語での報告に挑戦してみて下さい。

*1 これは前の記事でも紹介した、実際に報告を行った事例です。実際の報告内容も併せて参照してみて下さい。

*2 後に続く文章が前の文章にそのまま続く事を示す接続。日本語なら「そして」「さらに」など、英語なら「and」「then」などがあたります。

*3 後に続く文章が前の文章を否定する接続。日本語なら「しかし」「なのに」、英語なら「but」「however」などがあたります。

*4 英語が得意で留学経験もあるような人が見ても、「なるほど確かにこう書けるな」と思えるレベルの翻訳結果が得られているようです。

*5 かといって、上達しようという意識を全く持たなくてもよいという訳でもありません。自分の言いたい事を適切に表現できる英語力があった方が、説明や説得をよりスムーズに進められる事は間違いありませんから。

*6 この対極の話として、努力で英語を身に付けられた人が「みんな英語にすればいいじゃん」とローカル言語への翻訳を否定する発言をした事例がありましたが、その事例ではプロジェクト運営者サイドからも改めて、英語だけが全てではないという事が述べられていました

2019-07-12

Fluentd meetup 2019でWindows EventLogに関するプラグイン回りの発表した話

先日OSS Summitの共催イベントのFluentd meetupでWindows EventLogに関するプラグイン回りの発表をしてきた畑ケです。

関連リンク:

経緯

Windows EventLogを取得したい要望がありました。
検討した結果、例えば以下の問題があることが分かりました。

  • fluent-plugin-windows-eventlogに含まれているin_windows_eventlogが時折SEGVする問題
  • WindowsのANSIコードページを超える文字セットを扱う時に文字化けしてしまう問題

これらの問題を解決するのにはWindows Vistaから導入された新しいAPIを元にしたライブラリを作成し、それを使用するプラグインの作成が必要となりました。
今回のスライドはこれらの問題の解決策を軽く導入として話し、実際に作成したプラグインがこれらの問題を解決しているかどうかを実例を交えつつ解説しました。

イベントの内容

共催イベントということで、会場設備はとても良かったです。
Fluentdはこれから先、より動的なサービスオペレーションにより親和性の高い改良がなされていくんだろうなぁと感じました。

また、筆者は登壇は何回かしたことがあるのですが、初めて英語で発表するということもあり若干早口になっていたかもしれません。

使用した英語スクリプト

数字やバージョン番号などは読み方を迷ってしまうと考えたため、一部数字やバージョン番号などを読み間違えないようにアルファベット表記にしてあったり、括弧付きの注記にしてあります。

My name is Hiroshi Hatake. 
Working at ClearCode Inc(operated) as a software developer.
Now, OK. Today, I'm gonna talk about "Fluentd meets Unicode Windows EventLog".

So, here is today’s my talk agenda.
First, I'll talk about motivation.
Then, I'll talk about winevt_c which is the newly created gem shortly, Unicode character handling, using ANSI code page issues, Unicode testing, benchmark, 
throughput benchmark, and conclusion.

Now, let's start to talk about motivation.

in_windows_eventlog which is the old plugin has some issues.
1st. Unicode character handling. Sometimes garbage characters are generated.
2nd. Memory consumption in flood of windows event
3rd. Sometimes it causes segmentation fault
4th. CPU spike when resuming operation
5th. At least one event should exist in the listening channel on start to listen. Otherwise, nothing to be read(red).

They are caused by dependent gem which is named win thirty two-eventlog.

Next, I'm talking about winevt_c.
This gem is a solution for Unicode handling issue, but this issue is very complicated. I’ll describe it later.

Then, I show you some winevt_c itself code examples.
First, this code is querying for specified channel.
In this case, specified channel is application.

Second, displayed example code is updating bookmark for querying channel.
Bookmark is useful to resume operation.

Third, the example code subscribes specified channel.
Subscribe API is battery included. 
This API is useful for most users.

The new gem which is named winevt_c solves win thirty-two-eventlog issues

1st. Improve Unicode character handling
2nd. It doesn’t cause segmentation fault on the same situation
3rd. CPU spike when resuming operation is declined
4th. Reduce Memory consumption in flood of windows event. This issue is partially solved but declined consuming memory.
5th. At least one event should exist in the listening channel on starting to listen. Empty channel can also subscribe. The older one will be staled.

1st Issue is the hardest problem and the others are solved by new API to read Windows EventLog.

The relationship of plugins and gems in this talk.
The old plugin which is named in_windows_eventlog depends on win32-eventlog.

And

The new plugin which is named in_windows_eventlog2 depends on winevt_c gem which is the newly created one.

Next, Unicode character handling.
This part and the next part treat the hardest issues in this talk.

Then, we should consider what Unicode means.

WHAT IS UNICODE?

In Windows context, it means UTF-sixteen.

Ruby also can handle UTF-sixteen in Ruby code world, although we'll consider Ruby C extension.
Ruby C extension API does not provide convenient API to handle UTF-sixteen. 
Thus, we should say: In Ruby C extension context, it means UTF-8.

What is the difference between ANSI and Unicode?

In Windows, ANSI means current code page.
In Japanese edition Windows, it's code page nine-hundred thirty-two.
Large A suffixed API uses ANSI character encoding.

In Windows, Unicode means UTF-sixteen.
Large W suffixed API uses UTF-sixteen character encoding.
PWSTR and such Large W contained type API arguments also use UTF-sixteen character encoding.

So, we need to convert from UTF-sixteen to UTF-8 as described before.

The win thirty-two eventlog uses ANSI version of Windows API.
To handle Unicode characters correctly in win thirty-two eventlog, we need to use Unicode version of them.
 
In addition, win thirty-two eventlog gem development is inactive in recent days.
And Unicode version patch exists, but it have not been merged in.

Then, next topic is Using ANSI code page issues.

ANSI code page is lightweight encoding way for specific characters, but it causes encoding issue in some cases. 

In Japanese Edition Windows, it uses code page nine-hundred thirty-two by default.
It can handle
* Alphabets
* Greek letters
* Cyrillic alphabets
* Hiragana and Katakana
* JIS level one and two Kanji Sets

Other characters cannot handle with code page nine-hundred thirty-two.

UTF-8 contains more characters!
UTF-8 encoding can handle code page nine-hundred thirty-two characters and,
Diacritical mark such as umlaut in German
Hebrew, Arabic, Devanagari. Devanagari is the letters for Hindi.
South east asia characters such as Thai, Laotian... etsetra.
And Emoji characters.

So, we reached ANSI code page issues solution.

We decide to develop the brand-new gem which is named winevt_c.
1. It uses new Windows API that is defined in <winevt.h>
2. The new API provides bookmark which is used to resume operation
3. Unicode native API

This gem's detail was described in about winevt_c section.

This gem is written in C and C plus plus.
So, users need to build C and C plus plus extension code by themselves.
Current RubyInstaller bundles MSYS2 system. We needn't  worry about it more than necessary.

The next topic is Unicode testing.

In this section, I'm talking about Unicode testing.
This test environment is:

* Japanese edition Windows ten home nineteen-ow-three six-four bits
* Prepared writing Windows EventLog tool which is written in C-sharp
* And I used PowerShell Core six on Windows Terminal. Not command prompt and raw Powershell terminal.
'Cause command prompt and raw PowerShell terminal cannot display Thai, Devanagari, Emoji and so on. 

Here is the snippet of writing Windows EventLog code that contains...
Alphabets
Non-ASCII symbols
Japanese
Thai
Cyrillic
Greek letters
Arabic alphabets
Devanagari
Unicod-ish Kaomoji
And Emoji

Unicode testing uses benchmarking tool which is created for heavily loaded benchmark, but it accepts parameters for lightweight writing.
The shown command-line means ten events written into benchmark channel.
And sleep ten milliseconds before it displays flow rate.

This slide and the next slide describe configuration for Unicode testing.
Fluentd will use in_windows_eventlog as a source and out_forward as an output.
Note that the old plugin requests to use from_encoding and encoding parameters to handle character encoding correctly.
And also default read_interval parameter which is two-seconds is used.
But still unhandled characters exist.

Fluentd will use in_windows_eventlog2 as a source and out_forward as an output.
Note that the new plugin doesn't request to use from_encoding and encoding parameters.
And also default read_interval parameter which is two-seconds is used.
The new plugin always handles character encoding as UTF-8.
So, they needn't anymore!

Then, let's see the old plugin execution log.
Symbol, Thai, Arabic, Devanagari, Unicode contains Kaomoji, and Emoji are displayed broken. 

The other result is the new plugin execution log.
It displays Symbol, Thai, Devanagari, Unicode contains Kaomoji, and Emoji are correctly displayed.
Arabic is slightly broken but it is Windows Terminal's displaying issue.

Now, we can handle Emoji characters contaminated Windows EventLog.

So, we reached benchmark section.

In this section, I'm talking about Benchmark.

Benchmark environment has two nodes:
The collector node is Windows ten eighteen-ow-nine and has two of virtual CPUs, four gigabytes memory and standard SSD.
The aggregator node is Ubuntu eighteen point zero-four and has two virtual CPUs, four gigabytes memory and standard SSD.

The collector Fluentd process and writing EventLog tool are running on the collector node.
And the aggregator Fluentd process is running on the aggregator node.

The collector Fluentd sends events with forward protocol and the aggregator Fluentd receives with the same protocol.

1 million events will write into collector node by benchmarking tool.
Its flow rate is about ninety-one per seconds.

The shown command-line generates events and writing them into the benchmark channel.

The left side configuration is for the collector node.
In_windows_eventlog is used as a source and out_forward is used as an output.
The right side configuration is for the aggregator node.
It receives events from the collector Fluentd with forward protocol. Output is out_null or out_stdout.

The left side configuration is for the collector node.
In_windows_eventlog2 is used as a source and out_forward is used as an output.
The right side configuration is for the aggregator node.
It also receives events from collector Fluentd with forward protocol. Output is out_null or out_stdout.

Now, let's see the benchmarking results.
First, here is the old plugin benchmarking result.
This graph shows that the old plugin uses around between one-hundred ten and two-hundred forty megabytes memory.
And CPU usage sometimes spikes but around ten percent is used in the entire node. 

Second, here is the new plugin benchmarking result.
This graph shows that the new plugin uses around one-hundred twenty megabytes memory.
But CPU usage slightly higher than the previous. 

Now, we can say pros and cons for the old plugin.

Pros: 
* Low CPU usage
Cons: 
* High memory usage
* And incomplete Unicode handling

Also, we can say pros and cons for the new plugin.

Pros: 
* Low memory usage
* Unicode handling
* immediately subscribe channel event if it's empty on subscribe

Cons: 
* Slightly higher CPU usage rather than the old plugin's

The last topic is throughput benchmarking.

In this section, I’m gonna talk about Throughput Benchmark.

Benchmark environment has two nodes:
The collector node and the aggregator node.
They are the same setup at Benchmarking test which is used in the previous section.

Throughput Benchmark setup is as follows:
Writing five-hundred-thousand events
Increase flow rate of events step by step
* around one-hundred-sixty events per second
* around two-hundred-ninety events per second
* around three-hundred-ten events per second
* around three-hundred-twenty events per second
* And the last case is stuck.
  (Chunk bytes limit exceeds for an emitted event stream warning is generated from Fluentd...)

The left side configuration is for collector node.
In_windows_eventlog2 is used as a source and out_forward is used as an output.
The right side configuration is for the aggregator node.
This configuration is same as the previous benchmarking test.

Here is around one-hundred-sixty events per second case. The new plugin can consume benchmark channel events.

The next is around two-hundred-ninety events per second case. The new plugin also can consume benchmark channel events.

Third is around three-hundred-ten events per second case. The new plugin also can consume benchmark channel events.
But it is slightly growing up memory consumption.

The last normal case is around three-hundred-twenty events per second case. The new plugin also can consume benchmark channel events.
But it is also slightly growing up memory consumption.

Finally, here is the conclusion.

The new plugin which is named in_windows_eventlog2 does...
* Improve Unicode handling. Thanks to winevt_c gem!
* Reduce memory consumption
* Solve CPU spike after resuming operation

But still there is an issue in the new plugin:
* Slightly higher CPU usage than the old one

From throughput benchmarking result, the new plugin can handle about three-hundred event per second with default read_interval parameter.

The new plugin which is named in_windows_eventlog2’s status is…

included fluent-plugin-windows-eventlog2 version zero-point-three-point-ow.
We wanna to hear more user voices and use cases
Note that installation is a bit of harder than the older one

Let's enjoy Monitoring Windows EventLog! 

Thank you for listening!
(Any questions?)

ふむ、こうして改めて眺めてみると中々長いですね。スライドの枚数が増えてしまったので20分の持ち時間でデモは難しそうということで当日はデモを見送りました。

まとめ

Fluentd meetupが久々に東京で開催されるということで登壇しました。
Windows EventLogのEventDataへはUTF-16でValidな文字であれば、どのような文字でも入れることができます。
現代では、UTF-16の文字セットの範囲内にアルファベットやキリル文字・ギリシャ文字だけでなく、東南アジア系の文字や、ヘブライ文字・アラビア文字といったものまで入っています。
また、扱いが手ごわそうな絵文字ですらも扱うことが出来たので、in_windows_eventlog2プラグインが動き始めた時には衝撃を受けました。

文字コードをうまく扱うのは大変ですし地味ですけれども、絵文字の登場によって文字コードの話題が実例を見せた時に見た目が賑やかになることで実際に扱えていることが視覚的に分かりやすくなったのは喜ばしいですね。

タグ: Fluentd
2019-07-18

Fluent Bitプロジェクトの概要とWindows対応

2019年7月17日に3年ぶりとなるFluentd Meetupが開催されました (Webサイト)。クリアコードからは、筆者(藤本)を含めて2名がスピーカーとして参加しました。

私のパートでは、現在取り組んでいるFluent Bitプロジェクトについてお話ししました。大きなテーマは、今年の春に追加された「Fluent BitのWindows対応」ですが、そもそもFluent Bitが(まだ)それほど日本では広まっていないので、前提となるプロジェクトの基礎知識を含めてお話しました。

今回のトークのサマリーは次の通りです。

  • この数年で、アプリケーションのログ管理は格段に難しくなっています。この一つの要因には、マイクロサービスや仮想化の進展があり、監視の対象となるログの数そのものが増えているという事情があります。
  • 私たちは、この問題を解決するためにFluent Bitを開発しています。これはCで書かれた高速なログ転送デーモンで、このプログラムを使うことで、様々なサービス間で効率よくログを転送することができます。
  • このトークでは、プロジェクトの最近のサポートアーキテクチャ拡張(Windows対応)を含めて、Fluent Bitの要旨をご説明します。

全文付きのスライドはこちらに掲載しています。

http://ceptord.net/20190717-FluentdMeetup.html

とくに予備知識のないエンジニアをターゲットにトークを組み立てたので、ログ管理まわりのエコシステムに興味があればぜひご参考にどうぞ。


今回の発表にあたって、イベントに招いてくださったArm Treasure Dataの中川さんとEduardo Silvaさんに感謝いたします。

タグ: Fluentd
2019-07-19

Groongaの回帰テストで既知の差分を吸収するには

はじめに

オープンソースのカラムストア機能付き全文検索エンジンに、Groongaがあります。
Groongaを使うと全文検索機能付き高性能アプリケーションを開発することができます。

Groongaは毎月29日(肉の日)ごろに新しいバージョンをリリースしています。
そう頻繁なことではありませんが、非互換な変更が入ったりすることもあります。

バージョンアップによって、Groongaを利用するアプリケーションで検索結果が変わっていたりすると大変です。
そんなときに便利なのが、groonga-query-log です。
groonga-query-log には回帰テスト用のツールが含まれているので、古いGroongaと新しいGroongaに対して既存のクエリーログを使って回帰テストが実行できます。
ただ、そこそこ古いGroongaとの回帰テストの結果を比較すると、そのままでは大量に差分がでてしまって困ることがあります。

今回は、回帰テストを実行する際に既知の非互換な差分は吸収しつつ、本当に必要な差分のみを検出するやりかたを紹介します。

groonga-query-log とは

groonga-query-log はGroongaのクエリーログに関する便利ツールを集めたものです。
groonga-query-log できることについては、いくつか記事がありますのでそちらを参考にしてみてください。

回帰テストに関しては、groonga-query-log-run-regression-test というスクリプトが含まれているのでそれを使います。

回帰テストの実行のしかた

groonga-query-log-run-regression-testを使って回帰テストを実行できます。

groonga-query-log-run-regression-testはgroonga-query-logに含まれています。

% gem install groonga-query-log

回帰テストを実行するには、Groongaのダンプデータと回帰テストの対象となるクエリーログが必要です。

groonga-query-log-run-regression-test \
  --old-groonga=(比較元のGroongaのパス) \
  --new-groonga=(比較先のGroongaのパス) \
  --input-directory=(ダンプやクエリーログのあるディレクトリ)

詳細については、ドキュメントを参照してください。

差分を吸収するためのオプションの解説

回帰テストの差分を吸収するためのオプションがいくつかあります。

  • --ignore-drilldown-key
  • --vector-accessor
  • --rewrite-vector-equal
  • --rewrite-vector-not-equal-empty-string
  • --rewrite-nullable-reference-number
  • --nullable-reference-number-accessor
  • --rewrite-not-or-regular-expression

それぞれのオプションの使い方を説明します。

--ignore-drilldown-key

Groonga 7.1.0でベクターカラムのドリルダウンをサポートしたことによる影響を無視するためのオプションです。
7.0.9以前の古いバージョンのGroongaでは、正しい結果を返していませんでした。
7.0.9以前の古いバージョンとの7.1.0以降のGroongaを比較する際に、ドリルダウン対象となっているベクターカラムを指定します。

--vector-accessor

後述する --rewrite-vector-equal--rewrite-vector-not-equal-empty-string を適用する対象のベクターカラムを指定します。
対象が複数あるなら、このオプションを複数指定します。

--rewrite-vector-equal

ベクターカラムに対して vector == ... で検索しているクエリー(正しくない使い方)を vector @ ... に書き換えてテストを実行します。
クエリーで書き換える対象は --vector-accessor で指定します。

古いGroongaでは、vector == ... で検索するときは先頭の要素と同じならヒットするようになっていました。(ベクターカラムをスカラーカラムとして扱っていてたまたま動いていた)
新しいGroongaではヒットしなくなっているので差分が発生します。その差分を吸収するために指定します。

--rewrite-vector-not-equal-empty-string

ベクターカラムに対して vector != "" で検索しているクエリー(正しくない使い方)を vector_size(vector) > 0 に書き換えてテストを実行します。
クエリーで書き換える対象は --vector-accessor で指定します。
vector_sizeは Groonga 5.0.3以降で使えるものなのでそれより古いGroongaと新しいGroongaとの回帰テストには使えません。

--rewrite-nullable-reference-number--nullable-reference-number-accessor

古いバージョンでは参照先のレコードが存在しないとき、そのレコードのカラムの値のデフォルト(数値なら0とか)を使ってfilterを処理していました。
新しいバージョンでは必ず false になるため、同等の内容になるように以下のようにクエリーを書き換えます。

(参照型のカラム) <= 10000 というクエリーは (参照先の_key == null ? 0 : 参照型のカラム) <= 10000 に置き換えます。

--rewrite-not-or-regular-expression

正規表現で否定先読みを用いた検索で、古いGroongaと新しいGroongaで差分がでないようにします。
&! マッチしたレコードを除く演算子と @ 全文検索を組み合わせてGroonga 5.0.7とそれ以前で挙動が同じになるようにします。

例えば、column1 @ "keyword1" && column2 @~ "^(?!.*keyword2|keyword3|...).+$
'column1 @ "keyword1" &! column2 @ "keyword2" &! column2 @ "keyword3" &! ...' に置き換えてテストを実行します。

まとめ

今回は、回帰テストを実行する際に既知の非互換な差分は吸収しつつ、本当に必要な差分のみを検出するやりかたを紹介しました。

タグ: Groonga
2019-07-22

Fluent-bit-go-s3とFluent BitのGo Pluginプロキシの話

はじめに

Fluent BitはFluentdファミリーを構成するソフトウェアの一つです。
Fleunt BitのWindows対応はプラグインの対応だけではなく、Go Pluginプロキシについても対応を行っています。

Fluent BitのGo Pluginプロキシはv1.1.0からWindowsでも利用可能です。

Fluent BitのGo Pluginプロキシとは

Fluent Bitは共有オブジェクトファイルを読み込んでOutputプラグインとして振る舞わせることができます。

例えば、Golangを使って共有オブジェクトファイルを作成する場合のコードは以下のようになります。

package main

import "github.com/fluent/fluent-bit-go/output"

//export FLBPluginRegister
func FLBPluginRegister(def unsafe.Pointer) int {
    // Gets called only once when the plugin.so is loaded
	return output.FLBPluginRegister(ctx, "gskeleton", "Hey GO!")
}

//export FLBPluginInit
func FLBPluginInit(plugin unsafe.Pointer) int {
    // Gets called only once for each instance you have configured.
    return output.FLB_OK
}

//export FLBPluginFlushCtx
func FLBPluginFlushCtx(ctx, data unsafe.Pointer, length C.int, tag *C.char) int {
    // Gets called with a batch of records to be written to an instance.
    return output.FLB_OK
}

//export FLBPluginExit
func FLBPluginExit() int {
	return output.FLB_OK
}

func main() {
}

このコードを用いて以下のようにするとGolangで共有オブジェクトを作成することができます。

$ go build -buildmode=c-shared -o out_skeleton.so .

GolangをOutputプラグインに使う利点

GolangでFluent BitのOutputプラグインに使う利点はC言語向けには提供されていないライブラリであり、Golang向けには提供されているライブラリが使用できることです。
例えば、Golang向けにはAWS SDKが提供されています。

このSDKを用いてFluent BitからAWSのサービスへ直接接続することができます。

Golang製Fluent Bitプラグインの例

Golang製のFluent Bitプラグインの例として、AWS S3へFluent Bitから直接接続できるようにするプラグインを実際に作成しました。
GolangはWindowsではdllを作成することが可能です。Windows向けのDLLに関してはリリースページに置いてあります。

まとめ

Fluent BitのGo Pluginプロキシとその実例を紹介しました。Golang製であれば共有オブジェクトとしてもさほど依存が含まれません。
例として、Debian 10上でGo 1.12.7でビルドした場合には以下の参照が共有オブジェクトへ含まれます。

% ldd out_s3.so
	linux-vdso.so.1 (0x00007ffd615ca000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f6e182a3000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6e180e2000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f6e19933000)

Fluent BitのGolang製のプラグインという選択肢が増えたことにより、AWS SDKを使ってAWSのサービスへ直接接続するプラグインを開発することが出来るようになりました。
依存ライブラリもGolangのみであれば一つのソースでWindowsで動くDLLの作成やLinuxで動作する共有オブジェクトの作成や、macOSでの動的ロードライブラリの作成ができます。そしてそれらをFluent Bit読み込ませて動かすことができます。ぜひ試してみてください。

タグ: Fluentd
2019-07-24

winevt_c gemを開発をした話

はじめに

Windows EventLogをRubyで取得するには元々win32-eventlog gemがありました。
このgemはWindows EventLogへ読み込みと書き込みができるものです。
ただし、文字コードに関してはCP_ACP(Windowsの初期コードページ)を使っていたため、日本語版WindowsではCP932(Windows-31J)の範囲内の文字列しか読み込むことができませんでした。

エンコーディングをより正確に扱うには?

Windows APIではchar *wchar *を相互に変換するAPIを提供しています。

どの関数も先ず変換先の文字列を長さを取得してから再度取得した変換先の文字列の長さを用いてchar *wchar *の変換を行います。

winevt_c gemでは、wchar *からRubyのUTF8の文字列を作成する関数を多用しました。

VALUE
wstr_to_rb_str(UINT cp, const WCHAR *wstr, int clen)
{
    VALUE vstr;
    CHAR *ptr;
    int len = WideCharToMultiByte(cp, 0, wstr, clen, nullptr, 0, nullptr, nullptr);
    ptr = ALLOCV_N(CHAR, vstr, len);
    WideCharToMultiByte(cp, 0, wstr, clen, ptr, len, nullptr, nullptr);
    VALUE str = rb_utf8_str_new_cstr(ptr);
    ALLOCV_END(vstr);

    return str;
}

この関数は任意のコードページを指定できるようにしていますが、winevt_cでは基本的にCP_UTF8(UTF8のコードページ)を指定して呼び出しています。

VALUE utf8str;
// some stuff.
utf8str = wstr_to_rb_str(CP_UTF8, <Wide Char Result>, -1);

Windows EventLogの新しいAPI

Windows Vistaより、Windows EventLogを読み取る新しいAPIが提供されています。
winevt_c gemではこのAPIを用いてRubyからWindows EventLogを読み込めるように実装しました。

EvtFormatMessageを用いたEventLogのメッセージの取得方法

EvtFormatMessageを用いると、EventLogのメッセージがWindowsの組み込みの管理ツールであるイベントビューアで取得できるメッセージと同じものが取得できます。winevt_cでは、get_message関数内でEvtFormatMessageを使用しています。このEvtFormatMessage関数の引数のFlagsEvtFormatMessageEventを指定しているところがポイントです。

winevt_cの現状

winevt_cはfluent-plugin-windows-eventlogに新しく実装したin_windows_eventlog2のWindows EventLogを取得するメソッドを中心に実装しています。そのため、現状では以下の機能は実装されていません。

  • リモートにあるWindowsの認証情報を作成し、リモートのWindowsにあるWindows EventLogを取得する
  • 指定したWindows EventLogのチャンネルからevtxファイルとしてエクスポートする
  • チャンネルのメタデータや設定をRubyから取得する関数

まとめ

fluent-plugin-windows-eventlogのin_windows_eventlog2を実装するにあたって作成したwinevt_c gemについて簡単に解説しました。この記事はFluentd meetup 2019でWindows EventLogに関するプラグイン回りの発表した話では軽くしか触れられていないwinevt_cの役割と、現状を解説する目的で執筆しました。winevt_c gem単体ではWindows EventLogはXMLで取得され、中身を見るためにはXMLのパースが現状では必要になってしまいますが、RubyでWindows EventLogを扱う方法の一つとして検討してみてください。

タグ: Fluentd
2019-07-25

Fluent BitのGolang製のプラグインのDockerfileを作った話

はじめに

Fluent BitはFluentdファミリーを構成するソフトウェアの一つです。
Fluent BitはGo Pluginプロキシが提供されており、Golangにて共有ライブラリを作成することにより、プラグインとして振る舞わせることのできるインターフェースが提供されています。
この機能については、fluent-bit-go-s3とFluent BitのGo Pluginプロキシの話でも解説しました。

最近はDockerイメージ上での問題が報告される事が多くなって来たので、Dockerfileを眺めている事が多くなりました。

Golang製のツールをビルドするDockerfileの書き方

Golangはコンパイルをしなければ動作しない静的型付きの言語です。
また、Golangの実行バイナリは使用したライブラリを静的にリンクしています。*1
このことが、libcすら何もないLinuxのユーザーランドでのGolang製の実行バイナリを実行することを可能にしています。

Golangのコードをビルドするのに必要なツールチェインが揃っているDockerイメージがDockerHubで提供されています。

例えば、GolangのGOPATHを指定してビルドするDockerfileの例は次のようになります。

FROM golang:1.12.7-stretch
ENV GOOS=linux
ENV GOARCH=amd64
ENV GOPATH=/go
ADD . /go/src/path/to/githubrepo
WORKDIR /go/src/path/to/githubrepo
RUN go build .

Golang製の共有オブジェクトをDockerイメージに載せるには

Golangから作成した共有オブジェクトは基本的に共有ライブラリへの依存が少ないため、Golangのコンパイラで作成された実行ファイルや共有オブジェクトは更に別のDockerfileへコピーして載せることが容易です。
また、最近のDockerではmulti stage buildという機能が入っているため、Dockerfileを多段階に組み合わせることなく一つのDockerfileで多段階のDockerイメージのビルドができるようになっています。

では、ここまでの背景知識を元に、筆者が作成しているfluent-bit-go-lokiプラグインの実例*2を出しつつ解説します。

Golangの共有ライブラリをビルドするには前述のGolangツールチェインがインストールされているイメージを使います。また、fluent-bit-go-lokiは共有ライブラリをCGOを経由して作成するため、それに必要な環境変数も有効にしておきます。

FROM golang:1.12.7-stretch AS build-env
ENV CGO_ENABLED=1
ENV GOOS=linux
ENV GOARCH=amd64
ENV GOPATH=/go
ADD . /go/src/github.com/cosmo0920/fluent-bit-go-loki
WORKDIR /go/src/github.com/cosmo0920/fluent-bit-go-loki
RUN go build -buildmode=c-shared -o out_loki.so .

out_loki.soの作成に必要なCGO_ENABLED=1, GOOS=linux, GOARCH=amd64, GOPATH=/goの設定が完了しました。
後の三行はout_loki.soをビルドするのに必要なディレクトリ構造を整える行と、ワーキングディレクトリの設定と、実際にビルドを行う行です。

ここで、FROM golang:1.12.7-stretch AS build-envとして、このout_loki.soをビルドするイメージをbuild-envという別名をつけている事に注意してください。この名前はビルドパイプラインの後で使用する事になります。

前段のパイプラインの成果物のout_loki.soをただコピーしてfluent/fluent-bit:1.2イメージ*3から派生させています。

作成したout_loki.soのシステムの共有ライブラリの依存関係を見ると、

% ldd out_loki.so
	linux-vdso.so.1 (0x00007ffc58381000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fd36a51d000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd36a35c000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fd36bc46000)

となるため、libc相当の共有ライブラリがシステムに居れば十分に動作させることができそうです。
ここでは、fluent/fluent-bit:1.2がgcr.io/distroless/ccイメージを利用しており、このイメージ内に必要なライブラリが揃っていると見做すことで問題なさそうです。

FROM fluent/fluent-bit:1.2
COPY --from=build-env /go/src/github.com/cosmo0920/fluent-bit-go-loki/out_loki.so /usr/lib/x86_64-linux-gnu/
# ...

これらと、fluent-bit-go-lokiプラグインの実行に必要なエントリポイントについてはfluent-bit-go-lokiプラグインのDockerfileを参照してください。

まとめ

このようなイメージの作成の仕方をする事でGolangの実行バイナリや共有オブジェクトのみを載せたサイズが軽量となるイメージを作成することができます。
Golang製のツールをDockerイメージに載せるこの方法は始めはなんてまどろっこしい方法を取るんだ!と思っていたら、実行ファイルとその実行に必要な共有オブジェクトのみにしておける利点がある事がわかり腑に落ちました。
Dockerfileを書く事ができればDocker上にデプロイするのは非常に簡単なのでDocker上でもガンガンFluentdファミリーでログを収集してみてください。

*1 https://golang.org/doc/faq#Why_is_my_trivial_program_such_a_large_binary

*2 LokiはGrafanaの新しく開発されたデータソースです。

*3 fluent-bitのイメージはlibc, libgcc程度しか入っていない軽量イメージから派生しています。

タグ: Fluentd
2019-07-30

Fluent BitからGrafana Lokiに転送するには

はじめに

Fluent BitはFluentdファミリーを構成するソフトウェアの一つです。
Fluent BitはGo Pluginプロキシが提供されており、Golangにて共有ライブラリを作成することにより、プラグインとして振る舞わせることのできるインターフェースが提供されています。
この機能については、fluent-bit-go-s3とfluent-bitのGo Pluginプロキシの話でも解説しました。
Fluent BitのGolang製のプラグインのDockerfileを作った話にて突然fluent-bit-go-lokiプラグインが登場してしまっていたので、そのプラグインについての解説を書きます。

Grafana Lokiとは

Lokiとは、新しく開発されたGrafanaのデータソースです。
Lokiにはログを入力するためのAPIが整備されています。

Lokiにレコードを送信するには

ログをPushするのであればPOST /api/prom/pushがAPIのエンドポイントになります。

このAPIのエンドポイントにはJSONまたはProtocol BufferでログをPushできます。
JSON形式でログをLokiに送るにはlabelsを用意するのが少々面倒だったため、fluent-bit-go-lokiではProtocol Bufferでやり取りを行うLokiのクライアントライブラリを使用することにしました。

これをGolangのコードで表現すると次のようになります。

package main

import "github.com/grafana/loki/pkg/promtail/client"
import "github.com/sirupsen/logrus"
import kit "github.com/go-kit/kit/log/logrus"
import "github.com/cortexproject/cortex/pkg/util/flagext"
import "github.com/prometheus/common/model"

import "fmt"
import "time"

func main() {
	cfg := client.Config{}
	// Init everything with default values.
	flagext.RegisterFlags(&cfg)
	var clientURL flagext.URLValue

	url := "http://localhost:3100/api/prom/push"
	// Override some of those defaults
	err := clientURL.Set(url)
	if err != nil {
		fmt.Println("Failed to parse client URL")
		return
	}
	cfg.URL = clientURL
	cfg.BatchWait = 1
	cfg.BatchSize = 10 * 1024

	log := logrus.New()

	loki, err := client.New(cfg, kit.NewLogrusLogger(log))

	line := `{"message": "Sent from Golang!"}`

	labelValue := "from-golang"
	labelSet := model.LabelSet{"lang": model.LabelValue(labelValue)}
	err = loki.Handle(labelSet, time.Now(), line)
	if err != nil {
		fmt.Println("Failed to send Loki")
	} else {
		fmt.Println("Success")
	}
	// Ensure to send record into Loki.
	time.Sleep(3 * time.Second)
}

このLoki向けのクライアントライブラリはバッチ単位で送るため、Handleを呼び出してもすぐにはLokiのAPIエンドポイントには送られないことに注意してください。

Fluent BitのGolang製のプラグインでLokiへイベントを送る

前節でLokiへアクセスするためのGolangのクライアントライブラリの使い方が分かったので、実際にfluent-bit-go-lokiへ組み込んでみます。
FLBPluginInitでLokiにアクセスするための設定を組み立て、FLBPluginFlushでLokiに一行づつイベントを送信するためのバッファに溜めています。
また、Fluent Bitのレコードの情報を余さずLokiに送信するためにJSONへエンコードし直しています。

package main

import "github.com/fluent/fluent-bit-go/output"
import "github.com/grafana/loki/pkg/promtail/client"
import "github.com/sirupsen/logrus"
import kit "github.com/go-kit/kit/log/logrus"
import "github.com/prometheus/common/model"
import "github.com/cortexproject/cortex/pkg/util/flagext"
import "github.com/json-iterator/go"

import (
	"C"
	"fmt"
	"log"
	"time"
	"unsafe"
)

var loki *client.Client
var ls model.LabelSet

//export FLBPluginRegister
func FLBPluginRegister(ctx unsafe.Pointer) int {
	return output.FLBPluginRegister(ctx, "loki", "Loki GO!")
}

//export FLBPluginInit
// (fluentbit will call this)
// ctx (context) pointer to fluentbit context (state/ c code)
func FLBPluginInit(ctx unsafe.Pointer) int {
	// Example to retrieve an optional configuration parameter
	url := output.FLBPluginConfigKey(ctx, "url")
	var clientURL flagext.URLValue
	err := clientURL.Set(url)
	if err != nil {
		log.Fatalf("Failed to parse client URL")
	}
	fmt.Printf("[flb-go] plugin URL parameter = '%s'\n", url)

	cfg := client.Config{}
	// Init everything with default values.
	flagext.RegisterFlags(&cfg)

	// Override some of those defaults
	cfg.URL = clientURL
	cfg.BatchWait = 10 * time.Millisecond
	cfg.BatchSize = 10 * 1024

	log := logrus.New()

	loki, err = client.New(cfg, kit.NewLogrusLogger(log))
	if err != nil {
		log.Fatalf("client.New: %s\n", err)
	}
	ls = model.LabelSet{"job": "fluent-bit"}

	return output.FLB_OK
}

//export FLBPluginFlush
func FLBPluginFlush(data unsafe.Pointer, length C.int, tag *C.char) int {
	var ret int
	var ts interface{}
	var record map[interface{}]interface{}

	dec := output.NewDecoder(data, int(length))

	for {
		ret, ts, record = output.GetRecord(dec)
		if ret != 0 {
			break
		}

		// Get timestamp
		timestamp := ts.(output.FLBTime).Time

		js, err := createJSON(timestamp, record)
		if err != nil {
			fmt.Errorf("error creating message for Grafana Loki: %v", err)
			continue
		}

		err = loki.Handle(ls, timestamp, string(js))
		if err != nil {
			fmt.Errorf("error sending message for Grafana Loki: %v", err)
			return output.FLB_RETRY
		}
	}

	// Return options:
	//
	// output.FLB_OK    = data have been processed.
	// output.FLB_ERROR = unrecoverable error, do not try this again.
	// output.FLB_RETRY = retry to flush later.
	return output.FLB_OK
}

func createJSON(timestamp time.Time, record map[interface{}]interface{}) (string, error) {
	m := make(map[string]interface{})

	for k, v := range record {
		switch t := v.(type) {
		case []byte:
			// prevent encoding to base64
			m[k.(string)] = string(t)
		default:
			m[k.(string)] = v
		}
	}

	js, err := jsoniter.Marshal(m)
	if err != nil {
		return "{}", err
	}

	return string(js), nil
}

//export FLBPluginExit
func FLBPluginExit() int {
	loki.Stop()
	return output.FLB_OK
}

func main() {
}

このファイルをout_loki.goとして保存します。
依存関係のパッケージを準備した後*1、以下のコマンドを実行するとFluent Bit用のLokiプラグインの振る舞いをする共有オブジェクトが作成できます。

$ go build -buildmode=c-shared -o out_loki.so .
Golang製のプラグインの動かし方

Fluent BitのGolang製の共有オブジェクトのプラグインを動かすには例えば、以下のような設定ファイルとコマンドが必要です。

[INPUT]
    Name cpu
    Tag  cpu.local
    # Interval Sec
    # ====
    # Read interval (sec) Default: 1
    Interval_Sec 1

[OUTPUT]
    Name  loki
    Match *
    Url http://localhost:3100/api/prom/push
$ fluent-bit -c /path/to/fluent-bit.conf -e /path/to/out_loki.so

Fluent Bitが以下のようなログを吐き出していれば読み込みに成功して動作しています。


Fluent Bit v1.2.2
Copyright (C) Treasure Data

[2019/07/31 12:15:20] [ info] [storage] initializing...
[2019/07/31 12:15:20] [ info] [storage] in-memory
[2019/07/31 12:15:20] [ info] [storage] normal synchronization mode, checksum disabled, max_chunks_up=128
[2019/07/31 12:15:20] [ info] [engine] started (pid=13346)
[flb-go] plugin URL parameter = 'http://localhost:3100/api/prom/push'
[2019/07/31 12:15:20] [ info] [sp] stream processor started
まとめ

Fluent BitのGo製の共有オブジェクトでのプラグインについてまとまった解説を書きました。
実際のfluent-bit-go-lokiはlabelSetsが複数指定できるようになっていたり、テストが書きやすいようにFluent Bitが関わる部分をinterfaceに分離しています。*2
GolangでもFluent Bitのプラグインを書くことが出来ますからぜひ試してみてください。

*1 筆者は執筆時点ではGolangの依存関係を管理するパッケージマネージャーはdepを使用しています。depでの依存パッケージの管理の開始方法はdepのドキュメントを参照してください。

*2 実際のコードはGitHubリポジトリを参照してください。

タグ: Fluentd
2019-07-31

«前月 最新記事 翌月»
タグ:
年・日ごとに見る
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|