ernie 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,7 @@
1
+ = 2.1.0 / 2010-02-20
2
+ * Major Additions
3
+ * Add access logging
4
+
1
5
  = 2.0.0 / 2010-02-16
2
6
  * Major Changes
3
7
  * Use configuration file for defining handlers
data/README.md CHANGED
@@ -40,13 +40,13 @@ versioning.
40
40
  Installation
41
41
  ------------
42
42
 
43
- Step 1: Install Erlang.
43
+ Step 1: Install Erlang (R13B or higher).
44
44
 
45
45
  http://www.erlang.org/download.html
46
46
 
47
47
  Step 2: Install Ernie:
48
48
 
49
- $ gem install ernie
49
+ $ [sudo] gem install ernie
50
50
 
51
51
 
52
52
  Running
@@ -56,6 +56,7 @@ Running
56
56
  -c, --config CONFIG Config file.
57
57
  -p, --port PORT Port.
58
58
  -l, --log-level Log level (0-4).
59
+ -a, --access-log LOGFILE Access log file
59
60
  -d, --detached Run as a daemon.
60
61
  -P, --pidfile PIDFILE Location to write pid file.
61
62
 
@@ -149,6 +150,32 @@ identifies a native module 'nat' that resides in the nat.beam file under the
149
150
  {count, 2}].
150
151
 
151
152
 
153
+ Access Log
154
+ ----------
155
+
156
+ If you have requested that an access log be written (using the -a or
157
+ --access-log option) then all requests will be logged to that file. Each
158
+ request is printed on a single line. The elements of the log line are as
159
+ follows (with comments on the right side):
160
+
161
+ ACC type of message [ ACC | ERR ]
162
+ [2010-02-20T11:42:25.259750] time the connection was accepted
163
+ 0.000053 seconds from connection to processing start
164
+ 0.000237 seconds from processing start to finish
165
+ - delimiter
166
+ 0 size of high queue at connect time
167
+ 0 size of low queue at connect time
168
+ nat type of handler [ nat | ext ]
169
+ high priority [ high | low ]
170
+ - delimiter
171
+ {call,nat,add,[1,2]} message
172
+
173
+
174
+ Log lines are written when the request completes so they may appear out of
175
+ order with respect to connection time. To facilitate log rotation, Ernie will
176
+ create a new access log file if the current log file is moved or deleted.
177
+
178
+
152
179
  Native (Erlang) Handler
153
180
  -----------------------
154
181
 
@@ -181,6 +208,7 @@ BERT-RPC clients.
181
208
 
182
209
  Using a Ruby module and Ernie.expose:
183
210
 
211
+ require 'rubygems'
184
212
  require 'ernie'
185
213
 
186
214
  module Ext
data/VERSION.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
2
  :major: 2
3
- :minor: 0
3
+ :minor: 1
4
4
  :patch: 0
5
5
  :build:
data/bin/ernie CHANGED
@@ -54,6 +54,10 @@ OptionParser.new do |opts|
54
54
  options[:log_level] = x
55
55
  end
56
56
 
57
+ opts.on("-a LOGFILE", "--access-log LOGFILE", "Access log file") do |x|
58
+ options[:access_log] = x
59
+ end
60
+
57
61
  opts.on("-d", "--detached", "Run as a daemon") do
58
62
  options[:detached] = true
59
63
  end
@@ -87,6 +91,7 @@ else
87
91
  log_level = options[:log_level] || 2
88
92
  pidfile = options[:pidfile] ? "-ernie_server_app pidfile \"'#{options[:pidfile]}'\"" : ''
89
93
  detached = options[:detached] ? '-detached' : ''
94
+ access_log = options[:access_log] ? "-ernie_server_app access_log '\"#{options[:access_log]}\"'" : ''
90
95
 
91
96
  cmd = %Q{erl -boot start_sasl \
92
97
  #{detached} \
@@ -95,6 +100,7 @@ else
95
100
  -smp enable \
96
101
  #{code_paths}
97
102
  #{pidfile} \
103
+ #{access_log} \
98
104
  -ernie_server_app port #{port} \
99
105
  -ernie_server_app config '"#{config}"' \
100
106
  -ernie_server_app log_level #{log_level} \
data/elib/ernie.hrl CHANGED
@@ -5,6 +5,14 @@
5
5
  map = undefined}). % module map. tuples of {Mod, Id}
6
6
 
7
7
  -record(request, {sock = undefined, % connection socket
8
+ log = undefined, % log information
8
9
  infos = [], % list of info binaries
9
10
  action = undefined, % action binary
10
- priority = high}). % priority [ high | low ]
11
+ priority = high}). % priority [ high | low ]
12
+
13
+ -record(log, {taccept = erlang:now(), % time that connection was accepted
14
+ tprocess = erlang:now(), % time that processing started
15
+ tdone = erlang:now(), % time that processing and response is done
16
+ hq = 0, % size of high queue at acceptance
17
+ lq = 0, % size of low queue at acceptance
18
+ type = unk}). % type [ unk | nat | ext ]
@@ -0,0 +1,170 @@
1
+ -module(ernie_access_logger).
2
+ -behaviour(gen_server).
3
+
4
+ %% api
5
+ -export([start_link/1, start/1, acc/1, err/3, reopen/0]).
6
+
7
+ %% gen_server callbacks
8
+ -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
9
+ terminate/2, code_change/3]).
10
+
11
+ -include_lib("ernie.hrl").
12
+
13
+ -record(lstate, {access_file_name = undefined,
14
+ access_file = undefined}).
15
+
16
+ %%====================================================================
17
+ %% API
18
+ %%====================================================================
19
+
20
+ start_link(Args) ->
21
+ gen_server:start_link({global, ?MODULE}, ?MODULE, Args, []).
22
+
23
+ start(Args) ->
24
+ gen_server:start({global, ?MODULE}, ?MODULE, Args, []).
25
+
26
+ acc(Request) ->
27
+ gen_server:cast({global, ?MODULE}, {acc, Request}).
28
+
29
+ err(Request, Msg, Args) ->
30
+ gen_server:cast({global, ?MODULE}, {err, Request, Msg, Args}).
31
+
32
+ reopen() ->
33
+ gen_server:cast({global, ?MODULE}, reopen).
34
+
35
+ %%====================================================================
36
+ %% gen_server callbacks
37
+ %%====================================================================
38
+
39
+ %%--------------------------------------------------------------------
40
+ %% Function: init(Args) -> {ok, State} |
41
+ %% {ok, State, Timeout} |
42
+ %% ignore |
43
+ %% {stop, Reason}
44
+ %% Description: Initiates the server
45
+ %%--------------------------------------------------------------------
46
+ init([undefined]) ->
47
+ error_logger:info_msg("~p starting~n", [?MODULE]),
48
+ {ok, #lstate{}};
49
+ init([AccessFileName]) ->
50
+ error_logger:info_msg("~p starting~n", [?MODULE]),
51
+ case file:open(AccessFileName, [append]) of
52
+ {ok, AccessFile} ->
53
+ {ok, _T} = timer:apply_interval(10000, ernie_access_logger, reopen, []),
54
+ {ok, #lstate{access_file_name = AccessFileName,
55
+ access_file = AccessFile}};
56
+ {error, Error} ->
57
+ error_logger:error_msg("Error opening access log ~p: ~p.~n", [AccessFileName, Error]),
58
+ {ok, #lstate{}}
59
+ end.
60
+
61
+ %%--------------------------------------------------------------------
62
+ %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
63
+ %% {reply, Reply, State, Timeout} |
64
+ %% {noreply, State} |
65
+ %% {noreply, State, Timeout} |
66
+ %% {stop, Reason, Reply, State} |
67
+ %% {stop, Reason, State}
68
+ %% Description: Handling call messages
69
+ %%--------------------------------------------------------------------
70
+ handle_call(_Request, _From, State) ->
71
+ {reply, ok, State}.
72
+
73
+ %%--------------------------------------------------------------------
74
+ %% Function: handle_cast(Msg, State) -> {noreply, State} |
75
+ %% {noreply, State, Timeout} |
76
+ %% {stop, Reason, State}
77
+ %% Description: Handling cast messages
78
+ %%--------------------------------------------------------------------
79
+ handle_cast({acc, Request}, State) ->
80
+ case State#lstate.access_file_name of
81
+ undefined -> ok;
82
+ _AccessFilename -> acc(Request, State)
83
+ end,
84
+ {noreply, State};
85
+ handle_cast({err, Request, Msg, Args}, State) ->
86
+ case State#lstate.access_file_name of
87
+ undefined -> ok;
88
+ _AccessFilename -> err(Request, Msg, Args, State)
89
+ end,
90
+ {noreply, State};
91
+ handle_cast(reopen, State) ->
92
+ case State#lstate.access_file_name of
93
+ undefined ->
94
+ {noreply, State};
95
+ AccessFileName ->
96
+ case file:read_file_info(AccessFileName) of
97
+ {ok, _FileInfo} ->
98
+ {noreply, State};
99
+ {error, enoent} ->
100
+ ok = file:close(State#lstate.access_file),
101
+ {ok, AccessFile} = file:open(AccessFileName, [append]),
102
+ {noreply, State#lstate{access_file = AccessFile}};
103
+ _OtherError ->
104
+ {noreply, #lstate{}}
105
+ end
106
+ end;
107
+ handle_cast(_Msg, State) ->
108
+ {noreply, State}.
109
+
110
+ handle_info(Msg, State) ->
111
+ error_logger:error_msg("Unexpected message: ~p~n", [Msg]),
112
+ {noreply, State}.
113
+
114
+ terminate(_Reason, _State) -> ok.
115
+ code_change(_OldVersion, State, _Extra) -> {ok, State}.
116
+
117
+ %%====================================================================
118
+ %% Internal
119
+ %%====================================================================
120
+
121
+ acc(Request, State) ->
122
+ StatString = stat_string(Request),
123
+ ActionString = action_string(Request),
124
+ Line = io_lib:fwrite("ACC ~s - ~s~n", [StatString, ActionString]),
125
+ file:write(State#lstate.access_file, Line).
126
+
127
+ err(Request, Msg, Args, State) ->
128
+ StatString = stat_string(Request),
129
+ ActionString = action_string(Request),
130
+ ErrString = io_lib:fwrite(Msg, Args),
131
+ Line = io_lib:fwrite("ERR ~s - ~s : ~s~n", [StatString, ErrString, ActionString]),
132
+ file:write(State#lstate.access_file, Line).
133
+
134
+ stat_string(Request) ->
135
+ Log = Request#request.log,
136
+ TAccept = time_tuple_to_iso_8601_date(Log#log.taccept),
137
+ D1 = time_difference_in_seconds(Log#log.taccept, Log#log.tprocess),
138
+ D2 = time_difference_in_seconds(Log#log.tprocess, Log#log.tdone),
139
+ Type = Log#log.type,
140
+ HQ = Log#log.hq,
141
+ LQ = Log#log.lq,
142
+ Prio = Request#request.priority,
143
+ Args = [TAccept, D1, D2, HQ, LQ, Type, Prio],
144
+ io_lib:fwrite("[~s] ~f ~f - ~B ~B ~3s ~p", Args).
145
+
146
+ action_string(Request) ->
147
+ TermAction = binary_to_term(Request#request.action),
148
+ RawAction = lists:flatten(io_lib:fwrite("~1000000000.0.0p", [TermAction])),
149
+ case string:len(RawAction) > 150 of
150
+ true ->
151
+ Action = re:replace(RawAction, "\n", "", [global, {return, list}]),
152
+ [string:sub_string(Action, 1, 150), "..."];
153
+ false ->
154
+ RawAction
155
+ end.
156
+
157
+ time_tuple_to_iso_8601_date(TimeTuple) ->
158
+ {{YY, MM, DD}, {H, M, S}} = calendar:now_to_local_time(TimeTuple),
159
+ {_MegaSecs, _Secs, MicroSecs} = TimeTuple,
160
+ Args = [YY, MM, DD, H, M, S, MicroSecs],
161
+ io_lib:fwrite("~4B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0B.~-6.10.0B", Args).
162
+
163
+ time_difference_in_seconds(T1, T2) ->
164
+ {_, _, MS1} = T1,
165
+ {_, _, MS2} = T2,
166
+ S1 = calendar:datetime_to_gregorian_seconds(calendar:now_to_local_time(T1)),
167
+ S2 = calendar:datetime_to_gregorian_seconds(calendar:now_to_local_time(T2)),
168
+ F1 = S1 + (MS1 / 1000000),
169
+ F2 = S2 + (MS2 / 1000000),
170
+ F2 - F1.
@@ -0,0 +1,15 @@
1
+ -module(ernie_access_logger_sup).
2
+ -behaviour(supervisor).
3
+ -export([start_link/1, init/1]).
4
+
5
+ start_link(AccessLog) ->
6
+ supervisor:start_link({local, ?MODULE}, ?MODULE, [AccessLog]).
7
+
8
+ init([AccessLog]) ->
9
+ case AccessLog of
10
+ undefined -> io:format("No access log~n", []);
11
+ Any -> io:format("Using access log ~p~n", [Any])
12
+ end,
13
+ {ok, {{one_for_one, 1, 60},
14
+ [{ernie_access_logger, {ernie_access_logger, start_link, [[AccessLog]]},
15
+ permanent, brutal_kill, worker, [ernie_access_logger]}]}}.
@@ -19,4 +19,8 @@ process(ActionTerm, Request) ->
19
19
  Data = term_to_binary({error, [user, 0, <<"RuntimeError">>, BError, BTrace]}),
20
20
  gen_tcp:send(Sock, Data)
21
21
  end,
22
- ok = gen_tcp:close(Sock).
22
+ ok = gen_tcp:close(Sock),
23
+ Log = Request#request.log,
24
+ Log2 = Log#log{tdone = erlang:now()},
25
+ Request2 = Request#request{log = Log2},
26
+ ernie_access_logger:acc(Request2).
@@ -64,7 +64,10 @@ handle_call(_Request, _From, State) ->
64
64
  %% Description: Handling cast messages
65
65
  %%--------------------------------------------------------------------
66
66
  handle_cast({process, Sock}, State) ->
67
- Request = #request{sock = Sock},
67
+ Log = #log{hq = queue:len(State#state.hq),
68
+ lq = queue:len(State#state.lq),
69
+ taccept = erlang:now()},
70
+ Request = #request{sock = Sock, log = Log},
68
71
  State2 = receive_term(Request, State),
69
72
  {noreply, State2};
70
73
  handle_cast(kick, State) ->
@@ -244,7 +247,10 @@ process_native_request(ActionTerm, Request, Priority, Q2, State) ->
244
247
  Count = State#state.count,
245
248
  State2 = State#state{count = Count + 1},
246
249
  logger:debug("Count = ~p~n", [Count + 1]),
247
- spawn(fun() -> ernie_native:process(ActionTerm, Request) end),
250
+ Log = Request#request.log,
251
+ Log2 = Log#log{type = native, tprocess = erlang:now()},
252
+ Request2 = Request#request{log = Log2},
253
+ spawn(fun() -> ernie_native:process(ActionTerm, Request2) end),
248
254
  finish(Priority, Q2, State2).
249
255
 
250
256
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -257,7 +263,10 @@ process_external_request(Pid, Request, Priority, Q2, State) ->
257
263
  case asset_pool:lease(Pid) of
258
264
  {ok, Asset} ->
259
265
  logger:debug("Leased asset for pool ~p~n", [Pid]),
260
- spawn(fun() -> process_now(Pid, Request, Asset) end),
266
+ Log = Request#request.log,
267
+ Log2 = Log#log{type = external, tprocess = erlang:now()},
268
+ Request2 = Request#request{log = Log2},
269
+ spawn(fun() -> process_now(Pid, Request2, Asset) end),
261
270
  finish(Priority, Q2, State2);
262
271
  empty ->
263
272
  State
@@ -265,13 +274,23 @@ process_external_request(Pid, Request, Priority, Q2, State) ->
265
274
 
266
275
  process_now(Pid, Request, Asset) ->
267
276
  try unsafe_process_now(Request, Asset) of
268
- _AnyResponse -> ok
277
+ _AnyResponse ->
278
+ Log = Request#request.log,
279
+ Log2 = Log#log{tdone = erlang:now()},
280
+ Request2 = Request#request{log = Log2},
281
+ ernie_access_logger:acc(Request2)
269
282
  catch
270
- _AnyClass:_AnyError -> ok
283
+ AnyClass:AnyError ->
284
+ Log = Request#request.log,
285
+ Log2 = Log#log{tdone = erlang:now()},
286
+ Request2 = Request#request{log = Log2},
287
+ ernie_access_logger:err(Request2, "External process error ~w: ~w", [AnyClass, AnyError])
271
288
  after
272
289
  asset_pool:return(Pid, Asset),
273
290
  ernie_server:kick(),
274
- gen_tcp:close(Request#request.sock)
291
+ logger:debug("Returned asset ~p~n", [Asset]),
292
+ gen_tcp:close(Request#request.sock),
293
+ logger:debug("Closed socket ~p~n", [Request#request.sock])
275
294
  end.
276
295
 
277
296
  unsafe_process_now(Request, Asset) ->
@@ -284,8 +303,7 @@ unsafe_process_now(Request, Asset) ->
284
303
  {asset, Port, Token} = Asset,
285
304
  logger:debug("Asset: ~p ~p~n", [Port, Token]),
286
305
  {ok, Data} = port_wrapper:rpc(Port, BinaryTerm),
287
- gen_tcp:send(Sock, Data),
288
- ok = gen_tcp:close(Sock);
306
+ ok = gen_tcp:send(Sock, Data);
289
307
  {cast, Mod, Fun, Args} ->
290
308
  logger:debug("Casting ~p:~p(~p)~n", [Mod, Fun, Args]),
291
309
  {asset, Port, Token} = Asset,
@@ -7,6 +7,12 @@ boot() ->
7
7
  application:start(ernie_server_app).
8
8
 
9
9
  start(_Type, _Args) ->
10
+ case application:get_env(ernie_server_app, access_log) of
11
+ {ok, AccessFile} ->
12
+ ernie_access_logger_sup:start_link(AccessFile);
13
+ undefined ->
14
+ ernie_access_logger_sup:start_link(undefined)
15
+ end,
10
16
  logger_sup:start_link(),
11
17
  ernie_server_sup:start_link().
12
18
 
data/ernie.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ernie}
8
- s.version = "2.0.0"
8
+ s.version = "2.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Tom Preston-Werner"]
12
- s.date = %q{2010-02-16}
12
+ s.date = %q{2010-02-24}
13
13
  s.default_executable = %q{ernie}
14
14
  s.email = %q{tom@mojombo.com}
15
15
  s.executables = ["ernie"]
@@ -33,6 +33,8 @@ Gem::Specification.new do |s|
33
33
  "elib/asset_pool_sup.erl",
34
34
  "elib/bert.erl",
35
35
  "elib/ernie.hrl",
36
+ "elib/ernie_access_logger.erl",
37
+ "elib/ernie_access_logger_sup.erl",
36
38
  "elib/ernie_admin.erl",
37
39
  "elib/ernie_config.erl",
38
40
  "elib/ernie_native.erl",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ernie
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Preston-Werner
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-02-16 00:00:00 -08:00
12
+ date: 2010-02-24 00:00:00 -08:00
13
13
  default_executable: ernie
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -57,6 +57,8 @@ files:
57
57
  - elib/asset_pool_sup.erl
58
58
  - elib/bert.erl
59
59
  - elib/ernie.hrl
60
+ - elib/ernie_access_logger.erl
61
+ - elib/ernie_access_logger_sup.erl
60
62
  - elib/ernie_admin.erl
61
63
  - elib/ernie_config.erl
62
64
  - elib/ernie_native.erl