schleyfox-ernie 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,155 @@
1
+ -module(asset_pool).
2
+ -behaviour(gen_server).
3
+
4
+ %% api
5
+ -export([start_link/2, lease/1, return/2, reload_assets/1, idle_worker_count/1]).
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
+ -record(state, {assets = undefined,
12
+ handler = undefined,
13
+ token = undefined}).
14
+
15
+ %%====================================================================
16
+ %% API
17
+ %%====================================================================
18
+
19
+ start_link(Handler, Count) ->
20
+ gen_server:start_link(?MODULE, [Handler, Count], []).
21
+
22
+ lease(Pid) ->
23
+ gen_server:call(Pid, lease).
24
+
25
+ return(Pid, Asset) ->
26
+ gen_server:call(Pid, {return, Asset}).
27
+
28
+ reload_assets(Pid) ->
29
+ gen_server:call(Pid, reload_assets).
30
+
31
+ idle_worker_count(Pid) ->
32
+ gen_server:call(Pid, idle_worker_count).
33
+
34
+ %%====================================================================
35
+ %% gen_server callbacks
36
+ %%====================================================================
37
+
38
+ %%--------------------------------------------------------------------
39
+ %% Function: init(Args) -> {ok, State} |
40
+ %% {ok, State, Timeout} |
41
+ %% ignore |
42
+ %% {stop, Reason}
43
+ %% Description: Initiates the server
44
+ %%--------------------------------------------------------------------
45
+ init([Handler, Count]) ->
46
+ process_flag(trap_exit, true),
47
+ error_logger:info_msg("~p starting~n", [?MODULE]),
48
+ Token = make_ref(),
49
+ Assets = start_handlers(Count, Handler, Token),
50
+ logger:debug("Assets = ~p~n", [Assets]),
51
+ {ok, #state{assets = Assets, handler = Handler, token = Token}}.
52
+
53
+ %%--------------------------------------------------------------------
54
+ %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
55
+ %% {reply, Reply, State, Timeout} |
56
+ %% {noreply, State} |
57
+ %% {noreply, State, Timeout} |
58
+ %% {stop, Reason, Reply, State} |
59
+ %% {stop, Reason, State}
60
+ %% Description: Handling call messages
61
+ %%--------------------------------------------------------------------
62
+ handle_call(lease, _From, State) ->
63
+ logger:debug("Leasing...~n", []),
64
+ Token = State#state.token,
65
+ case queue:out(State#state.assets) of
66
+ {{value, Asset}, Assets2} ->
67
+ {asset, Port, AssetToken} = Asset,
68
+ case AssetToken =:= Token of
69
+ false ->
70
+ port_wrapper:close(Port),
71
+ Handler = State#state.handler,
72
+ NewAsset = create_asset(Handler, Token);
73
+ true ->
74
+ NewAsset = Asset
75
+ end,
76
+ {reply, {ok, NewAsset}, State#state{assets = Assets2}};
77
+ {empty, _Assets2} ->
78
+ {reply, empty, State}
79
+ end;
80
+ handle_call({return, Asset}, _From, State) ->
81
+ Token = State#state.token,
82
+ {asset, Port, AssetToken} = Asset,
83
+ case AssetToken =:= Token of
84
+ false ->
85
+ port_wrapper:close(Port),
86
+ Handler = State#state.handler,
87
+ NewAsset = create_asset(Handler, Token);
88
+ true ->
89
+ NewAsset = Asset
90
+ end,
91
+ Assets2 = queue:in(NewAsset, State#state.assets),
92
+ {reply, ok, State#state{assets = Assets2}};
93
+ handle_call(reload_assets, _From, State) ->
94
+ Token = make_ref(),
95
+ {reply, ok, State#state{token = Token}};
96
+ handle_call(idle_worker_count, _From, State) ->
97
+ WorkerCount = queue:len(State#state.assets),
98
+ {reply, WorkerCount, State};
99
+ handle_call(_Request, _From, State) ->
100
+ {reply, ok, State}.
101
+
102
+ %%--------------------------------------------------------------------
103
+ %% Function: handle_cast(Msg, State) -> {noreply, State} |
104
+ %% {noreply, State, Timeout} |
105
+ %% {stop, Reason, State}
106
+ %% Description: Handling cast messages
107
+ %%--------------------------------------------------------------------
108
+ handle_cast(_Msg, State) -> {noreply, State}.
109
+
110
+ handle_info({'EXIT', _Pid, normal}, State) ->
111
+ {noreply, State};
112
+ handle_info({'EXIT', Pid, Error}, State) ->
113
+ error_logger:error_msg("Port ~p closed with ~p, restarting port...~n", [Pid, Error]),
114
+ ValidAssets = queue:filter(fun(Item) -> {asset, A, _T} = Item, A =/= Pid end, State#state.assets),
115
+ Handler = State#state.handler,
116
+ Token = State#state.token,
117
+ NewAsset = create_asset(Handler, Token),
118
+ Assets = queue:in(NewAsset, ValidAssets),
119
+ {noreply, State#state{assets = Assets}};
120
+ handle_info(Msg, State) ->
121
+ error_logger:error_msg("Unexpected message: ~p~n", [Msg]),
122
+ {noreply, State}.
123
+
124
+ terminate(_Reason, _State) -> ok.
125
+ code_change(_OldVersion, State, _Extra) -> {ok, State}.
126
+
127
+ %%====================================================================
128
+ %% Internal
129
+ %%====================================================================
130
+
131
+ start_handlers(Count, Handler, Token) ->
132
+ start_handlers(queue:new(), Count, Handler, Token).
133
+
134
+ start_handlers(Assets, 0, _Handler, _Token) ->
135
+ Assets;
136
+ start_handlers(Assets, Count, Handler, Token) ->
137
+ Asset = create_asset(Handler, Token),
138
+ Assets2 = queue:in(Asset, Assets),
139
+ start_handlers(Assets2, Count - 1, Handler, Token).
140
+
141
+ create_asset(Handler, Token) ->
142
+ Len = length(Handler),
143
+ case Len > 150 of
144
+ true -> Cmd = Handler;
145
+ false -> Cmd = lists:flatten(Handler ++ " --procline " ++ pad(150 - Len - 12))
146
+ end,
147
+ io:format("~p~n", [Cmd]),
148
+ {asset, port_wrapper:wrap_link(Cmd), Token}.
149
+
150
+ pad(Size) ->
151
+ pad(Size, []).
152
+ pad(0, Acc) ->
153
+ Acc;
154
+ pad(Size, Acc) ->
155
+ pad(Size - 1, ["x" | Acc]).
@@ -0,0 +1,13 @@
1
+ -module(asset_pool_sup).
2
+ -behaviour(supervisor).
3
+ -export([start_link/2, init/1]).
4
+
5
+ start_link(Handler, Number) ->
6
+ supervisor:start_link(?MODULE, [Handler, Number]).
7
+
8
+ init([Handler, Number]) ->
9
+ io:format("Using handler ~p~n", [Handler]),
10
+ io:format("Using ~p handler instances~n", [Number]),
11
+ {ok, {{one_for_one, 1, 60},
12
+ [{asset_pool, {asset_pool, start_link, [Handler, Number]},
13
+ permanent, brutal_kill, worker, [asset_pool]}]}}.
data/elib/bert.erl ADDED
@@ -0,0 +1,69 @@
1
+ %%% See http://github.com/mojombo/bert.erl for documentation.
2
+ %%% MIT License - Copyright (c) 2009 Tom Preston-Werner <tom@mojombo.com>
3
+
4
+ -module(bert).
5
+ -version('1.1.0').
6
+ -author("Tom Preston-Werner").
7
+
8
+ -export([encode/1, decode/1]).
9
+
10
+ -ifdef(TEST).
11
+ -include("test/bert_test.erl").
12
+ -endif.
13
+
14
+ %%---------------------------------------------------------------------------
15
+ %% Public API
16
+
17
+ -spec encode(term()) -> binary().
18
+
19
+ encode(Term) ->
20
+ term_to_binary(encode_term(Term)).
21
+
22
+ -spec decode(binary()) -> term().
23
+
24
+ decode(Bin) ->
25
+ decode_term(binary_to_term(Bin)).
26
+
27
+ %%---------------------------------------------------------------------------
28
+ %% Encode
29
+
30
+ -spec encode_term(term()) -> term().
31
+
32
+ encode_term(Term) ->
33
+ case Term of
34
+ [] -> {bert, nil};
35
+ true -> {bert, true};
36
+ false -> {bert, false};
37
+ Dict when is_record(Term, dict, 8) ->
38
+ {bert, dict, dict:to_list(Dict)};
39
+ List when is_list(Term) ->
40
+ lists:map((fun encode_term/1), List);
41
+ Tuple when is_tuple(Term) ->
42
+ TList = tuple_to_list(Tuple),
43
+ TList2 = lists:map((fun encode_term/1), TList),
44
+ list_to_tuple(TList2);
45
+ _Else -> Term
46
+ end.
47
+
48
+ %%---------------------------------------------------------------------------
49
+ %% Decode
50
+
51
+ -spec decode_term(term()) -> term().
52
+
53
+ decode_term(Term) ->
54
+ case Term of
55
+ {bert, nil} -> [];
56
+ {bert, true} -> true;
57
+ {bert, false} -> false;
58
+ {bert, dict, Dict} ->
59
+ dict:from_list(Dict);
60
+ {bert, Other} ->
61
+ {bert, Other};
62
+ List when is_list(Term) ->
63
+ lists:map((fun decode_term/1), List);
64
+ Tuple when is_tuple(Term) ->
65
+ TList = tuple_to_list(Tuple),
66
+ TList2 = lists:map((fun decode_term/1), TList),
67
+ list_to_tuple(TList2);
68
+ _Else -> Term
69
+ end.
data/elib/ernie.hrl ADDED
@@ -0,0 +1,18 @@
1
+ -record(state, {lsock = undefined, % the listen socket
2
+ hq = queue:new(), % high priority queue
3
+ lq = queue:new(), % low priority queue
4
+ count = 0, % total request count
5
+ map = undefined}). % module map. tuples of {Mod, Id}
6
+
7
+ -record(request, {sock = undefined, % connection socket
8
+ log = undefined, % log information
9
+ infos = [], % list of info binaries
10
+ action = undefined, % action binary
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]}]}}.
@@ -0,0 +1,60 @@
1
+ -module(ernie_admin).
2
+ -export([process/4]).
3
+ -include_lib("ernie.hrl").
4
+
5
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6
+ % Process entry point
7
+
8
+ process(Sock, reload_handlers, _Args, State) ->
9
+ spawn(fun() -> process_reload_assets(Sock, State) end),
10
+ State;
11
+ process(Sock, stats, _Args, State) ->
12
+ spawn(fun() -> process_stats(Sock, State) end),
13
+ State;
14
+ process(Sock, _Fun, _Args, State) ->
15
+ gen_tcp:send(Sock, term_to_binary({reply, <<"Admin function not supported.">>})),
16
+ ok = gen_tcp:close(Sock),
17
+ State.
18
+
19
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
20
+ % Reload handlers
21
+
22
+ process_reload_assets(Sock, State) ->
23
+ lists:map((fun reload/1), State#state.map),
24
+ gen_tcp:send(Sock, term_to_binary({reply, <<"Handlers reloaded.">>})),
25
+ ok = gen_tcp:close(Sock).
26
+
27
+ reload({_Mod, native}) ->
28
+ ok;
29
+ reload({_Mod, Pid}) ->
30
+ asset_pool:reload_assets(Pid).
31
+
32
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
33
+ % Stats
34
+
35
+ process_stats(Sock, State) ->
36
+ CountString = stat(count, State),
37
+ IdleWorkersString = stat(idle, State),
38
+ QueueLengthString = stat(queue, State),
39
+ StatString = list_to_binary([CountString, IdleWorkersString, QueueLengthString]),
40
+ Data = term_to_binary({reply, StatString}),
41
+ gen_tcp:send(Sock, Data),
42
+ ok = gen_tcp:close(Sock).
43
+
44
+ stat(count, State) ->
45
+ Count = State#state.count,
46
+ list_to_binary([<<"connections.total=">>, integer_to_list(Count), <<"\n">>]);
47
+ stat(idle, State) ->
48
+ IdleMap = lists:map((fun idle/1), State#state.map),
49
+ list_to_binary(IdleMap);
50
+ stat(queue, State) ->
51
+ HighQueueLength = queue:len(State#state.hq),
52
+ LowQueueLength = queue:len(State#state.lq),
53
+ list_to_binary([<<"queue.high=">>, integer_to_list(HighQueueLength), <<"\n">>,
54
+ <<"queue.low=">>, integer_to_list(LowQueueLength), <<"\n">>]).
55
+
56
+ idle({Mod, native}) ->
57
+ list_to_binary([<<"workers.idle.">>, atom_to_list(Mod), <<"=native\n">>]);
58
+ idle({Mod, Pid}) ->
59
+ IdleCount = integer_to_list(asset_pool:idle_worker_count(Pid)),
60
+ list_to_binary([<<"workers.idle.">>, atom_to_list(Mod), <<"=">>, IdleCount, <<"\n">>]).
@@ -0,0 +1,30 @@
1
+ -module(ernie_config).
2
+ -export([load/1]).
3
+
4
+ load(ConfigFile) ->
5
+ {ok, Configs} = file:consult(ConfigFile),
6
+ Configs2 = lists:map((fun load_single/1), Configs),
7
+ {ok, Configs2}.
8
+
9
+ load_single(Config) ->
10
+ case proplists:get_value(type, Config) of
11
+ native ->
12
+ verify(native, Config),
13
+ CodePaths = proplists:get_value(codepaths, Config),
14
+ lists:map((fun code:add_patha/1), CodePaths),
15
+ Mod = proplists:get_value(module, Config),
16
+ code:load_file(Mod),
17
+ [{id, native} | Config];
18
+ external ->
19
+ verify(external, Config),
20
+ Handler = proplists:get_value(command, Config),
21
+ Number = proplists:get_value(count, Config),
22
+ {ok, SupPid} = asset_pool_sup:start_link(Handler, Number),
23
+ [{_Id, ChildPid, _Type, _Modules}] = supervisor:which_children(SupPid),
24
+ [{id, ChildPid} | Config]
25
+ end.
26
+
27
+ verify(native, _Config) ->
28
+ ok;
29
+ verify(external, _Config) ->
30
+ ok.
@@ -0,0 +1,26 @@
1
+ -module(ernie_native).
2
+ -export([process/2]).
3
+ -include_lib("ernie.hrl").
4
+
5
+ process(ActionTerm, Request) ->
6
+ {_Type, Mod, Fun, Args} = ActionTerm,
7
+ Sock = Request#request.sock,
8
+ logger:debug("Calling ~p:~p(~p)~n", [Mod, Fun, Args]),
9
+ try apply(Mod, Fun, Args) of
10
+ Result ->
11
+ logger:debug("Result was ~p~n", [Result]),
12
+ Data = bert:encode({reply, Result}),
13
+ gen_tcp:send(Sock, Data)
14
+ catch
15
+ error:Error ->
16
+ BError = list_to_binary(io_lib:format("~p", [Error])),
17
+ Trace = erlang:get_stacktrace(),
18
+ BTrace = lists:map(fun(X) -> list_to_binary(io_lib:format("~p", [X])) end, Trace),
19
+ Data = term_to_binary({error, [user, 0, <<"RuntimeError">>, BError, BTrace]}),
20
+ gen_tcp:send(Sock, Data)
21
+ end,
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).