Skip to content

Commit

Permalink
Merge 9c1b56e into ed4df3f
Browse files Browse the repository at this point in the history
  • Loading branch information
dnlserrano committed Oct 11, 2018
2 parents ed4df3f + 9c1b56e commit b39b83a
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 5 deletions.
72 changes: 68 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,6 @@ Spandex.update_span(
)
```
Spandex used to ship with function decorators, but those decorators had a habit
of causing weird compilation issues for certain users, and could be easily
implemented by any user of the library.
## Asynchronous Processes
The current `trace_id` and `span_id` can be retrieved and later used (for
Expand All @@ -202,6 +198,74 @@ Each process should have its own store for its own generated spans. This should
be fine because you can send multiple batches of spans for the same trace
separately.
## Decorators
Because the `decorator` library can cause conflicts when it interacts with other dependencies in the same project, we support it as an optional dependency. This allows you to disable it if it causes problems for you, but it also means that you need to explicitly include some version of `decorator` in your application's dependency list:
```elixir
# mix.exs
defp deps do
[
{:decorator, "~> 1.2"}
]
end
```
Then, configure the Spandex decorator with your default tracer:
```elixir
config :spandex, :decorators, tracer: MyApp.Tracer
```
Span function decorators take an optional argument which is the attributes to update the span with. One of those attributes can be the `:tracer` in case you want to override the default tracer (e.g., in case you want to use multiple tracers).
IMPORTANT If you define multiple clauses for a function, you'll have to decorate all of the ones you want to span.
```elixir
defmodule TracedModule do
use Spandex.Decorators
@decorate trace(service: :my_app, type: :web)
def trace_me() do
span_1()
end
@decorate span(name: "span_1")
def span_1() do
inner_span_1()
end
@decorate span()
def inner_span_1() do
_ = ThirdPartyApi.different_service_call()
inner_span_2()
end
@decorate span(tracer: MyApp.OtherTracer)
def inner_span_2() do
"this produces a span stack to be reported by another tracer"
end
# Multiple Clauses
@decorate span()
def divide(n, 0), do: {:error, :divide_by_zero}
@decorate span()
def divide(n, m), do: n / m
end
defmodule ThirdPartyApi do
use Spandex.Decorators
@decorate span(service: :third_party, type: :cache)
def different_service_call() do
...
end
end
```
Note: Decorators don't magically do everything. It often makes a lot of sense to use `Tracer.update_span` from within your function to add details that are only available inside that same function.
## Ecto Tracing
Check out [spandex_ecto](https://github.com/spandex-project/spandex_ecto).
Expand Down
12 changes: 12 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ config :logger, :console,
format: "$time $metadata[$level] $message\n",
metadata: [:trace_id, :span_id]

config :spandex, :decorators, tracer: Spandex.Test.Support.Tracer

config :spandex, Spandex.Test.Support.Tracer,
service: :spandex_test,
adapter: Spandex.TestAdapter,
Expand All @@ -15,3 +17,13 @@ config :spandex, Spandex.Test.Support.Tracer,
services: [
spandex_test: :db
]

config :spandex, Spandex.Test.Support.OtherTracer,
service: :spandex_test,
adapter: Spandex.TestAdapter,
sender: Spandex.TestSender,
env: "test",
resource: "default",
services: [
spandex_test: :db
]
5 changes: 5 additions & 0 deletions coveralls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"skip_files": [
"lib/decorators.ex"
]
}
80 changes: 80 additions & 0 deletions lib/decorators.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
if Code.ensure_loaded?(Decorator.Define) do
defmodule Spandex.Decorators do
@moduledoc """
Provides a way of annotating functions to be traced.
Span function decorators take an optional argument which is the attributes to update the span with. One of those attributes can be the `:tracer` in case you want to override the default tracer (e.g., in case you want to use multiple tracers).
IMPORTANT If you define multiple clauses for a function, you'll have to decorate all of the ones you want to span.
Note: Decorators don't magically do everything. It often makes a lot of sense to use `Tracer.update_span` from within your function to add details that are only available inside that same function.
defmodule Foo do
use Spandex.Decorators
@decorate trace()
def bar(a) do
a * 2
end
@decorate trace(service: "ecto", type: "sql")
def databaz(a) do
a * 3
end
end
"""

@tracer Application.get_env(:spandex, :decorators)[:tracer]

use Decorator.Define, span: 0, span: 1, trace: 0, trace: 1

def trace(body, context) do
trace([], body, context)
end

def trace(attributes, body, context) do
name = Keyword.get(attributes, :name, default_name(context))
tracer = Keyword.get(attributes, :tracer, @tracer)
attributes = Keyword.delete(attributes, :tracer)

quote do
require unquote(tracer)

unquote(tracer).trace unquote(name), unquote(attributes) do
unquote(body)
end
end
end

def span(body, context) do
span([], body, context)
end

def span(attributes, body, context) do
name = Keyword.get(attributes, :name, default_name(context))
tracer = Keyword.get(attributes, :tracer, @tracer)

attributes =
attributes
|> Keyword.delete(:tracer)
|> Keyword.put_new(:resource, name)

quote do
require unquote(tracer)

unquote(tracer).span unquote(name), unquote(attributes) do
unquote(body)
end
end
end

defp default_name(%{module: module, name: function, arity: arity}) do
module =
module
|> Atom.to_string()
|> String.trim_leading("Elixir.")

"#{module}.#{function}/#{arity}"
end
end
end
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ defmodule Spandex.Mixfile do
{:excoveralls, "~> 0.6", only: :test},
{:inch_ex, "~> 0.5", only: [:dev, :test]},
{:optimal, "~> 0.3.3"},
{:plug, ">= 1.0.0"}
{:plug, ">= 1.0.0"},
{:decorator, "~> 1.2", optional: true}
]
end
end
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], []},
"certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], [], "hexpm"},
"credo": {:hex, :credo, "0.9.2", "841d316612f568beb22ba310d816353dddf31c2d94aa488ae5a27bb53760d0bf", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"decorator": {:hex, :decorator, "1.2.3", "258681ae943e57bd92d821ea995e3994b4e0b62ae8404b5d892cb8b23b55b050", [:mix], [], "hexpm"},
"deep_merge": {:hex, :deep_merge, "0.2.0", "c1050fa2edf4848b9f556fba1b75afc66608a4219659e3311d9c9427b5b680b3", [:mix], [], "hexpm"},
"dialyxir": {:hex, :dialyxir, "0.5.0", "5bc543f9c28ecd51b99cc1a685a3c2a1a93216990347f259406a910cf048d1d7", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"},
Expand Down
44 changes: 44 additions & 0 deletions test/decorators_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
defmodule Spandex.DecoratorsTest do
use ExUnit.Case, async: true

alias Spandex.Test.Support.Decorated
alias Spandex.Test.Support.OtherTracer
alias Spandex.Test.Support.Tracer
alias Spandex.Test.Util

test "creates trace when decorating function with trace annotation" do
Decorated.test_trace()

assert Util.find_span("decorated_trace") != nil
end

test "creates trace named after module name, function name and arity when nameless" do
Decorated.test_nameless_trace()

assert Util.find_span("Spandex.Test.Support.Decorated.test_nameless_trace/0") != nil
end

test "creates span when decorating function with span annotation" do
Tracer.start_trace("my_trace")
Decorated.test_span()
Tracer.finish_trace()

assert Util.find_span("decorated_span") != nil
end

test "creates span named after module name, function name and arity when nameless" do
Tracer.start_trace("my_trace")
Decorated.test_nameless_span()
Tracer.finish_trace()

assert Util.find_span("Spandex.Test.Support.Decorated.test_nameless_span/0") != nil
end

test "uses another tracer when overriding it via the tracer option" do
OtherTracer.start_trace("my_trace")
Decorated.test_other_tracer()
OtherTracer.finish_trace()

assert Util.find_span("Spandex.Test.Support.Decorated.test_other_tracer/0") != nil
end
end
23 changes: 23 additions & 0 deletions test/support/decorated.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule Spandex.Test.Support.Decorated do
@moduledoc """
Simple module to test span and trace decorators (`Spandex.Decorators`)
"""

use Spandex.Decorators
alias Spandex.Test.Support.OtherTracer

@decorate trace(name: "decorated_trace")
def test_trace, do: :trace

@decorate trace()
def test_nameless_trace, do: :nameless_trace

@decorate span(name: "decorated_span")
def test_span, do: :span

@decorate span()
def test_nameless_span, do: :nameless_span

@decorate span(tracer: OtherTracer)
def test_other_tracer, do: :other_tracer
end
4 changes: 4 additions & 0 deletions test/support/other_tracer.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
defmodule Spandex.Test.Support.OtherTracer do
@moduledoc false
use Spandex.Tracer, otp_app: :spandex
end

0 comments on commit b39b83a

Please sign in to comment.