|
| 1 | +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- |
| 2 | +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. |
| 3 | +%%% |
| 4 | +%%% This file is provided to you under the Apache License, |
| 5 | +%%% Version 2.0 (the "License"); you may not use this file |
| 6 | +%%% except in compliance with the License. You may obtain |
| 7 | +%%% a copy of the License at |
| 8 | +%%% |
| 9 | +%%% http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +%%% |
| 11 | +%%% Unless required by applicable law or agreed to in writing, |
| 12 | +%%% software distributed under the License is distributed on an |
| 13 | +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 14 | +%%% KIND, either express or implied. See the License for the |
| 15 | +%%% specific language governing permissions and limitations |
| 16 | +%%% under the License. |
| 17 | +%%%--------------------------------------------------------------------------- |
| 18 | +%%% @author Eric Merritt <ericbmerritt@gmail.com> |
| 19 | +%%% @copyright (C) 2012 Erlware, LLC. |
| 20 | +%%% |
| 21 | +%%% @doc This provides simple output functions for command line apps. You should |
| 22 | +%%% use this to talk to the users if you are wrting code for the system |
| 23 | +-module(ec_cmd_log). |
| 24 | + |
| 25 | +-export([new/1, |
| 26 | + new/2, |
| 27 | + log/4, |
| 28 | + should/2, |
| 29 | + debug/2, |
| 30 | + debug/3, |
| 31 | + info/2, |
| 32 | + info/3, |
| 33 | + error/2, |
| 34 | + error/3, |
| 35 | + warn/2, |
| 36 | + warn/3, |
| 37 | + log_level/1, |
| 38 | + atom_log_level/1, |
| 39 | + format/1]). |
| 40 | + |
| 41 | +-include_lib("erlware_commons/include/ec_cmd_log.hrl"). |
| 42 | + |
| 43 | +-define(RED, 31). |
| 44 | +-define(GREEN, 32). |
| 45 | +-define(YELLOW, 33). |
| 46 | +-define(BLUE, 34). |
| 47 | +-define(MAGENTA, 35). |
| 48 | +-define(CYAN, 36). |
| 49 | + |
| 50 | +-define(PREFIX, "===> "). |
| 51 | + |
| 52 | +-record(state_t, {mod=?MODULE :: ec_log, |
| 53 | + log_level=0 :: int_log_level(), |
| 54 | + caller=api :: api | command_line}). |
| 55 | + |
| 56 | +%%============================================================================ |
| 57 | +%% types |
| 58 | +%%============================================================================ |
| 59 | +-export_type([t/0, |
| 60 | + int_log_level/0, |
| 61 | + atom_log_level/0, |
| 62 | + log_level/0, |
| 63 | + log_fun/0]). |
| 64 | + |
| 65 | +-type log_level() :: int_log_level() | atom_log_level(). |
| 66 | + |
| 67 | +-type int_log_level() :: 0..3. |
| 68 | + |
| 69 | +-type atom_log_level() :: error | warn | info | debug. |
| 70 | + |
| 71 | +-type log_fun() :: fun(() -> iolist()). |
| 72 | + |
| 73 | +-type color() :: 31..36. |
| 74 | + |
| 75 | +-opaque t() :: record(state_t). |
| 76 | + |
| 77 | +%%============================================================================ |
| 78 | +%% API |
| 79 | +%%============================================================================ |
| 80 | +%% @doc Create a new 'log level' for the system |
| 81 | +-spec new(log_level()) -> t(). |
| 82 | +new(LogLevel) -> |
| 83 | + new(LogLevel, api). |
| 84 | + |
| 85 | +new(LogLevel, Caller) when LogLevel >= 0, LogLevel =< 3 -> |
| 86 | + #state_t{mod=?MODULE, log_level=LogLevel, caller=Caller}; |
| 87 | +new(AtomLogLevel, Caller) |
| 88 | + when AtomLogLevel =:= error; |
| 89 | + AtomLogLevel =:= warn; |
| 90 | + AtomLogLevel =:= info; |
| 91 | + AtomLogLevel =:= debug -> |
| 92 | + LogLevel = case AtomLogLevel of |
| 93 | + error -> 0; |
| 94 | + warn -> 1; |
| 95 | + info -> 2; |
| 96 | + debug -> 3 |
| 97 | + end, |
| 98 | + new(LogLevel, Caller). |
| 99 | + |
| 100 | +%% @doc log at the debug level given the current log state with a string or |
| 101 | +%% function that returns a string |
| 102 | +-spec debug(t(), string() | log_fun()) -> ok. |
| 103 | +debug(LogState, Fun) |
| 104 | + when erlang:is_function(Fun) -> |
| 105 | + log(LogState, ?EC_DEBUG, fun() -> |
| 106 | + colorize(LogState, ?CYAN, false, Fun()) |
| 107 | + end); |
| 108 | +debug(LogState, String) -> |
| 109 | + debug(LogState, "~s~n", [String]). |
| 110 | + |
| 111 | +%% @doc log at the debug level given the current log state with a format string |
| 112 | +%% and argements @see io:format/2 |
| 113 | +-spec debug(t(), string(), [any()]) -> ok. |
| 114 | +debug(LogState, FormatString, Args) -> |
| 115 | + log(LogState, ?EC_DEBUG, colorize(LogState, ?CYAN, false, FormatString), Args). |
| 116 | + |
| 117 | +%% @doc log at the info level given the current log state with a string or |
| 118 | +%% function that returns a string |
| 119 | +-spec info(t(), string() | log_fun()) -> ok. |
| 120 | +info(LogState, Fun) |
| 121 | + when erlang:is_function(Fun) -> |
| 122 | + log(LogState, ?EC_INFO, fun() -> |
| 123 | + colorize(LogState, ?GREEN, false, Fun()) |
| 124 | + end); |
| 125 | +info(LogState, String) -> |
| 126 | + info(LogState, "~s~n", [String]). |
| 127 | + |
| 128 | +%% @doc log at the info level given the current log state with a format string |
| 129 | +%% and argements @see io:format/2 |
| 130 | +-spec info(t(), string(), [any()]) -> ok. |
| 131 | +info(LogState, FormatString, Args) -> |
| 132 | + log(LogState, ?EC_INFO, colorize(LogState, ?GREEN, false, FormatString), Args). |
| 133 | + |
| 134 | +%% @doc log at the error level given the current log state with a string or |
| 135 | +%% format string that returns a function |
| 136 | +-spec error(t(), string() | log_fun()) -> ok. |
| 137 | +error(LogState, Fun) |
| 138 | + when erlang:is_function(Fun) -> |
| 139 | + log(LogState, ?EC_ERROR, fun() -> |
| 140 | + colorize(LogState, ?RED, false, Fun()) |
| 141 | + end); |
| 142 | +error(LogState, String) -> |
| 143 | + error(LogState, "~s~n", [String]). |
| 144 | + |
| 145 | +%% @doc log at the error level given the current log state with a format string |
| 146 | +%% and argements @see io:format/2 |
| 147 | +-spec error(t(), string(), [any()]) -> ok. |
| 148 | +error(LogState, FormatString, Args) -> |
| 149 | + log(LogState, ?EC_ERROR, colorize(LogState, ?GREEN, false, FormatString), Args). |
| 150 | + |
| 151 | +%% @doc log at the warn level given the current log state with a string or |
| 152 | +%% format string that returns a function |
| 153 | +-spec warn(t(), string() | log_fun()) -> ok. |
| 154 | +warn(LogState, Fun) |
| 155 | + when erlang:is_function(Fun) -> |
| 156 | + log(LogState, ?EC_WARN, fun() -> colorize(LogState, ?MAGENTA, false, Fun()) end); |
| 157 | +warn(LogState, String) -> |
| 158 | + warn(LogState, "~s~n", [String]). |
| 159 | + |
| 160 | +%% @doc log at the warn level given the current log state with a format string |
| 161 | +%% and argements @see io:format/2 |
| 162 | +-spec warn(t(), string(), [any()]) -> ok. |
| 163 | +warn(LogState, FormatString, Args) -> |
| 164 | + log(LogState, ?EC_WARN, colorize(LogState, ?MAGENTA, false, FormatString), Args). |
| 165 | + |
| 166 | +%% @doc Execute the fun passed in if log level is as expected. |
| 167 | +-spec log(t(), int_log_level(), log_fun()) -> ok. |
| 168 | +log(#state_t{mod=?MODULE, log_level=DetailLogLevel}, LogLevel, Fun) |
| 169 | + when DetailLogLevel >= LogLevel -> |
| 170 | + io:format("~s~n", [Fun()]); |
| 171 | +log(_, _, _) -> |
| 172 | + ok. |
| 173 | + |
| 174 | +%% @doc when the module log level is less then or equal to the log level for the |
| 175 | +%% call then write the log info out. When its not then ignore the call. |
| 176 | +-spec log(t(), int_log_level(), string(), [any()]) -> ok. |
| 177 | +log(#state_t{mod=?MODULE, log_level=DetailLogLevel}, LogLevel, FormatString, Args) |
| 178 | + when DetailLogLevel >= LogLevel, |
| 179 | + erlang:is_list(Args) -> |
| 180 | + io:format(FormatString, Args); |
| 181 | +log(_, _, _, _) -> |
| 182 | + ok. |
| 183 | + |
| 184 | +%% @doc return a boolean indicating if the system should log for the specified |
| 185 | +%% levelg |
| 186 | +-spec should(t(), int_log_level() | any()) -> boolean(). |
| 187 | +should(#state_t{mod=?MODULE, log_level=DetailLogLevel}, LogLevel) |
| 188 | + when DetailLogLevel >= LogLevel -> |
| 189 | + true; |
| 190 | +should(_, _) -> |
| 191 | + false. |
| 192 | + |
| 193 | +%% @doc get the current log level as an integer |
| 194 | +-spec log_level(t()) -> int_log_level(). |
| 195 | +log_level(#state_t{mod=?MODULE, log_level=DetailLogLevel}) -> |
| 196 | + DetailLogLevel. |
| 197 | + |
| 198 | +%% @doc get the current log level as an atom |
| 199 | +-spec atom_log_level(t()) -> atom_log_level(). |
| 200 | +atom_log_level(#state_t{mod=?MODULE, log_level=?EC_ERROR}) -> |
| 201 | + error; |
| 202 | +atom_log_level(#state_t{mod=?MODULE, log_level=?EC_WARN}) -> |
| 203 | + warn; |
| 204 | +atom_log_level(#state_t{mod=?MODULE, log_level=?EC_INFO}) -> |
| 205 | + info; |
| 206 | +atom_log_level(#state_t{mod=?MODULE, log_level=?EC_DEBUG}) -> |
| 207 | + debug. |
| 208 | + |
| 209 | +-spec format(t()) -> iolist(). |
| 210 | +format(Log) -> |
| 211 | + [<<"(">>, |
| 212 | + ec_cnv:to_binary(log_level(Log)), <<":">>, |
| 213 | + ec_cnv:to_binary(atom_log_level(Log)), |
| 214 | + <<")">>]. |
| 215 | + |
| 216 | +-spec colorize(t(), color(), boolean(), string()) -> string(). |
| 217 | +colorize(#state_t{caller=command_line}, Color, false, Msg) when is_integer(Color) -> |
| 218 | + colorize_(Color, 0, Msg); |
| 219 | +colorize(_LogState, _Color, _Bold, Msg) -> |
| 220 | + Msg. |
| 221 | + |
| 222 | +-spec colorize_(color(), integer(), string()) -> string(). |
| 223 | +colorize_(Color, Bold, Msg) when is_integer(Color), is_integer(Bold)-> |
| 224 | + lists:flatten(io_lib:format("\033[~B;~Bm~s~s\033[0m", [Bold, Color, ?PREFIX, Msg])). |
| 225 | + |
| 226 | +%%%=================================================================== |
| 227 | +%%% Test Functions |
| 228 | +%%%=================================================================== |
| 229 | + |
| 230 | +-ifndef(NOTEST). |
| 231 | +-include_lib("eunit/include/eunit.hrl"). |
| 232 | + |
| 233 | +should_test() -> |
| 234 | + ErrorLogState = new(error), |
| 235 | + ?assertMatch(true, should(ErrorLogState, ?EC_ERROR)), |
| 236 | + ?assertMatch(true, not should(ErrorLogState, ?EC_INFO)), |
| 237 | + ?assertMatch(true, not should(ErrorLogState, ?EC_DEBUG)), |
| 238 | + ?assertEqual(?EC_ERROR, log_level(ErrorLogState)), |
| 239 | + ?assertEqual(error, atom_log_level(ErrorLogState)), |
| 240 | + |
| 241 | + InfoLogState = new(info), |
| 242 | + ?assertMatch(true, should(InfoLogState, ?EC_ERROR)), |
| 243 | + ?assertMatch(true, should(InfoLogState, ?EC_INFO)), |
| 244 | + ?assertMatch(true, not should(InfoLogState, ?EC_DEBUG)), |
| 245 | + ?assertEqual(?EC_INFO, log_level(InfoLogState)), |
| 246 | + ?assertEqual(info, atom_log_level(InfoLogState)), |
| 247 | + |
| 248 | + DebugLogState = new(debug), |
| 249 | + ?assertMatch(true, should(DebugLogState, ?EC_ERROR)), |
| 250 | + ?assertMatch(true, should(DebugLogState, ?EC_INFO)), |
| 251 | + ?assertMatch(true, should(DebugLogState, ?EC_DEBUG)), |
| 252 | + ?assertEqual(?EC_DEBUG, log_level(DebugLogState)), |
| 253 | + ?assertEqual(debug, atom_log_level(DebugLogState)). |
| 254 | + |
| 255 | +-endif. |
0 commit comments