Sionの技術ブログ

SREとして日々の学習を書いて行きます。twitterは@sion_cojp

Djangoでコンテナ用にjsonログ出力を実装する

f:id:sion_cojp:20211209175650p:plain:w0:h0

TL;DR

  • djangoの標準ログは質素すぎて足りない部分が多かった
  • djangoのログ出力をjson形式にするformatterを作成
  • healthcheck pathなど、特定URIをログ出力させないfilterを作成

Why?

筆者はdjango初心者ですが、自社サービスで一部djangoを使っており、

「datadog logsにjsonログを流したい」という要件が出てきました。

標準のログ機能じゃ要件を満たせず。。またdjangoのloggingはclassが設定できるので、

「標準でダメだったら自分たちで作ってね」という仕様なのかなぁと思いました。

Why formatter?

formatterを作ろうと思ったのは2つ理由があります。

  1. ログを出力させても質素なログしかGETできず、http request/response周りのデータが欲しかった点です。 _
  2. 理由は、json型で出力させて、datadog logsにフィールド認識させたかった点です。

https://b0uh.github.io/django-json-logging.html のようにやるとjsonは出力され、2つ目は解決しそうですが、1つ目が解決しません。

https://docs.datadoghq.com/ja/logs/log_collection/python/?tab=jsonlogformatter をみると

https://github.com/namespace-ee/django-datadog-logger が推奨となってたので使ってみるとこのような出力になりました。

{
    "message": "HTTP 200 OK",
    "logger.name": "django_datadog_logger.middleware.request_log",
    "logger.thread_name": "Thread-2",
    "logger.method_name": "log_request",
    "syslog.timestamp": "2021-12-02T09:10:00.541469+00:00",
    "syslog.severity": "INFO",
    "network.client.ip": "172.23.0.1",
    "http.url": "/hoge",
    "http.url_details.host": "localhost",
    "http.url_details.port": 8000,
    "http.url_details.path": "/hoge",
    "http.url_details.queryString": {},
    "http.url_details.scheme": "http",
    "http.method": "GET",
    "http.referer": null,
    "http.useragent": "curl/7.77.0",
    "http.request_version": null,
    "http.request_id": "f44778b6-0e78-433a-a2ca-24d1ae1736a3",
    "duration": 1774787.9028320312,
    "http.status_code": 200
}

とても良さそうですが、自由にフィールドを追加したい/フィールド名を変えれたら便利

(その他サービスのログフィールドと一緒にすると検索しやすいから)

だと思って、一からformatterを作成することにしました。

Why filter

healthcheck pathなどはノイズになるため、リクエストが来てもログを出力しないようにしたかったです。

djangoにはfilter機能があったので、それも一から作ってみました。

実装したものを適用した後のログ出力

$ curl -X POST -H "Content-Type: application/json" \
-d '{"Name":"hogehoge", "fugarfuga": "fugaaaaaa"}' localhost:8000/hoge
{
    "middleware": "django_datadog_logger.middleware.request_log",
    "levelname": "WARNING",
    "filename": "request_log.py",
    "status": 404,
    "duration": 28986692.428588867,
    "error.message": "Not Found",
    "host": "localhost",
    "port": 8000,
    "protocol": "http",
    "method": "POST",
    "path": "/hoge",
    "query_string": {},
    "request_body": "{\"Name\":\"hogehoge\", \"fugarfuga\": \"fugaaaaaa\"}",
    "user-agent": "curl/7.77.0",
    "remote_ip": "172.23.0.1",
    "referer": null,
    "time": "2021-12-08T12:44:50.447804+00:00"
}

How

こちらにプログラムを置いてます。

github.com

設定方法

required

httpリクエストを解析するために下記が必要です。

# requirements.txt
django-datadog-logger==0.5.0

config/base.py

MIDDLEWARE = [
    "django_datadog_logger.middleware.request_id.RequestIdMiddleware",
    .
    .
    .
    "django_datadog_logger.middleware.error_log.ErrorLoggingMiddleware",
    "django_datadog_logger.middleware.request_log.RequestLoggingMiddleware",
]

のようにして、django-datadog-loggerをmiddlewareに登録させます。 これは、 logging_formatter.py にhttpの詳細ログを送るためです。

config/logging_formatter.py

format用のプログラムです。 django-datadog-loggerでも足りない必要なログを追加したり、

datadog logsに合わせてフィールド名を変更できます。

最後にjson型に変更して出力して終わり。

config/logging_filter.py

filter用です。

ログの出力をするorしないをジャッジします。主にヘルスチェックなど、有効なURIだがロギングしたくないものに対して行ってます

config/environemnt名.py

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    'filters': {
        'custom': {
            '()': 'config.logging_filter.Main',
        },
    },
    "formatters": {
        "json": {"()": "config.logging_formatter.Main"},
    },
    "handlers": {
        "console": {"level": "INFO", "class": "logging.StreamHandler", "formatter": "json", 'filters': ['custom']},
    },
    "loggers": {
        'django.server': {'level': 'ERROR'},
        "django_datadog_logger.middleware.request_log": {"handlers": ["console"], "level": "INFO", "propagate": False},
    },
}
DEBUG: False

formatにlogging_formatter.pyを設定

filterはlogging_filter.pyを設定

handlerはconsole(コンソールにstdoutとして出力用)の1つだけ作成し、filter/formatterは上記を選択

loggersは2つです

  1. django.server
    • djangoが生成する標準ログ。質素で使えないため("GET / HTTP/1.1" 404 179 のようなログ)出力させないようにしてます
    • level: ERRORにすることで、 上記のログを出さないようにしています
    • handlerも設定不要
  2. django_datadog_logger.middleware.request_log
    • httpリクエストを解析したログも出力されるやつ。これをベースにログを生成します

またDEBUG: Trueにすることで、ルーティングしてないURIアクセス時の無駄なログ( Not Found: / )を消しています。

developmentだけTrue推奨です。