Skip to content
This repository was archived by the owner on Dec 8, 2020. It is now read-only.

Commit 66d47a0

Browse files
Merge pull request #31 from timberio/features/support-phoenix-instrumentation
Add Phoenix instrumentation
2 parents b086f5f + 2d86bb3 commit 66d47a0

File tree

6 files changed

+209
-3
lines changed

6 files changed

+209
-3
lines changed

README.md

+29
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,35 @@ existing pipeline or pass multiple pipeliness to `Phoenix.Router.pipe_through/1`
136136
Note: If you use both, it's recommended that the `Timber.ContextPlug` be called
137137
first.
138138

139+
### Phoenix Controllers & Templates
140+
141+
Timber can also log additional information about Phoenix controllers and
142+
templates using the instrumentation tools built right into Phoenix. All you need
143+
to do is add `Timber.PhoenixInstrumenter` to the list of `:instrumenters` in
144+
your endpoint configuration:
145+
146+
```elixir
147+
config :my_app, MyApp.Endpoint,
148+
http: [port: 4001],
149+
root: Path.dirname(__DIR__),
150+
instrumenters: [Timber.PhoenixInstrumenter],
151+
pubsub: [name: MyApp.PubSub,
152+
adapter: Pheonix.PubSub.PG2]
153+
```
154+
155+
You will need to recompile Phoenix using `mix deps.compile phoenix` for this
156+
change to take effect. If you see duplicate output, try turning off Phoenix's
157+
default logging by modifying the `controller` definition block in `MyApp.Web`
158+
to add the `log: false` option:
159+
160+
```elixir
161+
def controller do
162+
quote do
163+
use Phoenix.Controller, log: false
164+
end
165+
end
166+
```
167+
139168
### Ecto
140169

141170
Timber can collect information about the queries Ecto runs if you declare

lib/timber/events/controller_call.ex

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
defmodule Timber.Events.ControllerCall do
2+
@moduledoc """
3+
Represents a controller being called
4+
"""
5+
6+
@type t :: %__MODULE__{
7+
action: String.t | nil,
8+
controller: String.t | nil,
9+
description: IO.chardata | nil
10+
}
11+
12+
defstruct [
13+
:action,
14+
:controller,
15+
:description
16+
]
17+
18+
@spec new(Keyword.t) :: t
19+
def new(opts) do
20+
event = struct(__MODULE__, opts)
21+
description = ["Processing by ", event.controller, ?., event.action]
22+
23+
%__MODULE__{ event | description: description }
24+
end
25+
end

lib/timber/events/template_render_event.ex

+17-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,23 @@ defmodule Timber.Events.TemplateRenderEvent do
44
"""
55

66
@type t :: %__MODULE__{
7-
name: String.t,
8-
time_ms: float
7+
name: String.t | nil,
8+
description: IO.chardata | nil,
9+
time_ms: float | nil,
910
}
1011

11-
defstruct [:name, :time_ms]
12+
defstruct [
13+
:description,
14+
:name,
15+
:time_ms
16+
]
17+
18+
@spec new(Keyword.t) :: t
19+
def new(opts) do
20+
event = struct(__MODULE__, opts)
21+
time = Float.to_string(event.time_ms)
22+
description = ["Rendered ", ?", event.name, ?", " in ", time, "ms"]
23+
24+
%__MODULE__{ event | description: description }
25+
end
1226
end

lib/timber/phoenix_instrumenter.ex

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
defmodule Timber.PhoenixInstrumenter do
2+
@moduledoc """
3+
Handles instrumentation of `Phoenix.Endpoint`
4+
5+
This module is designed to log events when Phoenix calls a controller or
6+
renders a template. It hooks into the instrumentation tools built into
7+
Phoenix. Because of this, you will have to trigger a Phoenix recompile
8+
in order for the instrumentation to take effect.
9+
10+
## Adding Instrumentation
11+
12+
Phoenix instrumenetation is controlled through the configuration for your
13+
Phoenix endpoint module, typically named along the lines of `MyApp.Endpoint`.
14+
This module will be configured in `config/config.exs` similar to the following:
15+
16+
```
17+
config :my_app, MyApp.Endpoint,
18+
http: [port: 4001],
19+
root: Path.dirname(__DIR__),
20+
pubsub: [name: MyApp.PubSub,
21+
adapter: Phoenix.PubSub.PG2]
22+
```
23+
24+
You will need to add an `:instrumenters` key to this configuration with
25+
a value of `[Timber.PhoenixInstrumenter]`. This would update the configuration
26+
to something like the following:
27+
28+
29+
```
30+
config :my_app, MyApp.Endpoint,
31+
http: [port: 4001],
32+
root: Path.dirname(__DIR__),
33+
instrumenters: [Timber.PhoenixInstrumenter],
34+
pubsub: [name: MyApp.PubSub,
35+
adapter: Phoenix.PubSub.PG2]
36+
```
37+
38+
In order for this to take affect locally, you will need to recompile Phoenix using
39+
the command `mix deps.compile phoenix`. By default, Timber will log calls to controllers
40+
and template renders at the `:info` level. You can change this by adding an additional
41+
configuration line:
42+
43+
```
44+
config :timber, :instrumentation_level, :debug
45+
```
46+
47+
If you're currently displaying logs at the `:debug` level, you will also see that
48+
Phoenix has built-in logging already at this level. The Phoenix logger will not emit
49+
Timber events, so you can turn it off to stop the duplicate output. The Phoenix logger
50+
is controlled through the `MyApp.Web` module. Look for a definition block like the
51+
following:
52+
53+
```
54+
def controller do
55+
quote do
56+
use Phoenix.Controller
57+
end
58+
end
59+
```
60+
61+
You will want to modify this to the following
62+
63+
```
64+
def controller do
65+
quote do
66+
use Phoenix.Controller, log: false
67+
end
68+
end
69+
```
70+
"""
71+
72+
require Logger
73+
74+
alias Timber.Events.ControllerCall
75+
alias Timber.Events.TemplateRenderEvent
76+
77+
@doc false
78+
@spec phoenix_controller_call(:start | :stop, map | non_neg_integer, map | :ok) :: :ok
79+
def phoenix_controller_call(:start, %{module: module}, %{conn: conn}) do
80+
log_level = get_log_level(:info)
81+
82+
controller = inspect(module)
83+
action_name =
84+
conn
85+
|> Phoenix.Controller.action_name()
86+
|> Atom.to_string
87+
88+
# Phoenix actions are always 2 arity function
89+
action = action_name <> "/2"
90+
91+
event = ControllerCall.new(
92+
action: action,
93+
controller: controller
94+
)
95+
96+
Logger.log(log_level, event.description, timber_event: event)
97+
98+
:ok
99+
end
100+
101+
def phoenix_controller_call(:stop, _time_diff, :ok) do
102+
:ok
103+
end
104+
105+
@doc false
106+
@spec phoenix_controller_render(:start | :stop, map | non_neg_integer, map | :ok) :: :ok
107+
def phoenix_controller_render(:start, _compile_metadata, %{template: template_name}) do
108+
{:ok, template_name}
109+
end
110+
111+
def phoenix_controller_render(:stop, time_diff, {:ok, template_name}) do
112+
log_level = get_log_level(:info)
113+
114+
# This comes in as native time but is expected to be a float representing
115+
# milliseconds
116+
time_ms =
117+
time_diff
118+
|> System.convert_time_unit(:native, :milliseconds)
119+
|> :erlang.float()
120+
121+
event = TemplateRenderEvent.new(
122+
name: template_name,
123+
time_ms: time_ms
124+
)
125+
126+
Logger.log(log_level, event.description, timber_event: event)
127+
128+
:ok
129+
end
130+
131+
@spec get_log_level(atom) :: atom
132+
defp get_log_level(default) do
133+
Application.get_env(:timber, :instrumentation_level, default)
134+
end
135+
end

mix.exs

+1
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ defmodule Timber.Mixfile do
159159
{:ex_doc, "~> 0.14", only: [:dev, :docs]},
160160
{:excoveralls, "~> 0.5", only: [:test]},
161161
{:plug, "~> 1.2", optional: true},
162+
{:phoenix, "~> 1.2", optional: true},
162163
{:poison, "~> 2.2.0"},
163164
]
164165
end

mix.lock

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
1515
"mime": {:hex, :mime, "1.0.1", "05c393850524767d13a53627df71beeebb016205eb43bfbd92d14d24ec7a1b51", [:mix], []},
1616
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []},
17+
"phoenix": {:hex, :phoenix, "1.2.1", "6dc592249ab73c67575769765b66ad164ad25d83defa3492dc6ae269bd2a68ab", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.1", [hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]},
18+
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.1", "c10ddf6237007c804bf2b8f3c4d5b99009b42eca3a0dfac04ea2d8001186056a", [:mix], []},
1719
"plug": {:hex, :plug, "1.2.2", "cfbda521b54c92ab8ddffb173fbaabed8d8fc94bec07cd9bb58a84c1c501b0bd", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]},
1820
"poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []},
1921
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []},

0 commit comments

Comments
 (0)