Elixir / Phoenix:如何自定义HTTP请求日志格式?

时间:2019-06-14 14:54:39

标签: logging elixir phoenix-framework

默认情况下,我的Phoenix应用程序在大约5行日志输出中记录有关每个HTTP请求的基本信息。只要将日志级别设置为:debug,就可以看到每个请求的方法,路径,控制器和操作,参数,响应代码和持续时间:

2019-06-14 16:05:35.099 [info] GET /manage/projects/7qDjSk
2019-06-14 16:05:35.103 [debug] Processing with RTLWeb.Manage.ProjectController.show/2
  Parameters: %{"project_uuid" => "7qDjSk"}
  Pipelines: [:browser, :require_login]
2019-06-14 16:05:35.116 [info] Sent 200 in 17ms

这是一个很好的起点。但是我想自定义应用程序以将所有这些信息记录在一行上,这很有用。在Papertrail之类的工具中筛选大量日志输出时。特别是,我希望每个请求都以这样的格式显示:

[PUT /manage/projects/74/prompts/290] params=%{"project_uuid" => "74", "prompt" => %{"html" => "<div>Test question 3</div>"}, "prompt_uuid" => "290"} user=38 (Topher Hunt) status=302 redirected_to=/manage/projects/74 duration=423ms

the Phoenix.Controller docs中,我看到可以为Phoenix控制器日志记录配置日志级别,或者完全禁用它,但是我看不到自定义格式的方法。我该怎么办?

2 个答案:

答案 0 :(得分:1)

编辑:我刚刚意识到有一个库Logster,可以为您提供非常相似的可自定义输出,而工作量却少得多。我建议在下面的解决方案中使用Logster。


一旦我记住两件事,这便成为一个简单的问题:

  1. Phoenix正在处理请求时,everything is just a plug。每个插件仅采用conn结构,以某种方式对其进行转换,然后返回更新的conn结构。即使对于路由器和控制器操作,也是如此!
  2. conn结构存储有关请求的所有重要数据,因此它应该包含我要记录的所有数据。

我的解决方案是禁用以不需要的格式记录请求的内容,然后编写一个新的自定义插件以所需的格式记录请求。这是我所做的:

lib/my_app_web.ex的{​​{1}}引号块中,禁用Phoenix.Controller日志记录,因此如果您的日志级别为controller,它将不记录冗余信息:

debug

def controller do quote do use Phoenix.Controller, namespace: MyAppWeb, log: false ... 中,删除lib/my_app_web/endpoint.ex(更多冗余的内容),然后用我要写的新插头替换它。请注意,该插头是在会话和路由器插头之前 插入的,但是在响应发送到客户端之前使用plug(Plug.Logger)调用来运行log语句。

register_before_send

最后创建具有以下内容的 # Custom one-line request logging # Must come before the session & router plugs. plug MyAppWeb.RequestLogger

lib/my_app_web/plugs/request_logger.ex

现在重新启动服务器,您应该看到每个请求都生成一个日志条目(并且只有一个),其中包含所有基本信息:

# One-line full request logging inspired by Plug.Logger.
# See https://github.com/elixir-plug/plug/blob/v1.8.0/lib/plug/logger.ex
# Need to restart the server after updating this file.
defmodule MyAppWeb.RequestLogger do
  require Logger

  @behaviour Plug

  def init(opts), do: opts

  def call(conn, _opts) do
    start_time = System.monotonic_time()

    Plug.Conn.register_before_send(conn, fn(conn) ->
      Logger.log(:info, fn ->
        # We don't want passwords etc. being logged
        params = inspect(Phoenix.Logger.filter_values(conn.params))

        # Log any important session data eg. logged-in user
        user = conn.assigns[:current_user]
        user_string = if user, do: "#{user.id} (#{user.name})", else: "(none)"

        # Note redirect, if any
        redirect = Plug.Conn.get_resp_header(conn, "location")
        redirect_string = if redirect != [], do: " redirected_to=#{redirect}", else: ""

        # Calculate time taken (always in ms for consistency)
        stop_time = System.monotonic_time()
        time_us = System.convert_time_unit(stop_time - start_time, :native, :microsecond)
        time_ms = div(time_us, 100) / 10

        "[#{conn.method} #{conn.request_path}] user=#{user_string} params=#{params} "<>
        "status=#{conn.status}#{redirect_string} duration=#{time_ms}ms"
        # Other data I could include, but feels redundant: remote_ip, port, owner (PID).
      end)

      conn
    end)
  end
end

有用的参考文献:

答案 1 :(得分:0)

如果您想要JSON输出,也有一个:https://github.com/Nebo15/logger_json/tree/master/lib/logger_json