lager.erl 11 KB
Newer Older
Andrew Thompson's avatar
Andrew Thompson committed
1
%% Copyright (c) 2011-2012 Basho Technologies, Inc.  All Rights Reserved.
Andrew Thompson's avatar
Andrew Thompson committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
%%
%% 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
-include("lager.hrl").
22
-include_lib("eunit/include/eunit.hrl").
23

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

Andrew Thompson's avatar
Andrew Thompson committed
33
34
35
36
37
-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
38
39
%% API

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

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

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

55

56
-spec dispatch_log(log_level(), list(), string(), list() | none, pos_integer()) ->  ok | {error, lager_not_running}.
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
dispatch_log(Severity, Metadata, Format, Args, Size) when is_atom(Severity)->
    case whereis(lager_event) of
        undefined ->
            %% lager isn't running
            {error, lager_not_running};
        Pid ->
            {LevelThreshold,TraceFilters} = lager_mochiglobal:get(loglevel,{?LOG_NONE,[]}),
            SeverityAsInt=lager_util:level_to_num(Severity),
            Destinations = case TraceFilters of 
                [] -> [];
                _ -> 
                    lager_util:check_traces(Metadata,SeverityAsInt,TraceFilters,[])
            end,
            case (LevelThreshold >= SeverityAsInt orelse Destinations =/= []) of
                true -> 
                    Timestamp = lager_util:format_time(),
                    Msg=case Args of 
74
                        A when is_list(A) ->safe_format_chop(Format,Args,Size);
75
76
                        _ -> Format
                    end,
77
78
                    gen_event:sync_notify(Pid, {log, lager_msg:new(Msg, Timestamp,
                                Severity, Metadata, Destinations)});
79
80
81
                _ -> 
                    ok
            end
82
83
    end.

Andrew Thompson's avatar
Andrew Thompson committed
84
%% @doc Manually log a message into lager without using the parse transform.
85
86
-spec log(log_level(), pid() | atom() | [tuple(),...], list()) -> ok | {error, lager_not_running}.
log(Level, Pid, Message) when is_pid(Pid); is_atom(Pid) ->
87
    dispatch_log(Level, [{pid,Pid}], Message, [], ?DEFAULT_TRUNCATION);
88
log(Level, Metadata, Message) when is_list(Metadata) ->
89
    dispatch_log(Level, Metadata, Message, [], ?DEFAULT_TRUNCATION).
90

Andrew Thompson's avatar
Andrew Thompson committed
91
%% @doc Manually log a message into lager without using the parse transform.
92
93
-spec log(log_level(), pid() | atom() | [tuple(),...], string(), list()) -> ok | {error, lager_not_running}.
log(Level, Pid, Format, Args) when is_pid(Pid); is_atom(Pid) ->
94
    dispatch_log(Level, [{pid,Pid}], Format, Args, ?DEFAULT_TRUNCATION);
95
log(Level, Metadata, Format, Args) when is_list(Metadata) ->
96
    dispatch_log(Level, Metadata, Format, Args, ?DEFAULT_TRUNCATION).
97

98
99
100
101
102
103
104
105
106
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
107
            Res = case lists:member({lager_file_backend, File}, Handlers) of
108
109
                false ->
                    %% install the handler
110
111
                    supervisor:start_child(lager_handler_watcher_sup,
                        [lager_event, {lager_file_backend, File}, {File, none}]);
112
                _ ->
113
                    {ok, exists}
114
            end,
115
            case Res of
116
              {ok, _} ->
117
118
119
120
                %% install the trace.
                {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
                case lists:member(Trace, Traces) of
                  false ->
121
                    lager_mochiglobal:put(loglevel, {MinLevel, [Trace|Traces]});
122
123
124
125
126
127
128
                  _ ->
                    ok
                end,
                {ok, Trace};
              {error, _} = E ->
                E
            end;
129
130
131
132
133
        Error ->
            Error
    end.

trace_console(Filter) ->
134
    trace_console(Filter, debug).
135
136
137
138
139

trace_console(Filter, Level) ->
    Trace0 = {Filter, Level, lager_console_backend},
    case lager_util:validate_trace(Trace0) of
        {ok, Trace} ->
140
            {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
141
142
143
144
145
            case lists:member(Trace, Traces) of
                false ->
                    lager_mochiglobal:put(loglevel, {MinLevel, [Trace|Traces]});
                _ -> ok
            end,
146
            {ok, Trace};
147
148
149
150
        Error ->
            Error
    end.

151
stop_trace({_Filter, _Level, Target} = Trace) ->
152
    {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
153
154
155
156
157
158
159
160
161
162
163
164
165
166
    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,
167
168
    ok.

169
170
clear_all_traces() ->
    {MinLevel, _Traces} = lager_mochiglobal:get(loglevel),
171
    lager_mochiglobal:put(loglevel, {MinLevel, []}),
172
173
174
175
176
177
178
179
    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)).
180

181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
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
197
198
                    io_lib:format("Tracing messages matching ~p at level ~p to ~p\n",
                        [Filter, lager_util:num_to_level(Level), Destination])
199
200
            end || {Filter, Level, Destination} <- element(2, lager_mochiglobal:get(loglevel))]],
    io:put_chars(Status).
201

Andrew Thompson's avatar
Andrew Thompson committed
202
%% @doc Set the loglevel for a particular backend.
203
set_loglevel(Handler, Level) when is_atom(Level) ->
204
    Reply = gen_event:call(lager_event, Handler, {set_loglevel, Level}, infinity),
205
206
    %% recalculate min log level
    MinLog = minimum_loglevel(get_loglevels()),
207
208
    {_, Traces} = lager_mochiglobal:get(loglevel),
    lager_mochiglobal:put(loglevel, {MinLog, Traces}),
209
    Reply.
210

Andrew Thompson's avatar
Andrew Thompson committed
211
212
%% @doc Set the loglevel for a particular backend that has multiple identifiers
%% (eg. the file backend).
213
set_loglevel(Handler, Ident, Level) when is_atom(Level) ->
214
    Reply = gen_event:call(lager_event, {Handler, Ident}, {set_loglevel, Level}, infinity),
215
216
    %% recalculate min log level
    MinLog = minimum_loglevel(get_loglevels()),
217
218
    {_, Traces} = lager_mochiglobal:get(loglevel),
    lager_mochiglobal:put(loglevel, {MinLog, Traces}),
219
    Reply.
220

Andrew Thompson's avatar
Andrew Thompson committed
221
222
%% @doc Get the loglevel for a particular backend. In the case that the backend
%% has multiple identifiers, the lowest is returned
223
get_loglevel(Handler) ->
224
    case gen_event:call(lager_event, Handler, get_loglevel, infinity) of
225
        X when is_integer(X) ->
Andrew Thompson's avatar
Andrew Thompson committed
226
            lager_util:num_to_level(X);
227
228
229
        Y -> Y
    end.

230
231
232
233
234
235
236
237
%% @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) ->
238
    safe_format_chop("~p", [Error], ?DEFAULT_TRUNCATION).
239

Andrew Thompson's avatar
Andrew Thompson committed
240
%% @private
241
get_loglevels() ->
242
    [gen_event:call(lager_event, Handler, get_loglevel, infinity) ||
Andrew Thompson's avatar
Andrew Thompson committed
243
244
        Handler <- gen_event:which_handlers(lager_event)].

Andrew Thompson's avatar
Andrew Thompson committed
245
%% @private
246
minimum_loglevel([]) ->
247
    -1; %% lower than any log level, logging off
248
minimum_loglevel(Levels) ->
249
    erlang:hd(lists:reverse(lists:sort(Levels))).
250

251
252
253
254
255
256
%% @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) ->
257
258
259
    safe_format(Fmt, Args, Limit, []).

safe_format(Fmt, Args, Limit, Options) ->
260
    try lager_trunc_io:format(Fmt, Args, Limit, Options)
261
262
263
264
    catch
        _:_ -> lager_trunc_io:format("FORMAT ERROR: ~p ~p", [Fmt, Args], Limit)
    end.

265
%% @private
266
safe_format_chop(Fmt, Args, Limit) ->
267
    safe_format(Fmt, Args, Limit, [{chomp, true}]).
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297

%% @doc Print a record lager found during parse transform
pr(Record, Module) when is_tuple(Record), is_atom(element(1, Record)) ->
    try Module:module_info(attributes) of
        Attrs ->
            case lists:keyfind(lager_records, 1, Attrs) of
                false ->
                    Record;
                {lager_records, Records} ->
                    RecordName = element(1, Record),
                    RecordSize = tuple_size(Record) - 1,
                    case lists:filter(fun({Name, Fields}) when Name == RecordName,
                                length(Fields) == RecordSize ->
                                    true;
                                (_) ->
                                    false
                            end, Records) of
                        false ->
                            Record;
                        [{RecordName, RecordFields}|_] ->
                            {'$lager_record', RecordName,
                                lists:zip(RecordFields, tl(tuple_to_list(Record)))}
                    end
            end
    catch
        error:undef ->
            Record
    end;
pr(Record, _) ->
    Record.