macOS の syslog で大量に記録される #DECODE failed to resolve UUID というエラーを除く方法

syslog を使うようなプログラムを書いていると `/var/log/system.log` に以下のようなログが記録されることに気づきました。

```
Mar 14 18:59:25 hoge-host logd[79]: #DECODE failed to resolve UUID: [pc:0x7fff7b699748 ns:0x06 type:0x81 flags:0x8208 main:01384547-12FB-3DE8-8626-C1D693E35538 pid:489]
```

このような、全く情報のない謎のログが数十行連続して、しかも数秒ごとに繰り返し記録されているのです。これでは `system.log` に出したログがすぐに流れてしまいます。

これは macOS 標準の[日本語入力プログラム][]を使うと発生するようです。どうやら既知のバグのようで、ググるといくつか記事が見つかるのですがいずれも根本的な解決には至っておりません。もちろん、ATOK その他を利用すれば避けられるのですが……いやそれはちょっと……（ライブ変換が好きなんです）。

[日本語入力プログラム]: https://support.apple.com/ja-jp/guide/japanese-input-method/welcome/mac

とりあえず `system.log` がすぐ流れてしまうことが問題なので、これだけを解決することにしました。

## 忙しい方、あるいは解決法だけ知ることができれば良い方へ

syslog とか ASL（後述）とかどうでもいい！このログを消したいんだ！って方のために、先に解決法だけ書いておきます。

1. `/etc/asl/japaneseim` というファイル名で以下の内容を書き込みます。編集には root 権限が必要です。

    ```:/etc/asl/japaneseim
    > /var/log/japaneseim.log mode=0640 format=bsd rotate=seq compress file_max=1M all_max=10M

    ? [A= Message #DECODE failed to resolve UUID:] claim only 
    ? [A= Message #DECODE failed to resolve UUID:] file japaneseim.log
    ```

2. `syslogd` に HUP シグナルを送って設定ファイルを再読み込みさせます。

    ```sh
    sudo pkill -HUP syslogd
    ```

これで終わり。`/var/log/system.log` にはこのうざいログが消え、代わりに `/var/log/japaneseim.log` という別のファイルに記録されるようになります。もし、別のファイルに記録する必要なんてない、無視してしまえ！ という向きには、`/etc/asl/japaneseim` を以下のように書き換えることをお勧めします（書き換え後は HUP シグナルを送ることを忘れずに）。

```
? [A= Message #DECODE failed to resolve UUID:] claim only
```

## macOS で syslog を設定する

ここからは、macOS で syslog を整理するための方法を書いていきます。

macOS でも他の UNIX と同じく syslog を使ってログを記録するわけですが、実際にログを書き込む際は ASL（Apple System Logger）を優先するようになっています。`/etc/syslog.conf` も一応あるのですが中身はほとんど空っぽです。

```sh
# Note that flat file logs are now configured in /etc/asl.conf
```

`/etc/asl.conf` を見て、と書いてありますね。そちらを見てみましょう。

```sh:asl.conf（部分）
##
# configuration file for syslogd and aslmanager
##

...

# Rules for /var/log/system.log
> system.log mode=0640 format=bsd rotate=seq compress file_max=5M all_max=50M
? [= Sender kernel] file system.log
? [<= Level notice] file system.log
? [= Facility auth] [<= Level info] file system.log
? [= Facility authpriv] [<= Level info] file system.log

...
```

`system.log` について書いてありますね。詳しい書式については [`man asl.conf`][asl.conf] を読んでもらうとして、大雑把に書いてあることを解説してみましょう。

まず、`>` で始まる行によってログを書き出すファイルを定義します。

[asl.conf]: https://www.manpagez.com/man/5/asl.conf/

```sh
> system.log mode=0640 format=bsd rotate=seq compress file_max=5M all_max=50M
# mode=0640     新規ファイルのパーミッションは 0640
# format=bsd    フォーマットは BSD 形式
# rotate=seq    ローテーション時はファイル名の後ろに続き番号をつける
# compress      ローテーション時はファイルを gz 圧縮する
# file_max=5M   ファイルの最大容量は 5MB（圧縮前）
# all_max=50M   ローテーションしたファイルも含めた、最大合計容量は 50MB
```

これにより、`system.log`, `system.log.0.gz`, `system.log.1.gz`, ... というファイルができます。

次に、`?` で始まる行は何を書き出すべきか定義します。

```sh
? [= Sender kernel] file system.log
# カーネルが出力したものは全て system.log へ
? [<= Level notice] file system.log
# NOTICE 以下のログは全て system.log へ
? [= Facility auth] [<= Level info] file system.log
# Facility が auth、かつ、INFO 以下のログは全て system.log へ
? [= Facility authpriv] [<= Level info] file system.log
# Facility が authpriv、かつ、INFO 以下のログは全て system.log へ
```

まあみたまんまですね。`[ ... ]` が if 文のような条件句、その後に続く `file ...` が実際に行いたい処理です。

ここで `Sender`, `Level`, `Facility` って何？ という話になると思うのですが、これについては ASL では特に意味を持つものではありません（syslog で定義されています）。ASL で処理するログの各行は `[Key Value] [Key Value] ...` の形で表される辞書のようなものとして扱われており、そこに現れる `Key` が `Sender`, `Level`, `Facility` というわけなのです。

試しに `format=raw` で出力すると、辞書の形式でログを読むことができます。

```
[Sender logd]
[Level 5]
[Facility com.apple.logd]
[Message #DECODE failed to resolve UUID: [pc:0x7fff7b699748 ns:0x06 type:0x82 flags:0x8208 main:01384547-12FB-3DE8-8626-C1D693E35538 pid:489]]
[PID 79]
[UID -2]
[GID -2]
[Time 1552555689]
[TimeNanoSec 637259000]
[Host hoge-host]
[ReadGID 80]
[SenderMachUUID 0D24C032-5662-3E09-B74F-E8E297EBCDDA]
```

`format=raw` で吐き出したログを、読みやすく改行して表示してみました。ここでは今回ターゲットにした `#DECODE failed...` のログを例示していますが、ログの種類によって利用されている Key/Value は異なります。

## 指定したログを別ファイルに書き出す

### 狙ったログを指定する方法

というわけで、お分かりですね。特定のログを `system.log` から除くためには、ログに特徴的な情報をもとに `[ ... ]` で表される条件句を決めればいいわけです。今回の場合は `Message` 値が `#DECODE failed...` という語句で始まっているわけですからこれを使いたいです。

「`Message` がホゲホゲで始まるもの」をどうやって `[ ... ]` で表しましょう。ここでもう一度 [`man asl.conf`][asl.conf] を読んでみます。

```sh
     クエリは各要素にマッチする一つ以上の語句からなり、それぞれは以下の形式になっています。

           [OP KEY VAL]

     OP は比較演算子です。以下の値を持つことができます:

           T     真 (常にマッチします)
           =     等しい
           !     等しくない
           >     より大きい
           >=    以上
           <     より小さい
           <=    以下

     一つ以上の修飾子を前置することもできます:

           C     大文字小文字を区別しない
           N     数値として比較する
           S     部分一致
           A     前方一致
           Z     後方一致
```

今回の場合、「Message が `#DECODE failed...` で始まるもの」を表したいわけですから、

```
[A= Message #DECODE failed to resolve UUID:]
```

これで OK ですね。

### 別のファイルに書き出すには

次に、この条件句について行われる処理を付け加えます。今回の場合は該当するログを `/var/log/japaneseim.log` に書き出したいわけですから、

```
> /var/log/japaneseim.log mode=0640 format=bsd rotate=seq compress file_max=1M all_max=10M

? [A= Message #DECODE failed to resolve UUID:] file japaneseim.log
```

のようにすれば良い……のですが、これではうまくいきません。これでは `japaneseim.log` とともに、`system.log` にも相変わらず出力されてしまいます。

```
> /var/log/japaneseim.log mode=0640 format=bsd rotate=seq compress file_max=1M all_max=10M

? [A= Message #DECODE failed to resolve UUID:] claim only 
? [A= Message #DECODE failed to resolve UUID:] file japaneseim.log
```

もう一行、`claim only` を加えます。これにより、該当するログは以降に続く命令でのみ、処理されることが約束されます。

## 終わりに

macOS で syslog を整理する方法について駆け足で解説しました。実際には他にも様々な処理ができる、かなり高機能なものになってますので、ぜひ [`man asl.conf`][asl.conf] を読んでみてください。また、`/etc/asl` 配下にあります他の設定を読んでも参考になると思います。