View Source LoggerJSON.Formatters.Elastic (logger_json v6.0.3)

Custom Erlang's :logger formatter which writes logs in a JSON-structured format that conforms to the Elastic Common Schema (ECS), so it can be consumed by ElasticSearch, LogStash, FileBeat and Kibana.

Formatter Configuration

For list of options see "Shared options" in LoggerJSON.

Metadata

For list of other well-known metadata keys see "Metadata" in LoggerJSON.

Any custom metadata that you set with Logger.metadata/1 will be included top-level in the log entry.

Examples

Example of an info log (Logger.info("Hello") without any metadata):

%{
  "@timestamp" => "2024-05-17T16:20:00.000Z",
  "ecs.version" => "8.11.0",
  "log.level" => "info",
  "log.logger" => "Elixir.LoggerJSON.Formatters.ElasticTest",
  "log.origin" => %{
    "file.name" => "/app/logger_json/test/formatters/elastic_test.exs",
    "file.line" => 18,
    "function" => "test logs an LogEntry of every level/1"
  },
  "message" => "Hello"
}

Example of logging by keywords or by map (Logger.info(%{message: "Hello", foo: :bar, fiz: %{buz: "buz"}})). The keywords or map items are added to the top-level of the log entry:

%{
  "@timestamp" => "2024-05-17T16:20:00.000Z",
  "ecs.version" => "8.11.0",
  "fiz" => %{"buz" => "buz"},
  "foo" => "bar",
  "log.level" => "debug",
  "log.logger" => "Elixir.LoggerJSON.Formatters.ElasticTest",
  "log.origin" => %{
    "file.line" => 68,
    "file.name" => "/app/logger_json/test/formatters/elastic_test.exs",
    "function" => "test logs an LogEntry with a map payload containing message/1"
  },
  "message" => "Hello"
}

Example of logging due to raising an exception (raise RuntimeError):

%{
  "@timestamp" => "2024-05-17T16:20:00.000Z",
  "ecs.version" => "8.11.0",
  "error.message" => "runtime error",
  "error.stack_trace" => "** (RuntimeError) runtime error
    test/logger_json/formatters/elastic_test.exs:191: anonymous fn/0 in LoggerJSON.Formatters.ElasticTest."test logs exceptions"/1
",
  "error.type" => "Elixir.RuntimeError",
  "log.level" => "error",
  "message" => "Process #PID<0.322.0> raised an exception
** (RuntimeError) runtime error
    test/logger_json/formatters/elastic_test.exs:191: anonymous fn/0 in LoggerJSON.Formatters.ElasticTest."test logs exceptions"/1"
}

Note that if you raise an exception that contains an id or a code property, they will be included in the log entry as error.id and error.code respectively.

Example:

defmodule TestException do
  defexception [:message, :id, :code]
end

...

raise TestException, id: :oops_id, code: 42, message: "oops!"

results in:

%{
  "@timestamp" => "2024-05-17T16:20:00.000Z",
  "ecs.version" => "8.11.0",
  "error.code" => 42,
  "error.id" => "oops_id",
  "error.message" => "oops!",
  "error.stack_trace" => "** (LoggerJSON.Formatters.ElasticTest.TestException) oops!
    test/logger_json/formatters/elastic_test.exs:223: anonymous fn/0 in LoggerJSON.Formatters.ElasticTest."test logs exceptions with id and code"/1
",
  "error.type" => "Elixir.LoggerJSON.Formatters.ElasticTest.TestException",
  "log.level" => "error",
  "message" => "Process #PID<0.325.0> raised an exception
** (LoggerJSON.Formatters.ElasticTest.TestException) oops!
    test/logger_json/formatters/elastic_test.exs:223: anonymous fn/0 in LoggerJSON.Formatters.ElasticTest."test logs exceptions with id and code"/1"
}

You can also choose to log caught exceptions with a custom message. For example, after catching an exception with try/rescue:

try do
  raise "oops"
rescue
  e in RuntimeError -> Logger.error("Something went wrong", crash_reason: {e, __STACKTRACE__})
end

then you'll get a message like:

%{
  "@timestamp" => "2024-05-17T16:20:00.000Z",
  "ecs.version" => "8.11.0",
  "error.message" => "oops",
  "error.stack_trace" => "** (RuntimeError) oops
    test/logger_json/formatters/elastic_test.exs:421: anonymous fn/0 in LoggerJSON.Formatters.ElasticTest."test logs caught errors"/1
    (logger_json 6.0.2) test/support/logger_case.ex:16: anonymous fn/1 in LoggerJSON.Case.capture_log/2
    (ex_unit 1.16.3) lib/ex_unit/capture_io.ex:258: ExUnit.CaptureIO.do_with_io/3
    (ex_unit 1.16.3) lib/ex_unit/capture_io.ex:134: ExUnit.CaptureIO.capture_io/2
    (logger_json 6.0.2) test/support/logger_case.ex:15: LoggerJSON.Case.capture_log/2
    test/logger_json/formatters/elastic_test.exs:419: LoggerJSON.Formatters.ElasticTest."test logs caught errors"/1
    (ex_unit 1.16.3) lib/ex_unit/runner.ex:472: ExUnit.Runner.exec_test/2
    (stdlib 5.2.3) timer.erl:270: :timer.tc/2
    (ex_unit 1.16.3) lib/ex_unit/runner.ex:394: anonymous fn/6 in ExUnit.Runner.spawn_test_monitor/4
",
  "error.type" => "Elixir.RuntimeError",
  "log.level" => "error",
  "log.logger" => "Elixir.LoggerJSON.Formatters.ElasticTest",
  "log.origin" => %{
    "file.line" => 423,
    "file.name" => "/app/logger_json/test/logger_json/formatters/elastic_test.exs",
    "function" => "test logs caught errors/1"
  },
  "message" => "Something went wrong"
}