Current section
Files
Jump to
Current section
Files
src/glight.erl
-module(glight).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([set_level/2, set_is_color/2, set_msg_key/2, set_level_key/2, set_time_key/2, set_dispatch_mode/2, with/3, emergency/2, alert/2, critical/2, error/2, warn/2, notice/2, info/2, debug/2, start/1, must_start/1]).
-export_type([state/0, message/0, dispatch_mode/0, log_config/0, log_context/0, transport/0, glight_error/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type state() :: {state,
list(fun((logging:log_level(), binary(), gleam@dict:dict(binary(), binary()), log_config()) -> nil))}.
-type message() :: {log,
gleam@option:option(gleam@erlang@process:subject(boolean())),
logging:log_level(),
binary(),
gleam@dict:dict(binary(), binary()),
log_config()}.
-type dispatch_mode() :: dispatch_call | dispatch_cast.
-type log_config() :: {log_config,
logging:log_level(),
boolean(),
dispatch_mode(),
binary(),
binary(),
binary()}.
-type log_context() :: {log_context,
gleam@erlang@process:subject(message()),
log_config(),
gleam@dict:dict(binary(), binary())}.
-type transport() :: console_transport |
{file_transport, binary()} |
{custom_transport,
fun((logging:log_level(), binary(), gleam@dict:dict(binary(), binary()), log_config()) -> nil)}.
-type glight_error() :: {file_stream_error,
file_streams@file_stream_error:file_stream_error()} |
{actor_start_error, gleam@otp@actor:start_error()}.
-file("src/glight.gleam", 64).
?DOC(
" Set the log level for the logger. Logs below this level will be ignored.\n"
" \n"
" # Examples\n"
" ```gleam\n"
" glight.must_start([glight.ConsoleTransport])\n"
" |> glight.set_level(logging.Debug)\n"
" |> glight.debug(\"this message will be logged\")\n"
" |> glight.warn(\"this message will also be logged\")\n"
" \n"
" glight.must_start([glight.ConsoleTransport])\n"
" |> glight.set_level(logging.Warning)\n"
" |> glight.debug(\"this message not will be logged\")\n"
" |> glight.warn(\"but this message will be\")\n"
" ```\n"
).
-spec set_level(log_context(), logging:log_level()) -> log_context().
set_level(Ctx, Level) ->
_record = Ctx,
{log_context,
erlang:element(2, _record),
begin
_record@1 = erlang:element(3, Ctx),
{log_config,
Level,
erlang:element(3, _record@1),
erlang:element(4, _record@1),
erlang:element(5, _record@1),
erlang:element(6, _record@1),
erlang:element(7, _record@1)}
end,
erlang:element(4, _record)}.
-file("src/glight.gleam", 80).
?DOC(
" Set whether color strings are used in the console transport.\n"
" \n"
" # Examples\n"
" ```gleam\n"
" glight.must_start([glight.ConsoleTransport])\n"
" |> glight.set_is_color(True)\n"
" |> glight.info(\"this log will be colorful\")\n"
" \n"
" glight.must_start([glight.ConsoleTransport])\n"
" |> glight.set_is_color(False)\n"
" |> glight.info(\"this will not be colorful\")\n"
" ```\n"
).
-spec set_is_color(log_context(), boolean()) -> log_context().
set_is_color(Ctx, Is_color) ->
_record = Ctx,
{log_context,
erlang:element(2, _record),
begin
_record@1 = erlang:element(3, Ctx),
{log_config,
erlang:element(2, _record@1),
Is_color,
erlang:element(4, _record@1),
erlang:element(5, _record@1),
erlang:element(6, _record@1),
erlang:element(7, _record@1)}
end,
erlang:element(4, _record)}.
-file("src/glight.gleam", 103).
?DOC(
" Set the key that is used for the log message in the JSON output. `msg` \n"
" by default.\n"
"\n"
" # Examples\n"
" ```gleam\n"
" \n"
" glight.must_start([glight.FileTransport(\"server.log\")])\n"
" |> glight.info(\"this message will be keyed with 'msg'\")\n"
" |> glight.set_msg_key(\"message\")\n"
" |> glight.info(\"this message will be keyed with 'message'\")\n"
" |> glight.set_msg_key(\"MASSAGE\")\n"
" |> glight.info(\"this message will be humorously keyed with 'MASSAGE'\")\n"
" ```\n"
" ```shell\n"
" → cat server.log\n"
" {\"time\":\"2025-04-05T10:18:31.410-07:00\",\"level\":\"info\",\"msg\":\"this message will be keyed with 'msg'\"}\n"
" {\"time\":\"2025-04-05T10:18:31.410-07:00\",\"level\":\"info\",\"message\":\"this message will be keyed with 'message'\"}\n"
" {\"time\":\"2025-04-05T10:18:31.428-07:00\",\"level\":\"info\",\"MASSAGE\":\"this message will be humorously keyed with 'MASSAGE'\"}\n"
"```\n"
).
-spec set_msg_key(log_context(), binary()) -> log_context().
set_msg_key(Ctx, Msg_key) ->
_record = Ctx,
{log_context,
erlang:element(2, _record),
begin
_record@1 = erlang:element(3, Ctx),
{log_config,
erlang:element(2, _record@1),
erlang:element(3, _record@1),
erlang:element(4, _record@1),
erlang:element(5, _record@1),
Msg_key,
erlang:element(7, _record@1)}
end,
erlang:element(4, _record)}.
-file("src/glight.gleam", 123).
?DOC(
" Set the key that is used for the log level in the JSON output. `level` \n"
" by default.\n"
"\n"
" # Examples\n"
" ```gleam\n"
" \n"
" glight.must_start([glight.FileTransport(\"server.log\")])\n"
" |> glight.info(\"this message's level will be keyed with 'level'\")\n"
" |> glight.set_level_key(\"lvl\")\n"
" |> glight.info(\"this message's level will be keyed with 'lvl'\")\n"
" ```\n"
" ```shell\n"
" → cat server.log\n"
" {\"time\":\"2025-04-05T10:18:31.410-07:00\",\"level\":\"info\",\"msg\":\"this message's level will be keyed with 'level'\"}\n"
" {\"time\":\"2025-04-05T10:18:31.410-07:00\",\"lvl\":\"info\",\"msg\":\"this message's level will be keyed with 'lvl'\"}\n"
"```\n"
).
-spec set_level_key(log_context(), binary()) -> log_context().
set_level_key(Ctx, Level_key) ->
_record = Ctx,
{log_context,
erlang:element(2, _record),
begin
_record@1 = erlang:element(3, Ctx),
{log_config,
erlang:element(2, _record@1),
erlang:element(3, _record@1),
erlang:element(4, _record@1),
erlang:element(5, _record@1),
erlang:element(6, _record@1),
Level_key}
end,
erlang:element(4, _record)}.
-file("src/glight.gleam", 142).
?DOC(
" Set the key that is used for the time in the JSON output. `time` by default.\n"
" \n"
" # Examples\n"
" ```gleam\n"
" \n"
" glight.must_start([glight.FileTransport(\"server.log\")])\n"
" |> glight.info(\"this message's time will be keyed with 'time'\")\n"
" |> glight.set_time_key(\"ts\")\n"
" |> glight.info(\"this message's time will be keyed with 'ts'\")\n"
" ```\n"
" ```shell\n"
" → cat server.log\n"
" {\"time\":\"2025-04-05T10:18:31.410-07:00\",\"level\":\"info\",\"msg\":\"this message's time will be keyed with 'time'\"}\n"
" {\"ts\":\"2025-04-05T10:18:31.410-07:00\",\"lvl\":\"info\",\"msg\":\"this message's time will be keyed with 'ts'\"}\n"
"```\n"
).
-spec set_time_key(log_context(), binary()) -> log_context().
set_time_key(Ctx, Time_key) ->
_record = Ctx,
{log_context,
erlang:element(2, _record),
begin
_record@1 = erlang:element(3, Ctx),
{log_config,
erlang:element(2, _record@1),
erlang:element(3, _record@1),
erlang:element(4, _record@1),
Time_key,
erlang:element(6, _record@1),
erlang:element(7, _record@1)}
end,
erlang:element(4, _record)}.
-file("src/glight.gleam", 158).
?DOC(
" Set whether the logger functions use `call` or `cast` to send messages to \n"
" the logger actor. `DispatchCast` by default.\n"
" \n"
" # Examples\n"
" ```gleam\n"
" glight.must_start([glight.ConsoleTransport])\n"
" |> glight.info(\"in a one off script, this log may be dropped\")\n"
" \n"
" glight.must_start([glight.ConsoleTransport])\n"
" |> glight.set_dispatch_mode(glight.DispatchCall)\n"
" |> glight.info(\"this log will always be processed before returning\")\n"
" ```\n"
).
-spec set_dispatch_mode(log_context(), dispatch_mode()) -> log_context().
set_dispatch_mode(Ctx, Dispatch_mode) ->
_record = Ctx,
{log_context,
erlang:element(2, _record),
begin
_record@1 = erlang:element(3, Ctx),
{log_config,
erlang:element(2, _record@1),
erlang:element(3, _record@1),
Dispatch_mode,
erlang:element(5, _record@1),
erlang:element(6, _record@1),
erlang:element(7, _record@1)}
end,
erlang:element(4, _record)}.
-file("src/glight.gleam", 231).
?DOC(
" This function accumulates structured data for the log message. This data\n"
" will show up keyed in the json logs and formatted nicely in the console logs.\n"
).
-spec with(log_context(), binary(), binary()) -> log_context().
with(Ctx, Key, Value) ->
_record = Ctx,
{log_context,
erlang:element(2, _record),
erlang:element(3, _record),
gleam@dict:insert(erlang:element(4, Ctx), Key, Value)}.
-file("src/glight.gleam", 291).
?DOC(
" The logger actor itself - handle_message processes each log message. This \n"
" actor is quite simple and there isn't much delegation here, every message\n"
" that is sent is a log that gets processed.\n"
).
-spec handle_message(message(), state()) -> gleam@otp@actor:next(message(), state()).
handle_message(Message, State) ->
case Message of
{log, Maybe_caller, Level, Log_msg, Data, Config} ->
gleam@list:map(
erlang:element(2, State),
fun(Log) -> Log(Level, Log_msg, Data, Config) end
),
case Maybe_caller of
{some, Caller} ->
gleam@otp@actor:send(Caller, true);
none ->
nil
end,
gleam@otp@actor:continue(State)
end.
-file("src/glight.gleam", 487).
?DOC(" regular string representation of each log level\n").
-spec level_to_string(logging:log_level()) -> binary().
level_to_string(Level) ->
case Level of
emergency ->
<<"emergency"/utf8>>;
alert ->
<<"alert"/utf8>>;
critical ->
<<"critical"/utf8>>;
error ->
<<"error"/utf8>>;
warning ->
<<"warn"/utf8>>;
notice ->
<<"notice"/utf8>>;
info ->
<<"info"/utf8>>;
debug ->
<<"debug"/utf8>>
end.
-file("src/glight.gleam", 410).
?DOC(
" Logging function for the file transport. This formats the log message into\n"
" a JSON object, respecting configuration for object keys, and streams it to\n"
" the provided file.\n"
).
-spec file_out(binary()) -> {ok,
fun((logging:log_level(), binary(), gleam@dict:dict(binary(), binary()), log_config()) -> nil)} |
{error, glight_error()}.
file_out(File) ->
_pipe = file_streams@file_stream:open(File, [append]),
_pipe@1 = gleam@result:map_error(
_pipe,
fun(Field@0) -> {file_stream_error, Field@0} end
),
gleam@result:map(
_pipe@1,
fun(Stream) ->
fun(Log_level, Log_msg, Data, Config) ->
Log = <<(begin
_pipe@9 = [{erlang:element(5, Config),
begin
_pipe@2 = birl:now(),
_pipe@3 = birl:to_iso8601(_pipe@2),
gleam@json:string(_pipe@3)
end},
{erlang:element(7, Config),
begin
_pipe@4 = Log_level,
_pipe@5 = level_to_string(_pipe@4),
gleam@json:string(_pipe@5)
end},
{erlang:element(6, Config),
begin
_pipe@6 = Log_msg,
gleam@json:string(_pipe@6)
end} |
begin
_pipe@7 = Data,
_pipe@8 = maps:to_list(_pipe@7),
gleam@list:map(
_pipe@8,
fun(E) ->
{erlang:element(1, E),
gleam@json:string(
erlang:element(2, E)
)}
end
)
end],
_pipe@10 = gleam@json:object(_pipe@9),
gleam@json:to_string(_pipe@10)
end)/binary,
"\n"/utf8>>,
case file_streams@file_stream:write_bytes(
Stream,
<<Log/binary>>
) of
{error, Err} ->
logging:log(
error,
<<"file_stream_write_error: "/utf8,
(gleam@string:inspect(Err))/binary>>
),
nil;
_ ->
nil
end,
case file_streams@file_stream:sync(Stream) of
{error, Err@1} ->
logging:log(
error,
<<"file_stream_sync_error: "/utf8,
(gleam@string:inspect(Err@1))/binary>>
),
nil;
_ ->
nil
end,
nil
end
end
).
-file("src/glight.gleam", 529).
?DOC(" comparable values for individual log levels\n").
-spec level_to_cmp(logging:log_level()) -> integer().
level_to_cmp(Level) ->
case Level of
emergency ->
0;
alert ->
1;
critical ->
2;
error ->
3;
warning ->
4;
notice ->
5;
info ->
6;
debug ->
7
end.
-file("src/glight.gleam", 543).
?DOC(" check if provided log level is important enough to be logged\n").
-spec should_log(logging:log_level(), logging:log_level()) -> boolean().
should_log(Log_level, Configured_level) ->
level_to_cmp(Log_level) =< level_to_cmp(Configured_level).
-file("src/glight.gleam", 238).
?DOC(
" Helper function that is trivially wrapped by the exported logging \n"
" functions. It passes the given log level along to the actor, and decides\n"
" wheter to wati for response or not based on dispatch mode.\n"
).
-spec log(log_context(), logging:log_level(), binary()) -> boolean().
log(Ctx, Level, Log_msg) ->
case should_log(Level, erlang:element(2, erlang:element(3, Ctx))) of
true ->
case erlang:element(4, erlang:element(3, Ctx)) of
dispatch_call ->
gleam@otp@actor:call(
erlang:element(2, Ctx),
fun(Client) ->
{log,
{some, Client},
Level,
Log_msg,
erlang:element(4, Ctx),
erlang:element(3, Ctx)}
end,
100
);
dispatch_cast ->
gleam@otp@actor:send(
erlang:element(2, Ctx),
{log,
none,
Level,
Log_msg,
erlang:element(4, Ctx),
erlang:element(3, Ctx)}
),
true
end;
false ->
false
end.
-file("src/glight.gleam", 168).
?DOC(
" Log at the emergency level. See the erlang log level documentation for \n"
" more information and context.\n"
" https://www.erlang.org/doc/apps/kernel/logger_chapter.html#log-level\n"
).
-spec emergency(log_context(), binary()) -> log_context().
emergency(Ctx, Log_msg) ->
log(Ctx, emergency, Log_msg),
Ctx.
-file("src/glight.gleam", 176).
?DOC(
" Log at the alert level. See the erlang log level documentation for \n"
" more information and context.\n"
" https://www.erlang.org/doc/apps/kernel/logger_chapter.html#log-level\n"
).
-spec alert(log_context(), binary()) -> log_context().
alert(Ctx, Log_msg) ->
log(Ctx, alert, Log_msg),
Ctx.
-file("src/glight.gleam", 184).
?DOC(
" Log at the critical level. See the erlang log level documentation for \n"
" more information and context.\n"
" https://www.erlang.org/doc/apps/kernel/logger_chapter.html#log-level\n"
).
-spec critical(log_context(), binary()) -> log_context().
critical(Ctx, Log_msg) ->
log(Ctx, critical, Log_msg),
Ctx.
-file("src/glight.gleam", 192).
?DOC(
" Log at the error level. See the erlang log level documentation for \n"
" more information and context.\n"
" https://www.erlang.org/doc/apps/kernel/logger_chapter.html#log-level\n"
).
-spec error(log_context(), binary()) -> log_context().
error(Ctx, Log_msg) ->
log(Ctx, error, Log_msg),
Ctx.
-file("src/glight.gleam", 200).
?DOC(
" Log at the warning level. See the erlang log level documentation for \n"
" more information and context.\n"
" https://www.erlang.org/doc/apps/kernel/logger_chapter.html#log-level\n"
).
-spec warn(log_context(), binary()) -> log_context().
warn(Ctx, Log_msg) ->
log(Ctx, warning, Log_msg),
Ctx.
-file("src/glight.gleam", 208).
?DOC(
" Log at the notice level. See the erlang log level documentation for \n"
" more information and context.\n"
" https://www.erlang.org/doc/apps/kernel/logger_chapter.html#log-level\n"
).
-spec notice(log_context(), binary()) -> log_context().
notice(Ctx, Log_msg) ->
log(Ctx, notice, Log_msg),
Ctx.
-file("src/glight.gleam", 216).
?DOC(
" Log at the info level. See the erlang log level documentation for \n"
" more information and context.\n"
" https://www.erlang.org/doc/apps/kernel/logger_chapter.html#log-level\n"
).
-spec info(log_context(), binary()) -> log_context().
info(Ctx, Log_msg) ->
log(Ctx, info, Log_msg),
Ctx.
-file("src/glight.gleam", 224).
?DOC(
" Log at the debug level. See the erlang log level documentation for \n"
" more information and context.\n"
" https://www.erlang.org/doc/apps/kernel/logger_chapter.html#log-level\n"
).
-spec debug(log_context(), binary()) -> log_context().
debug(Ctx, Log_msg) ->
log(Ctx, debug, Log_msg),
Ctx.
-file("src/glight.gleam", 457).
?DOC(" set text to red\n").
-spec red(binary()) -> binary().
red(S) ->
<<<<"\x{001b}[91m"/utf8, S/binary>>/binary, "\x{001b}[0m"/utf8>>.
-file("src/glight.gleam", 462).
?DOC(" set text to magenta\n").
-spec magenta(binary()) -> binary().
magenta(S) ->
<<<<"\x{001b}[95m"/utf8, S/binary>>/binary, "\x{001b}[0m"/utf8>>.
-file("src/glight.gleam", 467).
?DOC(" set text to yellow\n").
-spec yellow(binary()) -> binary().
yellow(S) ->
<<<<"\x{001b}[93m"/utf8, S/binary>>/binary, "\x{001b}[0m"/utf8>>.
-file("src/glight.gleam", 472).
?DOC(" set text to green\n").
-spec green(binary()) -> binary().
green(S) ->
<<<<"\x{001b}[92m"/utf8, S/binary>>/binary, "\x{001b}[0m"/utf8>>.
-file("src/glight.gleam", 477).
?DOC(" set text to blue\n").
-spec blue(binary()) -> binary().
blue(S) ->
<<<<"\x{001b}[94m"/utf8, S/binary>>/binary, "\x{001b}[0m"/utf8>>.
-file("src/glight.gleam", 482).
?DOC(" set text to grey\n").
-spec grey(binary()) -> binary().
grey(S) ->
<<<<"\x{001b}[90m"/utf8, S/binary>>/binary, "\x{001b}[0m"/utf8>>.
-file("src/glight.gleam", 501).
?DOC(" color coded(?) all caps string representation of each log level for std out\n").
-spec level_to_std_out_string(logging:log_level(), log_config()) -> binary().
level_to_std_out_string(Level, Config) ->
case erlang:element(3, Config) of
true ->
case Level of
emergency ->
red(<<"EMERG"/utf8>>);
alert ->
red(<<"ALERT"/utf8>>);
critical ->
magenta(<<"CRIT"/utf8>>);
error ->
magenta(<<"ERROR"/utf8>>);
warning ->
yellow(<<"WARN"/utf8>>);
notice ->
yellow(<<"NOTICE"/utf8>>);
info ->
green(<<"INFO"/utf8>>);
debug ->
blue(<<"DEBUG"/utf8>>)
end;
false ->
case Level of
emergency ->
<<"EMERG"/utf8>>;
alert ->
<<"ALERT"/utf8>>;
critical ->
<<"CRIT"/utf8>>;
error ->
<<"ERROR"/utf8>>;
warning ->
<<"WARN"/utf8>>;
notice ->
<<"NOTICE"/utf8>>;
info ->
<<"INFO"/utf8>>;
debug ->
<<"DEBUG"/utf8>>
end
end.
-file("src/glight.gleam", 383).
?DOC(
" Logging function for the console transport. This formats the main log \n"
" message and optional structured data into human readable format, and \n"
" optionally colors the log.\n"
).
-spec std_out(
logging:log_level(),
binary(),
gleam@dict:dict(binary(), binary()),
log_config()
) -> nil.
std_out(Log_level, Log_msg, Data, Config) ->
Ts = begin
_pipe = birl:now(),
birl:to_time_string(_pipe)
end,
Level = begin
_pipe@1 = (<<<<" ["/utf8,
(level_to_std_out_string(Log_level, Config))/binary>>/binary,
"]"/utf8>>),
gleam@string:pad_end(_pipe@1, 18, <<" "/utf8>>)
end,
Msg = <<<<<<Ts/binary, Level/binary>>/binary, " -> "/utf8>>/binary,
Log_msg/binary>>,
gleam_stdlib:println(case maps:size(Data) of
0 ->
Msg;
_ ->
<<<<Msg/binary, "\n"/utf8>>/binary,
(begin
_pipe@2 = maps:to_list(Data),
_pipe@3 = gleam@list:map(
_pipe@2,
fun(E) ->
Key = erlang:element(1, E),
Value = erlang:element(2, E),
<<<<<<"\t\t\t\t\t"/utf8, Key/binary>>/binary,
": "/utf8>>/binary,
Value/binary>>
end
),
_pipe@4 = gleam@string:join(_pipe@3, <<"\n"/utf8>>),
grey(_pipe@4)
end)/binary>>
end),
nil.
-file("src/glight.gleam", 367).
-spec init_state(list(transport())) -> {ok, state()} | {error, glight_error()}.
init_state(Transports) ->
_pipe = Transports,
_pipe@1 = gleam@list:map(_pipe, fun(Transport) -> case Transport of
console_transport ->
{ok, fun std_out/4};
{file_transport, File} ->
file_out(File);
{custom_transport, Log} ->
{ok, Log}
end end),
_pipe@2 = gleam@result:all(_pipe@1),
gleam@result:map(_pipe@2, fun(Field@0) -> {state, Field@0} end).
-file("src/glight.gleam", 22).
?DOC(
" Initialize the logger actor. This can fail if the actor does not start or\n"
" if the transports are invalid (mostly likely a file that cannot be streamed to).\n"
).
-spec start(list(transport())) -> {ok, log_context()} | {error, glight_error()}.
start(Transports) ->
_pipe = init_state(Transports),
_pipe@2 = gleam@result:'try'(
_pipe,
fun(State) ->
_pipe@1 = gleam@otp@actor:start(State, fun handle_message/2),
gleam@result:map_error(
_pipe@1,
fun(Field@0) -> {actor_start_error, Field@0} end
)
end
),
gleam@result:'try'(
_pipe@2,
fun(Logger) ->
_pipe@3 = {log_context,
Logger,
{log_config,
info,
true,
dispatch_cast,
<<"time"/utf8>>,
<<"msg"/utf8>>,
<<"level"/utf8>>},
maps:new()},
{ok, _pipe@3}
end
).
-file("src/glight.gleam", 45).
?DOC(" Convenience function that will panic if the logger cannot be started.\n").
-spec must_start(list(transport())) -> log_context().
must_start(Transports) ->
_assert_subject = start(Transports),
{ok, Logger} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
value => _assert_fail,
module => <<"glight"/utf8>>,
function => <<"must_start"/utf8>>,
line => 46})
end,
Logger.