Skip to content

Commit c26acb9

Browse files
Add one-line logs with time (#392)
* Add on-line logs with time * Fix typo * Left assign * Fix format * Apply suggestions: add try/rescue and improve testing * Raise only on GRPC errors * Improve logging and recover deleted file * chore: changes due to code review --------- Co-authored-by: Paulo Valente <[email protected]>
1 parent 987bcce commit c26acb9

File tree

2 files changed

+123
-22
lines changed

2 files changed

+123
-22
lines changed

Diff for: lib/grpc/client/interceptors/logger.ex

+65-20
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ defmodule GRPC.Client.Interceptors.Logger do
22
@moduledoc """
33
Print log around client rpc calls, like
44
5-
17:13:33.021 [info] Call say_hello of helloworld.Greeter
6-
17:13:33.079 [info] Got :ok in 58ms
5+
17:13:33.021 [info] Call helloworld.Greeter.say_hello -> :ok (58 ms)
6+
17:13:33.021 [error] Call helloworld.Greeter.say_hello -> %GRPC.RPCError{status: 3, message: "Invalid argument"} (58 ms)
77
88
## Options
99
@@ -18,42 +18,87 @@ defmodule GRPC.Client.Interceptors.Logger do
1818
{:ok, channel} = GRPC.Stub.connect("localhost:50051", interceptors: [{GRPC.Client.Interceptors.Logger, level: :warning}])
1919
"""
2020

21-
require Logger
22-
2321
@behaviour GRPC.Client.Interceptor
2422

25-
@impl true
23+
require Logger
24+
25+
@impl GRPC.Client.Interceptor
2626
def init(opts) do
2727
level = Keyword.get(opts, :level) || :info
2828
[level: level]
2929
end
3030

31-
@impl true
31+
@impl GRPC.Client.Interceptor
3232
def call(%{grpc_type: grpc_type} = stream, req, next, opts) do
3333
level = Keyword.fetch!(opts, :level)
3434

3535
if Logger.compare_levels(level, Logger.level()) != :lt do
36-
Logger.log(level, fn ->
37-
["Call ", to_string(elem(stream.rpc, 0)), " of ", stream.service_name]
38-
end)
39-
4036
start = System.monotonic_time()
41-
result = next.(stream, req)
42-
stop = System.monotonic_time()
4337

44-
if grpc_type == :unary do
45-
status = elem(result, 0)
38+
try do
39+
result = next.(stream, req)
40+
stop = System.monotonic_time()
4641

47-
Logger.log(level, fn ->
48-
diff = System.convert_time_unit(stop - start, :native, :microsecond)
42+
log_result(result, level, grpc_type, stream, start, stop)
43+
result
44+
rescue
45+
error in GRPC.RPCError ->
46+
stop = System.monotonic_time()
47+
log_error(error, stream, start, stop)
4948

50-
["Got ", inspect(status), " in ", GRPC.Server.Interceptors.Logger.formatted_diff(diff)]
51-
end)
49+
reraise error, __STACKTRACE__
5250
end
53-
54-
result
5551
else
5652
next.(stream, req)
5753
end
5854
end
55+
56+
defp log_error(error, stream, start, stop) do
57+
diff = System.convert_time_unit(stop - start, :native, :microsecond)
58+
59+
Logger.log(:error, fn ->
60+
[
61+
"Call ",
62+
stream.service_name,
63+
".",
64+
to_string(elem(stream.rpc, 0)),
65+
" -> ",
66+
inspect(error),
67+
" (",
68+
formatted_diff(diff),
69+
")"
70+
]
71+
end)
72+
end
73+
74+
defp log_result(result, level, grpc_type, stream, start, stop) do
75+
case grpc_type do
76+
:unary ->
77+
status = elem(result, 0)
78+
79+
diff = System.convert_time_unit(stop - start, :native, :microsecond)
80+
81+
Logger.log(level, fn ->
82+
[
83+
"Call ",
84+
stream.service_name,
85+
".",
86+
to_string(elem(stream.rpc, 0)),
87+
" -> ",
88+
inspect(status),
89+
" (",
90+
formatted_diff(diff),
91+
")"
92+
]
93+
end)
94+
95+
_otherwise ->
96+
Logger.log(level, fn ->
97+
["Call ", to_string(elem(stream.rpc, 0)), " of ", stream.service_name]
98+
end)
99+
end
100+
end
101+
102+
def formatted_diff(diff) when diff > 1000, do: [diff |> div(1000) |> Integer.to_string(), "ms"]
103+
def formatted_diff(diff), do: [Integer.to_string(diff), "µs"]
59104
end

Diff for: test/grpc/client/interceptors/logger_test.exs

+58-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ defmodule GRPC.Client.Interceptors.LoggerTest do
3131
LoggerInterceptor.call(stream, request, next, opts)
3232
end)
3333

34-
assert logs =~ ~r/\[info\]\s+Call #{to_string(elem(@rpc, 0))} of #{@service_name}/
34+
assert logs =~
35+
~r/\[info\]\s+Call #{@service_name}\.#{to_string(elem(@rpc, 0))} -> :ok \(\d+\.\d+ ms|µs\)/
3536
end
3637

3738
test "allows customizing log level" do
@@ -47,7 +48,25 @@ defmodule GRPC.Client.Interceptors.LoggerTest do
4748
LoggerInterceptor.call(stream, request, next, opts)
4849
end)
4950

50-
assert logs =~ ~r/\[warn(?:ing)?\]\s+Call #{to_string(elem(@rpc, 0))} of #{@service_name}/
51+
assert logs =~
52+
~r/\[warn(?:ing)?\]\s+Call #{@service_name}\.#{to_string(elem(@rpc, 0))} -> :ok \(\d+\.\d+ ms|µs\)/
53+
end
54+
55+
test "logs stream requests" do
56+
Logger.configure(level: :all)
57+
58+
request = %FakeRequest{}
59+
stream = %Stream{grpc_type: :client_stream, rpc: @rpc, service_name: @service_name}
60+
next = fn _stream, _request -> {:ok, :stream} end
61+
opts = LoggerInterceptor.init([])
62+
63+
logs =
64+
capture_log(fn ->
65+
LoggerInterceptor.call(stream, request, next, opts)
66+
end)
67+
68+
assert logs =~
69+
~r/\[info\]\s+Call #{to_string(elem(@rpc, 0))} of #{@service_name}/
5170
end
5271

5372
@tag capture_log: true
@@ -76,4 +95,41 @@ defmodule GRPC.Client.Interceptors.LoggerTest do
7695

7796
assert_receive {:next_called, ^stream, ^request}
7897
end
98+
99+
test "logs error when next raises" do
100+
Logger.configure(level: :all)
101+
102+
request = %FakeRequest{}
103+
stream = %Stream{grpc_type: :unary, rpc: @rpc, service_name: @service_name}
104+
next = fn _stream, _request -> raise GRPC.RPCError, status: :invalid_argument end
105+
opts = LoggerInterceptor.init(level: :info)
106+
107+
assert_raise(GRPC.RPCError, fn ->
108+
logs =
109+
capture_log(fn ->
110+
LoggerInterceptor.call(stream, request, next, opts)
111+
end)
112+
113+
assert logs =~
114+
~r/\[error\]\s+Call #{@service_name}\.#{to_string(elem(@rpc, 0))} -> %GRPC.RPCError{status: 3, message: "Client specified an invalid argument"}/
115+
end)
116+
end
117+
118+
test "does not log when error is not a GRPC.RPCError" do
119+
Logger.configure(level: :all)
120+
121+
request = %FakeRequest{}
122+
stream = %Stream{grpc_type: :unary, rpc: @rpc, service_name: @service_name}
123+
next = fn _stream, _request -> raise "oops" end
124+
opts = LoggerInterceptor.init(level: :info)
125+
126+
assert_raise(RuntimeError, fn ->
127+
logs =
128+
capture_log(fn ->
129+
LoggerInterceptor.call(stream, request, next, opts)
130+
end)
131+
132+
assert logs == ""
133+
end)
134+
end
79135
end

0 commit comments

Comments
 (0)