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

fix: don't silence errors on span update calls #90

Merged
merged 3 commits into from
Nov 27, 2018
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
164 changes: 138 additions & 26 deletions lib/spandex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ defmodule Spandex do
@typedoc "Unix timestamp in nanoseconds"
@type timestamp :: non_neg_integer()

@doc """
Starts a new trace.

Span updates for the first span may be passed in. They are skipped if they are
invalid updates. As such, if you aren't sure if your updates are valid, it is
safer to perform a second call to `update_span/2` and check the return value.
"""
@spec start_trace(binary(), Tracer.opts()) ::
{:ok, Trace.t()}
| {:error, :disabled}
Expand All @@ -37,11 +44,17 @@ defmodule Spandex do
end
end

@doc """
Start a new span.

Span updates for that span may be passed in. They are skipped if they are
invalid updates. As such, if you aren't sure if your updates are valid, it is
safer to perform a second call to `update_span/2` and check the return value.
"""
@spec start_span(String.t(), Tracer.opts()) ::
{:ok, Span.t()}
| {:error, :disabled}
| {:error, :no_trace_context}
| {:error, [Optimal.error()]}
def start_span(_, :disabled), do: {:error, :disabled}

def start_span(name, opts) do
Expand All @@ -59,6 +72,11 @@ defmodule Spandex do
end
end

@doc """
Updates the current span.

In the case of an invalid update, validation errors are returned.
"""
@spec update_span(Tracer.opts(), boolean()) ::
{:ok, Span.t()}
| {:error, :disabled}
Expand Down Expand Up @@ -86,6 +104,15 @@ defmodule Spandex do
end
end

@doc """
Updates the top-most parent span.

Any spans that have already been started will not inherit any of the updates
from that span. For instance, if you change `service`, it will not be
reflected in already-started spans.

In the case of an invalid update, validation errors are returned.
"""
@spec update_top_span(Tracer.opts()) ::
{:ok, Span.t()}
| {:error, :disabled}
Expand All @@ -95,6 +122,11 @@ defmodule Spandex do

def update_top_span(opts), do: update_span(opts, true)

@doc """
Updates all spans, whether complete or in-progress.

In the case of an invalid update for any span, validation errors are returned.
"""
@spec update_all_spans(Tracer.opts()) ::
{:ok, Trace.t()}
| {:error, :disabled}
Expand All @@ -105,25 +137,25 @@ defmodule Spandex do
def update_all_spans(opts) do
strategy = opts[:strategy]

case strategy.get_trace(opts[:trace_key]) do
{:error, :no_trace_context} = error ->
error

{:ok, %Trace{stack: stack, spans: spans} = trace} ->
new_stack = Enum.map(stack, &update_or_keep(&1, opts))
new_spans = Enum.map(spans, &update_or_keep(&1, opts))
strategy.put_trace(opts[:trace_key], %{trace | stack: new_stack, spans: new_spans})

{:error, _} = error ->
error
with {:ok, %Trace{stack: stack, spans: spans} = trace} <- strategy.get_trace(opts[:trace_key]),
{:ok, new_spans} <- update_many_spans(spans, opts),
{:ok, new_stack} <- update_many_spans(stack, opts) do
strategy.put_trace(opts[:trace_key], %{trace | stack: new_stack, spans: new_spans})
end
end

@doc """
Finishes the current trace.

Span updates for the top span may be passed in. They are skipped if they are
invalid updates. As such, if you aren't sure if your updates are valid, it is
safer to perform a call to `update_span/2` and check the return value before
finishing the trace.
"""
@spec finish_trace(Tracer.opts()) ::
{:ok, Trace.t()}
| {:error, :disabled}
| {:error, :no_trace_context}
| {:error, [Optimal.error()]}
def finish_trace(:disabled), do: {:error, :disabled}

def finish_trace(opts) do
Expand Down Expand Up @@ -151,12 +183,19 @@ defmodule Spandex do
end
end

@doc """
Finishes the current span.

Span updates for that span may be passed in. They are skipped if they are
zachdaniel marked this conversation as resolved.
Show resolved Hide resolved
invalid updates. As such, if you aren't sure if your updates are valid, it is
safer to perform a call to `update_span/2` and check the return value before
finishing the span.
"""
@spec finish_span(Tracer.opts()) ::
{:ok, Span.t()}
| {:error, :disabled}
| {:error, :no_trace_context}
| {:error, :no_span_context}
| {:error, [Optimal.error()]}
def finish_span(:disabled), do: {:error, :disabled}

def finish_span(opts) do
Expand Down Expand Up @@ -190,6 +229,11 @@ defmodule Spandex do
end
end

@doc """
Updates the current span with error details.

In the case of an invalid value, validation errors are returned.
"""
@spec span_error(Exception.t(), Enum.t(), Tracer.opts()) ::
{:ok, Span.t()}
| {:error, :disabled}
Expand All @@ -203,6 +247,9 @@ defmodule Spandex do
update_span(Keyword.put_new(opts, :error, updates))
end

@doc """
Returns the id of the currently-running trace.
"""
@spec current_trace_id(Tracer.opts()) :: Spandex.id() | nil
def current_trace_id(:disabled), do: nil

Expand All @@ -220,6 +267,9 @@ defmodule Spandex do
end
end

@doc """
Returns the id of the currently-running span.
"""
@spec current_span_id(Tracer.opts()) :: Spandex.id() | nil
def current_span_id(:disabled), do: nil

Expand All @@ -230,6 +280,9 @@ defmodule Spandex do
end
end

@doc """
Returns the `%Span{}` struct for the currently-running span
"""
@spec current_span(Tracer.opts()) :: Span.t() | nil
def current_span(:disabled), do: nil

Expand All @@ -250,12 +303,20 @@ defmodule Spandex do
end
end

@doc """
Returns the current `%SpanContext{}` or an error.

### DEPRECATION WARNING

Expect changes to this in the future, as this will eventualy be refactored to
only ever return a `%SpanContext{}`, or at least to always return something
consistent.
zachdaniel marked this conversation as resolved.
Show resolved Hide resolved
"""
@spec current_context(Tracer.opts()) ::
{:ok, SpanContext.t()}
| {:error, :disabled}
| {:error, :no_span_context}
| {:error, :no_trace_context}
| {:error, [Optimal.error()]}
def current_context(:disabled), do: {:error, :disabled}

def current_context(opts) do
Expand All @@ -273,11 +334,17 @@ defmodule Spandex do
end
end

@doc """
Given a `%SpanContext{}`, resumes a trace from a different process or service.

Span updates for the top span may be passed in. They are skipped if they are
invalid updates. As such, if you aren't sure if your updates are valid, it is
safer to perform a second call to `update_span/2` and check the return value.
"""
@spec continue_trace(String.t(), SpanContext.t(), Keyword.t()) ::
{:ok, Trace.t()}
| {:error, :disabled}
| {:error, :trace_already_present}
| {:error, [Optimal.error()]}
def continue_trace(_, _, :disabled), do: {:error, :disabled}

def continue_trace(name, %SpanContext{} = span_context, opts) do
Expand All @@ -291,23 +358,35 @@ defmodule Spandex do
end
end

@doc """
Given a trace_id and span_id, resumes a trace from a different process or service.

Span updates for the top span may be passed in. They are skipped if they are
invalid updates. As such, if you aren't sure if your updates are valid, it is
safer to perform a second call to `update_span/2` and check the return value.
"""
@spec continue_trace(String.t(), Spandex.id(), Spandex.id(), Keyword.t()) ::
{:ok, Trace.t()}
| {:error, :disabled}
| {:error, :trace_already_present}
| {:error, [Optimal.error()]}
@deprecated "Use continue_trace/3 instead"
def continue_trace(_, _, _, :disabled), do: {:error, :disabled}

def continue_trace(name, trace_id, span_id, opts) do
continue_trace(name, %SpanContext{trace_id: trace_id, parent_id: span_id}, opts)
end

@doc """
Given a span struct, resumes a trace from a different process or service.

Span updates for the top span may be passed in. They are skipped if they are
invalid updates. As such, if you aren't sure if your updates are valid, it is
safer to perform a second call to `update_span/2` and check the return value.
"""
@spec continue_trace_from_span(String.t(), Span.t(), Tracer.opts()) ::
{:ok, Trace.t()}
| {:error, :disabled}
| {:error, :trace_already_present}
| {:error, [Optimal.error()]}
def continue_trace_from_span(_name, _span, :disabled), do: {:error, :disabled}

def continue_trace_from_span(name, span, opts) do
Expand All @@ -321,17 +400,23 @@ defmodule Spandex do
end
end

@doc """
Returns the context from a given set of HTTP headers, as determined by the adapter.
"""
@spec distributed_context(Plug.Conn.t(), Tracer.opts()) ::
{:ok, SpanContext.t()}
| {:error, :disabled}
| {:error, [Optimal.error()]}
def distributed_context(_, :disabled), do: {:error, :disabled}

def distributed_context(conn, opts) do
adapter = opts[:adapter]
adapter.distributed_context(conn, opts)
end

@doc """
Alters headers to include the outgoing HTTP headers necessary to continue a
distributed trace, as determined by the adapter.
"""
@spec inject_context(headers(), SpanContext.t(), Tracer.opts()) :: headers()
def inject_context(headers, %SpanContext{} = span_context, opts) do
adapter = opts[:adapter]
Expand All @@ -340,6 +425,30 @@ defmodule Spandex do

# Private Helpers

defp update_many_spans(spans, opts) do
spans
|> Enum.reduce({:ok, []}, fn
span, {:ok, acc} ->
case Span.update(span, opts) do
{:ok, updated} ->
{:ok, [updated | acc]}

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

_, {:error, error} ->
{:error, error}
end)
|> case do
{:ok, list} ->
{:ok, Enum.reverse(list)}

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

defp do_continue_trace(name, span_context, opts) do
strategy = opts[:strategy]
adapter = opts[:adapter]
Expand Down Expand Up @@ -405,20 +514,23 @@ defmodule Spandex do

defp do_update_span(%Trace{stack: stack} = trace, opts, true) do
strategy = opts[:strategy]
new_stack = List.update_at(stack, -1, &update_or_keep(&1, opts))

with {:ok, _trace} <- strategy.put_trace(opts[:trace_key], %{trace | stack: new_stack}) do
{:ok, Enum.at(new_stack, -1)}
top_span = Enum.at(stack, -1)

with {:ok, updated} <- Span.update(top_span, opts),
new_stack <- List.replace_at(stack, -1, updated),
{:ok, _trace} <- strategy.put_trace(opts[:trace_key], %{trace | stack: new_stack}) do
{:ok, updated}
end
end

defp do_update_span(%Trace{stack: [current_span | other_spans]} = trace, opts, false) do
strategy = opts[:strategy]
updated_span = update_or_keep(current_span, opts)
new_stack = [updated_span | other_spans]

with {:ok, _trace} <- strategy.put_trace(opts[:trace_key], %{trace | stack: new_stack}) do
{:ok, updated_span}
with {:ok, updated} <- Span.update(current_span, opts),
new_stack <- [updated | other_spans],
{:ok, _trace} <- strategy.put_trace(opts[:trace_key], %{trace | stack: new_stack}) do
{:ok, updated}
end
end

Expand Down
Loading