Skip to content

Commit 1a72c79

Browse files
committed
Do not write warnings when telemetry, Ecto or Plug are not available
1 parent 09f8164 commit 1a72c79

File tree

5 files changed

+87
-82
lines changed

5 files changed

+87
-82
lines changed

lib/logger_json/ecto.ex

Lines changed: 80 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,96 @@
1-
defmodule LoggerJSON.Ecto do
2-
@moduledoc """
3-
A telemetry handler that logs Ecto query metrics in JSON format.
1+
if Code.ensure_loaded?(Ecto) and Code.ensure_loaded?(:telemetry) do
2+
defmodule LoggerJSON.Ecto do
3+
@moduledoc """
4+
A telemetry handler that logs Ecto query metrics in JSON format.
45
5-
This module is not recommended to be used in production, as it can be
6-
costly to log every single database query.
7-
"""
8-
require Logger
6+
This module is not recommended to be used in production, as it can be
7+
costly to log every single database query.
8+
"""
9+
require Logger
910

10-
@doc """
11-
Attaches the telemetry handler to the given event.
11+
@doc """
12+
Attaches the telemetry handler to the given event.
1213
13-
### Available options
14+
### Available options
1415
15-
* `:level` - log level which is used to log requests. Defaults to `:info`.
16+
* `:level` - log level which is used to log requests. Defaults to `:info`.
1617
17-
### Dynamic log level
18+
### Dynamic log level
1819
19-
In some cases you may wish to set the log level dynamically
20-
on a per-query basis. To do so, set the `:level` option to
21-
a tuple, `{Mod, Fun, Args}`. The query and map of time measures
22-
will be prepended to the provided list of arguments.
20+
In some cases you may wish to set the log level dynamically
21+
on a per-query basis. To do so, set the `:level` option to
22+
a tuple, `{Mod, Fun, Args}`. The query and map of time measures
23+
will be prepended to the provided list of arguments.
2324
24-
When invoked, your function must return a
25-
[`Logger.level()`](`t:Logger.level()/0`) or `false` to
26-
disable logging for the request.
25+
When invoked, your function must return a
26+
[`Logger.level()`](`t:Logger.level()/0`) or `false` to
27+
disable logging for the request.
2728
28-
### Examples
29+
### Examples
2930
30-
Attaching the telemetry handler to the `MyApp.Repo` events with the `:info` log level:
31+
Attaching the telemetry handler to the `MyApp.Repo` events with the `:info` log level:
3132
32-
LoggerJSON.Ecto.attach("logger-json-queries", [:my_app, :repo, :query], :info)
33+
LoggerJSON.Ecto.attach("logger-json-queries", [:my_app, :repo, :query], :info)
3334
34-
For more details on event and handler naming see
35-
(`Ecto.Repo` documentation)[https://hexdocs.pm/ecto/Ecto.Repo.html#module-telemetry-events].
36-
"""
37-
@spec attach(
38-
name :: String.t(),
39-
event :: [atom()],
40-
level ::
41-
Logger.level()
42-
| {module :: module(), function :: atom(), arguments :: [term()]}
43-
| false
44-
) :: :ok | {:error, :already_exists}
45-
def attach(name, event, level) do
46-
:telemetry.attach(name, event, &__MODULE__.telemetry_logging_handler/4, level)
47-
end
35+
For more details on event and handler naming see
36+
(`Ecto.Repo` documentation)[https://hexdocs.pm/ecto/Ecto.Repo.html#module-telemetry-events].
37+
"""
38+
@spec attach(
39+
name :: String.t(),
40+
event :: [atom()],
41+
level ::
42+
Logger.level()
43+
| {module :: module(), function :: atom(), arguments :: [term()]}
44+
| false
45+
) :: :ok | {:error, :already_exists}
46+
def attach(name, event, level) do
47+
:telemetry.attach(name, event, &__MODULE__.telemetry_logging_handler/4, level)
48+
end
4849

49-
@doc """
50-
A telemetry handler that logs Ecto query along with it's metrics in a structured format.
51-
"""
52-
@spec telemetry_logging_handler(
53-
event_name :: [atom()],
54-
measurements :: %{
55-
query_time: non_neg_integer(),
56-
queue_time: non_neg_integer(),
57-
decode_time: non_neg_integer(),
58-
total_time: non_neg_integer()
59-
},
60-
metadata :: %{required(:query) => String.t(), required(:repo) => module()},
61-
level ::
62-
Logger.level()
63-
| {module :: module(), function :: atom(), arguments :: [term()]}
64-
| false
65-
) :: :ok
66-
def telemetry_logging_handler(_event_name, measurements, %{query: query, repo: repo}, level) do
67-
query_time = Map.get(measurements, :query_time) |> format_time(:nanosecond)
68-
queue_time = Map.get(measurements, :queue_time) |> format_time(:nanosecond)
69-
decode_time = Map.get(measurements, :decode_time) |> format_time(:nanosecond)
70-
latency = Map.get(measurements, :total_time) |> format_time(:nanosecond)
71-
72-
metadata = [
73-
query: %{
74-
repo: inspect(repo),
75-
execution_time_μs: query_time,
76-
decode_time_μs: decode_time,
77-
queue_time_μs: queue_time,
78-
latency_μs: latency
79-
}
80-
]
81-
82-
if level = level(level, query, measurements) do
83-
Logger.log(level, query, metadata)
84-
else
85-
:ok
50+
@doc """
51+
A telemetry handler that logs Ecto query along with it's metrics in a structured format.
52+
"""
53+
@spec telemetry_logging_handler(
54+
event_name :: [atom()],
55+
measurements :: %{
56+
query_time: non_neg_integer(),
57+
queue_time: non_neg_integer(),
58+
decode_time: non_neg_integer(),
59+
total_time: non_neg_integer()
60+
},
61+
metadata :: %{required(:query) => String.t(), required(:repo) => module()},
62+
level ::
63+
Logger.level()
64+
| {module :: module(), function :: atom(), arguments :: [term()]}
65+
| false
66+
) :: :ok
67+
def telemetry_logging_handler(_event_name, measurements, %{query: query, repo: repo}, level) do
68+
query_time = Map.get(measurements, :query_time) |> format_time(:nanosecond)
69+
queue_time = Map.get(measurements, :queue_time) |> format_time(:nanosecond)
70+
decode_time = Map.get(measurements, :decode_time) |> format_time(:nanosecond)
71+
latency = Map.get(measurements, :total_time) |> format_time(:nanosecond)
72+
73+
metadata = [
74+
query: %{
75+
repo: inspect(repo),
76+
execution_time_μs: query_time,
77+
decode_time_μs: decode_time,
78+
queue_time_μs: queue_time,
79+
latency_μs: latency
80+
}
81+
]
82+
83+
if level = level(level, query, measurements) do
84+
Logger.log(level, query, metadata)
85+
else
86+
:ok
87+
end
8688
end
87-
end
8889

89-
defp level({m, f, a}, query, measurements), do: apply(m, f, [query, measurements | a])
90-
defp level(level, _query, _measurements) when is_atom(level), do: level
90+
defp level({m, f, a}, query, measurements), do: apply(m, f, [query, measurements | a])
91+
defp level(level, _query, _measurements) when is_atom(level), do: level
9192

92-
defp format_time(nil, _unit), do: 0
93-
defp format_time(time, unit), do: System.convert_time_unit(time, unit, :microsecond)
93+
defp format_time(nil, _unit), do: 0
94+
defp format_time(time, unit), do: System.convert_time_unit(time, unit, :microsecond)
95+
end
9496
end

lib/logger_json/formatters/basic.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ defmodule LoggerJSON.Formatters.Basic do
1414
"time" => "2024-04-11T21:31:01.403Z"
1515
}
1616
"""
17-
import Jason.Helpers, only: [json_map: 1]
1817
import LoggerJSON.Formatter.{MapBuilder, DateTime, Message, Metadata, RedactorEncoder}
1918

2019
@behaviour LoggerJSON.Formatter
@@ -72,6 +71,8 @@ defmodule LoggerJSON.Formatters.Basic do
7271
end
7372

7473
if Code.ensure_loaded?(Plug.Conn) do
74+
import Jason.Helpers, only: [json_map: 1]
75+
7576
defp format_http_request(%{conn: %Plug.Conn{} = conn}) do
7677
json_map(
7778
connection:

lib/logger_json/formatters/datadog.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ defmodule LoggerJSON.Formatters.Datadog do
4141
}
4242
}
4343
"""
44-
import Jason.Helpers, only: [json_map: 1]
4544
import LoggerJSON.Formatter.{MapBuilder, DateTime, Message, Metadata, Code, RedactorEncoder}
4645

4746
@behaviour LoggerJSON.Formatter
@@ -195,6 +194,8 @@ defmodule LoggerJSON.Formatters.Datadog do
195194
defp safe_chardata_to_string(other), do: other
196195

197196
if Code.ensure_loaded?(Plug.Conn) do
197+
import Jason.Helpers, only: [json_map: 1]
198+
198199
defp format_http_request(%{conn: %Plug.Conn{} = conn} = meta) do
199200
request_url = Plug.Conn.request_url(conn)
200201
user_agent = LoggerJSON.Formatter.Plug.get_header(conn, "user-agent")

lib/logger_json/formatters/elastic.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ defmodule LoggerJSON.Formatters.Elastic do
9696
}
9797
```
9898
"""
99-
import Jason.Helpers, only: [json_map: 1]
10099
import LoggerJSON.Formatter.{MapBuilder, DateTime, Message, Metadata, RedactorEncoder}
101100

102101
@behaviour LoggerJSON.Formatter
@@ -223,6 +222,8 @@ defmodule LoggerJSON.Formatters.Elastic do
223222
defp format_logger_fields(_meta), do: nil
224223

225224
if Code.ensure_loaded?(Plug.Conn) do
225+
import Jason.Helpers, only: [json_map: 1]
226+
226227
# See the formats for the following fields in ECS:
227228
# - client.ip: https://www.elastic.co/guide/en/ecs/8.11/ecs-client.html
228229
# - http.*: https://www.elastic.co/guide/en/ecs/8.11/ecs-http.html

lib/logger_json/plug.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
if Code.ensure_loaded?(Plug) do
1+
if Code.ensure_loaded?(Plug) and Code.ensure_loaded?(:telemetry) do
22
defmodule LoggerJSON.Plug do
33
@moduledoc """
44
A telemetry handler that logs request information in JSON format.

0 commit comments

Comments
 (0)