lager.erl 10.9 KB
Newer Older
Andrew Thompson's avatar
Andrew Thompson committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
%% Copyright (c) 2011 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
%%
%%   http://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.

Andrew Thompson's avatar
Andrew Thompson committed
17
18
%% @doc The lager logging framework.

Andrew Thompson's avatar
Andrew Thompson committed
19
20
-module(lager).

21
22
-include("lager.hrl").

23
%% API
24
-export([start/0,
25
        log/8, log_dest/9, log/3, log/4,
26
27
        trace_file/2, trace_file/3, trace_console/1, trace_console/2,
        clear_all_traces/0, stop_trace/1, status/0,
28
        get_loglevel/1, set_loglevel/2, set_loglevel/3, get_loglevels/0,
29
        minimum_loglevel/1, posix_error/1,
30
        safe_format/3, safe_format_chop/3,dispatch_log/8]).
Andrew Thompson's avatar
Andrew Thompson committed
31

Andrew Thompson's avatar
Andrew Thompson committed
32
33
34
35
36
-type log_level() :: debug | info | notice | warning | error | critical | alert | emergency.
-type log_level_number() :: 0..7.

-export_type([log_level/0, log_level_number/0]).

Andrew Thompson's avatar
Andrew Thompson committed
37
38
%% API

Andrew Thompson's avatar
Andrew Thompson committed
39
40
%% @doc Start the application. Mainly useful for using `-s lager' as a command
%% line switch to the VM to make lager start on boot.
41
42
43
44
start() -> start(lager).

start(App) ->
    start_ok(App, application:start(App, permanent)).
Andrew Thompson's avatar
Andrew Thompson committed
45

46
47
48
49
50
51
52
start_ok(_App, ok) -> ok;
start_ok(_App, {error, {already_started, _App}}) -> ok;
start_ok(App, {error, {not_started, Dep}}) -> 
    ok = start(Dep),
    start(App);
start_ok(App, {error, Reason}) -> 
    erlang:error({app_start_failed, App, Reason}).
Andrew Thompson's avatar
Andrew Thompson committed
53

54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

-spec dispatch_log(log_level(), atom(), atom(), pos_integer(), pid(), list(), string(), list()) ->
    ok | {error, lager_not_running}.

dispatch_log(Severity, Module, Function, Line, Pid, Traces, Format, Args) ->
    {LevelThreshold,TraceFilters} = lager_mochiglobal:get(loglevel,{?LOG_NONE,[]}),
    Result=
    case LevelThreshold >= lager_util:level_to_num(Severity) of
        true -> lager:log(Severity,Module,Function,Line,Pid,
                lager_util:maybe_utc(lager_util:localtime_ms()),
                Format,Args);
        _ -> ok
    end,
    case TraceFilters of
        [] -> Result;
        Match when is_list(Match) ->
            lager:log_dest(Severity,Module,Function,Line,Pid,
                lager_util:maybe_utc(lager_util:localtime_ms()),
                lager_util:check_traces(Traces,
                    lager_util:level_to_num(Severity),
                    TraceFilters,
                    []),
                Format,Args);
        _ -> ok
    end.

Andrew Thompson's avatar
Andrew Thompson committed
80
%% @private
81
-spec log(log_level(), atom(), atom(), pos_integer(), pid(), tuple(), string(), list()) ->
Andrew Thompson's avatar
Andrew Thompson committed
82
    ok | {error, lager_not_running}.
83
log(Level, Module, Function, Line, Pid, Time, Format, Args) ->
84
    Timestamp = lager_util:format_time(Time),
85
86
    Msg = [["[", atom_to_list(Level), "] "],
           io_lib:format("~p@~p:~p:~p ", [Pid, Module, Function, Line]),
87
88
           safe_format_chop(Format, Args, 4096)],
    safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}).
Andrew Thompson's avatar
Andrew Thompson committed
89

Andrew Thompson's avatar
Andrew Thompson committed
90
%% @private
91
-spec log_dest(log_level(), atom(), atom(), pos_integer(), pid(), tuple(), list(), string(), list()) ->
Andrew Thompson's avatar
Andrew Thompson committed
92
    ok | {error, lager_not_running}.
93
94
95
log_dest(_Level, _Module, _Function, _Line, _Pid, _Time, [], _Format, _Args) ->
    ok;
log_dest(Level, Module, Function, Line, Pid, Time, Dest, Format, Args) ->
96
    Timestamp = lager_util:format_time(Time),
Andrew Thompson's avatar
Andrew Thompson committed
97
    Msg = [["[", atom_to_list(Level), "] "],
98
           io_lib:format("~p@~p:~p:~p ", [Pid, Module, Function, Line]),
99
           safe_format_chop(Format, Args, 4096)],
100
101
    safe_notify({log, Dest, lager_util:level_to_num(Level), Timestamp, Msg}).

Andrew Thompson's avatar
Andrew Thompson committed
102

Andrew Thompson's avatar
Andrew Thompson committed
103
%% @doc Manually log a message into lager without using the parse transform.
Andrew Thompson's avatar
Andrew Thompson committed
104
-spec log(log_level(), pid(), list()) -> ok | {error, lager_not_running}.
105
log(Level, Pid, Message) ->
106
    Timestamp = lager_util:format_time(),
Andrew Thompson's avatar
Andrew Thompson committed
107
    Msg = [["[", atom_to_list(Level), "] "], io_lib:format("~p ", [Pid]),
108
           safe_format_chop("~s", [Message], 4096)],
109
    safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}).
110

Andrew Thompson's avatar
Andrew Thompson committed
111
%% @doc Manually log a message into lager without using the parse transform.
Andrew Thompson's avatar
Andrew Thompson committed
112
-spec log(log_level(), pid(), string(), list()) -> ok | {error, lager_not_running}.
113
log(Level, Pid, Format, Args) ->
114
    Timestamp = lager_util:format_time(),
Andrew Thompson's avatar
Andrew Thompson committed
115
    Msg = [["[", atom_to_list(Level), "] "], io_lib:format("~p ", [Pid]),
116
           safe_format_chop(Format, Args, 4096)],
117
    safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}).
118

119
120
121
122
123
124
125
126
127
trace_file(File, Filter) ->
    trace_file(File, Filter, debug).

trace_file(File, Filter, Level) ->
    Trace0 = {Filter, Level, {lager_file_backend, File}},
    case lager_util:validate_trace(Trace0) of
        {ok, Trace} ->
            Handlers = gen_event:which_handlers(lager_event),
            %% check if this file backend is already installed
128
            Res = case lists:member({lager_file_backend, File}, Handlers) of
129
130
131
132
133
134
135
                false ->
                    %% install the handler
                    supervisor:start_child(lager_handler_watcher_sup,
                        [lager_event, {lager_file_backend, File}, {File, none}]);
                _ ->
                    ok
            end,
136
137
138
139
140
141
            case Res of
              {ok, _} ->
                %% install the trace.
                {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
                case lists:member(Trace, Traces) of
                  false ->
142
                    lager_mochiglobal:put(loglevel, {MinLevel, [Trace|Traces]});
143
144
145
146
147
148
149
                  _ ->
                    ok
                end,
                {ok, Trace};
              {error, _} = E ->
                E
            end;
150
151
152
153
154
        Error ->
            Error
    end.

trace_console(Filter) ->
155
    trace_console(Filter, debug).
156
157
158
159
160

trace_console(Filter, Level) ->
    Trace0 = {Filter, Level, lager_console_backend},
    case lager_util:validate_trace(Trace0) of
        {ok, Trace} ->
161
            {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
162
163
164
165
166
            case lists:member(Trace, Traces) of
                false ->
                    lager_mochiglobal:put(loglevel, {MinLevel, [Trace|Traces]});
                _ -> ok
            end,
167
            {ok, Trace};
168
169
170
171
        Error ->
            Error
    end.

172
stop_trace({_Filter, _Level, Target} = Trace) ->
173
    {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
174
175
176
177
178
179
180
181
182
183
184
185
186
187
    NewTraces =  lists:delete(Trace, Traces),
    lager_mochiglobal:put(loglevel, {MinLevel, NewTraces}),
    case get_loglevel(Target) of
        none ->
            %% check no other traces point here
            case lists:keyfind(Target, 3, NewTraces) of
                false ->
                    gen_event:delete_handler(lager_event, Target, []);
                _ ->
                    ok
            end;
        _ ->
            ok
    end,
188
189
    ok.

190
191
clear_all_traces() ->
    {MinLevel, _Traces} = lager_mochiglobal:get(loglevel),
192
    lager_mochiglobal:put(loglevel, {MinLevel, []}),
193
194
195
196
197
198
199
200
    lists:foreach(fun(Handler) ->
          case get_loglevel(Handler) of
            none ->
              gen_event:delete_handler(lager_event, Handler, []);
            _ ->
              ok
          end
      end, gen_event:which_handlers(lager_event)).
201

202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
status() ->
    Handlers = gen_event:which_handlers(lager_event),
    Status = ["Lager status:\n",
        [begin
                    Level = get_loglevel(Handler),
                    case Handler of
                        {lager_file_backend, File} ->
                            io_lib:format("File ~s at level ~p\n", [File, Level]);
                        lager_console_backend ->
                            io_lib:format("Console at level ~p\n", [Level]);
                        _ ->
                            []
                    end
            end || Handler <- Handlers],
        "Active Traces:\n",
        [begin
218
219
                    io_lib:format("Tracing messages matching ~p at level ~p to ~p\n",
                        [Filter, lager_util:num_to_level(Level), Destination])
220
221
            end || {Filter, Level, Destination} <- element(2, lager_mochiglobal:get(loglevel))]],
    io:put_chars(Status).
222

Andrew Thompson's avatar
Andrew Thompson committed
223
%% @doc Set the loglevel for a particular backend.
224
set_loglevel(Handler, Level) when is_atom(Level) ->
225
    Reply = gen_event:call(lager_event, Handler, {set_loglevel, Level}, infinity),
226
227
    %% recalculate min log level
    MinLog = minimum_loglevel(get_loglevels()),
228
229
    {_, Traces} = lager_mochiglobal:get(loglevel),
    lager_mochiglobal:put(loglevel, {MinLog, Traces}),
230
    Reply.
231

Andrew Thompson's avatar
Andrew Thompson committed
232
233
%% @doc Set the loglevel for a particular backend that has multiple identifiers
%% (eg. the file backend).
234
set_loglevel(Handler, Ident, Level) when is_atom(Level) ->
235
236
    io:format("handler: ~p~n", [{Handler, Ident}]),
    Reply = gen_event:call(lager_event, {Handler, Ident}, {set_loglevel, Level}, infinity),
237
238
    %% recalculate min log level
    MinLog = minimum_loglevel(get_loglevels()),
239
240
    {_, Traces} = lager_mochiglobal:get(loglevel),
    lager_mochiglobal:put(loglevel, {MinLog, Traces}),
241
    Reply.
242

Andrew Thompson's avatar
Andrew Thompson committed
243
244
%% @doc Get the loglevel for a particular backend. In the case that the backend
%% has multiple identifiers, the lowest is returned
245
get_loglevel(Handler) ->
246
    case gen_event:call(lager_event, Handler, get_loglevel, infinity) of
247
        X when is_integer(X) ->
Andrew Thompson's avatar
Andrew Thompson committed
248
            lager_util:num_to_level(X);
249
250
251
        Y -> Y
    end.

252
253
254
255
256
257
258
259
%% @doc Try to convert an atom to a posix error, but fall back on printing the
%% term if its not a valid posix error code.
posix_error(Error) when is_atom(Error) ->
    case erl_posix_msg:message(Error) of
        "unknown POSIX error" -> atom_to_list(Error);
        Message -> Message
    end;
posix_error(Error) ->
260
    safe_format_chop("~p", [Error], 4096).
261

Andrew Thompson's avatar
Andrew Thompson committed
262
%% @private
263
get_loglevels() ->
264
    [gen_event:call(lager_event, Handler, get_loglevel, infinity) ||
Andrew Thompson's avatar
Andrew Thompson committed
265
266
        Handler <- gen_event:which_handlers(lager_event)].

Andrew Thompson's avatar
Andrew Thompson committed
267
%% @private
268
minimum_loglevel([]) ->
269
    -1; %% lower than any log level, logging off
270
minimum_loglevel(Levels) ->
271
    erlang:hd(lists:reverse(lists:sort(Levels))).
272

273
safe_notify(Event) ->
274
275
276
277
278
    case whereis(lager_event) of
        undefined ->
            %% lager isn't running
            {error, lager_not_running};
        Pid ->
279
            gen_event:sync_notify(Pid, Event)
280
281
    end.

282
283
284
285
286
287
%% @doc Print the format string `Fmt' with `Args' safely with a size
%% limit of `Limit'. If the format string is invalid, or not enough
%% arguments are supplied 'FORMAT ERROR' is printed with the offending
%% arguments. The caller is NOT crashed.

safe_format(Fmt, Args, Limit) ->
288
289
290
    safe_format(Fmt, Args, Limit, []).

safe_format(Fmt, Args, Limit, Options) ->
291
    try lager_trunc_io:format(Fmt, Args, Limit, Options)
292
293
294
295
    catch
        _:_ -> lager_trunc_io:format("FORMAT ERROR: ~p ~p", [Fmt, Args], Limit)
    end.

296
%% @private
297
safe_format_chop(Fmt, Args, Limit) ->
298
    safe_format(Fmt, Args, Limit, [{chomp, true}]).