Packages
rotating_file_writer
0.1.0
Write dispatcher which automatically (re)opens file for writing on the basis of pattern and current time
Current section
Files
Jump to
Current section
Files
lib/rotating_file_writer.ex
defmodule RotatingFileWriter do
use GenServer
require Logger
@default_mode [:write, :append, :delayed_write]
@default_gen_server_opts []
@default_check_interval 1000
def start_link(file_name_spec, opts \\ [])
def start_link({file_name_pattern, time_zone}, opts) do
file_name_fun = make_strftime_fun(file_name_pattern, time_zone)
start_link(file_name_fun, opts)
end
def start_link(file_name_fun, opts) when is_function(file_name_fun) do
mode = Keyword.get(opts, :mode, @default_mode)
gen_server_opts = Keyword.get(opts, :gen_server_opts, @default_gen_server_opts)
check_interval = Keyword.get(opts, :check_interval, @default_check_interval)
initial_time = Keyword.get(opts, :initial_time, now)
GenServer.start_link(__MODULE__, [file_name_fun, mode, check_interval, initial_time], gen_server_opts)
end
def stop(pid) do
GenServer.call(pid, :stop)
end
def write(pid, data) do
GenServer.call(pid, {:write, data})
end
def reopen(pid) do
GenServer.call(pid, :reopen)
end
def file_name(pid) do
GenServer.call(pid, :file_name)
end
def init([file_name_fun, mode, check_interval, initial_time]) do
Process.flag(:trap_exit, true)
file_name = file_name_fun.(initial_time)
timer_ref = :erlang.start_timer(check_interval, self, :check_file_name_actuality)
st = %{
file_name_fun: file_name_fun,
file_name: file_name,
file: nil,
mode: mode,
timer_ref: timer_ref,
check_interval: check_interval
}
{:ok, reopen_file(st)}
end
def check_file_name_actuality(pid, time \\ now) do
GenServer.cast(pid, {:check_file_name_actuality, time})
end
def handle_call({:write, data}, _from, st) do
result = IO.write(st.file, data)
{:reply, result, st}
end
def handle_call(:file_name, _from, st) do
{:reply, st.file_name, st}
end
def handle_call(:stop, _from, st) do
{:stop, :normal, :ok, st}
end
def handle_call(:reopen, _from, st) do
{:reply, :ok, reopen_file(st)}
end
def handle_cast({:check_file_name_actuality, time}, st) do
new_st = maybe_reopen_file(time, st)
{:noreply, new_st}
end
def handle_info({:timeout, _timer_ref, :check_file_name_actuality}, st) do
new_timer_ref = :erlang.start_timer(st.check_interval, self, :check_file_name_actuality)
:erlang.cancel_timer(st.timer_ref)
check_file_name_actuality(self)
{:noreply, %{st | timer_ref: new_timer_ref}}
end
def terminate(_reason, st) do
close_file(st)
end
defp reopen_file(st) do
close_file(st)
Logger.info("Opening #{st.file_name}")
file = File.open!(st.file_name, st.mode)
%{st | file: file}
end
defp close_file(st) do
if st.file do
Logger.info("Closing #{st.file_name}")
:ok = File.close(st.file)
end
end
defp make_strftime_fun(file_name_pattern, time_zone) do
fn(time) ->
local_time = Timex.Timezone.convert(time, time_zone)
Timex.format!(local_time, file_name_pattern, :strftime)
end
end
defp now do
Timex.now
end
defp maybe_reopen_file(new_time, st) do
new_file_name = st.file_name_fun.(new_time)
if new_file_name != st.file_name do
reopen_file(%{st | file_name: new_file_name})
else
st
end
end
end