report browser and error_logger

Martin Bjorklund <>
Wed Jan 9 21:42:31 CET 2002


Hal Snyder <> wrote:
> I think I am missing something obvious. The error_logger looks like a
> very useful thing for tracking events in OTP and in one's own
> applications. 
> 
> I think the way to use it is thus:
> 
>   - add the sasl app to your .rel file
>   - tune error file settings by setting sasl_error_logger,
>     error_logger_mf_dir, and so forth in your .config file
>   - write messages with error_logger:error_msg/1 and
>     error_logger:info_msg/1
>   - view results with rb: commands
> 
> 1. Is the above typical in real OTP apps?

Since I implemented this in OTP, I've done some rethinking - why
bother to store the logs in binary format anyway?  In most cases it's
better to store the error log in plain text, but using a wrap log so
the log won't grow.  This is what we do in our systems.

I've attached disk_log_h, a module which should have been implemented
years ago.  It's a gen_event handler for disk_log.  This handler can
be used e.g. for an error log, or whatever.  This module could very
well be part of OTP (it's intended to be).

disk_log_h needs a small patch to disk_log.erl, also attached.

Finally, I've included some code that uses this to format error_logger
as plain text.


/martin

-------------- next part --------------
%%%----------------------------------------------------------------------
%%% File    : disk_log_h.erl
%%% Author  : Martin Bjorklund <>
%%% Purpose : gen_event handler for disk_log.  The process which owns
%%%           the log is the gen_event process, thus no extra process
%%%           communcation overhead is involved.
%%%           The alternative is to let the gen_event process send the
%%%           message to a disk_log process, which then sends it to
%%%           disk.  In this case, the disk_log process is avoided.
%%%
%%%           This module is intended to replace log_mf_h.erl.
%%% Created :  1 Dec 2000 by Martin Bjorklund <>
%%%----------------------------------------------------------------------
-module(disk_log_h).
-author('').

-behaviour(gen_event).

%% External exports
-export([init/2, info/2]).

%% gen_event callbacks
-export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2]).

-record(state, {log, cnt, func}).

-include_lib("kernel/src/disk_log.hrl").

%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------

%%-----------------------------------------------------------------
%% This module is intended to be used as a gen_event handler. Instead
%% of duplicating the functions to gen_event (add_handler etc), it
%% described here hoe to use these function with this module.
%%
%% The init function expects a list [Func, Opts], where:
%%        Func = fun(Event) -> false | binary() | [binary()]
%%        Opts = <as disk_log:open>
%% To add a hander to a gen_event process, call e.g.:
%%   gen_event:add_handler(EventMgr, disk_log_h, [Func, Opts])
%%
%% No terminate arguments are needed.
%%
%% Here's a minimal but working example:
%%
%%   tr_event(Event) ->
%%       list_to_binary(io_lib:format("tr_event: ~p\n", [Event])).
%%
%%   start() ->
%%     Args = disk_log_h:init({?MODULE, tr_event}, [{name, tst},
%%                                                  {format, external},
%%                                                  {file, "/tmp/tst.log"}]),
%%     gen_event:add_handler(error_logger, {disk_log_h, tst}, Args).
%%
%%   stop ->
%%     gen_event:delete_handler(error, logger, {disk_log_h, tst}).
%%
%%-----------------------------------------------------------------
init(Func, DiskLogOpts) ->
    [Func, DiskLogOpts].


info(EventMgr, Handler) ->
    gen_event:call(EventMgr, Handler, info).
    

%%%----------------------------------------------------------------------
%%% Callback functions from gen_event
%%%----------------------------------------------------------------------

%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, S}          |
%%          Other
%%----------------------------------------------------------------------
init([Func, Opts]) ->
    case disk_log:ll_open(Opts) of
	{ok, _, Log, Cnt} ->
	    {ok, #state{log = Log, cnt = Cnt, func = Func}};
	Error ->
	    Error
    end.

%%----------------------------------------------------------------------
%% Func: handle_event/2
%% Returns: {ok, S}                                |
%%          {swap_handler, Args1, S1, Mod2, Args2} |
%%          remove_handler                              
%%----------------------------------------------------------------------
handle_event(Event, S) ->
    case (S#state.func)(Event) of
	false ->
	    {ok, S};
	Bin ->
	    case disk_log:do_log(S#state.log, Bin) of
		{N, L1} when integer(N) ->
		    {ok, S#state{cnt = S#state.cnt+N, log = L1}};
		{error, {error, {full, _Name}}, L1, 0} ->
		    {ok, S#state{log = L1}};
		{error, Error, L1, N} ->
		    Error;
		Error ->
		    Error
	    end
    end.

%%----------------------------------------------------------------------
%% Func: handle_call/2
%% Returns: {ok, Reply, S}                                |
%%          {swap_handler, Reply, Args1, S1, Mod2, Args2} |
%%          {remove_handler, Reply}                            
%%----------------------------------------------------------------------
handle_call(info, S) ->
    Reply = disk_log:do_info(S#state.log, S#state.cnt),
    {ok, Reply, S}.

%%----------------------------------------------------------------------
%% Func: handle_info/2
%% Returns: {ok, S}                                |
%%          {swap_handler, Args1, S1, Mod2, Args2} |
%%          remove_handler                              
%%----------------------------------------------------------------------
handle_info({emulator, GL, Chars}, S) ->
    %% this is very unfortunate...
    handle_event({emulator, GL, Chars}, S);
handle_info(Info, S) ->
    {ok, S}.

%%----------------------------------------------------------------------
%% Func: terminate/2
%% Purpose: Shutdown the server
%% Returns: any
%%----------------------------------------------------------------------
terminate(Arg, S) ->
    disk_log:ll_close(S#state.log).

%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
-------------- next part --------------
*** disk_log.erl	Mon Oct  8 10:37:59 2001
--- /tmp/disk_log.erl	Wed Jan  9 21:30:53 2002
***************
*** 37,42 ****
--- 37,45 ----
  %% To be used by wrap_log_reader only.
  -export([ichunk_end/2]).
  
+ %% To be used by disk_log_h only.
+ -export([ll_open/1, ll_close/1, do_log/2, do_info/2]).
+ 
  %% To be used for debugging only:
  -export([pid2name/1]).
  
***************
*** 64,69 ****
--- 67,81 ----
  open(A) ->
      disk_log_server:open(check_arg(A, #arg{options = A})).
  
+ ll_open(A) ->
+     case check_arg(A, #arg{options = A}) of
+ 	{ok, L} -> do_open(L);
+ 	Error -> Error
+     end.
+ 
+ ll_close(Log) ->
+     close_disk_log2(Log).
+ 
  log(Log, Term) -> 
      req(Log, {log, term_to_binary(Term)}).
  
-------------- next part --------------
%%%-------------------------------------------------------------------
%%% File    : logger.erl
%%% Created :  9 Jan 2002 by Martin Bjorklund <>
%%% Purpose : Simple module to show how disk_log_h can be used 
%%%           to format error_logger as plain text to a wrap log.
%%%-------------------------------------------------------------------
-module(logger).
-author('').
-author('').

-export([add_error_logger_mf/4, delete_error_logger_mf/0]).
-export([form_all/1, form_no_progress/1]).

%% Type = all | error
add_error_logger_mf(File, MaxB, MaxF, Type) ->
    Opts = [{name, logger},
	    {file, File},
	    {type, wrap},
	    {format, external},
	    {size, {MaxB, MaxF}}],
    gen_event:add_handler(error_logger,
			  {disk_log_h, logger},
			  disk_log_h:init(form_func(Type), Opts)).

delete_error_logger_mf() ->
    gen_event:delete_handler(error_logger, {disk_log_h, logger}, stop).


form_func(all) -> {logger, form_all};
form_func(_)   -> {logger, form_no_progress}.

form_all({Type, GL, Msg}) when node(GL) /= node() ->
    false;
form_all(Event) ->
    Str = 
	case Event of
	    {error_report, _GL, {Pid, Type, Report}} ->
		[mk_hdr("ERROR REPORT", Type, Pid),
		 io_lib:format("~p\n", [Report])];
	    {info_report, _GL, {Pid, Type, Report}} ->
		[mk_hdr("INFO REPORT", Type, Pid),
		 io_lib:format("~p\n", [Report])];
	    {error, _GL, {Pid, Format, Args}} ->
		[mk_hdr("ERROR", undefined, Pid),
		 io_lib:format(Format, Args)];
	    {info_msg, _GL, {Pid, Format, Args}} ->
		[mk_hdr("INFO MSG", undefined, Pid),
		 io_lib:format(Format, Args)];
	    {info, _GL, {Pid, Term, _Empty}} ->
		[mk_hdr("INFO", undefined, Pid),
		 io_lib:format("~p\n", [Term])];
	    {emulator, _GL, EStr} ->
		[mk_hdr("EMULATOR", undefined, undefined),
		 EStr];
	    _ ->
		[mk_hdr("UNKNOWN", undefined, undefined),
		 io_lib:format("~p\n", [Event])]
	end,
    list_to_binary([Str, "\n"]).

mk_hdr(HStr, Type, Who) ->
    ["== ", t2s(erlang:localtime()), " == ", HStr, " - ", 
     pstr(Type), " ", pstr(Who), "\n"].

pstr(undefined) -> "";
pstr(T) -> io_lib:format("~p", [T]).
   
t2s({{Year,Month,Day},{Hour,Minute,Second}}) ->
    io_lib:format("~w-~s-~w::~2..0w:~2..0w:~2..0w",
		  [Day,m2s(Month),Year,Hour,Minute,Second]).

m2s(1) -> "Jan";
m2s(2) -> "Feb";
m2s(3) -> "Mar";
m2s(4) -> "Apr";
m2s(5) -> "May";
m2s(6) -> "Jun";
m2s(7) -> "Jul";
m2s(8) -> "Aug";
m2s(9) -> "Sep";
m2s(10) -> "Oct";
m2s(11) -> "Nov";
m2s(12) -> "Dec".

form_no_progress({Type, GL, Msg}) when node(GL) /= node() ->
    false;
form_no_progress({info_report, _, {_, progress, [{application,_},
						 {started_at, _}]}} = Event) ->
    form_all(Event);
form_no_progress({info_report, _, {_, progress, _}}) ->
    false;
form_no_progress(Event) ->
    form_all(Event).


More information about the erlang-questions mailing list