Pythonでログ出力

bottleで簡単なサーバを立てたので、そこにアクセスする端末情報をアクセスログとしてファイルに出力したいと思いました。まず、何をしたらいいのかと思い、「python ログ出力」などと調べてみると、どうやら__logging module__というものを使用すると簡単にできるらしい！また、プログラム実行中に起こった出来事の記録を「Log(ログ)」といい、それを時系列に記録をする履歴のことを「Logging(ロギング)」ということも理解。このログの出力でどんなエラーや不具合なのか判別できるようになるので、開発や運用に即したレベルを指定する必要があるというわけ。ということでレッツコーディング！！
#ロギングチュートリアル
##簡単なロギング用法に便利な関数群

|関数名||||
|:---|:---|:---|:---|
|logging.info()|logging.debug()|warnings.warn()|logging.warning()|
|logging.error()|logging.exception()|logging.critical()|logging.basicConfig()|
意味は、大体英単語そのままですので割愛。
色々調べた結果、pythonの基本チュートリアルに記載されているlogging.~~~と記述するのは避けた方が良いという結論です。
使用するなら「from logging import debug, warn...」といった感じで記述する。階層構造の「import logging」でlogging.errorなど記述していくと、階層構造の一番根本(root)に直接突っ込む形になり、グローバル変数に影響が及ぶため複数人が関わるライブラリ構築では使用を推奨しないということらしいです→ https://qiita.com/amedama/items/b856b2f30c2f38665701#import-logging-%E3%81%99%E3%82%89%E8%85%B9%E7%AB%8B%E3%81%9F%E3%81%97%E3%81%84
というわけなので、以下もこれにならってコーディングしていきます。
→まだ直していない

##ロギングに関する用語
・Logger：アプリケーションコードが直接使うインタフェースを公開。
・Handler：(ロガーによって生成された) ログ記録を適切な送信先に送る。
・Filter：どのログ記録を出力するかを決定する、きめ細かい機能を提供。(あまり使わないらしい)
・Formatter：ログ記録が最終的に出力されるレイアウトを指定。
基本的に__logging.basicConfig()__のオプションで色々指定できるので、こいつだけで事足りそうなのだが、代表的な__logging.~~~__のいろいろについて少し紹介します。

##レベルごとの出力情報の設定
__logging.\[loglevel\]()__：ログレベル (実行時に影響を及ぼす度合いによるレベル分け) に応じて、どんな内容を出力するか指定できる。

|ログレベル|意味|
|:---|:---|
|DEBUG|主に診断をするときに使用する詳細情報|
|INFO|想定内のこと |
|WARNING|想定外のこと (デフォルト)|
|ERROR|比較的重大なエラー。ソフトウェアの特定の機能が実行できない |
|CRITICAL |致命的に重大なエラー。プログラム自体実行停止！ |
※なお、これは言語によって分け方がさまざまある模様。

```python
#simple example_01
from logger import warning, info, debug

logging.warning("Watch out!")
logging.info("I told you so")
logging.debug("Debug test")

出力結果
WARNING:root:Watch out!   #デフォルトレベルはWARNINGなのでINFO,DEBUGメッセージは表示されない。 
```
今回はシンプルに文字列の出力のみを指定した。
##出力フォーマット指定
__logging.format()__：ロギングの出力形式の指定

```python
#simple example_02
import logging

logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')

出力結果
2021-03-01 11:41:42,612 is when this event was logged.
```

##出力する基準ログレベルの指定
__logging.basicConfig()__：出力内容をオプション使用でまとめて指定できるが、ここではlevelオプションの使用を紹介。

```python
import logging

logging.basicConfig(level=logging.WARNING)
logging.debug('<1>debug.')
logging.info('<2>info.')
logging.warning('<3>warning.')
logging.error('<4>error.')
logging.critical('<5>critical')

出力結果
WARNING:root:<3>warning
ERROR:root:<4>error
CRITICAL:root:<5>critical
```
ログレベルにWARNINGを設定したので、これ以上のレベルが出力された。
※basicConfig()はroot logger() にStreamHandlerが設定されていない時に、StreamHandlerを設定するために使われる。 したがって既にHandlerが設定されていたら、原則無視されるのでプログラム内で basicConfig の1回目のみ呼び出し有効。なお、2回目以降でも設定を反映したい場合は、basicConfig()にオプション「force=True」を付加することで有効にできる(3.8ver以降)。なぜこのような仕様なのかはこちらで詳しく解説してくれている方がいます→ https://own-search-and-study.xyz/2019/10/20/python-logging-clear/
これを回避するために、冒頭でも書きましたが、「from logger import debug, ...」などとして書くことを推奨してくれている人がいます。

##出力内容の詳細設定_01
ログ出力の設定をソースコード内に記述する方法。
__logging.basicConfig()__のオプションを使用して設定していく。しかし、logger全体(rootLogger)への一括設定となり簡単ではあるが細かい設定はできないし、上述した注意点(2回目以降は無視される)がある。

```python
#simple example_03
import  logging

logging.basicConfig(
    filename='/home/example.log',                                #出力ファイルの指定
    filemode='w',   # default ='a'                               #ログファイルを開くモード指定
    encoding='utf-8',                                            #文字コード指定
    format=('%(asctime)s:%(levelname)s:%(name)s:%(message)s'),   #出力形式の指定
    datefmt='%Y-%m-%d %H:%M:%S',                                 #タイムスタンプの指定
    level=logging.WARNING)                                       #基準ログレベルの指定

出力結果
#「example.log」ファイルに以下が確認できた。


```
変数データとして指定子を用いて情報付加できる。asctime は「実行時間」を取得。

##出力ファイルの詳細指定
一般的なloggerはstderr(標準エラー)に出力されるが、出力ファイルを指定することも可能。
ロギングイベントをファイルに記録するのは頻出の手段なので早速コーディング！

```python
#example_04
import bottle
from bottle_log import LoggingPlugin

app = bottle.Bottle()
app.install(LoggingPlugin(app.config))

@app.get('/test')
def test(logger):
    logger.warning('This is only a test')
    return {}
```

##出力内容の詳細設定_02
ログ出力の設定をソースコード内に記述せずに、ログの設定ファイルを利用する方法。
実際のコーディングでは以下のように、ログの設定ファイルを別個の設定ファイルに記述しておき、 必要に応じてレベルに応じたログを出力できるようにしておく方法がとられる。

```python
[loggers]
keys=root

[handlers]
keys=consoleHandler,fileHandler

[formatters]
keys=f1

[logger_root]
level=WARNING
handlers=fileHandler,consoleHandler

[handler_consoleHandler]
class=StreamHandler
formatter=f1
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
formatter=f1
args=('/home/example.log',)

[formatter_f1]
format=[%(asctime)s]%(filename)s(%(lineno)d): %(message)s
datefmt=%Y-%m-%d %H:%M:%S
```
次に設定ファイルを呼び出す

```python
import logging
import logging.config

logging.config.fileConfig('logging.conf')
logger = logging.getLogger('root')
```

```python
#example_05
import bottle
from logentries import LogentriesHandler
from bottle_log import LoggingPlugin

app = bottle.Bottle()
app.install(LoggingPlugin(app.config))
le_handler = LogentriesHandler('logentries-api-token')
logging.getLogger('bottle.exception').addHandler(le_handler)
```
(参考資料：https://pypi.org/project/bottle-log/, https://docs.python.org/ja/3/howto/logging.html)