core/handler/gear_action/gear_action.ex

# Copyright(c) 2015-2023 ACCESS CO., LTD. All rights reserved.

use Croma

defmodule AntikytheraCore.Handler.GearAction do
  alias Antikythera.{Conn, Context, Request, Time, GearName, PathInfo}
  alias Antikythera.ExecutorPool.Id, as: EPoolId
  alias AntikytheraCore.Conn, as: CoreConn
  alias AntikytheraCore.{MetricsUploader, Handler.HelperModules, GearLog.Writer}

  @http_headers_to_log Application.compile_env!(:antikythera, :http_headers_to_log)

  defun split_path_to_segments(path :: v[String.t()]) :: PathInfo.t() do
    String.split(path, "/")
    # neglect leading '/' but DO include the last "" due to trailing '/'
    |> tl()
    |> Enum.map(&URI.decode_www_form/1)
  end

  defun with_logging_and_metrics_reporting(
          %Conn{request: %Request{sender: {_web_or_gear, sender_info}}} = conn,
          %HelperModules{metrics_uploader: metrics_uploader} = helper_modules,
          f :: (() -> Conn.t())
        ) :: Conn.t() do
    {conn2, t_end, processing_time} = with_logging(conn, helper_modules, sender_info, f)
    %Conn{status: status, context: %Context{executor_pool_id: epool_id0}} = conn2
    epool_id1 = epool_id0 || EPoolId.nopool()

    MetricsUploader.submit_with_time(
      metrics_uploader,
      t_end,
      make_metrics_data(status, processing_time, sender_info),
      epool_id1
    )

    conn2
  end

  defp make_metrics_data(status, processing_time, sender_info) do
    prefix = if is_atom(sender_info), do: "g2g_", else: "web_"

    [
      # If status is not given at this point it's due to a bug in the gear's action.
      {prefix <> "request_count", :request_count, status || 500},
      {prefix <> "response_time_ms", :time_distribution, processing_time}
    ]
  end

  defunp with_logging(
           conn :: v[Conn.t()],
           %HelperModules{logger: logger},
           sender_info :: v[String.t() | GearName.t()],
           f :: (() -> Conn.t())
         ) :: {Conn.t(), Time.t(), non_neg_integer} do
    %Conn{context: %Context{start_time: t_start, context_id: context_id}} = conn
    log_message_base = "#{CoreConn.request_info(conn)} from=#{sender_info}"

    headers =
      @http_headers_to_log
      |> Enum.map_join(fn key -> " #{key}=#{Conn.get_req_header(conn, key) || "(none)"}" end)

    Writer.info(logger, t_start, context_id, "#{log_message_base} START#{headers}")
    %Conn{status: status} = conn2 = f.()
    t_end = Time.now()
    processing_time = Time.diff_milliseconds(t_end, t_start)

    Writer.info(
      logger,
      t_end,
      context_id,
      "#{log_message_base} END status=#{status} time=#{processing_time}ms"
    )

    {conn2, t_end, processing_time}
  end
end