Packages
Professional observability suite for Gleam: structured logging, metrics, and statistical benchmarking
Current section
Files
Jump to
Current section
Files
src/viva_telemetry@log.erl
-module(viva_telemetry@log).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/viva_telemetry/log.gleam").
-export([handlers/0, would_log/1, configure/1, configure_console/1, configure_json/2, configure_full/3, add_handler/1, with_context/2, bind_context/1, clear_context/0, log/3, emergency/2, alert/2, critical/2, error/2, warning/2, notice/2, info/2, debug/2, trace/2, log_lazy/3, log_lazy_all/3, emergency_lazy/2, alert_lazy/2, critical_lazy/2, error_lazy/2, warning_lazy/2, notice_lazy/2, info_lazy/2, debug_lazy/2, trace_lazy/2, log_from/4, sampled/4]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" viva_telemetry/log - Structured logging for Gleam\n"
"\n"
" Inspired by: structlog (Python), zap (Go), tracing (Rust)\n"
"\n"
" ## Features\n"
" - Structured logging with key-value fields\n"
" - Multiple handlers (console, file, JSON)\n"
" - Log levels (RFC 5424)\n"
" - Context propagation\n"
" - Sampling for high-volume logs\n"
"\n"
" ## Quick Start\n"
"\n"
" ```gleam\n"
" import viva_telemetry/log\n"
"\n"
" pub fn main() {\n"
" // Simple logging\n"
" log.info(\"Server started\", [#(\"port\", \"8080\")])\n"
"\n"
" // With context\n"
" log.with_context([#(\"request_id\", \"abc123\")], fn() {\n"
" log.debug(\"Processing request\", [])\n"
" })\n"
" }\n"
" ```\n"
).
-file("src/viva_telemetry/log.gleam", 122).
?DOC(" Get current handlers\n").
-spec handlers() -> list(viva_telemetry@log@handler:handler()).
handlers() ->
viva_telemetry_ffi:get_handlers().
-file("src/viva_telemetry/log.gleam", 188).
?DOC(
" Check if any handler would log at this level\n"
" Use this to avoid expensive computations when log won't be emitted\n"
).
-spec would_log(viva_telemetry@log@level:level()) -> boolean().
would_log(Lvl) ->
Hs = viva_telemetry_ffi:get_handlers(),
case Hs of
[] ->
viva_telemetry@log@level:is_enabled(Lvl, info);
_ ->
gleam@list:any(
Hs,
fun(H) -> viva_telemetry@log@handler:should_log(H, Lvl) end
)
end.
-file("src/viva_telemetry/log.gleam", 74).
?DOC(" Configure global handlers\n").
-spec configure(list(viva_telemetry@log@handler:handler())) -> nil.
configure(Handlers) ->
viva_telemetry_ffi:set_handlers(Handlers).
-file("src/viva_telemetry/log.gleam", 84).
?DOC(
" Quick setup: console handler with specified level\n"
"\n"
" Example:\n"
" ```gleam\n"
" log.configure_console(log.debug_level)\n"
" ```\n"
).
-spec configure_console(viva_telemetry@log@level:level()) -> nil.
configure_console(Lvl) ->
viva_telemetry_ffi:set_handlers(
[viva_telemetry@log@handler:console_with_level(Lvl)]
).
-file("src/viva_telemetry/log.gleam", 94).
?DOC(
" Quick setup: JSON file handler with specified level\n"
"\n"
" Example:\n"
" ```gleam\n"
" log.configure_json(\"app.jsonl\", log.info_level)\n"
" ```\n"
).
-spec configure_json(binary(), viva_telemetry@log@level:level()) -> nil.
configure_json(Path, Lvl) ->
viva_telemetry_ffi:set_handlers(
[viva_telemetry@log@handler:json_with_level(Path, Lvl)]
).
-file("src/viva_telemetry/log.gleam", 104).
?DOC(
" Quick setup: console + JSON file\n"
"\n"
" Example:\n"
" ```gleam\n"
" log.configure_full(log.debug_level, \"app.jsonl\", log.info_level)\n"
" ```\n"
).
-spec configure_full(
viva_telemetry@log@level:level(),
binary(),
viva_telemetry@log@level:level()
) -> nil.
configure_full(Console_level, Json_path, Json_level) ->
viva_telemetry_ffi:set_handlers(
[viva_telemetry@log@handler:console_with_level(Console_level),
viva_telemetry@log@handler:json_with_level(Json_path, Json_level)]
).
-file("src/viva_telemetry/log.gleam", 116).
?DOC(" Add a handler to the existing configuration\n").
-spec add_handler(viva_telemetry@log@handler:handler()) -> nil.
add_handler(Handler) ->
Current = viva_telemetry_ffi:get_handlers(),
viva_telemetry_ffi:set_handlers([Handler | Current]).
-file("src/viva_telemetry/log.gleam", 322).
?DOC(" Execute function with additional context\n").
-spec with_context(list({binary(), binary()}), fun(() -> EJI)) -> EJI.
with_context(Context, F) ->
Old_context = viva_telemetry_ffi:get_context(),
New_context = maps:merge(Old_context, maps:from_list(Context)),
viva_telemetry_ffi:set_context(New_context),
Result = F(),
viva_telemetry_ffi:set_context(Old_context),
Result.
-file("src/viva_telemetry/log.gleam", 332).
?DOC(" Add context fields that persist for this process\n").
-spec bind_context(list({binary(), binary()})) -> nil.
bind_context(Context) ->
Old_context = viva_telemetry_ffi:get_context(),
New_context = maps:merge(Old_context, maps:from_list(Context)),
viva_telemetry_ffi:set_context(New_context).
-file("src/viva_telemetry/log.gleam", 339).
?DOC(" Clear all context\n").
-spec clear_context() -> nil.
clear_context() ->
viva_telemetry_ffi:set_context(maps:new()).
-file("src/viva_telemetry/log.gleam", 377).
-spec dispatch_to_handler(
viva_telemetry@log@entry:entry(),
viva_telemetry@log@handler:handler()
) -> nil.
dispatch_to_handler(E, H) ->
case viva_telemetry@log@handler:should_log(H, erlang:element(2, E)) of
false ->
nil;
true ->
case H of
{console_handler, Config} ->
Output = viva_telemetry@log@entry:to_console_string(
E,
erlang:element(3, Config)
),
case erlang:element(4, Config) andalso (viva_telemetry@log@level:to_int(
erlang:element(2, E)
)
=< 3) of
true ->
io:format(Output);
false ->
gleam_stdlib:println(Output)
end;
{json_handler, Config@1} ->
Json_str = viva_telemetry@log@entry:to_json_string(E),
_ = simplifile:append(
erlang:element(3, Config@1),
<<Json_str/binary, "\n"/utf8>>
),
nil;
{file_handler, Config@2} ->
Text = viva_telemetry@log@entry:to_console_string(E, false),
_ = simplifile:append(
erlang:element(3, Config@2),
<<Text/binary, "\n"/utf8>>
),
nil;
{custom_handler, Config@3} ->
(erlang:element(3, Config@3))(E)
end
end.
-file("src/viva_telemetry/log.gleam", 364).
-spec dispatch(viva_telemetry@log@entry:entry()) -> nil.
dispatch(E) ->
Hs = viva_telemetry_ffi:get_handlers(),
case Hs of
[] ->
dispatch_to_handler(E, viva_telemetry@log@handler:console());
_ ->
gleam@list:each(Hs, fun(H) -> dispatch_to_handler(E, H) end)
end.
-file("src/viva_telemetry/log.gleam", 176).
?DOC(" Log with explicit level\n").
-spec log(
viva_telemetry@log@level:level(),
binary(),
list({binary(), binary()})
) -> nil.
log(Lvl, Message, Fields) ->
E = viva_telemetry@log@entry:new(Lvl, Message, Fields),
E_with_ctx = viva_telemetry@log@entry:with_context(
E,
viva_telemetry_ffi:get_context()
),
dispatch(E_with_ctx).
-file("src/viva_telemetry/log.gleam", 131).
?DOC(" Log at emergency level\n").
-spec emergency(binary(), list({binary(), binary()})) -> nil.
emergency(Message, Fields) ->
log(emergency, Message, Fields).
-file("src/viva_telemetry/log.gleam", 136).
?DOC(" Log at alert level\n").
-spec alert(binary(), list({binary(), binary()})) -> nil.
alert(Message, Fields) ->
log(alert, Message, Fields).
-file("src/viva_telemetry/log.gleam", 141).
?DOC(" Log at critical level\n").
-spec critical(binary(), list({binary(), binary()})) -> nil.
critical(Message, Fields) ->
log(critical, Message, Fields).
-file("src/viva_telemetry/log.gleam", 146).
?DOC(" Log at error level\n").
-spec error(binary(), list({binary(), binary()})) -> nil.
error(Message, Fields) ->
log(err, Message, Fields).
-file("src/viva_telemetry/log.gleam", 151).
?DOC(" Log at warning level\n").
-spec warning(binary(), list({binary(), binary()})) -> nil.
warning(Message, Fields) ->
log(warning, Message, Fields).
-file("src/viva_telemetry/log.gleam", 156).
?DOC(" Log at notice level\n").
-spec notice(binary(), list({binary(), binary()})) -> nil.
notice(Message, Fields) ->
log(notice, Message, Fields).
-file("src/viva_telemetry/log.gleam", 161).
?DOC(" Log at info level\n").
-spec info(binary(), list({binary(), binary()})) -> nil.
info(Message, Fields) ->
log(info, Message, Fields).
-file("src/viva_telemetry/log.gleam", 166).
?DOC(" Log at debug level\n").
-spec debug(binary(), list({binary(), binary()})) -> nil.
debug(Message, Fields) ->
log(debug, Message, Fields).
-file("src/viva_telemetry/log.gleam", 171).
?DOC(" Log at trace level\n").
-spec trace(binary(), list({binary(), binary()})) -> nil.
trace(Message, Fields) ->
log(trace, Message, Fields).
-file("src/viva_telemetry/log.gleam", 202).
?DOC(
" Lazy log - only evaluates message function if log will be emitted\n"
"\n"
" Example:\n"
" ```gleam\n"
" log.debug_lazy(fn() { \"Processing \" <> expensive_to_string(data) }, [])\n"
" ```\n"
).
-spec log_lazy(
viva_telemetry@log@level:level(),
fun(() -> binary()),
list({binary(), binary()})
) -> nil.
log_lazy(Lvl, Message_fn, Fields) ->
case would_log(Lvl) of
false ->
nil;
true ->
log(Lvl, Message_fn(), Fields)
end.
-file("src/viva_telemetry/log.gleam", 214).
?DOC(" Lazy log with lazy fields - both message and fields evaluated only if needed\n").
-spec log_lazy_all(
viva_telemetry@log@level:level(),
fun(() -> binary()),
fun(() -> list({binary(), binary()}))
) -> nil.
log_lazy_all(Lvl, Message_fn, Fields_fn) ->
case would_log(Lvl) of
false ->
nil;
true ->
log(Lvl, Message_fn(), Fields_fn())
end.
-file("src/viva_telemetry/log.gleam", 226).
?DOC(" Lazy emergency\n").
-spec emergency_lazy(fun(() -> binary()), list({binary(), binary()})) -> nil.
emergency_lazy(Message_fn, Fields) ->
log_lazy(emergency, Message_fn, Fields).
-file("src/viva_telemetry/log.gleam", 234).
?DOC(" Lazy alert\n").
-spec alert_lazy(fun(() -> binary()), list({binary(), binary()})) -> nil.
alert_lazy(Message_fn, Fields) ->
log_lazy(alert, Message_fn, Fields).
-file("src/viva_telemetry/log.gleam", 242).
?DOC(" Lazy critical\n").
-spec critical_lazy(fun(() -> binary()), list({binary(), binary()})) -> nil.
critical_lazy(Message_fn, Fields) ->
log_lazy(critical, Message_fn, Fields).
-file("src/viva_telemetry/log.gleam", 250).
?DOC(" Lazy error\n").
-spec error_lazy(fun(() -> binary()), list({binary(), binary()})) -> nil.
error_lazy(Message_fn, Fields) ->
log_lazy(err, Message_fn, Fields).
-file("src/viva_telemetry/log.gleam", 258).
?DOC(" Lazy warning\n").
-spec warning_lazy(fun(() -> binary()), list({binary(), binary()})) -> nil.
warning_lazy(Message_fn, Fields) ->
log_lazy(warning, Message_fn, Fields).
-file("src/viva_telemetry/log.gleam", 266).
?DOC(" Lazy notice\n").
-spec notice_lazy(fun(() -> binary()), list({binary(), binary()})) -> nil.
notice_lazy(Message_fn, Fields) ->
log_lazy(notice, Message_fn, Fields).
-file("src/viva_telemetry/log.gleam", 274).
?DOC(" Lazy info\n").
-spec info_lazy(fun(() -> binary()), list({binary(), binary()})) -> nil.
info_lazy(Message_fn, Fields) ->
log_lazy(info, Message_fn, Fields).
-file("src/viva_telemetry/log.gleam", 288).
?DOC(
" Lazy debug - most common use case for lazy logging\n"
"\n"
" Example:\n"
" ```gleam\n"
" // String only constructed if debug level is enabled\n"
" log.debug_lazy(fn() { \"Item: \" <> int.to_string(i) }, [])\n"
" ```\n"
).
-spec debug_lazy(fun(() -> binary()), list({binary(), binary()})) -> nil.
debug_lazy(Message_fn, Fields) ->
log_lazy(debug, Message_fn, Fields).
-file("src/viva_telemetry/log.gleam", 296).
?DOC(" Lazy trace - for high-frequency logs that should be cheap when disabled\n").
-spec trace_lazy(fun(() -> binary()), list({binary(), binary()})) -> nil.
trace_lazy(Message_fn, Fields) ->
log_lazy(trace, Message_fn, Fields).
-file("src/viva_telemetry/log.gleam", 304).
?DOC(" Log with source module\n").
-spec log_from(
binary(),
viva_telemetry@log@level:level(),
binary(),
list({binary(), binary()})
) -> nil.
log_from(Source, Lvl, Message, Fields) ->
E = begin
_pipe = viva_telemetry@log@entry:new(Lvl, Message, Fields),
_pipe@1 = viva_telemetry@log@entry:with_source(_pipe, Source),
viva_telemetry@log@entry:with_context(
_pipe@1,
viva_telemetry_ffi:get_context()
)
end,
dispatch(E).
-file("src/viva_telemetry/log.gleam", 348).
?DOC(" Log with sampling (only log rate% of messages)\n").
-spec sampled(
viva_telemetry@log@level:level(),
float(),
binary(),
list({binary(), binary()})
) -> nil.
sampled(Lvl, Rate, Message, Fields) ->
case viva_telemetry_ffi:should_sample(Rate) of
true ->
log(Lvl, Message, Fields);
false ->
nil
end.