Skip to content

Commit 56a2ebc

Browse files
committed
Implement binary formatter for Logger
0 parents  commit 56a2ebc

File tree

10 files changed

+231
-0
lines changed

10 files changed

+231
-0
lines changed

.formatter.exs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Used by "mix format"
2+
[
3+
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
4+
]

.gitignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# The directory Mix will write compiled artifacts to.
2+
/_build/
3+
4+
# If you run "mix test --cover", coverage assets end up here.
5+
/cover/
6+
7+
# The directory Mix downloads your dependencies sources to.
8+
/deps/
9+
10+
# Where third-party dependencies like ExDoc output generated docs.
11+
/doc/
12+
13+
# Ignore .fetch files in case you like to edit your project deps locally.
14+
/.fetch
15+
16+
# If the VM crashes, it generates a dump, let's ignore it too.
17+
erl_crash.dump
18+
19+
# Also ignore archive artifacts (built via "mix archive.build").
20+
*.ez
21+
22+
# Ignore package tarball (built via "mix hex.build").
23+
logger_binary-*.tar
24+
25+
# Temporary files, for example, from tests.
26+
/tmp/

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# LoggerBinary
2+
3+
A custom `Logger` formatter for handling binary data.
4+
5+
This formatter is designed to correctly format binary messages. It converts
6+
non-printable binary data into a hexadecimal string representation and can
7+
optionally add a directional indicator (`"< "` or `"> "`) when needed.
8+
9+
## Usage
10+
11+
config :logger, :console,
12+
format: {LoggerBinary.Formatter, :format},
13+
metadata: [:direction]
14+
15+
## Features
16+
17+
* Formats binary messages as uppercase hexadecimal strings.
18+
* Prepends formatted binary messages with a directional indicator if the `:direction`
19+
metadata is present. Supported values for the `:direction` metadata are `:in` and `:out`.
20+
* Uses the default Logger format for all other types of messages (e.g., printable strings and charlists).
21+
22+
## Direction Indicator
23+
24+
You can add the `:direction` metadata to your log messages to indicate if the
25+
binary data is an incoming or outgoing message:
26+
27+
Logger.debug(<<0x01, 0x02, 0x03>>, direction: :in)
28+
# Logs: "[debug] < 01 02 03"
29+
30+
Logger.debug(<<0x01, 0x02, 0x03>>, direction: :out)
31+
# Logs: "[debug] > 01 02 03"
32+
33+
Without directional metadata, it simply logs the formatted binary:
34+
35+
Logger.debug(<<0x01, 0x02, 0x03>>)
36+
# Logs: "[debug] 01 02 03"
37+
38+
## Installation
39+
40+
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
41+
by adding `logger_binary` to your list of dependencies in `mix.exs`:
42+
43+
```elixir
44+
def deps do
45+
[
46+
{:logger_binary, "~> 0.1.0"}
47+
]
48+
end
49+
```
50+
51+
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
52+
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
53+
be found at <https://hexdocs.pm/logger_binary>.

config/config.exs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Config
2+
3+
config :logger, :console,
4+
format: {LoggerBinary.Formatter, :format},
5+
metadata: [:direction]

lib/logger_binary.ex

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
defmodule LoggerBinary do
2+
@moduledoc """
3+
A custom `Logger` formatter for handling binary data.
4+
5+
This formatter is designed to correctly format binary messages. It converts
6+
non-printable binary data into a hexadecimal string representation and can
7+
optionally add a directional indicator (`"< "` or `"> "`) when needed.
8+
9+
## Usage
10+
11+
config :logger, :console,
12+
format: {LoggerBinary.Formatter, :format},
13+
metadata: [:direction]
14+
15+
## Features
16+
17+
* Formats binary messages as uppercase hexadecimal strings.
18+
* Prepends formatted binary messages with a directional indicator if the `:direction`
19+
metadata is present. Supported values for the `:direction` metadata are `:in` and `:out`.
20+
* Uses the default Logger format for all other types of messages (e.g., printable strings and charlists).
21+
22+
## Direction Indicator
23+
24+
You can add the `:direction` metadata to your log messages to indicate if the
25+
binary data is an incoming or outgoing message:
26+
27+
Logger.debug(<<0x01, 0x02, 0x03>>, direction: :in)
28+
# Logs: "[debug] < 01 02 03"
29+
30+
Logger.debug(<<0x01, 0x02, 0x03>>, direction: :out)
31+
# Logs: "[debug] > 01 02 03"
32+
33+
Without directional metadata, it simply logs the formatted binary:
34+
35+
Logger.debug(<<0x01, 0x02, 0x03>>)
36+
# Logs: "[debug] 01 02 03"
37+
"""
38+
end

lib/logger_binary/formatter.ex

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
defmodule LoggerBinary.Formatter do
2+
@moduledoc false
3+
4+
@default_format Logger.Formatter.compile(
5+
Application.compile_env(:config, [:console, :format], "[$level] $message\n")
6+
)
7+
8+
def format(level, message, timestamp, metadata) when is_binary(message) do
9+
message =
10+
if String.printable?(message) do
11+
message
12+
else
13+
formatted_message =
14+
message
15+
|> Base.encode16(case: :upper)
16+
|> String.graphemes()
17+
|> Enum.chunk_every(2)
18+
|> Enum.map_join(" ", &Enum.join(&1))
19+
20+
case Keyword.get(metadata, :direction) do
21+
:in -> "< " <> formatted_message
22+
:out -> "> " <> formatted_message
23+
_ -> formatted_message
24+
end
25+
end
26+
27+
Logger.Formatter.format(@default_format, level, message, timestamp, metadata)
28+
end
29+
30+
def format(level, message, timestamp, metadata) do
31+
Logger.Formatter.format(@default_format, level, message, timestamp, metadata)
32+
end
33+
end

mix.exs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
defmodule LoggerBinary.MixProject do
2+
use Mix.Project
3+
4+
def project do
5+
[
6+
app: :logger_binary,
7+
version: "0.1.0",
8+
elixir: "~> 1.16",
9+
start_permanent: Mix.env() == :prod,
10+
deps: deps()
11+
]
12+
end
13+
14+
# Run "mix help compile.app" to learn about applications.
15+
def application do
16+
[
17+
extra_applications: [:logger]
18+
]
19+
end
20+
21+
# Run "mix help deps" to learn about dependencies.
22+
defp deps do
23+
[
24+
{:ex_doc, "~> 0.36.1", only: :dev, runtime: false}
25+
]
26+
end
27+
end

mix.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
%{
2+
"earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"},
3+
"ex_doc": {:hex, :ex_doc, "0.36.1", "4197d034f93e0b89ec79fac56e226107824adcce8d2dd0a26f5ed3a95efc36b1", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d7d26a7cf965dacadcd48f9fa7b5953d7d0cfa3b44fa7a65514427da44eafd89"},
4+
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
5+
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
6+
"makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"},
7+
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
8+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
defmodule LoggerBinary.FormatterTest do
2+
use ExUnit.Case, async: true
3+
4+
require Logger
5+
import ExUnit.CaptureLog
6+
7+
test "formats binary data" do
8+
assert capture_log(fn ->
9+
Logger.debug(<<0x01, 0x02, 0x03>>)
10+
end) =~ "[debug] 01 02 03"
11+
end
12+
13+
test "formats binary input" do
14+
assert capture_log(fn ->
15+
Logger.debug(<<0x01, 0x02, 0x03>>, direction: :in)
16+
end) =~ "[debug] < 01 02 03"
17+
end
18+
19+
test "formats binary output" do
20+
assert capture_log(fn ->
21+
Logger.debug(<<0x01, 0x02, 0x03>>, direction: :out)
22+
end) =~ "[debug] > 01 02 03"
23+
end
24+
25+
test "ignores any other binary message" do
26+
assert capture_log(fn ->
27+
Logger.debug("message")
28+
end) =~ "[debug] message"
29+
end
30+
31+
test "ignores charlist messages" do
32+
assert capture_log(fn ->
33+
Logger.debug(~c"message")
34+
end) =~ "[debug] message"
35+
end
36+
end

test/test_helper.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ExUnit.start()

0 commit comments

Comments
 (0)