# 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:

``` elixir
  # ... 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
  |> Absinthe.Plug.default_pipeline(pipeline_opts)
  |> ApolloTracing.Pipeline.add_phases()
  |> Opencensus.Absinthe.add_phases()

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][c:middleware/3] 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)

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)


### 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

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)

Fire off a few requests and check the `{span, <<NAME>` lines on standard

* If you see names matching your GraphQL route, e.g. `<</api>>`, you set up
  `opencensus_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 your `field :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`][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`][opencensus] to keep track of spans in flight.

This package adds new [phases] to your [Absinthe Pipeline][pipeline]
to start new spans for each [resolution] and call, using both methods

> `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 dependencies
* `mix deps.compile` to compile them
* `mix licenses` to check their license declarations, recursively

Finding problems:

* `mix compile` to compile your code
* `mix credo` to suggest more idiomatic style for it
* `mix dialyzer` to find problems static typing might spot... *slowly*
* `mix test` to run unit tests
* `mix` to run the tests again whenever you change something
* `mix coveralls` to check test coverage


* `mix docs` to generate documentation for this project
* `mix help` to find out what else you can do with `mix`

### 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`][PR663] to add [`telemetry`][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.