Awesome
Opencensus.Absinthe
Extends Absinthe to automatically create OpenCensus spans. Designed to work with whatever is producing spans upstream, e.g. Opencensus.Plug.
Installation
- Take the dependency
- Set up the pipeline
- Set up the middleware
- Adjust your schema
- Check it's all working
Dependency
If you're using Absinthe.Plug
, add opencensus_absinthe
to your deps
in mix.exs
using a tighter version constraint than:
{:absinthe_plug, ">= 0.0.0"},
{:opencensus_absinthe, ">= 0.0.0"},
Pipeline
Add a :pipeline
to your t:Absinthe.Plug.opts/0
to have it call
Opencensus.Absinthe.Plug.traced_pipeline/2
. If you're using
Phoenix.Router.forward/4
, for example:
forward(
path,
Absinthe.Plug,
# ... existing config ...
pipeline: {Opencensus.Absinthe.Plug, :traced_pipeline}
)
If you already have a pipeline
, you can define your own and call both to
insert their phases. To work with ApolloTracing
, for example:
def your_custom_pipeline(config, pipeline_opts \\ []) do
config
|> Absinthe.Plug.default_pipeline(pipeline_opts)
|> ApolloTracing.Pipeline.add_phases()
|> Opencensus.Absinthe.add_phases()
end
Worst case, you'll need to copy the code from the current pipeline
target
and add a call to Opencensus.Absinthe.add_phases/1
as above.
Middleware
Your middleware callback needs to run its output through
the matching function in Opencensus.Absinthe.Middleware
to add the
middleware to only the fields that need it:
def middleware(middleware, field, object) do
Opencensus.Absinthe.middleware(middleware, field, object)
end
If you've already got some middleware, like above, you might need to copy some code around to get the job done:
def middleware(middleware, field, object) do
([ApolloTracing.Middleware.Tracing, ApolloTracing.Middleware.Caching] ++ middleware)
|> Opencensus.Absinthe.middleware(field, object)
end
Schema
Until Absinthe merge and publish their telemetry support (see below) and
you upgrade, you'll also need to set :trace
in the metadata for any
field
for which you want tracing to happen:
query do
@desc "List all the things"
field :things, list_of(:thing), meta: [trace: true] do
resolve(&Resolvers.Account.all_things/2)
end
Once you're on a telemetry-capable Absinthe, you'll get tracing for every
field
containing a resolve
.
Verification
Check your installation with iex -S mix phx.server
, assuming Phoenix, and:
iex> :oc_reporter.register(:oc_reporter_stdout)
:ok
Fire off a few requests and check the {span, <<NAME>
lines on standard
output.
-
If you see names matching your GraphQL route, e.g.
<</api>>
, you set upopencensus_plug
properly. -
If you see
<<"Absinthe.Blueprint">>
, the pipeline is working. -
If you see
<<"YourProject.Schema:thefield">>
, the middleware is working and you've either:-
Added
meta: [trace: true]
to yourfield :thefield
as above, or -
Upgraded to a telemetry-capable Absinthe.
-
Behaviour
Each Absinthe query runs in the process of its caller. If you hook up
opencensus_plug
, or something else that'll take trace
details off the wire, the process dictionary will have an :oc_span_ctx_key
key used by opencensus
to keep track of spans in flight.
This package adds new phases to your Absinthe Pipeline to start new spans for each resolution and call, using both methods available:
opencensus
provides two methods for tracking [trace and span] context, the process dictionary and a variable holding a ctx record.
Specifically, this package:
-
Starts a new span registered in the process dictionary for each query, and
-
Without any use of the process dictionary, starts a new span for each field, using the query span as the parent.
The latter is necessary because the fields don't necessarily start and stop
without overlap. Naïve use of :ocp.with_child_span
and :ocp.finish_span
will yield incorrect traces.
Development
Dependency management:
mix deps.get
to get your dependenciesmix deps.compile
to compile themmix licenses
to check their license declarations, recursively
Finding problems:
mix compile
to compile your codemix credo
to suggest more idiomatic style for itmix dialyzer
to find problems static typing might spot... slowlymix test
to run unit testsmix test.watch
to run the tests again whenever you change somethingmix coveralls
to check test coverage
Documentation:
mix docs
to generate documentation for this projectmix help
to find out what else you can do withmix
Next Steps
Obvious next steps include stronger tests and many minor tweaks:
- Rename the outer span according to the schema
- Set some attributes on the outer span
- Trim the path from references so it starts with the closest
lib
- Set the span status on completion
- Retire
lib/opencensus/absinthe/logger.ex
when possible
The biggest looming change would be telemetry integration:
absinthe-graphql/absinthe#663
to add telemetry
to
Absinthe could give us start and stop calls from within the calling process
suitable for calling :ocp.with_child_span
and :ocp.finish_span
to
maintain the main trace. In turn, that'd mean we didn't need the pipeline.
#663
won't help us generate spans for fields, because there's no way to
pass state back through :telemetry.execute
. That said, it'll automatically
set :absinthe_telemetry
in the field metadata if query
is present.
Rather than push back on the telemetry support to make it better support tracing, we could integrate this capability directly with Absinthe if:
- The community deploy a lot of
opencensus
- It proves to be as lightweight and stable as
telemetry
- Its impact when not hooked up is minimal or zero
We could then retire this module except for users with older versions.