-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, <> ) 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 = <<<<<>/binary, " -> "/utf8>>/binary, Log_msg/binary>>, gleam_stdlib:println(case maps:size(Data) of 0 -> Msg; _ -> <<<>/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.