2019年8月12日

erlang lager with date in log filename

erlang lager 預設是以設定中的 filename 加上 .1 .2 的 postfix 作為 logfile rotate 的依據,但通常在使用 logfile,會希望直接在 logfile 看到產生該 log 的日期,這時需要使用 Custom Log Rotation 的功能,自己撰寫 log_rotator。

首先我們先找到 lager 原始程式碼中預設的 lagerrotatordefault.erl,先複製成 mylagerlog_rotator,然後修改裡面的程式碼。

-module(my_lager_log_rotator).

-include_lib("kernel/include/file.hrl").

-behaviour(lager_rotator_behaviour).

-export([
  create_logfile/2, open_logfile/2, ensure_logfile/4, rotate_logfile/2
]).

create_logfile(Name, Buffer) ->
  {{Y, M, D}, {H, _, _}} = calendar:now_to_local_time(os:timestamp()),
  DateHour =  {Y, M, D, H},
  FileName = filename(Name, DateHour, 1),
  file:delete(Name),
  file:make_symlink(filename:absname(FileName), Name),
  open_logfile(Name, Buffer).

open_logfile(Name, Buffer) ->
  case filelib:ensure_dir(Name) of
    ok ->
      Options = [append, raw] ++
        case  Buffer of
          {Size, Interval} when is_integer(Interval), Interval >= 0, is_integer(Size), Size >= 0 ->
            [{delayed_write, Size, Interval}];
          _ -> []
        end,
      case file:open(Name, Options) of
        {ok, FD} ->
          case file:read_file_info(Name) of
            {ok, FInfo} ->
              Inode = FInfo#file_info.inode,
              {ok, {FD, Inode, FInfo#file_info.size}};
            X -> X
          end;
        Y -> Y
      end;
    Z -> Z
  end.


ensure_logfile(Name, FD, Inode, Buffer) ->
  case file:read_link(Name) of
    {ok, _} ->
      lager_ensure_logfile(Name, FD, Inode, Buffer);
    _ ->
      create_logfile(Name, Buffer)
  end.


lager_ensure_logfile(Name, undefined, _Inode, Buffer) ->
  open_logfile(Name, Buffer);
lager_ensure_logfile(Name, FD, Inode, Buffer) ->
  case file:read_file_info(Name) of
    {ok, FInfo} ->
      Inode2 = FInfo#file_info.inode,
      case Inode == Inode2 of
        true ->
          {ok, {FD, Inode, FInfo#file_info.size}};
        false ->
          %% delayed write can cause file:close not to do a close
          _ = file:close(FD),
          _ = file:close(FD),
          case open_logfile(Name, Buffer) of
            {ok, {FD2, Inode3, Size}} ->
              %% inode changed, file was probably moved and
              %% recreated
              {ok, {FD2, Inode3, Size}};
            Error ->
              Error
          end
      end;
    _ ->
      %% delayed write can cause file:close not to do a close
      _ = file:close(FD),
      _ = file:close(FD),
      case open_logfile(Name, Buffer) of
        {ok, {FD2, Inode3, Size}} ->
          %% file was removed
          {ok, {FD2, Inode3, Size}};
        Error ->
          Error
      end
  end.
%%
%%%% renames failing are OK
%%rotate_logfile(File, 0) ->
%%  %% open the file in write-only mode to truncate/create it
%%  case file:open(File, [write]) of
%%    {ok, FD} ->
%%      file:close(FD),
%%      ok;
%%    Error ->
%%      Error
%%  end;
%%rotate_logfile(File0, 1) ->
%%  File1 = File0 ++ ".0",
%%  _ = file:rename(File0, File1),
%%  rotate_logfile(File0, 0);
%%rotate_logfile(File0, Count) ->
%%  File1 = File0 ++ "." ++ integer_to_list(Count - 2),
%%  File2 = File0 ++ "." ++ integer_to_list(Count - 1),
%%  _ = file:rename(File1, File2),
%%  rotate_logfile(File0, Count - 1).
%%

rotate_logfile(Name, _Count) ->
  case file:read_link(Name) of
    {ok, LinkedName} ->
      case filelib:file_size(LinkedName) of
        0 ->
          %% if the files size is zero, it is removed
          catch file:delete(LinkedName);
        _ ->
          void
      end;
    _ ->
      void
  end,
  {ok, {FD, _, _}} = create_logfile(Name, []),
  file:close(FD).

%% @doc Create name of a new file
%% @private
filename(BaseFileName, DateHour, Branch) ->
  FileName = lists:append([BaseFileName,
    suffix(DateHour, false), ".", integer_to_list(Branch)
  ]),
  case filelib:is_file(FileName) of
    true ->
      filename(BaseFileName, DateHour, Branch + 1);
    _ ->
      FileName
  end.

%% @doc Zero-padding number
%% @private
zeropad(Num, MinLength) ->
  NumStr = integer_to_list(Num),
  zeropad_str(NumStr, MinLength - length(NumStr)).
zeropad_str(NumStr, Zeros) when Zeros > 0 ->
  zeropad_str([$0 | NumStr], Zeros - 1);
zeropad_str(NumStr, _) ->
  NumStr.

%% @doc Create a suffix
%% @private
suffix({Y, M, D, H}, WithHour) ->
  YS = zeropad(Y, 4),
  MS = zeropad(M, 2),
  DS = zeropad(D, 2),
  HS = zeropad(H, 2),
  case WithHour of
    true ->
      lists:flatten([$., YS, MS, DS, $., HS]);
    _ ->
      lists:flatten([$., YS, MS, DS])
  end.

將 mylagerlogrotator 套用在 lager 的設定檔的 lagerfile_backend 中,{rotator, my_lager_log_rotator}

[
  {lager, [
    {log_root, "./log"},
    {crash_log, "crash.log"},
    {error_logger_redirect, false},
    {colored, true},
    {colors, [
      {debug,     "\e[0;36m" },
      {info,      "\e[1;37m" },
      {notice,    "\e[1;36m" },
      {warning,   "\e[1;33m" },
      {error,     "\e[1;31m" },
      {critical,  "\e[1;35m" },
      {alert,     "\e[1;44m" },
      {emergency, "\e[1;41m" }
    ]},
    {handlers, [
      {lager_console_backend, [{level, debug}, {formatter, lager_default_formatter},
        {formatter_config, [date, " ", time, color, " ", pid, " ", module, ":", line, " [", severity, "] ", message, "\e[0m\n"]}]},
      {lager_file_backend, [{file, "debug.log"}, {level, debug}, {size, 3000}, {date, "$H00"}, {count, 2},
        {formatter_config, [date, " ", time, " ", pid, " ", module, ":", line, " [", severity, "] ", message, "\n"]}, {rotator, my_lager_log_rotator}]}
    ]}
  ]}
].

現在就會產生像這樣的 logfile

debug.log (symbolic link to debug.log.20190426.2)
debug.log.20190426.1
debug.log.20190426.2

目前還會需要調整的是設定檔中的 count,如果在 logfile 加上日期,count 應該是要代表保留幾天的資料,但目前還是依照 lager 原本的定義,為保留幾個同樣 prefix 檔名的 logfile。

References

erlang lager

leologgerrotator.erl

沒有留言:

張貼留言