Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
nmohoric committed Oct 16, 2017
0 parents commit 6ce8f05
Show file tree
Hide file tree
Showing 10 changed files with 308 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/_build
/deps
erl_crash.dump
*.ez
/doc
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language: elixir
elixir:
- 1.5.1
otp_release:
- 20.0
21 changes: 21 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2017 Nick Mohoric

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# PlugOpenTracing

[![Build Status](https://travis-ci.org/nmohoric/plug_opentracing.svg?branch=master)](https://travis-ci.org/nmohoric/plug_opentracing)

An Elixir Plug for adding OpenTracing instrumentation.

## Usage

Update your `mix.exs` file and run `mix deps.get`.
```elixir
defp deps do
[{:plug_opentracing, "~> 0.1"}]
end
```

Add the plug to a pipeline. In this case we will look for the
`uber-trace-id` header to exist. If so, it is split on `:` and
the first and second values are used as the trace_id and parent_id
when creating the span.

```elixir
defmodule MyPhoenixApp.MyController do
use MyPhoenixApp.Web, :controller
alias Plug.Conn.Status

plug PlugOpenTracing, trace_id: 'uber-trace-id'
plug :action

def index(conn, _params) do
conn
|> put_status(Status.code :ok)
end
end

```
The created span can be accessed using `conn.assigns[:trace_span]`. This is useful
when you want to use this span as the parent of another span in your request, or
if you want to add tags/logs to the span before it is finished.

The request span will be finished at the end of your request using a callback,
which will send it to the Jaeger or Zipkin endpoint you've set up in your config.
24 changes: 24 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for third-
# party users, it should be done in your mix.exs file.

# Sample configuration:
#
# config :logger, :console,
# level: :info,
# format: "$date $time [$level] $metadata$message\n",
# metadata: [:user_id]

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
84 changes: 84 additions & 0 deletions lib/plug_opentracing.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
defmodule PlugOpenTracing do
@moduledoc """
A plug for adding OpenTracing instrumentation to requests.
If a request already has a trace-id, the parts will be split
out and added as trace-id and parent-id in the new span. Otherwise,
a new span will be created.
The default header is "uber-trace-id" but this can be set at compile time.
At any point in the request you can access the span using conn.assigns[:trace].
This can then be used to either add tags onto it or use the span as a parent span
for other requests.
To use it, just plug it into your module:
plug PlugOpenTracing
## Options
* `:trace_id` - An incoming trace-id. This should be hex and in the
form `trace-id:span-id` plus any optional bits that will be removed.
plug PlugOpenTracing, trace_id: "my-trace-header"
"""

alias Plug.Conn
alias :otter, as: Otter
@behaviour Plug

def init(opts) do
Keyword.get(opts, :trace_header, "uber-trace-id")
end

def call(conn, trace_id_header) do
conn
|> get_trace_id(trace_id_header)
|> start_span()
|> tag_span()
|> register_span()
end

defp get_trace_id(conn, header) do
case Conn.get_req_header(conn, header) do
[] -> {conn, nil}
[val|_] -> {conn, extract_id(String.split(val, ":"))}
end
end

defp start_span({conn, [nil, _]}), do: start_span({conn, nil})
defp start_span({conn, [_, nil]}), do: start_span({conn, nil})
defp start_span({conn, [trace_id, span_id]}) do
{conn, Otter.start(conn.method, trace_id, span_id)}
end
defp start_span({conn, _}), do: {conn, Otter.start(conn.method)}

defp tag_span({conn, span}) do
span = span
|> Otter.tag("path", Enum.join(Enum.map(conn.path_info, &URI.decode/1), "/"))
|> Otter.tag("method", conn.method)
conn |> Conn.assign(:trace_span, span)
end

defp register_span(conn) do
Conn.register_before_send(conn, fn c ->
c.assigns[:trace_span]
|> Otter.finish()
c
end)
end

defp extract_id(vals) when length(vals) >= 2 do
vals
|> Enum.take(2)
|> Enum.map(fn(s) -> s
|> Integer.parse(16)
|> case do
:error -> nil
i -> elem(i,0)
end
end)
end
defp extract_id(_), do: nil
end
43 changes: 43 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
defmodule PlugOpenTracing.Mixfile do
use Mix.Project

def project do
[
app: :plug_opentracing,
version: "0.0.1",
name: "PlugOpenTracing",
source_url: "https://github.com/nmohoric/plug_opentracing",
elixir: "~> 1.0",
deps: deps(),
description: description(),
package: package()
]
end

defp description do
"""
An Elixir Plug for adding opentracing instrumentation.
"""
end

defp package do
[
maintainers: ["Nick Mohoric"],
files: ["lib", "mix.exs", "README*", "LICENSE*"],
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/nmohoric/plug_opentracing"}
]
end

def application do
[applications: [:plug]]
end

defp deps do
[
{:plug, "~> 1.3"},
{:ibrowse, "~> 4.4"},
{:otter, "~> 0.2.0"},
]
end
end
4 changes: 4 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
%{"ibrowse": {:hex, :ibrowse, "4.4.0", "2d923325efe0d2cb09b9c6a047b2835a5eda69d8a47ed6ff8bc03628b764e991", [], [], "hexpm"},
"mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [], [], "hexpm"},
"otter": {:hex, :otter, "0.2.0", "0ca08e94db43c9ef6bf22a9b803a776880841e9f7fbf7b9207ade0cdeb0c6251", [], [], "hexpm"},
"plug": {:hex, :plug, "1.4.3", "236d77ce7bf3e3a2668dc0d32a9b6f1f9b1f05361019946aae49874904be4aed", [], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}}
53 changes: 53 additions & 0 deletions test/plug_opentracing_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
defmodule PlugOpenTracingTest do
use ExUnit.Case, async: true
use Plug.Test
alias Plug.Conn.Status

test "missing trace header results in new span" do
connection = conn(:get, "/test-path")
response = TestApp.call(connection, [])

assert response.status == Status.code(:ok)
assert response.resp_body != ""
end

test "bad trace header results in new span" do
connection = conn(:get, "/test-path") |> put_req_header("uber-trace-id", "1234")
response = TestApp.call(connection, [])

assert response.status == Status.code(:ok)
assert response.resp_body != ""
end

test "valid trace header results in span with those ids" do
connection = conn(:get, "/test-path") |> put_req_header("uber-trace-id", "6a5c63925e01051b:150b1b1adcde6f4:6a5c63925e01051b:1")
response = TestApp.call(connection, [])

assert response.status == Status.code(:ok)
assert response.resp_body == "7664110146171241755"
end

test "invalid hex trace header results in usable span" do
connection = conn(:get, "/test-path") |> put_req_header("uber-trace-id", "6a5x63925t01051b:150b1p1adcdq6f4:6a5c63925e01051b:1")
response = TestApp.call(connection, [])

assert response.status == Status.code(:ok)
assert response.resp_body == "1701"
end

test "empty trace header results in new span" do
connection = conn(:get, "/test-path") |> put_req_header("uber-trace-id", "")
response = TestApp.call(connection, [])

assert response.status == Status.code(:ok)
assert response.resp_body != ""
end

test "just some colons results in new span" do
connection = conn(:get, "/test-path") |> put_req_header("uber-trace-id", "::::")
response = TestApp.call(connection, [])

assert response.status == Status.code(:ok)
assert response.resp_body != ""
end
end
28 changes: 28 additions & 0 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
ExUnit.start()

defmodule AppMaker do
defmacro __using__(options) do
quote do
use Plug.Router
alias Plug.Conn.Status

plug PlugOpenTracing, unquote(options)
plug :match
plug :dispatch
end
end
end

defmodule TestApp do
alias :otter, as: Otter
use AppMaker, trace_header: "uber-trace-id"

get "/test-path" do
send_resp(conn, Status.code(:ok), ids_to_string(conn))
end

defp ids_to_string(conn) do
{trace_id, _} = Otter.ids(conn.assigns[:trace_span])
"#{Integer.to_string(trace_id)}"
end
end

0 comments on commit 6ce8f05

Please sign in to comment.