ernie 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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