spandex-project / spandex

A platform agnostic tracing library
MIT License
333 stars 53 forks source link

Only leaf spans #84

Closed ludwikbukowski closed 5 years ago

ludwikbukowski commented 5 years ago

I have the problem so that I'd like to include only bottom level spans in my Trace (only leaf spans) So that starting from some distinguished span, only the spans that does not have childs will be forwarded. Is it possible to achieve that without creating the fork of this library and implementing such a feature?

GregMefford commented 5 years ago

Can you clarify what you're trying to accomplish / why you need this feature? It sounds like you'd prefer for each trace to only display the root span (which is required as far as I know) and the most-detailed spans, but nothing in between (i.e. no intermediate calls in the flame graph).

Here are a few thoughts while we wait for more details:

ludwikbukowski commented 5 years ago

Thanks for clear and very good detailed response :) Exactly this is what I would like to have. The reason is that we wrote support for Absinthe library so that each resolver is span and the Trace is simply phoenix request. It was done by implementing custom middleware that calls start_span for each field (btw maybe I could share that code with you as something like spandex_absinthe)

The issue we faced is that we have quite deep flame graph trees. The resolvers that calls other resolvers aren't very informative for us since those that do some time-consuming operations are the bottom level resolvers only.

I will look at the code you suggest and try to figure out optimal solution that does not require modifying the library itself :)

GregMefford commented 5 years ago

Cool! Yes we would love to have a spandex_absinthe library that people can just drop into their project. Maybe that library could have some way to “compress” the stack of spans as well. I’m not sure how Absinthe middleware works.

zachdaniel commented 5 years ago

Agreed! Greg hit it all on the head. And absinthe is definitely something that deserves its own full integration :)

zachdaniel commented 5 years ago

I think we should close this now, as there isn't really much sense in giving spandex an option to only send leaf traces, but rather in building an integration with absinthe separately.

ludwikbukowski commented 5 years ago

Hey! Let's suppose I'd like to contribute regarding the Absinthe support Should I create PR into root spandex library so that you can review and in the end create separate spandex_absinthe repository?

zachdaniel commented 5 years ago

I would recommend creating your own repository, and making changes there. Then we will review your repo, and you can transfer it to the spandex-project org. I would also put you on the core team so you could help maintain it :)

ludwikbukowski commented 5 years ago

Oh right, makes much more sense :) Then will keep you updated!

cheerfulstoic commented 4 years ago

Hey @ludwikbukowski Do you still have that middleware for spandex + absinthe? I'm struggling a bit trying to get it to work. I followed this presentation (slide 37 is the main one for this) and I got spans in my Datadog traces, but they were more of a single, long spike of spans going down getting smaller and smaller, rather than the flamegraph that I'm used to.

The one thing I would point out about that slide is that it shows something like this:

  def middleware(middleware, _field, _object) do
    [MyAppWeb.Graphql.Middleware.DatadogTracing | middleware]

    middleware
  end

But that wouldn't actually add the middleware. It only "works" (partly) when I do this:

  def middleware(middleware, _field, _object) do
    [MyAppWeb.Graphql.Middleware.DatadogTracing | middleware]
  end
cheerfulstoic commented 4 years ago

Oh, wow, ok, I see that's your slideshow from after you made your comments 😆

Maybe there's more to it? This is what my middleware module looks like:

defmodule MyAppWeb.Graphql.Middleware.DatadogTracing do
  @moduledoc "Allows display on a flamegraph for GraphQL fields in Datadog traces"

  @behaviour Absinthe.Middleware

  alias MyAppWeb.Tracer

  @impl Absinthe.Middleware
  def call(res, _config) do
    Tracer.start_span("#{res.parent_type.name}.#{res.definition.name}")
    %{res | middleware: res.middleware ++ [{{__MODULE__, :after_field}, []}]}
  end

  def after_field(res, _) do
    Tracer.finish_span()

    res
  end
end

And this is an example of what I see in Datadog:

Screen Shot 2020-09-07 at 11 10 08

abmBispo commented 2 years ago

Any updates regarding the spandex_absinthe? The code of @cheerfulstoic is great to get the flamegraphs for the absinthe resolvers, however it doesn't help in segregating the requests. Any recommendations to break apart these distinct queries, like we have in normal REST requests?

image

abmBispo commented 2 years ago

To whom it may concern, I made this gambiarra so now I can convert the normal resource (HTTP Verb + Path) into the query/mutation name.

MyApp.Tracer

defmodule MyApp.Tracer do
  @moduledoc false
  use Spandex.Tracer, otp_app: :my_app

  def customize_metadata(%{body_params: %{"query" => query}} = conn) when is_binary(query) do
    name =
      query
      |> String.split("{")
      |> Enum.at(1)
      |> String.split("(")
      |> Enum.at(0)
      |> String.replace("\n", " ")
      |> String.replace(" ", "")

    conn
    |> SpandexPhoenix.default_metadata()
    |> Keyword.update(:resource, name, fn _ -> name end)
    |> Keyword.merge(tags: [query: conn.body_params["query"], variables: conn.body_params["variables"]])
  end

  def customize_metadata(conn),
    do: SpandexPhoenix.default_metadata(conn)
end

Application

defmodule MyApp.Application do
  ...
  SpandexPhoenix.Telemetry.install(customize_metadata: &MyApp.Tracer.customize_metadata/1)
  ...
end
LucasAmaral42 commented 1 month ago

I did something like you (adaptação técnica avançada/gambiarra), but using :before_send.

router.ex

post "/graphql", Absinthe.Plug,
  schema: MyAppWeb.Schema,
  before_send: {__MODULE__, :absinthe_before_send}

...

def absinthe_before_send(conn, %Absinthe.Blueprint{result: %{data: data}} = blueprint) do
    with [schema | rest] <- Map.keys(data),
         [] <- rest,
         type <- Absinthe.Blueprint.current_operation(blueprint).type do
        conn
        |> put_private(:"graphql-schema", "#{type} #{schema}")
    else
        _ -> conn
    end
end

Keeping the POST / on cases when is more than 1 query/mutation

MyApp.Tracer

defmodule MyApp.Tracer do
  @moduledoc "tracer information to datadog"

  use Spandex.Tracer, otp_app: :my_app

  def customize_metadata(%Plug.Conn{private: %{:"graphql-schema" => resource}} = conn) do
    conn
    |> SpandexPhoenix.default_metadata()
    |> Keyword.update(:resource, "", fn default_resource ->
      resource || default_resource
    end)
  end

  def customize_metadata(conn), do: SpandexPhoenix.default_metadata(conn)
end

Creating a customize_metadata to update resource that is sent.

application.ex

SpandexPhoenix.Telemetry.install(
      span_opts: [
        type: :web,
        tags: [version: version]
      ],
      customize_metadata: &MyApp.Tracer.customize_metadata/1
    )

Adding the customize_metadata to SpandexPhoenix.Telemetry.install opts.


On that way only updates resource in single query/mutation/subscription graphql requests and keep the others requests as usual.