django-structlogでDjangoのログを取る方法

2020年7月10日

Python
Django
サーバー
Nginx
Logging

django-structlogを導入してDjangoのアクセス/アクションログを取得した時の話

どうも、テック備忘LOGのYuki(@tech_bibo_log)です!!

今回はDjangoで作成した当ブログのログを取得するためにdjango-structlogを導入したときの手順に関する記事です。

当ブログに最近、無差別に営業メールが送られてくるので、その対策の一つを打つためにも、アクセス元の情報を取得したいと思いログ機能の追加を決めました。

余談ですが、次の記事では当ブログを作成した時の手順について説明しています。

▽▽▽ Djangoでブログを構築した時のお話し ▽▽▽

2020/5/11

Django + Nginx + uWSGIな環境を構築する!「Hello, World」公開まで。



▽▽▽ django-structlogとJupyter NoteBookでセキュリティー対策する方法を紹介 ▽▽▽



では早速やっていきましょう!!

[ 目次 (開く) ]

django-structlogとは?

「django-structlog」とは便利なモジュールの一つで、pypi.orgのサイトでは以下のように説明されています。

django-structlog is a structured logging integration for Django project using structlog
Logging will then produce additional cohesive metadata on each logs that makes it easier to track events or incidents.
[ by https://pypi.org/project/django-structlog/ ]


要約すると、、、

Django-structlogはstructlogを使ったDjangoプロジェクトのための構造化ロギング統合です。
ログを記録すると、各ログにまとまったメタデータが追加され、イベントやインシデントの追跡が容易になります。

とういことです。
とにかく簡単にDjangoのロギングを可能にしてくれるステキなモジュールという事です。

大元のstructlogについての説明は下記のドキュメントを参照してください。 後ほど設定していくタイムスタンプのゾーン設定など、細かい設定方法は下記から確認が可能です。
■structlog Documentation
 http://www.structlog.org/_/downloads/en/stable/pdf/

一般的な'logging'と'django-structlog'の違い

一般的なpythonのロギング機能loggingを使用した時と、django-structlogを使用した時の違いは下記の通りです。

pythonのロギング機能の場合

 >>> import logging
 >>> logger = logging.get_logger(name)
 >>> logger.info("An error occurred")
 ↓↓↓ 出力される内容 ↓↓↓
 An error occurred


django-structlogのflat_lineを使用した場合
 >>> import structlog
 >>> logger = structlog.get_logger(name)
 >>> logger.info("an_error_occurred", bar="Buz")
 ↓↓↓ 出力される内容 ↓↓↓
 timestamp='2019-04-13T19:39:31.089925Z' level='info' event='an_error_occurred' logger='my_awesome_project.my_awesome_module' request_id='3a8f801c-072b-4805-8f38-e1337f363ed4' user_id=1 ip='0.0.0.0' bar='Buz'

django-structlogてファイルに出力されたログを検索する際は下記のように行います。

 cat logs/flat_line.log | grep request_id='3a8f801c-072b-4805-8f38-e1337f363ed4'

導入方法

これからインストールを完了するまでの手順を説明しています。

1.インストールしよう!

# pip install django-structlog


2.ミドルウェアを追加しよう!

setting.pyミドルウェアの部分に追記します。

/...アプリまでのPath....../django<アプリ名>/websites/settings.py
MIDDLEWARE = [
    # ...
    'django_structlog.middlewares.RequestMiddleware',
]
...


3.structlogの設定をしよう!

structlogの設定内容を、同じくsettings.pyの末尾に追記しましょう。
※以下の設定はここ(https://pypi.org/project/django-structlog/)に記載されている設定例そのままで、ログの出力先だけを変更したコードです。

/...アプリまでのPath....../django<アプリ名>/websites/settings.py
import structlog
LOGGING = { "version": 1, "disable_existing_loggers": False, "formatters": { "json_formatter": { "()": structlog.stdlib.ProcessorFormatter, "processor": structlog.processors.JSONRenderer(), }, "plain_console": { "()": structlog.stdlib.ProcessorFormatter, "processor": structlog.dev.ConsoleRenderer(), }, "key_value": { "()": structlog.stdlib.ProcessorFormatter, "processor": structlog.processors.KeyValueRenderer(key_order=['timestamp', 'level', 'event', 'logger']), }, }, "handlers": { "console": { "class": "logging.StreamHandler", "formatter": "plain_console", }, "json_file": { "class": "logging.handlers.WatchedFileHandler", "filename": "logs/access__json.log", ##ここでjson形式のログファイルPathを指定する "formatter": "json_formatter", }, "flat_line_file": { "class": "logging.handlers.WatchedFileHandler", "filename": "logs/access__flat_line.log", ##ここでflat_line形式のログファイルPathを指定する "formatter": "key_value", }, }, "loggers": { "django_structlog": { "handlers": ["console", "flat_line_file", "json_file"], "level": "INFO", }, "django_structlog_demo_project": { "handlers": ["console", "flat_line_file", "json_file"], "level": "INFO", }, } }

structlog.configure( processors=[ structlog.stdlib.filter_by_level, structlog.processors.TimeStamper(fmt="iso"), structlog.stdlib.add_logger_name, structlog.stdlib.add_log_level, structlog.stdlib.PositionalArgumentsFormatter(), structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, structlog.processors.UnicodeDecoder(), structlog.processors.ExceptionPrettyPrinter(), structlog.stdlib.ProcessorFormatter.wrap_for_formatter, ], context_class=structlog.threadlocal.wrap_dict(dict), logger_factory=structlog.stdlib.LoggerFactory(), wrapper_class=structlog.stdlib.BoundLogger, cache_logger_on_first_use=True, )


4.ログファイルの出力先について

以下の部分で通常のテキストログと、Json形式のログの出力先を指定しています。

"filename": "logs/json.log",
"filename": "logs/flat_line.log",

上記の場合は、<プロジェクトフォルダ>/logs/の下にログファイルが生成されます。
※<アプリフォルダ>ではないのでご注意を



4.ログのタイムスタンプについて

django-structlogの初期設定ではタイムスタンプの時刻が協定世界時のUTCなっています。
日本標準時間との時差は-9時間で、サーバーのログ時間と見比べる際などに分かりづらいので変更してしまいましょう!
設定方法は次の通りです。
structlog.processors.TimeStamperに引数utc=Falseをセットします。

/...アプリまでのPath....../django<アプリ名>/websites/settings.py
structlog.configure(
    processors=[
        structlog.stdlib.filter_by_level,
        structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=False), ←← ここに追加
        ...
]
...



以上でインストール作業は完了です!
早速、アプリケーションサーバーを再起動してロギングを開始しましょう。
Let's Start logging with structlog instead of logging!!


使用方法

1.アプリケーションサーバーを再起動しよう!

私の場合はNginxuWSGIを組み合わせてDjangoアプリケーションを動かしているので、再起動のコマンドは下記のようになります。

これでDjangoアプリにアクセスすると(アクセスがあると)、プロジェクトフォルダ直下のlogs\access__flat_line.log内に以下のようなアクセスログが記録されます。

# killall -9 uwsgi
# uwsgi --ini uwsgi.ini &


お疲れ様でした。
これで「誰がいつアクセスしたか?」というログが残るようになるので、お問い合わせフォームからの迷惑メール悪意のあるユーザーのアクションに対して対策を取ることが少し簡単になりましたね。

まとめ

今回やった作業を簡単にまとめると下記の3つになります。

  1. django-structlogをインストール
    pip install django-structlogを実行

  2. structlogの設定を行う

  3. アプリケーションサーバーを再起動

とても簡単ですね!

以上、ありがとうございました。