From a62c8cc6df1f73972c8921143bc0b739e6bfb6a1 Mon Sep 17 00:00:00 2001 From: John Bell Date: Fri, 12 Jan 2024 14:03:37 +0000 Subject: [PATCH] [COOP-566]: Add Measurement System for SLIs on Stonehenge Mutations/Queries (#134) * [COOP-566]: Expose a telemetry event for handled graphql requests * Update lib/opentelemetry_absinthe.ex Co-authored-by: Cristiano Piemontese * Update change log and version number * Add the schema as part of the payload of the telemetry event --------- Co-authored-by: Cristiano Piemontese --- CHANGELOG.md | 6 +++ lib/instrumentation.ex | 80 ++++++++++++++++++++--------------- lib/opentelemetry_absinthe.ex | 7 +++ mix.exs | 2 +- 4 files changed, 61 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5729b1..7b4f541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.0] - 2024-01-12 + +### Changed + +- dispatch telemetry events for the handling of graphql requests + ## [2.0.1] - 2023-03-14 ### Changed diff --git a/lib/instrumentation.ex b/lib/instrumentation.ex index a56f48f..d08eca8 100644 --- a/lib/instrumentation.ex +++ b/lib/instrumentation.ex @@ -15,6 +15,19 @@ defmodule OpentelemetryAbsinthe.Instrumentation do require Logger require Record + @telemetry [:opentelemetry_absinthe, :graphql, :handled] + + @type graphql_handled_event_metadata :: %{ + operation_name: String.t() | nil, + operation_type: :query | :mutation, + schema: Absinthe.Schema.t(), + status: :ok | :error + } + + @type graphql_handled_event_measurements :: %{ + duration: :int + } + @span_ctx_fields Record.extract(:span_ctx, from_lib: "opentelemetry_api/include/opentelemetry.hrl" ) @@ -88,41 +101,40 @@ defmodule OpentelemetryAbsinthe.Instrumentation do Tracer.set_current_span(new_ctx) end - def handle_operation_stop(_event_name, _measurements, data, config) do + def handle_operation_stop(_event_name, measurements, data, config) do operation_type = get_operation_type(data) operation_name = get_operation_name(data) - span_name = span_name(operation_type, operation_name, config.span_name) - Tracer.update_name(span_name) + operation_type + |> span_name(operation_name, config.span_name) + |> Tracer.update_name() errors = data.blueprint.result[:errors] + status = status(errors) + set_status(status) + + [] + |> put_if(config.trace_request_type, {@graphql_operation_type, operation_type}) + |> put_if(config.trace_request_name, {@graphql_operation_name, operation_name}) + |> put_if(config.trace_response_result, {:"graphql.response.result", Jason.encode!(data.blueprint.result)}) + |> put_if(config.trace_response_errors, {:"graphql.response.errors", Jason.encode!(errors)}) + |> put_if( + config.trace_request_selections, + fn -> {:"graphql.request.selections", data |> get_graphql_selections() |> Jason.encode!()} end + ) + |> Tracer.set_attributes() + + :telemetry.execute( + @telemetry, + measurements, + %{ + operation_name: operation_name, + operation_type: operation_type, + schema: data.blueprint.schema, + status: status + } + ) - result_attributes = - [] - |> put_if( - config.trace_request_type, - {@graphql_operation_type, operation_type} - ) - |> put_if( - config.trace_request_name, - {@graphql_operation_name, operation_name} - ) - |> put_if( - config.trace_response_result, - {:"graphql.response.result", Jason.encode!(data.blueprint.result)} - ) - |> put_if( - config.trace_response_errors, - {:"graphql.response.errors", Jason.encode!(errors)} - ) - |> put_if( - config.trace_request_selections, - fn -> {:"graphql.request.selections", data |> get_graphql_selections() |> Jason.encode!()} end - ) - - set_status(errors) - - Tracer.set_attributes(result_attributes) Tracer.end_span() restore_parent_ctx() @@ -176,8 +188,10 @@ defmodule OpentelemetryAbsinthe.Instrumentation do Tracer.set_current_span(ctx) end - # set status as `:error` in case of errors in the graphql response - defp set_status(nil), do: :ok - defp set_status([]), do: :ok - defp set_status(_errors), do: Tracer.set_status(OpenTelemetry.status(:error, "")) + defp status(nil), do: :ok + defp status([]), do: :ok + defp status(_error), do: :error + + defp set_status(:ok), do: :ok + defp set_status(:error), do: Tracer.set_status(OpenTelemetry.status(:error, "")) end diff --git a/lib/opentelemetry_absinthe.ex b/lib/opentelemetry_absinthe.ex index d6c02f9..d2046c2 100644 --- a/lib/opentelemetry_absinthe.ex +++ b/lib/opentelemetry_absinthe.ex @@ -70,6 +70,13 @@ defmodule OpentelemetryAbsinthe do * `trace_response_result`(default: #{Keyword.fetch!(@config, :trace_response_result)}): attaches the result returned by the server as an attribute * `trace_response_errors`(default: #{Keyword.fetch!(@config, :trace_response_errors)}): attaches the errors returned by the server as an attribute + + ## Telemetry + + OpentelemetryAbsinthe exposes `telemetry` events which can be hooked into using `:telemetry.attach/4` or `:telemetry.attach_many/4`. + The events exposed are: + + - `[:opentelemetry_absinthe, :graphql, :handled]` for when a GraphQl query has been handled, the metadata and measurements are defined in `OpentelemetryAbsinthe.Instrumentation.graphql_handled_event_metadata()` and `OpentelemetryAbsinthe.Instrumentation.graphql_handled_event_measurements()` """ defdelegate setup(instrumentation_opts \\ []), to: Instrumentation diff --git a/mix.exs b/mix.exs index da1c3ae..2faddac 100644 --- a/mix.exs +++ b/mix.exs @@ -2,7 +2,7 @@ defmodule OpentelemetryAbsinthe.MixProject do use Mix.Project @source_url "https://github.com/primait/opentelemetry_absinthe" - @version "2.0.1-rc.0" + @version "2.1.0" def project do [