diff options
Diffstat (limited to 'deps/rabbitmq_web_dispatch/src/webmachine_log.erl')
-rw-r--r-- | deps/rabbitmq_web_dispatch/src/webmachine_log.erl | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/deps/rabbitmq_web_dispatch/src/webmachine_log.erl b/deps/rabbitmq_web_dispatch/src/webmachine_log.erl new file mode 100644 index 0000000000..dd4da91924 --- /dev/null +++ b/deps/rabbitmq_web_dispatch/src/webmachine_log.erl @@ -0,0 +1,239 @@ +%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% https://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. + +%% @doc Helper functions for webmachine's default log handlers + +-module(webmachine_log). + +-include("webmachine_logger.hrl"). + +-export([add_handler/2, + call/2, + call/3, + datehour/0, + datehour/1, + defer_refresh/1, + delete_handler/1, + fix_log/2, + fmt_ip/1, + fmtnow/0, + log_access/1, + log_close/3, + log_open/1, + log_open/2, + log_write/2, + maybe_rotate/3, + month/1, + refresh/2, + suffix/1, + zeropad/2, + zone/0]). + +-record(state, {hourstamp :: non_neg_integer(), + filename :: string(), + handle :: file:io_device()}). + +%% @doc Add a handler to receive log events +-type add_handler_result() :: ok | {'EXIT', term()} | term(). +-spec add_handler(atom() | {atom(), term()}, term()) -> add_handler_result(). +add_handler(Mod, Args) -> + gen_event:add_handler(?EVENT_LOGGER, Mod, Args). + +%% @doc Make a synchronous call directly to a specific event handler +%% module +-type error() :: {error, bad_module} | {'EXIT', term()} | term(). +-spec call(atom(), term()) -> term() | error(). +call(Mod, Msg) -> + gen_event:call(?EVENT_LOGGER, Mod, Msg). + +%% @doc Make a synchronous call directly to a specific event handler +%% module +-spec call(atom(), term(), timeout()) -> term() | error(). +call(Mod, Msg, Timeout) -> + gen_event:call(?EVENT_LOGGER, Mod, Msg, Timeout). + +%% @doc Return a four-tuple containing year, month, day, and hour +%% of the current time. +-type datehour() :: {calendar:year(), calendar:month(), calendar:day(), calendar:hour()}. +-spec datehour() -> datehour(). +datehour() -> + datehour(os:timestamp()). + +%% @doc Return a four-tuple containing year, month, day, and hour +%% of the specified time. +-spec datehour(erlang:timestamp()) -> datehour(). +datehour(TS) -> + {{Y, M, D}, {H, _, _}} = calendar:now_to_universal_time(TS), + {Y, M, D, H}. + +%% @doc Defer the refresh of a log file. +-spec defer_refresh(atom()) -> {ok, timer:tref()} | {error, term()}. +defer_refresh(Mod) -> + {_, {_, M, S}} = calendar:universal_time(), + Time = 1000 * (3600 - ((M * 60) + S)), + timer:apply_after(Time, ?MODULE, refresh, [Mod, os:timestamp()]). + +%% @doc Remove a log handler +-type delete_handler_result() :: term() | {error, module_not_found} | {'EXIT', term()}. +-spec delete_handler(atom() | {atom(), term()}) -> delete_handler_result(). +delete_handler(Mod) -> + gen_event:delete_handler(?EVENT_LOGGER, Mod, []). + +%% Seek backwards to the last valid log entry +-spec fix_log(file:io_device(), non_neg_integer()) -> ok. +fix_log(_FD, 0) -> + ok; +fix_log(FD, 1) -> + {ok, 0} = file:position(FD, 0), + ok; +fix_log(FD, Location) -> + case file:pread(FD, Location - 1, 1) of + {ok, [$\n | _]} -> + ok; + {ok, _} -> + fix_log(FD, Location - 1) + end. + +%% @doc Format an IP address or host name +-spec fmt_ip(undefined | string() | inet:ip4_address() | inet:ip6_address()) -> string(). +fmt_ip(IP) when is_tuple(IP) -> + inet_parse:ntoa(IP); +fmt_ip(undefined) -> + "0.0.0.0"; +fmt_ip(HostName) -> + HostName. + +%% @doc Format the current time into a string +-spec fmtnow() -> string(). +fmtnow() -> + {{Year, Month, Date}, {Hour, Min, Sec}} = calendar:local_time(), + io_lib:format("[~2..0w/~s/~4..0w:~2..0w:~2..0w:~2..0w ~s]", + [Date,month(Month),Year, Hour, Min, Sec, zone()]). + +%% @doc Notify registered log event handler of an access event. +-spec log_access(tuple()) -> ok. +log_access({_, _, _}=LogData) -> + gen_event:sync_notify(?EVENT_LOGGER, {log_access, LogData}). + +%% @doc Close a log file. +-spec log_close(atom(), string(), file:io_device()) -> ok | {error, term()}. +log_close(Mod, Name, FD) -> + error_logger:info_msg("~p: closing log file: ~p~n", [Mod, Name]), + file:close(FD). + +%% @doc Open a new log file for writing +-spec log_open(string()) -> {file:io_device(), non_neg_integer()}. +log_open(FileName) -> + DateHour = datehour(), + {log_open(FileName, DateHour), DateHour}. + +%% @doc Open a new log file for writing +-spec log_open(string(), non_neg_integer()) -> file:io_device(). +log_open(FileName, DateHour) -> + LogName = FileName ++ suffix(DateHour), + error_logger:info_msg("opening log file: ~p~n", [LogName]), + filelib:ensure_dir(LogName), + {ok, FD} = file:open(LogName, [read, write, raw]), + {ok, Location} = file:position(FD, eof), + fix_log(FD, Location), + file:truncate(FD), + FD. + +-spec log_write(file:io_device(), iolist()) -> ok | {error, term()}. +log_write(FD, IoData) -> + file:write(FD, lists:flatten(IoData)). + +%% @doc Rotate a log file if the hour it represents +%% has passed. +-spec maybe_rotate(atom(), erlang:timestamp(), #state{}) -> #state{}. +maybe_rotate(Mod, Time, State) -> + ThisHour = datehour(Time), + if ThisHour == State#state.hourstamp -> + State; + true -> + defer_refresh(Mod), + log_close(Mod, State#state.filename, State#state.handle), + Handle = log_open(State#state.filename, ThisHour), + State#state{hourstamp=ThisHour, handle=Handle} + end. + +%% @doc Convert numeric month value to the abbreviation +-spec month(1..12) -> string(). +month(1) -> + "Jan"; +month(2) -> + "Feb"; +month(3) -> + "Mar"; +month(4) -> + "Apr"; +month(5) -> + "May"; +month(6) -> + "Jun"; +month(7) -> + "Jul"; +month(8) -> + "Aug"; +month(9) -> + "Sep"; +month(10) -> + "Oct"; +month(11) -> + "Nov"; +month(12) -> + "Dec". + +%% @doc Make a synchronous call to instruct a log handler to refresh +%% itself. +-spec refresh(atom(), erlang:timestamp()) -> ok | {error, term()}. +refresh(Mod, Time) -> + call(Mod, {refresh, Time}, infinity). + +-spec suffix(datehour()) -> string(). +suffix({Y, M, D, H}) -> + YS = zeropad(Y, 4), + MS = zeropad(M, 2), + DS = zeropad(D, 2), + HS = zeropad(H, 2), + lists:flatten([$., YS, $_, MS, $_, DS, $_, HS]). + +-spec zeropad(integer(), integer()) -> string(). +zeropad(Num, MinLength) -> + NumStr = integer_to_list(Num), + zeropad_str(NumStr, MinLength - length(NumStr)). + +-spec zeropad_str(string(), integer()) -> string(). +zeropad_str(NumStr, Zeros) when Zeros > 0 -> + zeropad_str([$0 | NumStr], Zeros - 1); +zeropad_str(NumStr, _) -> + NumStr. + +-spec zone() -> string(). +zone() -> + Time = erlang:universaltime(), + LocalTime = calendar:universal_time_to_local_time(Time), + DiffSecs = calendar:datetime_to_gregorian_seconds(LocalTime) - + calendar:datetime_to_gregorian_seconds(Time), + zone((DiffSecs/3600)*100). + +%% Ugly reformatting code to get times like +0000 and -1300 + +-spec zone(integer()) -> string(). +zone(Val) when Val < 0 -> + io_lib:format("-~4..0w", [trunc(abs(Val))]); +zone(Val) when Val >= 0 -> + io_lib:format("+~4..0w", [trunc(abs(Val))]). |