1
+ % %% -------------------------------------------------------------------
2
+ % %% Author : Sungjin Park <[email protected] >
3
+ % %%
4
+ % %% Description : Fubar log manager.
5
+ % %%
6
+ % %% Created : Nov 30, 2012
7
+ % %% -------------------------------------------------------------------
8
+ -module (fubar_log ).
9
+ -
author (
" Sungjin Park <[email protected] >" ).
10
+ -behavior (gen_server ).
11
+
12
+ % %
13
+ % % Includes
14
+ % %
15
+ -ifdef (TEST ).
16
+ -include_lib (" eunit/include/eunit.hrl" ).
17
+ -endif .
18
+
19
+ -include (" fubar.hrl" ).
20
+ -include (" sasl_log.hrl" ).
21
+ -include (" props_to_record.hrl" ).
22
+
23
+ % %
24
+ % % Macros, records and types
25
+ % %
26
+ -record (? MODULE , {dir = " priv/log" :: string (),
27
+ max_bytes = 10485760 :: integer (),
28
+ max_files = 10 :: integer (),
29
+ classes = [] :: [{atom (), term (), null | standard_io | pid ()}],
30
+ interval = 500 :: timeout ()}).
31
+
32
+ % %
33
+ % % Exports
34
+ % %
35
+ -export ([start_link /0 , log /3 , trace /2 , dump /2 ,
36
+ open /1 , close /1 , show /1 , hide /1 , interval /1 , state /0 ]).
37
+ -export ([init /1 , handle_call /3 , handle_cast /2 , handle_info /2 , terminate /2 , code_change /3 ]).
38
+
39
+ % % @doc Start a log manager.
40
+ % % The log manager process manages disk_logs and polls them.
41
+ -spec start_link () -> {ok , pid ()} | {error , reason ()}.
42
+ start_link () ->
43
+ State = ? PROPS_TO_RECORD (fubar :settings (? MODULE ), ? MODULE ),
44
+ Path = filename :join (State #? MODULE .dir , io_lib :format (" ~s " , [node ()])),
45
+ ok = filelib :ensure_dir (Path ++ " /" ),
46
+ gen_server :start ({local , ? MODULE }, ? MODULE , State #? MODULE {dir = Path }, []).
47
+
48
+ % % @doc Leave a log.
49
+ % % The log is dropped unless the class is open in advance.
50
+ % % @sample fubar_log:log(debug, my_module, Term).
51
+ -spec log (atom (), term (), term ()) -> ok | {error , reason ()}.
52
+ log (Class , Tag , Term ) ->
53
+ Now = now (),
54
+ Calendar = calendar :now_to_universal_time (Now ),
55
+ Timestamp = httpd_util :rfc1123_date (Calendar ),
56
+ catch disk_log :log (Class , {{Class , Timestamp }, {Tag , self ()}, Term }).
57
+
58
+ % % @doc Leave a special trace type log.
59
+ % % @sample fubar_log:trace(my_module, Fubar).
60
+ -spec trace (term (), # fubar {}) -> ok .
61
+ trace (_ , # fubar {id = undefined }) ->
62
+ ok ;
63
+ trace (Tag , # fubar {id = Id , origin = {Origin , T1 }, from = {From , T2 }, via = {Via , T3 }, payload = Payload }) ->
64
+ Now = now (),
65
+ Calendar = calendar :now_to_universal_time (Now ),
66
+ Timestamp = httpd_util :rfc1123_date (Calendar ),
67
+ catch disk_log :log (trace , {{'TRACE' , Timestamp }, {Tag , self ()},
68
+ {fubar , Id },
69
+ {since , {Origin , timer :now_diff (Now , T1 )/ 1000 },
70
+ {From , timer :now_diff (Now , T2 )/ 1000 },
71
+ {Via , timer :now_diff (Now , T3 )/ 1000 }},
72
+ {payload , Payload }}).
73
+
74
+ % % @doc Dump a log class as a text file.
75
+ -spec dump (atom (), string ()) -> ok .
76
+ dump (Class , Path ) ->
77
+ case state () of
78
+ #? MODULE {dir = Dir } ->
79
+ case file :open (Path , [write ]) of
80
+ {ok , File } ->
81
+ LogFile = filename :join (Dir , io_lib :format (" ~s " , [Class ])),
82
+ disk_log :open ([{name , Class }, {file , LogFile }]),
83
+ consume_log (Class , start , File ),
84
+ disk_log :close (Class ),
85
+ file :close (File );
86
+ Error1 ->
87
+ Error1
88
+ end ;
89
+ Error ->
90
+ Error
91
+ end .
92
+
93
+ % % @doc Open a log class.
94
+ % % Opening a log class doesn't mean the logs in the class is shown in tty.
95
+ % % Need to call show/1 explicitly to do that.
96
+ -spec open (atom ()) -> ok .
97
+ open (Class ) ->
98
+ gen_server :call (? MODULE , {open , Class }).
99
+
100
+ % % @doc Close a log class.
101
+ % % Closing a log class mean that the logs in the class is no longer stored.
102
+ -spec close (atom ()) -> ok .
103
+ close (Class ) ->
104
+ gen_server :call (? MODULE , {close , Class }).
105
+
106
+ % % @doc Print logs in a class to tty.
107
+ -spec show (atom ()) -> ok .
108
+ show (Class ) ->
109
+ gen_server :call (? MODULE , {show , Class }).
110
+
111
+ % % @doc Hide logs in a class from tty.
112
+ -spec hide (atom ()) -> ok .
113
+ hide (Class ) ->
114
+ gen_server :call (? MODULE , {hide , Class }).
115
+
116
+ % % @doc Set tty refresh interval.
117
+ -spec interval (timeout ()) -> ok .
118
+ interval (T ) ->
119
+ gen_server :call (? MODULE , {interval , T }).
120
+
121
+ % % @doc Get the log manager state.
122
+ -spec state () -> #? MODULE {}.
123
+ state () ->
124
+ gen_server :call (? MODULE , state ).
125
+
126
+ % %
127
+ % % Callback Functions
128
+ % %
129
+ init (State = #? MODULE {dir = Dir , max_bytes = L , max_files = N , classes = Classes , interval = T }) ->
130
+ ? DEBUG ([init , State ]),
131
+ Init = fun (Class ) -> open (Class , Dir , L , N ) end ,
132
+ {ok , State #? MODULE {classes = lists :map (Init , Classes )}, T }.
133
+
134
+ handle_call ({open , Class }, _ , State = #? MODULE {dir = Dir , max_bytes = L , max_files = N , interval = T }) ->
135
+ open (Class , Dir , L , N ),
136
+ {reply , ok , State , T };
137
+ handle_call ({close , Class }, _ , State = #? MODULE {classes = Classes , interval = T }) ->
138
+ Result = disk_log :close (Class ),
139
+ NewClasses = case lists :keytake (Class , 1 , Classes ) of
140
+ {value , {Class , _ , _ }, Rest } -> Rest ;
141
+ false -> Classes
142
+ end ,
143
+ {reply , Result , State #? MODULE {classes = NewClasses }, T };
144
+ handle_call ({show , Class }, _ , State = #? MODULE {classes = Classes , interval = T }) ->
145
+ case lists :keytake (Class , 1 , Classes ) of
146
+ {value , {Class , Last , _ }, Rest } ->
147
+ Current = consume_log (Class , Last , null ),
148
+ {reply , ok , State #? MODULE {classes = [{Class , Current , standard_io } | Rest ]}, T };
149
+ false ->
150
+ Current = consume_log (Class , start , null ),
151
+ {reply , ok , State #? MODULE {classes = [{Class , Current , standard_io } | Classes ]}, T }
152
+ end ;
153
+ handle_call ({hide , Class }, _ , State = #? MODULE {classes = Classes , interval = T }) ->
154
+ case lists :keytake (Class , 1 , Classes ) of
155
+ {value , {Class , Current , _ }, Rest } ->
156
+ {reply , ok , State #? MODULE {classes = [{Class , Current , null } | Rest ]}, T };
157
+ false ->
158
+ {reply , ok , State , T }
159
+ end ;
160
+ handle_call ({interval , T }, _ , State = #? MODULE {interval = _ }) ->
161
+ {reply , ok , State #? MODULE {interval = T }, T };
162
+ handle_call (state , _ , State = #? MODULE {interval = T }) ->
163
+ {reply , State , State , T };
164
+ handle_call (Request , From , State ) ->
165
+ ? WARNING ([handle_call , Request , From , State , " dropping unknown" ]),
166
+ {reply , ok , State }.
167
+
168
+ handle_cast (Message , State ) ->
169
+ ? WARNING ([handle_cast , Message , State , " dropping unknown" ]),
170
+ {noreply , State }.
171
+
172
+ handle_info (timeout , State ) ->
173
+ F = fun ({Class , Last , Show }) ->
174
+ Current = consume_log (Class , Last , Show ),
175
+ {Class , Current , Show }
176
+ end ,
177
+ Classes = lists :map (F , State #? MODULE .classes ),
178
+ {noreply , State #? MODULE {classes = Classes }, State #? MODULE .interval };
179
+ handle_info (Info , State ) ->
180
+ ? WARNING ([handle_info , Info , State , " dropping unknown" ]),
181
+ {noreply , State }.
182
+
183
+ terminate (Reason , State ) ->
184
+ ? DEBUG ([terminate , Reason , State ]),
185
+ Close = fun ({Class , _ , _ }) ->
186
+ disk_log :close (Class )
187
+ end ,
188
+ lists :foreach (Close , State #? MODULE .classes ),
189
+ Reason .
190
+
191
+ code_change (OldVsn , State , Extra ) ->
192
+ ? WARNING ([code_change , OldVsn , State , Extra ]),
193
+ {ok , State }.
194
+
195
+ % %
196
+ % % Local Functions
197
+ % %
198
+ open (Class , Dir , L , N ) ->
199
+ File = filename :join (Dir , io_lib :format (" ~s " , [Class ])),
200
+ disk_log :open ([{name , Class }, {file , File }, {type , wrap }, {size , {L , N }}]),
201
+ Current = consume_log (Class , start , null ),
202
+ {Class , Current , standard_io }.
203
+
204
+ consume_log (Log , Last , Io ) ->
205
+ case disk_log :chunk (Log , Last ) of
206
+ {error , _ } ->
207
+ Last ;
208
+ eof ->
209
+ Last ;
210
+ {Current , Terms } ->
211
+ case Io of
212
+ null ->
213
+ start ;
214
+ _ ->
215
+ Print = fun (Term ) -> io :format (Io , " ~p~n " , [Term ]) end ,
216
+ lists :foreach (Print , Terms )
217
+ end ,
218
+ consume_log (Log , Current , Io )
219
+ end .
220
+
221
+ % %
222
+ % % Unit Tests
223
+ % %
224
+ -ifdef (TEST ).
225
+ -endif .
0 commit comments