Commit 03b07391 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Log durations for failing GraphQL-queries

If the query were to break by a Runtime error, even if it's handled
later on in the case of an `GraphQL::Error` subclass.

This makes sure that we still include a duration for those requests in
the logs.

This also adds information about the exception to the logs.
parent 2cccf3de
...@@ -11,19 +11,20 @@ module Gitlab ...@@ -11,19 +11,20 @@ module Gitlab
end end
def trace(key, data) def trace(key, data)
result = yield yield
rescue StandardError => e
data[:exception] = e
raise e
ensure
case key case key
when "execute_query" when "execute_query"
log_execute_query(**data) log_execute_query(**data)
end end
result
end end
private private
def log_execute_query(query: nil, duration_s: 0) def log_execute_query(query: nil, duration_s: 0, exception: nil)
# execute_query should always have :query, but we're just being defensive # execute_query should always have :query, but we're just being defensive
return unless query return unless query
...@@ -39,6 +40,8 @@ module Gitlab ...@@ -39,6 +40,8 @@ module Gitlab
query_string: query.query_string query_string: query.query_string
} }
Gitlab::ExceptionLogFormatter.format!(exception, info)
info.merge!(::Gitlab::ApplicationContext.current) info.merge!(::Gitlab::ApplicationContext.current)
info.merge!(analysis_info) if analysis_info info.merge!(analysis_info) if analysis_info
......
...@@ -17,13 +17,9 @@ module Gitlab ...@@ -17,13 +17,9 @@ module Gitlab
def trace(key, data) def trace(key, data)
start_time = Gitlab::Metrics::System.monotonic_time start_time = Gitlab::Metrics::System.monotonic_time
result = yield yield
ensure
duration_s = Gitlab::Metrics::System.monotonic_time - start_time data[:duration_s] = Gitlab::Metrics::System.monotonic_time - start_time
data[:duration_s] = duration_s
result
end end
end end
end end
......
# frozen_string_literal: true # frozen_string_literal: true
require "fast_spec_helper" require "spec_helper"
require "support/graphql/fake_query_type" require "support/graphql/fake_query_type"
RSpec.describe Gitlab::Graphql::Tracers::LoggerTracer do RSpec.describe Gitlab::Graphql::Tracers::LoggerTracer do
...@@ -49,4 +49,15 @@ RSpec.describe Gitlab::Graphql::Tracers::LoggerTracer do ...@@ -49,4 +49,15 @@ RSpec.describe Gitlab::Graphql::Tracers::LoggerTracer do
dummy_schema.execute(query_string, variables: variables) dummy_schema.execute(query_string, variables: variables)
end end
it 'logs exceptions for breaking queries' do
query_string = "query fooOperation { breakingField }"
expect(::Gitlab::GraphqlLogger).to receive(:info).with(a_hash_including({
'exception.message' => 'This field is supposed to break',
'exception.class' => 'RuntimeError'
}))
expect { dummy_schema.execute(query_string) }.to raise_error(/This field is supposed to break/)
end
end end
...@@ -20,6 +20,7 @@ RSpec.describe Gitlab::Graphql::Tracers::TimerTracer do ...@@ -20,6 +20,7 @@ RSpec.describe Gitlab::Graphql::Tracers::TimerTracer do
before do before do
current_time = 0 current_time = 0
allow(tracer_spy).to receive(:trace)
allow(Gitlab::Metrics::System).to receive(:monotonic_time) do allow(Gitlab::Metrics::System).to receive(:monotonic_time) do
current_time += expected_duration current_time += expected_duration
end end
...@@ -30,6 +31,18 @@ RSpec.describe Gitlab::Graphql::Tracers::TimerTracer do ...@@ -30,6 +31,18 @@ RSpec.describe Gitlab::Graphql::Tracers::TimerTracer do
dummy_schema.execute(query_string) dummy_schema.execute(query_string)
expect_to_have_traced(tracer_spy, expected_duration, query_string)
end
it "adds a duration_s even if the query failed" do
query_string = "query fooOperation { breakingField }"
expect { dummy_schema.execute(query_string) }.to raise_error(/This field is supposed to break/)
expect_to_have_traced(tracer_spy, expected_duration, query_string)
end
def expect_to_have_traced(tracer_spy, expected_duration, query_string)
# "parse" and "execute_query" are just arbitrary trace events # "parse" and "execute_query" are just arbitrary trace events
expect(tracer_spy).to have_received(:trace).with("parse", { expect(tracer_spy).to have_received(:trace).with("parse", {
duration_s: expected_duration, duration_s: expected_duration,
......
# frozen_string_literal: true # frozen_string_literal: true
require 'graphql'
module Graphql module Graphql
class FakeQueryType < Types::BaseObject class FakeQueryType < ::GraphQL::Schema::Object
graphql_name 'FakeQuery' graphql_name 'FakeQuery'
field :hello_world, String, null: true do field :hello_world, String, null: true do
argument :message, String, required: false argument :message, String, required: false
end end
field :breaking_field, String, null: true
def hello_world(message: "world") def hello_world(message: "world")
"Hello #{message}!" "Hello #{message}!"
end end
def breaking_field
raise "This field is supposed to break"
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment