logging ― (クラスが)離れていても、心(出力先)はひとつ！

# モチベーション

できるだけ簡単な方法で、複数のクラスから同一ログファイルにログを出力したい。

# 実験

コードは[こちら](https://github.com/hiro-uchi/samples/tree/master/exam_logging)。

まずは互いに継承関係にない２つのクラス`Class_A`と`Class_B`を準備します。

```
>exam_logging
│  exam_logging_1.ipynb　←実験（その１）
│  exam_logging_2.ipynb　←実験（その２）
│
├─lib
│  │  cls_a.py　　　　←Class_A
│  │
│  └─tmp
│          cls_b.py　←Class_B
│
└─logs
        logger_1.log　←自動で作られる
        logger_2.log　←自動で作られる
```

それぞれのクラスの中身は以下の通り。`Class_A`のほうにのみ`logging.basicConfig`でログファイルおよびログレベルの指定をしておきます。

```Python:lib/cls_a.py
import logging

class Class_A:

    def __init__(self, filename):

        logging.basicConfig(filename=filename, level=logging.DEBUG)
        self.logger = logging.getLogger(__name__)
        return

    def get_hello(self):

        self.logger.critical('\tHello, I\'m CRITICAL!')
        self.logger.error('\tHello, I\'m ERROR!')
        self.logger.warning('\tHello, I\'m WARNING!')
        self.logger.info('\tHello, I\'m INFO!')
        self.logger.debug('\tHello, I\'m DEBUG!')
        return
```

```Python:lib/tmp/cls_b.py
import logging

class Class_B:

    def __init__(self):

        self.logger = logging.getLogger(__name__)
        return

    def get_hello(self):

        self.logger.critical('\tHello, I\'m CRITICAL!')
        self.logger.error('\tHello, I\'m ERROR!')
        self.logger.warning('\tHello, I\'m WARNING!')
        self.logger.info('\tHello, I\'m INFO!')
        self.logger.debug('\tHello, I\'m DEBUG!')
        return
```

(蛇足) 出力結果をきれいにしたくてログの先頭に`\t`を入れてみたものの、思ったほどきれいじゃなかった。。

## 実験（その１）

exam_logging_1.ipynb を参照。

1. はじめに`Class_A`のインスタンスを作って`get_hello`を呼び出し
2. 次に`Class_B`のインスタンスを作って`get_hello`を呼び出し

できあがったログファイルの中身を見ると

```:logger_1.log
CRITICAL:lib.cls_a:	Hello, I'm CRITICAL!
ERROR:lib.cls_a:	Hello, I'm ERROR!
WARNING:lib.cls_a:	Hello, I'm WARNING!
INFO:lib.cls_a:	Hello, I'm INFO!
DEBUG:lib.cls_a:	Hello, I'm DEBUG!
CRITICAL:lib.tmp.cls_b:	Hello, I'm CRITICAL!
ERROR:lib.tmp.cls_b:	Hello, I'm ERROR!
WARNING:lib.tmp.cls_b:	Hello, I'm WARNING!
INFO:lib.tmp.cls_b:	Hello, I'm INFO!
DEBUG:lib.tmp.cls_b:	Hello, I'm DEBUG!
```

`Class_B`ではログファイル出力先等を設定していないにもかかわらず、`Class_A`で指定したログファイルに`Class_B`のログが出力されています。
いわば`logging`はシングルトンのようなものでしょうか。
なので基本的には、部品となるクラスでは出力先の設定等を行わずに、ふつうに`self.logger.debug()`等でログ出力。部品を使う側で`logging.basicConfig()`でログ出力先等の設定を行うような使い方ができるかと思います。(※)


## 実験（その２）

exam_logging_2.ipynb を参照。

1. こんどは先に`Class_B`のインスタンスを作って`get_hello`を呼び出し
2. そのあとで`Class_A`のインスタンスを作って`get_hello`を呼び出し

その結果：

```:logger_2.log
CRITICAL:lib.cls_a:	Hello, I'm CRITICAL!
ERROR:lib.cls_a:	Hello, I'm ERROR!
WARNING:lib.cls_a:	Hello, I'm WARNING!
INFO:lib.cls_a:	Hello, I'm INFO!
DEBUG:lib.cls_a:	Hello, I'm DEBUG!
```

`Class_B`のほうのログはファイル出力されていませんね。当然か。
で、標準出力のほうを見ると

```
Hello, I'm CRITICAL!
Hello, I'm ERROR!
Hello, I'm WARNING!
```

となっています。(デフォルトではログレベルが`INFO`または`DEBUG`のログは出力されないようです)

さらに、

3 . ふたたび`Class_B`の`get_hello`を呼び出し

すると、ファイルの中身は

```:logger_2.log
CRITICAL:lib.cls_a:	Hello, I'm CRITICAL!
ERROR:lib.cls_a:	Hello, I'm ERROR!
WARNING:lib.cls_a:	Hello, I'm WARNING!
INFO:lib.cls_a:	Hello, I'm INFO!
DEBUG:lib.cls_a:	Hello, I'm DEBUG!
CRITICAL:lib.tmp.cls_b:	Hello, I'm CRITICAL!
ERROR:lib.tmp.cls_b:	Hello, I'm ERROR!
WARNING:lib.tmp.cls_b:	Hello, I'm WARNING!
INFO:lib.tmp.cls_b:	Hello, I'm INFO!
DEBUG:lib.tmp.cls_b:	Hello, I'm DEBUG!
```

となり、ログ出力先だけでなくログレベルも`Class_A`での設定が反映されました。

# 結果

- クラスが分かれていても、`logging`の設定は全体に反映される
    - ⇒(クラスが)離れていても、心(出力先)はひとつ！
- (ついでに)ログレベルのデフォルトは`WARNING`

# 補足

(※)`import logging`を使うことの是非については議論があります。たとえば[こちら](https://qiita.com/amedama/items/b856b2f30c2f38665701)。