%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2017-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -module(logger_disk_log_h). -include("logger.hrl"). -include("logger_internal.hrl"). -include("logger_h_common.hrl"). %%% API -export([filesync/1]). %% logger_h_common callbacks -export([init/2, check_config/4, reset_state/2, filesync/3, write/4, handle_info/3, terminate/3]). %% logger callbacks -export([log/2, adding_handler/1, removing_handler/1, changing_config/3, filter_config/1]). %%%=================================================================== %%% API %%%=================================================================== %%%----------------------------------------------------------------- %%% -spec filesync(Name) -> ok | {error,Reason} when Name :: atom(), Reason :: handler_busy | {badarg,term()}. filesync(Name) -> logger_h_common:filesync(?MODULE,Name). %%%=================================================================== %%% logger callbacks %%%=================================================================== %%%----------------------------------------------------------------- %%% Handler being added adding_handler(Config) -> logger_h_common:adding_handler(Config). %%%----------------------------------------------------------------- %%% Updating handler config changing_config(SetOrUpdate, OldConfig, NewConfig) -> logger_h_common:changing_config(SetOrUpdate, OldConfig, NewConfig). %%%----------------------------------------------------------------- %%% Handler being removed removing_handler(Config) -> logger_h_common:removing_handler(Config). %%%----------------------------------------------------------------- %%% Log a string or report -spec log(LogEvent, Config) -> ok when LogEvent :: logger:log_event(), Config :: logger:handler_config(). log(LogEvent, Config) -> logger_h_common:log(LogEvent, Config). %%%----------------------------------------------------------------- %%% Remove internal fields from configuration filter_config(Config) -> logger_h_common:filter_config(Config). %%%=================================================================== %%% logger_h_common callbacks %%%=================================================================== init(Name, #{file:=File,type:=Type,max_no_bytes:=MNB,max_no_files:=MNF}) -> case open_disk_log(Name, File, Type, MNB, MNF) of ok -> {ok,#{log_opts => #{file => File, type => Type, max_no_bytes => MNB, max_no_files => MNF}, prev_log_result => ok, prev_sync_result => ok, prev_disk_log_info => undefined}}; Error -> Error end. check_config(Name,set,undefined,HConfig0) -> HConfig=merge_default_logopts(Name,maps:merge(get_default_config(),HConfig0)), check_config(HConfig); check_config(_Name,SetOrUpdate,OldHConfig,NewHConfig0) -> WriteOnce = maps:with([type,file,max_no_files,max_no_bytes],OldHConfig), Default = case SetOrUpdate of set -> %% Do not reset write-once fields to defaults maps:merge(get_default_config(),WriteOnce); update -> OldHConfig end, NewHConfig = maps:merge(Default,NewHConfig0), %% Fail if write-once fields are changed case maps:with([type,file,max_no_files,max_no_bytes],NewHConfig) of WriteOnce -> check_config(NewHConfig); Other -> {Old,New} = logger_server:diff_maps(WriteOnce,Other), {error,{illegal_config_change,?MODULE,Old,New}} end. check_config(HConfig) -> case check_h_config(maps:to_list(HConfig)) of ok -> {ok,HConfig}; {error,{Key,Value}} -> {error,{invalid_config,?MODULE,#{Key=>Value}}} end. check_h_config([{file,File}|Config]) when is_list(File) -> check_h_config(Config); check_h_config([{max_no_files,undefined}|Config]) -> check_h_config(Config); check_h_config([{max_no_files,N}|Config]) when is_integer(N), N>0 -> check_h_config(Config); check_h_config([{max_no_bytes,infinity}|Config]) -> check_h_config(Config); check_h_config([{max_no_bytes,N}|Config]) when is_integer(N), N>0 -> check_h_config(Config); check_h_config([{type,Type}|Config]) when Type==wrap; Type==halt -> check_h_config(Config); check_h_config([Other | _]) -> {error,Other}; check_h_config([]) -> ok. get_default_config() -> #{}. merge_default_logopts(Name, HConfig) -> Type = maps:get(type, HConfig, wrap), {DefaultNoFiles,DefaultNoBytes} = case Type of halt -> {undefined,infinity}; _wrap -> {10,1048576} end, {ok,Dir} = file:get_cwd(), Defaults = #{file => filename:join(Dir,Name), max_no_files => DefaultNoFiles, max_no_bytes => DefaultNoBytes, type => Type}, maps:merge(Defaults, HConfig). filesync(Name,_Mode,State) -> Result = ?disk_log_sync(Name), maybe_notify_error(Name, filesync, Result, prev_sync_result, State). write(Name, Mode, Bin, State) -> Result = ?disk_log_write(Name, Mode, Bin), maybe_notify_error(Name, log, Result, prev_log_result, State). reset_state(_Name, State) -> State#{prev_log_result => ok, prev_sync_result => ok, prev_disk_log_info => undefined}. %% The disk log owner must handle status messages from disk_log. handle_info(Name, {disk_log, _Node, Log, Info={truncated,_NoLostItems}}, State) -> maybe_notify_status(Name, Log, Info, prev_disk_log_info, State); handle_info(Name, {disk_log, _Node, Log, Info = {blocked_log,_Items}}, State) -> maybe_notify_status(Name, Log, Info, prev_disk_log_info, State); handle_info(Name, {disk_log, _Node, Log, Info = full}, State) -> maybe_notify_status(Name, Log, Info, prev_disk_log_info, State); handle_info(Name, {disk_log, _Node, Log, Info = {error_status,_Status}}, State) -> maybe_notify_status(Name, Log, Info, prev_disk_log_info, State); handle_info(_, _, State) -> State. terminate(Name, _Reason, _State) -> _ = close_disk_log(Name, normal), ok. %%%----------------------------------------------------------------- %%% Internal functions open_disk_log(Name, File, Type, MaxNoBytes, MaxNoFiles) -> case filelib:ensure_dir(File) of ok -> Size = if Type == halt -> MaxNoBytes; Type == wrap -> {MaxNoBytes,MaxNoFiles} end, Opts = [{name, Name}, {file, File}, {size, Size}, {type, Type}, {linkto, self()}, {repair, false}, {format, external}, {notify, true}, {quiet, true}, {mode, read_write}], case disk_log:open(Opts) of {ok,Name} -> ok; Error = {error,_Reason} -> Error end; Error -> Error end. close_disk_log(Name, _) -> _ = ?disk_log_sync(Name), _ = disk_log:lclose(Name), ok. disk_log_write(Name, sync, Bin) -> disk_log:blog(Name, Bin); disk_log_write(Name, async, Bin) -> disk_log:balog(Name, Bin). %%%----------------------------------------------------------------- %%% Print error messages, but don't repeat the same message maybe_notify_error(Name, Op, Result, Key, #{log_opts:=LogOpts}=State) -> {Result,error_notify_new({Name, Op, LogOpts, Result}, Result, Key, State)}. maybe_notify_status(Name, Log, Info, Key, State) -> error_notify_new({disk_log, Name, Log, Info}, Info, Key, State). error_notify_new(Term, What, Key, State) -> error_notify_new(What, maps:get(Key,State), Term), State#{Key => What}. error_notify_new(ok,_Prev,_Term) -> ok; error_notify_new(Same,Same,_Term) -> ok; error_notify_new(_New,_Prev,Term) -> logger_h_common:error_notify(Term).