diff --git a/.circleci/config.yml b/.circleci/config.yml index f111058..f1287ed 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,8 +5,9 @@ version: 2 jobs: build: docker: - # specify the version here - - image: circleci/elixir:1.7 + - image: cimg/elixir:1.11 + environment: + MIX_ENV: test # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images @@ -16,12 +17,12 @@ jobs: working_directory: ~/spandex steps: - checkout - - # specify any bash command here prefixed with `run: ` - - run: mix format --check-formatted - run: mix local.hex --force - run: mix local.rebar --force - run: mix deps.get + - run: mix format --check-formatted - run: mix compile --warnings-as-errors - - run: mix coveralls.circle + # (ExCoveralls.ReportUploadError) Failed to upload the report to 'https://coveralls.io' (reason: status_code = 422, body = {"message":"Couldn't find a repository matching this job.","error":true}). + # - run: mix coveralls.circle + - run: mix coveralls - run: mix inch.report diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d183b8c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,65 @@ +--- +name: CI +on: push + +jobs: + elixir: + name: Elixir Tests + runs-on: ubuntu-20.04 + env: + MIX_ENV: test + strategy: + matrix: + elixir: ['1.11.4', '1.13.4'] + otp: ['24.3'] + steps: + - name: Cancel previous runs + uses: styfle/cancel-workflow-action@0.5.0 + with: + access_token: ${{ github.token }} + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup Elixir + uses: erlef/setup-beam@v1 + with: + otp-version: ${{ matrix.otp }} + elixir-version: ${{ matrix.elixir }} + + - name: Get deps cache + uses: actions/cache@v2 + with: + path: deps/ + key: deps-${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }} + + - name: Get build cache + uses: actions/cache@v2 + with: + path: _build/test/ + key: build-${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }} + + - name: Install deps + run: | + mix local.rebar --force + mix local.hex --force + mix deps.get + + - name: Compile code + run: | + # Use of @deprecated causes compile warnings + # mix compile --warnings-as-errors + mix compile + + - name: Run tests + run: | + mix test + mix format --check-formatted + mix coveralls + mix inch.report + + # - name: Publish unit test results to GitHub + # uses: EnricoMi/publish-unit-test-result-action@v2 + # if: always() # always run even if tests fail + # with: + # junit_files: _build/test/lib/*/test-junit-report.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 68bb2a0..670bab9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +### Breaking Changes: + +* Don't set default priority to support distributed traces and sampling +* Use new `Config` instead of `Mix.Config` in `config` files +* Update libraries + +### Bug Fixes: +* Apply fix for interpolated string span and trace names from PR #136 + +### Features: +* Add functions to get and set trace priority + ## [3.1.0](https://github.com/spandex-project/spandex/compare/3.0.3...3.1.0) (2021-10-23) * Encode logger metadata as string. by @aselder in https://github.com/spandex-project/spandex/pull/127 diff --git a/README.md b/README.md index e58eff5..8438f45 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ monitoring tool that allows you get extremely granular information about the runtime of your system. Using distributed tracing, you can also get a view of how requests make their way through your entire ecosystem of microservices or applications. Currently, Spandex only supports integrating with -[datadog](https://www.datadoghq.com/), but it is built to be agnostic to what +[Datadog](https://www.datadoghq.com/), but it is built to be agnostic to what platform you choose to view your trace data. Eventually it should support Open Zipkin, Stackdriver, and any other trace viewer/aggregation tool you'd like to integrate with. We are still under active development, working on moving to a @@ -38,7 +38,7 @@ This is Datadog-specific since that's currently the only adapter. ## Adapters * [Datadog](https://github.com/spandex-project/spandex_datadog) -* Thats it so far! If you want another adapter, it should be relatively easy to +* That's it so far! If you want another adapter, it should be relatively easy to write! This library is in charge of handling the state management of spans, and the adapter is just in charge of generating certain values and ultimately sending the values to the service. @@ -282,3 +282,54 @@ Check out [spandex_ecto](https://github.com/spandex-project/spandex_ecto). ## Phoenix Tracing Check out [spandex_phoenix](https://github.com/spandex-project/spandex_phoenix). + +## Sampling and Rate Limiting + +When the load or cost from tracing increases, it is useful to use rate limiting +or sampling to reduce tracing. When many traces are the same, it's enough to +trace only e.g. 10% of them, reducing the bill by 90% while still preserving +the ability to troubleshoot the system. The tracing still happens, but it +may not be sent to the monitoring service, or the service may drop it or not +retain detailed information. + +Spandex stores the `priority` as an integer in the top level `Trace`. + +In Datadog, there are four values: +* `MANUAL_KEEP`(2) indicates that the application wants to ensure that a trace is + sampled, e.g. if there is an error +* `AUTO_KEEP` (1) indicates that a trace has been selected for sampling +* `AUTO_REJECT` (0) indicates that the trace has not been selected for sampling +* `MANUAL_REJECT` (-1) indicates that the application wants a trace to be dropped + +Similarly, OpenTracing uses 0 and 1 to indicate that a trace is sampled. + +In distributed tracing, multiple processes contribute to the same trace. When +sampling, the process that starts the trace can make a decision about whether +it should be sampled. It then passes that information to downstream processes +via an HTTP header. + +A trace may be sampled out, i.e. priority of 0, but the application can +override the priority manually. This is usually done for requests with errors, +as they are the ones that need troubleshooting. You can also enable tracing +dynamically with a feature flag to debug a feature in production. + +Spandex has functions to read and set the priority +(`Spandex.Tracer.current_priority/1` and `Spandex.Tracer.update_priority/2`). + +The following code overrides the `span_error` function on the tracer to set the +priority to `MANUAL_KEEP` when there is an error on a trace: + +```elixir +defmodule Foo.Tracer do + use Spandex.Tracer, otp_app: :foo + + @impl Spandex.Tracer + def span_error(error, stacktrace, opts) do + super(error, stacktrace, opts) + __MODULE__.update_priority(2) + end +``` + +The specific details of priority and other sampling and rate limiting are specific +to the observability back end, so look to e.g. +[spandex_datadog](https://github.com/spandex-project/spandex_datadog) for details. diff --git a/config/config.exs b/config/config.exs index 23afe79..40bbb8f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,5 +1,12 @@ # This file is responsible for configuring your application -# and its dependencies with the aid of the Mix.Config module. -use Mix.Config +# and its dependencies with the aid of the Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. -import_config "#{Mix.env()}.exs" +# General application configuration +import Config + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{config_env()}.exs" diff --git a/config/dev.exs b/config/dev.exs index da44dcd..751aca9 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -1,6 +1,4 @@ -# This file is responsible for configuring your application -# and its dependencies with the aid of the Mix.Config module. -use Mix.Config +import Config config :git_ops, mix_project: Spandex.Mixfile, diff --git a/config/test.exs b/config/test.exs index 2d79e84..c17f1f3 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,10 +1,10 @@ -use Mix.Config +import Config config :logger, :console, level: :debug, colors: [enabled: false], format: "$time $metadata[$level] $message\n", - metadata: [:trace_id, :span_id] + metadata: [:trace_id, :span_id, :file, :line] config :spandex, :decorators, tracer: Spandex.Test.Support.Tracer diff --git a/lib/span_context.ex b/lib/span_context.ex index 0e5f85b..39d6f6e 100644 --- a/lib/span_context.ex +++ b/lib/span_context.ex @@ -12,12 +12,12 @@ defmodule Spandex.SpanContext do @type t :: %__MODULE__{ trace_id: Spandex.id(), parent_id: Spandex.id(), - priority: integer(), + priority: integer() | nil, baggage: Keyword.t() } defstruct trace_id: nil, parent_id: nil, - priority: 1, + priority: nil, baggage: [] end diff --git a/lib/spandex.ex b/lib/spandex.ex index a757193..aa1061c 100644 --- a/lib/spandex.ex +++ b/lib/spandex.ex @@ -31,9 +31,9 @@ defmodule Spandex do | {:error, :disabled} | {:error, :trace_running} | {:error, [Optimal.error()]} - def start_trace(_, :disabled), do: {:error, :disabled} + def start_trace(name, :disabled) when is_binary(name), do: {:error, :disabled} - def start_trace(name, opts) do + def start_trace(name, opts) when is_binary(name) do strategy = opts[:strategy] if strategy.trace_active?(opts[:trace_key]) do @@ -55,9 +55,9 @@ defmodule Spandex do {:ok, Span.t()} | {:error, :disabled} | {:error, :no_trace_context} - def start_span(_, :disabled), do: {:error, :disabled} + def start_span(name, :disabled) when is_binary(name), do: {:error, :disabled} - def start_span(name, opts) do + def start_span(name, opts) when is_binary(name) do strategy = opts[:strategy] case strategy.get_trace(opts[:trace_key]) do @@ -104,6 +104,24 @@ defmodule Spandex do end end + @doc """ + Update the priority of the current trace. + """ + @spec update_priority(integer(), Tracer.opts()) :: + {:ok, Trace.t()} + | {:error, :disabled} + | {:error, :no_trace_context} + | {:error, [Optimal.error()]} + def update_priority(_, :disabled), do: {:error, :disabled} + + def update_priority(priority, opts) do + strategy = opts[:strategy] + + with {:ok, trace} <- strategy.get_trace(opts[:trace_key]) do + strategy.put_trace(opts[:trace_key], %{trace | priority: priority}) + end + end + @doc """ Updates the top-most parent span. @@ -247,6 +265,26 @@ defmodule Spandex do update_span(Keyword.put_new(opts, :error, updates)) end + @doc """ + Returns the priority of the currently running trace. + """ + @spec current_priority(Tracer.opts()) :: integer() | nil + def current_priority(:disabled), do: nil + + def current_priority(opts) do + strategy = opts[:strategy] + + case strategy.get_trace(opts[:trace_key]) do + {:ok, %Trace{priority: priority}} -> + priority + + {:error, _} -> + # TODO: Alter the return type of this interface to allow for returning + # errors from fetching the trace. + nil + end + end + @doc """ Returns the id of the currently-running trace. """ diff --git a/lib/trace.ex b/lib/trace.ex index e49d349..a2db9cf 100644 --- a/lib/trace.ex +++ b/lib/trace.ex @@ -10,7 +10,7 @@ defmodule Spandex.Trace do """ defstruct baggage: [], id: nil, - priority: 1, + priority: nil, spans: [], stack: [] @@ -18,7 +18,7 @@ defmodule Spandex.Trace do @type t :: %__MODULE__{ baggage: Keyword.t(), id: Spandex.id(), - priority: integer(), + priority: integer() | nil, spans: [Spandex.Span.t()], stack: [Spandex.Span.t()] } diff --git a/lib/tracer.ex b/lib/tracer.ex index 478ed21..bb0bcb6 100644 --- a/lib/tracer.ex +++ b/lib/tracer.ex @@ -26,11 +26,13 @@ defmodule Spandex.Tracer do @callback start_span(span_name, opts) :: tagged_tuple(Span.t()) @callback update_span(opts) :: tagged_tuple(Span.t()) @callback update_top_span(opts) :: tagged_tuple(Span.t()) + @callback update_priority(priority :: integer(), opts) :: tagged_tuple(Trace.t()) @callback finish_trace(opts) :: tagged_tuple(Trace.t()) @callback finish_span(opts) :: tagged_tuple(Span.t()) @callback span_error(error :: Exception.t(), stacktrace :: [term], opts) :: tagged_tuple(Span.t()) @callback continue_trace(span_name :: String.t(), trace_context :: SpanContext.t(), opts) :: tagged_tuple(Trace.t()) @callback continue_trace_from_span(span_name, span :: term, opts) :: tagged_tuple(Trace.t()) + @callback current_priority(opts) :: nil | integer() @callback current_trace_id(opts) :: nil | Spandex.id() @callback current_span_id(opts) :: nil | Spandex.id() @callback current_span(opts) :: nil | Span.t() @@ -96,8 +98,8 @@ defmodule Spandex.Tracer do defmacro __using__(opts) do # credo:disable-for-next-line Credo.Check.Refactor.LongQuoteBlocks - quote do - @otp_app unquote(opts)[:otp_app] || raise("Must provide `otp_app` to `use Spandex.Tracer`") + quote location: :keep, bind_quoted: [opts: opts] do + @otp_app Keyword.get(opts, :otp_app) || raise("Must provide `otp_app` to `use Spandex.Tracer`") @behaviour Spandex.Tracer @@ -119,7 +121,7 @@ defmodule Spandex.Tracer do end @impl Spandex.Tracer - defmacro trace(name, opts \\ [], do: body) when is_binary(name) do + defmacro trace(name, opts \\ [], do: body) do quote do opts = unquote(opts) @@ -140,7 +142,7 @@ defmodule Spandex.Tracer do end @impl Spandex.Tracer - defmacro span(name, opts \\ [], do: body) when is_binary(name) do + defmacro span(name, opts \\ [], do: body) do quote do opts = unquote(opts) name = unquote(name) @@ -179,6 +181,11 @@ defmodule Spandex.Tracer do Spandex.update_top_span(validate_update_config(opts, @otp_app)) end + @impl Spandex.Tracer + def update_priority(priority, opts \\ []) do + Spandex.update_priority(priority, config(opts, @otp_app)) + end + @impl Spandex.Tracer def finish_trace(opts \\ []) do opts @@ -220,6 +227,11 @@ defmodule Spandex.Tracer do Spandex.continue_trace_from_span(span_name, span, config(opts, @otp_app)) end + @impl Spandex.Tracer + def current_priority(opts \\ []) do + Spandex.current_priority(config(opts, @otp_app)) + end + @impl Spandex.Tracer def current_trace_id(opts \\ []) do Spandex.current_trace_id(config(opts, @otp_app)) @@ -296,6 +308,8 @@ defmodule Spandex.Tracer do |> Keyword.put(:sender, env[:sender]) end end + + defoverridable Spandex.Tracer end end end diff --git a/mix.exs b/mix.exs index fcf7c6b..6bf45ab 100644 --- a/mix.exs +++ b/mix.exs @@ -8,7 +8,7 @@ defmodule Spandex.Mixfile do [ app: :spandex, version: @version, - elixir: "~> 1.7", + elixir: "~> 1.11", elixirc_paths: elixirc_paths(Mix.env()), build_embedded: Mix.env() == :prod, start_permanent: Mix.env() == :prod, @@ -60,12 +60,12 @@ defmodule Spandex.Mixfile do defp deps do [ {:benchee, "~> 0.13.2", only: [:dev, :test]}, - {:credo, "~> 0.9.2", only: [:dev, :test], runtime: false}, - {:dialyxir, "~> 0.5", only: [:dev], runtime: false}, + {:credo, "~> 1.6", only: [:dev, :test], runtime: false}, + {:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false}, {:ex_doc, ">= 0.19.0", only: :dev, runtime: false}, {:excoveralls, "~> 0.10", only: :test}, {:git_ops, "~> 2.0.0", only: :dev}, - {:inch_ex, github: "rrrene/inch_ex", only: [:dev, :test]}, + {:inch_ex, "~> 2.0", only: [:dev, :test]}, {:makeup, "~> 1.0.1", only: :dev}, {:makeup_elixir, "~> 0.14.0", only: :dev}, {:nimble_parsec, "~> 0.5.3", only: :dev}, diff --git a/mix.lock b/mix.lock index 27cb226..01bb050 100644 --- a/mix.lock +++ b/mix.lock @@ -1,32 +1,35 @@ %{ "benchee": {:hex, :benchee, "0.13.2", "30cd4ff5f593fdd218a9b26f3c24d580274f297d88ad43383afe525b1543b165", [:mix], [{:deep_merge, "~> 0.1", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "d8b3f1720073413c36a21e56a1d1112a4d67a9ad0ec900437efed08b39e515b2"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, - "certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"}, - "credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "dcd1d45626f6a02abeef3fc424eaf101b05a851d3cceb9535b8ea3e14c3c17e6"}, - "decorator": {:hex, :decorator, "1.3.2", "63b8ac9e23b28053390abdda33bb9e1f3dd9e8f9a981f47a06fc2f2fe2e2f772", [:mix], [], "hexpm", "b80bd089e3c8579e6d9ea84eed307b1597a0d94af25331e424a209477ad1a7fc"}, + "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, + "credo": {:hex, :credo, "1.6.6", "f51f8d45db1af3b2e2f7bee3e6d3c871737bda4a91bff00c5eec276517d1a19c", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "625520ce0984ee0f9f1f198165cd46fa73c1e59a17ebc520038b8fce056a5bdc"}, + "decorator": {:hex, :decorator, "1.4.0", "a57ac32c823ea7e4e67f5af56412d12b33274661bb7640ec7fc882f8d23ac419", [:mix], [], "hexpm", "0a07cedd9083da875c7418dea95b78361197cf2bf3211d743f6f7ce39656597f"}, "deep_merge": {:hex, :deep_merge, "0.2.0", "c1050fa2edf4848b9f556fba1b75afc66608a4219659e3311d9c9427b5b680b3", [:mix], [], "hexpm", "e3bf435a54ed27b0ba3a01eb117ae017988804e136edcbe8a6a14c310daa966e"}, - "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm", "6c32a70ed5d452c6650916555b1f96c79af5fc4bf286997f8b15f213de786f73"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"}, - "ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"}, - "excoveralls": {:hex, :excoveralls, "0.13.2", "5ca05099750c086f144fcf75842c363fc15d7d9c6faa7ad323d010294ced685e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1e7ed75c158808a5a8f019d3ad63a5efe482994f2f8336c0a8c77d2f0ab152ce"}, + "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.26", "f4291134583f373c7d8755566122908eb9662df4c4b63caa66a0eabe06569b0a", [:mix], [], "hexpm", "48d460899f8a0c52c5470676611c01f64f3337bad0b26ddab43648428d94aabc"}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "ex_doc": {:hex, :ex_doc, "0.28.4", "001a0ea6beac2f810f1abc3dbf4b123e9593eaa5f00dd13ded024eae7c523298", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bf85d003dd34911d89c8ddb8bda1a958af3471a274a4c2150a9c01c78ac3f8ed"}, + "excoveralls": {:hex, :excoveralls, "0.14.6", "610e921e25b180a8538229ef547957f7e04bd3d3e9a55c7c5b7d24354abbba70", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "0eceddaa9785cfcefbf3cd37812705f9d8ad34a758e513bb975b081dce4eb11e"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.0.1", "9d3df6c710a80a8779dbb144c79fb24c777660ae862cc454ab3193afd0c02a37", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cd499a72523ba338c20973eadb707d25a42e4a77c46d2ff5c45e61e7adae6190"}, - "hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [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]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"}, - "idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"}, - "inch_ex": {:git, "https://github.com/rrrene/inch_ex.git", "c8eeaa65312df3ce150e91d7dddb50e2983b3209", []}, - "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, + "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "inch_ex": {:hex, :inch_ex, "2.0.0", "24268a9284a1751f2ceda569cd978e1fa394c977c45c331bb52a405de544f4de", [:mix], [{:bunt, "~> 0.2", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "96d0ec5ecac8cf63142d02f16b7ab7152cf0f0f1a185a80161b758383c9399a8"}, + "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mime": {:hex, :mime, "1.4.0", "5066f14944b470286146047d2f73518cf5cca82f8e4815cf35d196b58cf07c47", [:mix], [], "hexpm", "75fa42c4228ea9a23f70f123c74ba7cece6a03b1fd474fe13f6a7a85c6ea4ff6"}, + "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"}, "optimal": {:hex, :optimal, "0.3.6", "46bbf52fbbbd238cda81e02560caa84f93a53c75620f1fe19e81e4ae7b07d1dd", [:mix], [], "hexpm", "1a06ea6a653120226b35b283a1cd10039550f2c566edcdec22b29316d73640fd"}, - "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, - "plug": {:hex, :plug, "1.10.4", "41eba7d1a2d671faaf531fa867645bd5a3dce0957d8e2a3f398ccff7d2ef017f", [: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: false]}], "hexpm", "ad1e233fe73d2eec56616568d260777b67f53148a999dc2d048f4eb9778fe4a0"}, - "plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"}, + "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, + "plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.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.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, - "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"}, + "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/test/span_test.exs b/test/span_test.exs index 63a4590..1219be3 100644 --- a/test/span_test.exs +++ b/test/span_test.exs @@ -56,4 +56,22 @@ defmodule Spandex.Test.SpanTest do assert(span.service == :your_app) end + + test "trace names must be strings" do + assert_raise FunctionClauseError, + "no function clause matching in Spandex.start_trace/2", + fn -> + Tracer.trace name: "trace_name", service: :special_service do + :noop + end + end + end + + test "trace names can be interpolated at runtime" do + Tracer.trace Enum.join(["trace", "_", "name"]), service: :special_service do + :noop + end + + assert Util.find_span("trace_name") + end end diff --git a/test/spandex_test.exs b/test/spandex_test.exs index 2466546..f3390c4 100644 --- a/test/spandex_test.exs +++ b/test/spandex_test.exs @@ -453,6 +453,42 @@ defmodule Spandex.Test.SpandexTest do end end + describe "Spandex.current_priority/1" do + test "returns the priority if a trace is active" do + opts = @base_opts ++ @span_opts + adapter = opts[:adapter] + trace_id = adapter.trace_id() + span_context = %Spandex.SpanContext{trace_id: trace_id, priority: 2} + assert {:ok, %Trace{id: ^trace_id}} = Spandex.continue_trace("root_span", span_context, opts) + assert 2 == Spandex.current_priority(@base_opts) + end + + test "returns nil if no trace is active" do + assert nil == Spandex.current_priority(@base_opts) + end + + test "returns nil if tracing is disabled" do + assert nil == Spandex.current_priority(:disabled) + end + end + + describe "Spandex.update_priority/1" do + test "updates the priority" do + opts = @base_opts ++ @span_opts + adapter = opts[:adapter] + trace_id = adapter.trace_id() + span_context = %Spandex.SpanContext{trace_id: trace_id, priority: 1} + assert {:ok, %Trace{id: ^trace_id}} = Spandex.continue_trace("root_span", span_context, opts) + assert 1 == Spandex.current_priority(@base_opts) + assert {:ok, _trace} = Spandex.update_priority(2, @base_opts) + assert 2 == Spandex.current_priority(@base_opts) + end + + test "returns error if tracing is disabled" do + assert {:error, :no_trace_context} == Spandex.update_priority(2, @base_opts) + end + end + describe "Spandex.current_trace_id/1" do test "returns the active trace ID if a trace is active" do opts = @base_opts ++ @span_opts @@ -739,5 +775,19 @@ defmodule Spandex.Test.SpandexTest do {"header2", "value2"} ] end + + test "Default priority is nil" do + span_context = %SpanContext{trace_id: 123, parent_id: 456} + headers = [{"header1", "value1"}, {"header2", "value2"}] + + result = Spandex.inject_context(headers, span_context, @base_opts) + + assert result == [ + {"x-test-trace-id", "123"}, + {"x-test-parent-id", "456"}, + {"header1", "value1"}, + {"header2", "value2"} + ] + end end end diff --git a/test/support/adapter.ex b/test/support/adapter.ex index d003461..1b0660c 100644 --- a/test/support/adapter.ex +++ b/test/support/adapter.ex @@ -26,14 +26,13 @@ defmodule Spandex.TestAdapter do if they are present. """ @impl Spandex.Adapter - @spec distributed_context(conn :: Plug.Conn.t(), Keyword.t()) :: + @spec distributed_context(conn :: Plug.Conn.t(), Tracer.opts()) :: {:ok, SpanContext.t()} | {:error, :no_distributed_trace} def distributed_context(%Plug.Conn{} = conn, _opts) do trace_id = get_first_header(conn, "x-test-trace-id") parent_id = get_first_header(conn, "x-test-parent-id") - # We default the priority to 1 so that we capture all traces by default until we implement trace sampling - priority = get_first_header(conn, "x-test-sampling-priority") || 1 + priority = get_first_header(conn, "x-test-sampling-priority") if is_nil(trace_id) || is_nil(parent_id) do {:error, :no_distributed_trace} @@ -43,7 +42,7 @@ defmodule Spandex.TestAdapter do end @impl Spandex.Adapter - @spec distributed_context(headers :: Spandex.headers(), Keyword.t()) :: + @spec distributed_context(headers :: Spandex.headers(), Tracer.opts()) :: {:ok, SpanContext.t()} | {:error, :no_distributed_trace} def distributed_context(headers, _opts) when is_list(headers) do @@ -118,11 +117,18 @@ defmodule Spandex.TestAdapter do defp parse_header(_header), do: nil - defp tracing_headers(%SpanContext{trace_id: trace_id, parent_id: parent_id, priority: priority}) do + defp tracing_headers(%SpanContext{trace_id: trace_id, parent_id: parent_id} = span_context) do [ {"x-test-trace-id", to_string(trace_id)}, - {"x-test-parent-id", to_string(parent_id)}, - {"x-test-sampling-priority", to_string(priority)} - ] + {"x-test-parent-id", to_string(parent_id)} + ] ++ priority_header(span_context) + end + + defp priority_header(%SpanContext{priority: nil}) do + [] + end + + defp priority_header(%SpanContext{priority: priority}) do + [{"x-test-sampling-priority", to_string(priority)}] end end