Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use router dispatch start events to update name of span #4

Merged
merged 8 commits into from
Jun 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Used by "mix format"
[
import_deps: [:plug],
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
134 changes: 86 additions & 48 deletions lib/opentelemetry_plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule OpentelemetryPlug do

Example:

OpentelemetryPlug.setup([:my, :plug])
OpentelemetryPlug.setup([])

You may also supply the following options in the second argument:

Expand All @@ -23,78 +23,112 @@ defmodule OpentelemetryPlug do
defaults to the concatenation of the event name with periods, e.g.
`"my.plug.start"`.
"""
def setup(event_prefix, config \\ []) do
def setup(config \\ []) do
# register the tracer. just re-registers if called for multiple repos
_ = OpenTelemetry.register_application_tracer(:opentelemetry_plug)

:telemetry.attach({__MODULE__, {event_prefix, :opentelemetry_plug_tracer_start}},
event_prefix ++ [:start],
:telemetry.attach(
{__MODULE__, :phoenix_tracer_router_start},
[:phoenix, :router_dispatch, :start],
&__MODULE__.handle_route/4,
config
)

:telemetry.attach(
{__MODULE__, :plug_tracer_router_start},
[:plug, :router_dispatch, :start],
&__MODULE__.handle_route/4,
config
)

:telemetry.attach(
{__MODULE__, :plug_tracer_start},
[:plug_adapter, :call, :start],
&__MODULE__.handle_start/4,
config)
config
)

:telemetry.attach({__MODULE__, {event_prefix, :opentelemetry_plug_tracer_stop}},
event_prefix ++ [:stop],
:telemetry.attach(
{__MODULE__, :plug_tracer_stop},
[:plug_adapter, :call, :stop],
&__MODULE__.handle_stop/4,
config)
config
)

:telemetry.attach({__MODULE__, {event_prefix, :opentelemetry_plug_tracer_exception}},
event_prefix ++ [:exception],
:telemetry.attach(
{__MODULE__, :plug_tracer_exception},
[:plug_adapter, :call, :exception],
&__MODULE__.handle_exception/4,
config)
config
)
end

@doc false
def handle_start(_, _measurements, %{conn: conn}, _config) do
# TODO: add config for what paths are traced

# setup OpenTelemetry context based on request headers
:ot_propagation.http_extract(conn.req_headers)

# TODO: add config option to allow `conn.request_path` as span name
span_name = case Map.get(conn.private, :plug_route) do
nil ->
"HTTP " <> conn.method
{route, _fun} ->
route
end

{_, adapter} = conn.adapter
span_name = "HTTP " <> conn.method

peer_data = Plug.Conn.get_peer_data(conn)

user_agent = header_or_empty(conn, "User-Agent")
host = header_or_empty(conn, "Host")
peer_ip = Map.get(Map.get(adapter, :peer_data), :address)

attributes = [{"http.target", conn.request_path},
{"http.host", conn.host},
{"http.scheme", conn.scheme},
{"http.user_agent", user_agent},
{"http.method", conn.method},
{"net.peer.ip", to_string(:inet_parse.ntoa(peer_ip))},
{"net.peer.port", adapter.peer_data.port},
{"net.peer.name", host},
{"net.transport", "IP.TCP"},
{"net.host.ip", to_string(:inet_parse.ntoa(conn.remote_ip))},
{"net.host.port", conn.port} | optional_attributes(conn)
# {"net.host.name", HostName}
]
peer_ip = Map.get(peer_data, :address)

attributes = [
{"http.target", conn.request_path},
{"http.host", conn.host},
{"http.scheme", conn.scheme},
{"http.user_agent", user_agent},
{"http.method", conn.method},
{"net.peer.ip", to_string(:inet_parse.ntoa(peer_ip))},
{"net.peer.port", peer_data.port},
{"net.peer.name", host},
{"net.transport", "IP.TCP"},
{"net.host.ip", to_string(:inet_parse.ntoa(conn.remote_ip))},
{"net.host.port", conn.port} | optional_attributes(conn)
# {"net.host.name", HostName}
]

# TODO: Plug should provide a monotonic native time in measurements to use here
# for the `start_time` option
OpenTelemetry.Tracer.start_span(span_name, %{attributes: attributes})
end

@doc false
def handle_route(_, _measurements, %{route: route}, _config) do
# TODO: add config option to allow `conn.request_path` as span name
if in_span?() do
OpenTelemetry.Span.update_name(route)
end
end

@doc false
def handle_stop(_, _measurements, %{conn: conn}, _config) do
OpenTelemetry.Span.set_attribute("http.status", conn.status)
OpenTelemetry.Tracer.end_span()
if in_span?() do
OpenTelemetry.Span.set_attribute("http.status", conn.status)
OpenTelemetry.Tracer.end_span()
end
end

@doc false
def handle_exception(_, _measurements, %{conn: _conn}, _config) do
OpenTelemetry.Span.set_status(OpenTelemetry.status('UnknownError', "unknown error"))
OpenTelemetry.Tracer.end_span()
if in_span?() do
OpenTelemetry.Span.set_status(OpenTelemetry.status('UnknownError', "unknown error"))
OpenTelemetry.Tracer.end_span()
end
end

defp in_span?, do: OpenTelemetry.Tracer.current_ctx() != :undefined

defp header_or_empty(conn, header) do
case Plug.Conn.get_req_header(conn, header) do
[] ->
""

[host | _] ->
host
end
Expand All @@ -103,21 +137,25 @@ defmodule OpentelemetryPlug do
defp optional_attributes(conn) do
# for some reason Elixir removed Enum.filter_map in 1.5
# so just using Erlang's list module
Comment on lines 138 to 139
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use Enum.flat_map/2. Is the concern efficiency?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda doubt it; 𝑛=2. Just another tell that the author comes more from the Erlang side of things? I'd flat map with empty lists instead of false, yeah: this part of the diff is just mix format doing its thing while I edit elsewhere.

:lists.filtermap(fn({attr, fun}) ->
case fun.(conn) do
nil ->
false;
value ->
{true, {attr, value}}
end
end, [{"http.client_ip", &client_ip/1},
{"http.server_name", &server_name/1}])
:lists.filtermap(
fn {attr, fun} ->
case fun.(conn) do
nil ->
false

value ->
{true, {attr, value}}
end
end,
[{"http.client_ip", &client_ip/1}, {"http.server_name", &server_name/1}]
)
end

defp client_ip(conn) do
case Plug.Conn.get_req_header(conn, "X-Forwarded-For") do
[] ->
nil

[host | _] ->
host
end
Expand Down
4 changes: 3 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ defmodule OpentelemetryPlug.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:plug, "~> 1.10"},
{:hackney, "~> 1.0", only: :test, runtime: false},
{:opentelemetry_api, "~> 0.3"},
{:opentelemetry, "~> 0.3"},
{:plug, "~> 1.10.1"},
{:plug_cowboy, "~> 2.2", only: :test, runtime: false},
{:telemetry, "~> 0.4"}
]
end
Expand Down
14 changes: 13 additions & 1 deletion mix.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
%{
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"},
"cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"},
"cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"},
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"opentelemetry": {:hex, :opentelemetry, "0.4.0", "293729f014009b03a1a2c47e6367db6f280b41412faa5639f06dcce9733d18a6", [:rebar3], [{:opentelemetry_api, "~> 0.3.1", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "f0eb4281f26879147322b0a6f1c457c57f3f1c4121cbff1f6056e4c98b1647a7"},
"opentelemetry_api": {:hex, :opentelemetry_api, "0.3.1", "aa042f9ff0b774c3e9827e215fcf972d5cfccd9a79ed55194b58c329a945b486", [:mix, :rebar3], [], "hexpm", "91fc78c521b9fc7f72f47144bb9f0838d57584b1be5bbd3098d8aaf2f2d42686"},
"plug": {:hex, :plug, "1.10.0", "6508295cbeb4c654860845fb95260737e4a8838d34d115ad76cd487584e2fc4d", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "422a9727e667be1bf5ab1de03be6fa0ad67b775b2d84ed908f3264415ef29d4a"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"plug": {:hex, :plug, "1.10.1", "c56a6d9da7042d581159bcbaef873ba9d87f15dce85420b0d287bca19f40f9bd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b5cd52259817eb8a31f2454912ba1cff4990bca7811918878091cb2ab9e52cb8"},
"plug_cowboy": {:hex, :plug_cowboy, "2.2.1", "fcf58aa33227a4322a050e4783ee99c63c031a2e7f9a2eb7340d55505e17f30f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3b43de24460d87c0971887286e7a20d40462e48eb7235954681a20cee25ddeb6"},
"plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
}
41 changes: 34 additions & 7 deletions test/opentelemetry_plug_test.exs
Original file line number Diff line number Diff line change
@@ -1,15 +1,42 @@
defmodule OpentelemetryPlugTest do
use ExUnit.Case
use Plug.Test

setup_all do
OpentelemetryPlug.setup([])

{:ok, _} = Plug.Cowboy.http(MyPlug, [], ip: {127, 0, 0, 1}, port: 0)

on_exit(fn ->
:ok = Plug.Cowboy.shutdown(MyPlug.HTTP)
end)
end

test "creates span" do
conn = conn(:get, "/hello/world")
assert {200, _, "Hello world"} = request(:get, "/hello/world")
end

OpentelemetryPlug.setup([:my, :plug], [])
conn = MyRouter.call(conn, [])
defp base_url do
info = :ranch.info(MyPlug.HTTP)
port = Keyword.fetch!(info, :port)
ip = Keyword.fetch!(info, :ip)
"http://#{:inet.ntoa(ip)}:#{port}"
end

assert conn.state == :sent
assert conn.status == 200
defp request(:head = verb, path) do
{:ok, status, headers} = :hackney.request(verb, base_url() <> path, [], "", [])
{status, headers, nil}
end

defp request(verb, path, headers \\ [], body \\ "") do
case :hackney.request(verb, base_url() <> path, headers, body, []) do
{:ok, status, headers, client} ->
{:ok, body} = :hackney.body(client)
:hackney.close(client)
{status, headers, body}

{:error, _} = error ->
error
end
end
end

Expand All @@ -27,6 +54,7 @@ defmodule MyPlug do
conn
|> put_resp_content_type("text/plain")
|> send_resp(400, "no span context")

_ ->
conn
|> put_resp_content_type("text/plain")
Expand All @@ -39,7 +67,6 @@ defmodule MyRouter do
use Plug.Router

plug :match
plug Plug.Telemetry, event_prefix: [:my, :plug]
plug :dispatch

forward "/hello/:foo", to: MyPlug
Expand Down
1 change: 1 addition & 0 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
ExUnit.start()

Application.ensure_all_started(:hackney)
Application.ensure_all_started(:opentelemetry_api)
Application.ensure_all_started(:opentelemetry)