Skip to content
Draft
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
5 changes: 5 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ if Mix.env() == :test do
report_dir: "reports/exunit"

config :opencensus,
process_contexts: [
Opencensus.Unstable.ProcessContext.SeqTrace,
Opencensus.Unstable.ProcessContext.ProcessDictionary,
Opencensus.Unstable.ProcessContext.ProcessDictionaryWithRecovery
],
reporters: [{Opencensus.TestSupport.SpanCaptureReporter, []}],
send_interval_ms: 100
end
9 changes: 6 additions & 3 deletions lib/opencensus/trace.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ defmodule Opencensus.Trace do
end
```
"""

defmacro with_child_span(label, attributes \\ quote(do: %{}), do: block) do
line = __CALLER__.line
module = __CALLER__.module
Expand All @@ -60,21 +61,23 @@ defmodule Opencensus.Trace do
})

quote do
parent_span_ctx = :ocp.current_span_ctx()
previous_span_ctx = Opencensus.Unstable.current_span_ctx()
parent_span_ctx = Opencensus.Unstable.recover_span_ctx()

new_span_ctx =
:oc_trace.start_span(unquote(label), parent_span_ctx, %{
:attributes => unquote(computed_attributes)
})

_ = :ocp.with_span_ctx(new_span_ctx)
_ = Opencensus.Unstable.with_span_ctx(new_span_ctx)
^new_span_ctx = Opencensus.Unstable.current_span_ctx()
Opencensus.Logger.set_logger_metadata()

try do
unquote(block)
after
_ = :oc_trace.finish_span(new_span_ctx)
_ = :ocp.with_span_ctx(parent_span_ctx)
_ = Opencensus.Unstable.with_span_ctx(previous_span_ctx)
Opencensus.Logger.set_logger_metadata()
end
end
Expand Down
158 changes: 158 additions & 0 deletions lib/opencensus/unstable.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
defmodule Opencensus.Unstable do
@moduledoc """
Experimental higher-level API built on proposed `ot_ctx` behaviour.
"""

@doc """
Get the current span context.

Uses the first configured `process_context` only to ensure the value is safe to pass to
`with_span_ctx/1` and `with_span_ctx/2` after you've finished your work.
"""
@spec current_span_ctx() :: :opencensus.span_ctx() | :undefined
def current_span_ctx do
process_contexts()
|> hd
|> get_span_ctx_via()
end

@doc """
Recovers the span context.

Uses all configured `process_context`.
Results MAY be used as the parent of a new span.
Results MUST NOT be passed to `with_span_ctx/1` or `with_span_ctx/2`.
"""
@spec recover_span_ctx() :: :opencensus.span_ctx() | :undefined
def recover_span_ctx do
process_contexts()
|> Enum.find_value(:undefined, &get_span_ctx_via/1)
end

@doc """
Sets the span context. Replaces `:ocp.with_span_ctx/1`.

Uses all configured `process_context`.
Returns the previous value of `current_span_ctx/0`.
"""
@spec with_span_ctx(span_ctx :: :opencensus.span_ctx() | :undefined) ::
:opencensus.span_ctx() | :undefined
def with_span_ctx(span_ctx) do
return_span_ctx = current_span_ctx()
process_contexts() |> Enum.each(&put_span_ctx_via(&1, span_ctx))
return_span_ctx
end

defp get_span_ctx_via(module) do
apply(module, :get, [span_ctx_key()])
|> case do
nil -> :undefined
span_ctx -> span_ctx
end
end

defp put_span_ctx_via(module, value) do
apply(module, :with_value, [span_ctx_key(), value])
end

@spec span_ctx_key() :: atom()
defp span_ctx_key do
Application.get_env(:opencensus, :span_ctx_key, :oc_span_ctx_key)
end

@spec process_contexts() :: list(module())
defp process_contexts do
Application.get_env(:opencensus, :process_contexts, [
Opencensus.Unstable.ProcessContext.SeqTrace,
Opencensus.Unstable.ProcessContext.ProcessDictionary,
Opencensus.Unstable.ProcessContext.ProcessDictionaryWithRecovery
])
end
end

defmodule Opencensus.Unstable.ProcessContext do
@moduledoc "Abstraction over process-local storage."

@doc "Get a value."
@callback get(key :: atom()) :: any() | nil

@doc "Put a value."
@callback with_value(key :: atom, value :: any()) :: :ok
end

defmodule Opencensus.Unstable.ProcessContext.SeqTrace do
@moduledoc """
Process-local storage using `seq_trace`.

Shares well with any other use that maintains a namespace in the second element of a 2-tuple
`{:shared_label, _map}`. Otherwise leaves the trace label alone to avoid disrupting the other
usage.
"""

@behaviour Opencensus.Unstable.ProcessContext

@doc "Get a value from the shared `seq_trace` label."
@impl Opencensus.Unstable.ProcessContext
def get(key) do
case :seq_trace.get_token(:label) do
{:label, {:shared_label, %{^key => value}}} ->
value

_ ->
nil
end
end

@doc "Put a value to the shared `seq_trace` label if safe."
@impl Opencensus.Unstable.ProcessContext
def with_value(key, value) do
case :seq_trace.get_token(:label) do
[] ->
:seq_trace.set_token(:label, {:shared_label, %{key => value}})

{:label, {:shared_label, map}} when is_map(map) ->
:seq_trace.set_token(:label, {:shared_label, Map.put(map, key, value)})

_ ->
nil
end

:ok
end
end

defmodule Opencensus.Unstable.ProcessContext.ProcessDictionary do
@moduledoc """
Process-local storage using the process dictionary.
"""

@behaviour Opencensus.Unstable.ProcessContext

@doc "Get a value from the process dictionary."
@impl Opencensus.Unstable.ProcessContext
def get(key), do: Process.get(key)

@impl Opencensus.Unstable.ProcessContext
def with_value(key, value) do
Process.put(key, value)
:ok
end
end

defmodule Opencensus.Unstable.ProcessContext.ProcessDictionaryWithRecovery do
@moduledoc """
Process-local storage using the process dictionary.
"""

@behaviour Opencensus.Unstable.ProcessContext

@doc "Get a value from the process dictionary."
@impl Opencensus.Unstable.ProcessContext
def get(key) do
[self() | Process.get(:"$callers", [])]
|> Enum.find_value(fn pid -> pid |> Process.info() |> get_in([:dictionary, key]) end)
end

@impl Opencensus.Unstable.ProcessContext
defdelegate with_value(key, value), to: Opencensus.Unstable.ProcessContext.ProcessDictionary
end
4 changes: 2 additions & 2 deletions test/opencensus_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ defmodule OpencensusTest do
on_exit(make_ref(), &detach/0)

assert Logger.metadata() == []
assert :ocp.current_span_ctx() == :undefined
assert Opencensus.Unstable.current_span_ctx() == :undefined

with_child_span "child_span" do
:do_something

assert :ocp.current_span_ctx() != :undefined
assert Opencensus.Unstable.current_span_ctx() != :undefined

assert Logger.metadata() |> Keyword.keys() |> Enum.sort() == [
:span_id,
Expand Down
14 changes: 7 additions & 7 deletions test/opencensus_trace_async_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ defmodule Opencensus.AsyncTest do
alias Opencensus.Trace

test "Trace.async/1" do
assert :ocp.current_span_ctx() == :undefined
assert Opencensus.Unstable.current_span_ctx() == :undefined

{inner, outer} =
Trace.with_child_span "outside" do
outer = :ocp.current_span_ctx() |> Span.load()
outer = Opencensus.Unstable.current_span_ctx() |> Span.load()

Trace.async(fn ->
Trace.with_child_span "inside" do
inner = :ocp.current_span_ctx() |> Span.load()
inner = Opencensus.Unstable.current_span_ctx() |> Span.load()
{inner, outer}
end
end)
Expand All @@ -30,19 +30,19 @@ defmodule Opencensus.AsyncTest do
defmodule M do
def f(outer) do
Trace.with_child_span "inside" do
inner = :ocp.current_span_ctx() |> Span.load()
inner = Opencensus.Unstable.current_span_ctx() |> Span.load()
{inner, outer}
end
end
end

test "Trace.async/3" do
assert :ocp.current_span_ctx() == :undefined
assert Opencensus.Unstable.current_span_ctx() == :undefined

{inner, outer} =
Trace.with_child_span "outside" do
outer = :ocp.current_span_ctx() |> Span.load()
Trace.async(M, :f, [outer]) |> Trace.await(10)
outer = Opencensus.Unstable.current_span_ctx() |> Span.load()
M |> Trace.async(:f, [outer]) |> Trace.await(10)
end

assert inner.trace_id == outer.trace_id
Expand Down