0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

syslog が文字化けする本当の理由と、自動判定をやめた話

0
Posted at

はじめに

国内のネットワーク機器やサーバから syslog を集めていると、日本語のメッセージだけが
????? や 譁�蟄怜喧縺� になることがあります。受信側の設定をいくら変えても直らない、
という質問をよく見かけます。

原因は単純で、RFC 3164 には文字コードの規定がないからです。そして解決策として
真っ先に思いつく「自動判定」は、実際に作ってみると期待どおりには動きません。

自分たちで syslog サーバを作っていて、自動判定を 二回実装して二回とも間違えました。
その記録を残します。

1. なぜ文字化けするのか

syslog には二つの世代があります。

文字コードの規定
RFC 5424 (2009) UTF-8 を要求する
RFC 3164 (2001) 規定なし

現実の機器の多くは RFC 3164 で送ります。規定がないので、送信側は素直に
OS のローカルコードページでバイト列を作ります。

  • 日本語 Windows のイベントログ転送エージェント → Shift_JIS (CP932)
  • 韓国語 Windows → CP949
  • 簡体字中国語 → GBK

受信側がこれを UTF-8 として解釈すると、非 ASCII のバイトはすべて U+FFFD
(置換文字) になります。

ここが重要で、U+FFFD になった時点で元のバイトは失われます。あとから
「やっぱり Shift_JIS でした」と設定を変えても戻りません。デコードは
受信した瞬間に正しくなければならないのです。

ログを保存してから直せばいい、が通用しない数少ない場面です。

2. 自動判定を作ってみた(一回目)

素直に考えると、バイト列を候補のコードページで読んでみて、一番「それらしい」
ものを選べばよさそうです。文字種の分布でスコアをつけました。

結果:

  • 中国語を韓国語と判定した
  • ドイツ語を日本語と判定した

短い syslog の一行は統計を取るには短すぎました。%LINK-3-UPDOWN のような
ASCII が大半を占める行で、数バイトの日本語部分から言語を当てる、というのは
そもそも無理があります。

3. スコアリングを直して、また間違えた(二回目)

判定ロジックを書き直しました。それでも外しました。

いちばん厄介なのが Shift_JIS と GBK の組み合わせです。同じバイト列が
どちらでも「読める漢字」になります。実際に試すとこうなります。

送ったもの Shift_JIS で読む GBK で読む
日本語 日本語 擔杮岅
接続 接続 愙懕
障害発生 障害発生 忈奞敪惗
認証失敗 認証失敗 擣徹幐攕

右側は日本語話者には明らかにおかしいのですが、「CJK の漢字が並んでいるか」で
点をつけるアルゴリズムには区別がつきません
。どちらも正しく漢字です。

一方で EUC-KR はこのバイト列をデコーダごと拒否します(置換文字になる)。
つまり「明らかに違う」ものは機械が落とせます。落とせないのは、
どちらも成立してしまう組み合わせです。

ここで方針を変えました。

4. 「壊れている」の判定だけは機械にできる

全部あきらめたわけではありません。間違ったコードページで読んだときにだけ
現れる痕跡
があります。

C1 制御文字 (U+0080–U+009F) です。

function looksMisdecoded(text) {
  let controls = 0;
  for (let i = 0; i < text.length; i += 1) {
    const code = text.charCodeAt(i);
    if (code >= 0x80 && code <= 0x9f) controls += 1;
  }
  return controls >= 2;
}

普通の文章にこの範囲の文字はまず出てきません。一方、Shift_JIS や CP949 の
バイト列を windows-1252 として読むと、この範囲がぼろぼろ出てきます。
誤検知がほとんどない、信号として非常に素直な指標です。

つまり:

  • 「何かおかしい」の判定 → 機械にできる(C1 制御文字の検出)
  • 「では何が正しいか」の判定 → 機械には無理(二回失敗した)

5. 結論 — 人間は自分の言語を一目で見分ける

最終的にこうしました。

  1. まず UTF-8 で読む
  2. C1 制御文字が出たら「おかしい」と判定する
  3. 候補のコードページそれぞれで読んだプレビューを並べて見せる
  4. 人間が選ぶ

実際の出力です(インターフェース Gi0/3 がダウン を CP932 で送ったとき):

shift_jis       インターフェース Gi0/3 がダウン
gbk             僀儞僞乕僼僃乕僗 Gi0/3 偑僟僂儞
windows-1251    ѓCѓ"ѓ^Ѓ[ѓtѓFЃ[ѓX Gi0/3 ‚Єѓ_ѓEѓ"

これを見て、日本語話者が一行目を選び間違えることはありません。
機械が二回失敗した問題を、人間は一瞬で解きます。

ちなみに候補が 3 つしか出ていないのは、EUC-KR や Big5 では
デコーダが拒否したか C1 制御文字が出たため除外されたからです。
「確実に違う」ものは機械が落とせます。落とせないものだけ人に見せます。

自動判定は、当たれば楽で、外れると気づかないまま壊れたログが溜まるという
性質の悪い機能でした。外したときのコストが大きいものを機械に任せない、という
判断です。

6. 送信側で直せるなら、そちらが確実

受信側の工夫より、送信側を UTF-8 にできるならそれが一番です。

  • Windows のイベントログ転送: 転送ツールに UTF-8 送信の設定があれば有効にする
  • rsyslog: $OMFileEncoding ではなく、そもそもの入力を UTF-8 に揃える
  • RFC 5424 で送れる機器: フォーマットを 5424 にすると UTF-8 が前提になる

ただし国内の機器では選べないことが多く、その場合は受信側で対処するしかありません。

7. もう一つの落とし穴 — アプリのロケールと OS のロケール

実装中に踏んだので書いておきます。

Electron の app.getLocale() はアプリの UI 言語を返します。OS の設定とは
別物です。日本語 Windows でも、アプリが英語で動いていれば en-US が返ります。

これをフォールバックの推測に使っていて、日本語のログが windows-1252 で
読まれた事故がありました。app.getSystemLocale() と
app.getPreferredSystemLanguages() も合わせて見るようにしています。

「ロケールを取る」だけでも複数の意味があります。


まとめ

  • RFC 3164 に文字コードの規定はない。機器は OS のコードページで送ってくる
  • UTF-8 として読んだ時点で元のバイトは失われる。あとから直せない
  • 自動判定は二回作って二回外した。短い syslog 一行では統計が足りない
  • Shift_JIS と GBK は同じバイト列がどちらでも「読める漢字」になる
  • 「壊れている」の判定は機械にできる。「何が正しいか」は人間のほうが速い

自分たちは syslog を受けるデスクトップアプリ
(SyslogWatch) を作っていて、
この記事の内容はそこで実際に通った道です。無料枠があるので、同じ問題で
困っている方は試してみてください。

開示: 筆者は SyslogWatch の開発者です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?