0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【ひとりカレンダー】ClojureAdvent Calendar 2024

Day 17

Clojure: RingでTodoアプリを作る - logging

Last updated at Posted at 2024-12-16

advent_calendar_2024.png

Advent Calendar 2024 Day 17

昨日でTodoアプリのベースが完成しました。
今日はHttpログを出せるようにしていきます

目標

以下ログを出せるようにしたいと思います

  • リクエストログ
    • type("request")
    • timestamp
    • request_uri
    • request_method
  • レスポンスログ
    • type("response")
    • timestamp
    • status code
    • request_uri
    • request_method
    • response_time

SLF4J・logback

SLF4J(Simple Logging Facade for Java)は、Javaアプリケーション向けのロギングAPIの統一インターフェースです。

今回は clojure.tools.logging を使うつもりですが、
こちらがSLF4Jに依存しているため、プロジェクトに依存を追加します。

SLF4Jを実装しているバックエンドとして、logbackproject.clj に追加していきます

project.clj
  :dependencies [[org.clojure/clojure "1.11.1"]
                 [...]
                 [ch.qos.logback/logback-classic "1.4.11"]]

これでprojectにlogbackを追加できました

次にlogbackの設定ファイル resources/logback.xml を作成します

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="info">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

2020-01-01 12:00:00 INFO - {"type": "request", ... }

この設定で↑のような形で出力できるはずです

clojure.tools.logging

次にlogを出力するために、clojure.tools.logging を追加します

project.clj
  :dependencies [[org.clojure/clojure "1.11.1"]
                 [...]
                 [ch.qos.logback/logback-classic "1.4.11"]
                 [org.clojure/tools.logging "1.3.0"]]

これでログを出す準備ができました。

ミドルウェアで実装

リクエストログ

もう一度リクエストログの目標を確認します

  • リクエストログ
    • type("request")
    • timestamp
    • request_uri
    • request_method

以上を実現するためのミドルウェアを作成してみました。

core.clj
(ns todo.core
  (:require [ring.adapter.jetty :as jetty]
            [ring.util.response :as res]
            [ring.middleware.json :refer [wrap-json-response wrap-json-body]]
            [...]
            [clojure.data.json :as json]
            [clojure.tools.logging :as log]))

(defn- wrap-log-request
  [handler]
  (fn [req]
    (let [current-timestamp (-> (java.time.Instant/now)
                                (.toString))]
      (log/info (json/write-str {:type "request"
                                 :timestamp current-timestamp
                                 :request_uri (:uri req)
                                 :request_method (:request-method req)})))
    (handler req)))

(def handler
  (-> handler'
      (wrap-json-response)
      (wrap-json-body {:keywords? true})
      (wrap-log-request)))

logを出力する部分は clojure.tools.loggingを使用し、
jsonに変換する部分は ring.middleware.json を使いました。

実際にリクエストを送ってみます

$ lein run
2024-12-14 23:50:55 INFO  - {"type":"request","timestamp":"2024-12-14T14:50:55.825355298Z","request_uri":"\/v1\/todos","request_method":"get"

うまくログを出力することができました :thumbsup:

レスポンスログ

レスポンスログも一度目標を確認しておきます

  • レスポンスログ
    • type("response")
    • timestamp
    • status code
    • request_uri
    • request_method
    • response_time

こちらのログを実現するために先ほど作成したミドルウェアを修正します

core.clj
(ns todo.core
  (:require [ring.adapter.jetty :as jetty]
            [ring.util.response :as res]
            [ring.middleware.json :refer [wrap-json-response wrap-json-body]]
            [...]
            [clojure.data.json :as json]
            [clojure.tools.logging :as log]))

(defn- wrap-log-response
  [handler]
  (fn [req]
    (let [start-time (System/nanoTime)
          response (handler req)
          end-time (System/nanoTime)
          response-time (/ (- end-time start-time) 1e6)
          current-timestamp (-> (java.time.Instant/now)
                                (.toString))]
      (log/info (json/write-str {:type "response"
                                 :timestamp current-timestamp
                                 :status (:status response)
                                 :request_uri (:uri req)
                                 :request_method (:request-method req)
                                 :response_time response-time}))
      response)))

(def handler
  (-> handler'
      (wrap-log-response)
      (wrap-json-response)
      (wrap-json-body {:keywords? true})
      (wrap-log-request)))

こんな感じで実装してみました。

こちらも実際にリクエストを送ってみます

$ lein run
2024-12-15 00:09:09 INFO  - {"type":"response","timestamp":"2024-12-14T15:09:09.195763764Z","status":200,"request_uri":"\/v1\/todos","request_method":"get","response_time":108.078736}

いい感じで出力できました :thumbsup:

ライブラリを使う

このライブラリを使っても簡単に実装できます

core.clj
(ns todo.core
  (:require [ring.adapter.jetty :as jetty]
            [ring.util.response :as res]
            [ring.middleware.json :refer [wrap-json-response wrap-json-body]]
            [compojure.core :refer [defroutes GET POST PUT DELETE]]
            [compojure.route :as route]
            [todo.handler :as handler]
            [ring.logger :as logger]))

(def handler
  (-> handler'
      (logger/wrap-log-response)
      (wrap-json-body {:keywords? true})
      (wrap-json-response)
      (logger/wrap-log-request-start)))

(defn -main []
  (jetty/run-jetty handler {:port 3000}))

これでリクエストを送ってみます

$ lein run
2024-12-16 12:13:38 INFO  - {:request-method :get, :uri "/v1/todos", :server-name "localhost", :ring.logger/type :starting}
2024-12-16 12:13:38 INFO  - {:request-method :get, :uri "/v1/todos", :server-name "localhost", :ring.logger/type :finish, :status 200, :ring.logger/ms 126}

いい感じですね

自分で作ってカスタマイズしてもいいし、ライブラリを使っても良さそうです :thumbsup:

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?