Skip to main content

src/witness.erl

-module(witness).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/witness.gleam").
-export([bool/2, float/2, int/2, string/2, default_config/0, this/3, level_to_short_string/1, set_config/1, empty_config/0, with_sink/3, with_global_fields/2, set_process_fields/1, add_process_fields/1]).
-export_type([field/0, config/0, format/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.

-opaque field() :: {w_string, binary(), binary()} |
    {w_int, binary(), integer()} |
    {w_float, binary(), float()} |
    {w_bool, binary(), boolean()}.

-opaque config() :: {config,
        gleam@dict:dict(format(), list(fun((logging:log_level(), binary()) -> nil))),
        list(field())}.

-type format() :: json | text.

-file("src/witness.gleam", 216).
?DOC(
    " Create a bool field\n"
    "\n"
    " # Example\n"
    " \n"
    " ```gleam\n"
    " witness.this(logging.Info, \"Things happened\", [witness.bool(\"really\", True)])\n"
    " ```\n"
).
-spec bool(binary(), boolean()) -> field().
bool(Name, Value) ->
    {w_bool, Name, Value}.

-file("src/witness.gleam", 204).
?DOC(
    " Create a float field\n"
    "\n"
    " # Example\n"
    " \n"
    " ```gleam\n"
    " witness.this(logging.Info, \"Things happened\", [witness.float(\"how_many\", 3.14159)])\n"
    " ```\n"
).
-spec float(binary(), float()) -> field().
float(Name, Value) ->
    {w_float, Name, Value}.

-file("src/witness.gleam", 192).
?DOC(
    " Create an int field\n"
    "\n"
    " # Example\n"
    " \n"
    " ```gleam\n"
    " witness.this(logging.Info, \"Things happened\", [witness.int(\"how_many\", 21)])\n"
    " ```\n"
).
-spec int(binary(), integer()) -> field().
int(Name, Value) ->
    {w_int, Name, Value}.

-file("src/witness.gleam", 155).
-spec fields_to_string(field()) -> binary().
fields_to_string(Field) ->
    case Field of
        {w_string, Name, Value} ->
            <<<<Name/binary, ": "/utf8>>/binary, Value/binary>>;

        {w_int, Name@1, Value@1} ->
            <<<<Name@1/binary, ": "/utf8>>/binary,
                (erlang:integer_to_binary(Value@1))/binary>>;

        {w_float, Name@2, Value@2} ->
            <<<<Name@2/binary, ": "/utf8>>/binary,
                (gleam_stdlib:float_to_string(Value@2))/binary>>;

        {w_bool, Name@3, Value@3} ->
            <<<<Name@3/binary, ": "/utf8>>/binary,
                (gleam@bool:to_string(Value@3))/binary>>
    end.

-file("src/witness.gleam", 180).
?DOC(
    " Create a string field\n"
    "\n"
    " # Example\n"
    " \n"
    " ```gleam\n"
    " witness.this(logging.Info, \"Something happened\", [witness.string(\"what\", \"i dont know, honestly\")])\n"
    " ```\n"
).
-spec string(binary(), binary()) -> field().
string(Name, Value) ->
    {w_string, Name, Value}.

-file("src/witness.gleam", 103).
-spec timestamp_to_short_string(gleam@time@timestamp:timestamp()) -> binary().
timestamp_to_short_string(Timestamp) ->
    {_, {time_of_day, Hours, Minutes, Seconds, _}} = gleam@time@timestamp:to_calendar(
        Timestamp,
        gleam@time@calendar:local_offset()
    ),
    <<<<<<<<(gleam@string:pad_start(
                        erlang:integer_to_binary(Hours),
                        2,
                        <<"0"/utf8>>
                    ))/binary,
                    ":"/utf8>>/binary,
                (gleam@string:pad_start(
                    erlang:integer_to_binary(Minutes),
                    2,
                    <<"0"/utf8>>
                ))/binary>>/binary,
            ":"/utf8>>/binary,
        (gleam@string:pad_start(
            erlang:integer_to_binary(Seconds),
            2,
            <<"0"/utf8>>
        ))/binary>>.

-file("src/witness.gleam", 146).
-spec field_to_json(field()) -> {binary(), gleam@json:json()}.
field_to_json(Field) ->
    case Field of
        {w_string, Name, Value} ->
            {Name, gleam@json:string(Value)};

        {w_int, Name@1, Value@1} ->
            {Name@1, gleam@json:int(Value@1)};

        {w_float, Name@2, Value@2} ->
            {Name@2, gleam@json:float(Value@2)};

        {w_bool, Name@3, Value@3} ->
            {Name@3, gleam@json:bool(Value@3)}
    end.

-file("src/witness.gleam", 114).
-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 ->
            <<"warning"/utf8>>;

        notice ->
            <<"notice"/utf8>>;

        info ->
            <<"info"/utf8>>;

        debug ->
            <<"debug"/utf8>>
    end.

-file("src/witness.gleam", 55).
-spec format_message(logging:log_level(), format(), binary(), list(field())) -> binary().
format_message(Level, Format, Message, Fields) ->
    case Format of
        json ->
            _pipe = [string(
                    <<"timestamp"/utf8>>,
                    gleam@time@timestamp:to_rfc3339(
                        gleam@time@timestamp:system_time(),
                        {duration, 0, 0}
                    )
                ),
                string(<<"level"/utf8>>, level_to_string(Level)),
                string(<<"message"/utf8>>, Message) |
                Fields],
            _pipe@1 = gleam@list:map(_pipe, fun field_to_json/1),
            _pipe@2 = gleam@json:object(_pipe@1),
            gleam@json:to_string(_pipe@2);

        text ->
            Message@1 = <<<<(timestamp_to_short_string(
                        gleam@time@timestamp:system_time()
                    ))/binary,
                    " "/utf8>>/binary,
                Message/binary>>,
            _pipe@3 = Fields,
            gleam@list:fold(
                _pipe@3,
                Message@1,
                fun(Acc, Field) ->
                    <<<<Acc/binary, "\n  "/utf8>>/binary,
                        (fields_to_string(Field))/binary>>
                end
            )
    end.

-file("src/witness.gleam", 230).
-spec upsert_sink(fun((logging:log_level(), binary()) -> nil)) -> fun((gleam@option:option(list(fun((logging:log_level(), binary()) -> nil)))) -> list(fun((logging:log_level(), binary()) -> nil))).
upsert_sink(Sink) ->
    fun(X) -> case X of
            {some, X@1} ->
                [Sink | X@1];

            none ->
                [Sink]
        end end.

-file("src/witness.gleam", 224).
?DOC(" The default config logging to `io.println` with format `witness.Text`\n").
-spec default_config() -> config().
default_config() ->
    _pipe = maps:new(),
    _pipe@1 = gleam@dict:upsert(
        _pipe,
        text,
        upsert_sink(fun(_, Message) -> gleam_stdlib:println(Message) end)
    ),
    {config, _pipe@1, []}.

-file("src/witness.gleam", 31).
-spec this(logging:log_level(), binary(), list(field())) -> nil.
this(Level, Message, Fields) ->
    Config = witness_ffi:get_config(default_config()),
    Fields@1 = lists:append(erlang:element(3, Config), Fields),
    Fields@2 = lists:append(witness_ffi:get_process_fields([]), Fields@1),
    _pipe = erlang:element(2, Config),
    gleam@dict:each(
        _pipe,
        fun(Format, Sinks) ->
            Message@1 = format_message(Level, Format, Message, Fields@2),
            gleam@list:map(Sinks, fun(Sink) -> Sink(Level, Message@1) end)
        end
    ),
    nil.

-file("src/witness.gleam", 90).
?DOC(" Turn a log level into a \"[LEVEL]\" string\n").
-spec level_to_short_string(logging:log_level()) -> binary().
level_to_short_string(Level) ->
    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.

-file("src/witness.gleam", 285).
?DOC(
    " Set your config globally\n"
    "\n"
    " # Example\n"
    "\n"
    " ```gleam\n"
    " witness.empty_config()\n"
    " |> with_sink(witness.Json, logging.log)\n"
    " |> witness.set_config\n"
    " ```\n"
).
-spec set_config(config()) -> nil.
set_config(Config) ->
    witness_ffi:set_config(Config).

-file("src/witness.gleam", 297).
?DOC(
    " Get a new empty config \n"
    "\n"
    " # Example\n"
    "\n"
    " ```gleam\n"
    " witness.empty_config()\n"
    " |> with_sink(witness.Json, logging.log)\n"
    " |> witness.set_config\n"
    " ```\n"
).
-spec empty_config() -> config().
empty_config() ->
    {config, maps:new(), []}.

-file("src/witness.gleam", 314).
?DOC(
    " Add a sink with a specific formatting to a config\n"
    "\n"
    " # Example\n"
    "\n"
    " ```gleam\n"
    " witness.empty_config()\n"
    " |> with_sink(witness.Json, logging.log)\n"
    " |> witness.set_config\n"
    " ```\n"
).
-spec with_sink(config(), format(), fun((logging:log_level(), binary()) -> nil)) -> config().
with_sink(Config, Format, Sink) ->
    Sinks = begin
        _pipe = erlang:element(2, Config),
        gleam@dict:upsert(_pipe, Format, upsert_sink(Sink))
    end,
    {config, Sinks, erlang:element(3, Config)}.

-file("src/witness.gleam", 338).
?DOC(
    " These fields will be prepended to every message logged through witness.\n"
    " Does not replace fields from previous calls.\n"
    "\n"
    " # Example\n"
    "\n"
    " ```gleam \n"
    "\n"
    " witness.empty_config()\n"
    " |> with_sink(witness.Json, logging.log)\n"
    " |> witness.with_global_fields([\n"
    "   witness.string(\"application\", \"my_application\"),\n"
    "   witness.string(\"version\", \"1.0.0\"),\n"
    " ])\n"
    " |> witness.set_config\n"
    " ```\n"
).
-spec with_global_fields(config(), list(field())) -> config().
with_global_fields(Config, Fields) ->
    Global_fields = lists:append(erlang:element(3, Config), Fields),
    {config, erlang:element(2, Config), Global_fields}.

-file("src/witness.gleam", 358).
?DOC(
    " Put per process fields into the process dictionary.\n"
    " Overwrites all previously set fields for the process.\n"
    "\n"
    " These fields will be prepended to every message that process logs.\n"
    "\n"
    " # Example\n"
    "\n"
    " ```gleam \n"
    " witness.set_process_fields([\n"
    "   witness.string(\"process_name\", \"my special process\"),\n"
    " ])\n"
    " ```\n"
).
-spec set_process_fields(list(field())) -> nil.
set_process_fields(Fields) ->
    witness_ffi:set_process_fields(Fields).

-file("src/witness.gleam", 373).
?DOC(
    " Add per process fields into the process dictionary.\n"
    " Does not replace fields from previous calls.\n"
    "\n"
    " These fields will be prepended to every message that process logs.\n"
    "\n"
    " # Example\n"
    "\n"
    " ```gleam \n"
    " witness.set_process_fields([\n"
    "   witness.string(\"process_name\", \"my special process\"),\n"
    " ])\n"
    " ```\n"
).
-spec add_process_fields(list(field())) -> nil.
add_process_fields(Fields) ->
    _pipe = lists:append(witness_ffi:get_process_fields([]), Fields),
    witness_ffi:set_process_fields(_pipe).