{"id":21824368,"url":"https://github.com/shopify/graphql-metrics","last_synced_at":"2025-05-15T03:04:25.145Z","repository":{"id":49180603,"uuid":"139599402","full_name":"Shopify/graphql-metrics","owner":"Shopify","description":"Extract as much much detail as you want from GraphQL queries, served up from your Ruby app and the graphql gem. ","archived":false,"fork":false,"pushed_at":"2025-01-23T14:10:17.000Z","size":281,"stargazers_count":172,"open_issues_count":2,"forks_count":10,"subscribers_count":383,"default_branch":"main","last_synced_at":"2025-04-14T01:00:03.172Z","etag":null,"topics":["graphql"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Shopify.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-07-03T14:56:50.000Z","updated_at":"2025-02-11T21:34:08.000Z","dependencies_parsed_at":"2023-12-06T20:25:22.806Z","dependency_job_id":"b4401e58-c0e5-43df-919a-4c57e02e6c42","html_url":"https://github.com/Shopify/graphql-metrics","commit_stats":{"total_commits":145,"total_committers":21,"mean_commits":6.904761904761905,"dds":"0.46896551724137936","last_synced_commit":"f1a1eaa414629d16aaf1c450a40c93eb1480b461"},"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Shopify%2Fgraphql-metrics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Shopify%2Fgraphql-metrics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Shopify%2Fgraphql-metrics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Shopify%2Fgraphql-metrics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Shopify","download_url":"https://codeload.github.com/Shopify/graphql-metrics/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248804790,"owners_count":21164132,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["graphql"],"created_at":"2024-11-27T17:57:43.620Z","updated_at":"2025-04-14T01:00:26.672Z","avatar_url":"https://github.com/Shopify.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GraphQL Metrics\n\n![](https://github.com/Shopify/graphql-metrics/workflows/Ruby/badge.svg)\n\nExtract as much detail as you want from GraphQL queries, served up from your Ruby app and the [`graphql` gem](https://github.com/rmosolgo/graphql-ruby).\nCompatible with the [`graphql-batch` gem](https://github.com/Shopify/graphql-batch), to extract batch-loaded fields resolution timings.\n\nBe sure to read the [CHANGELOG](CHANGELOG.md) to stay updated on feature additions, breaking changes made to this gem.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'graphql-metrics'\n```\n\nYou can require it with in your code as needed with:\n```ruby\nrequire 'graphql/metrics'\n```\n\nOr globally in the Gemfile with:\n```ruby\ngem 'graphql-metrics', require: 'graphql/metrics'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install graphql-metrics\n\n## Usage\n\nGet started by defining your own Analyzer, inheriting from `GraphQL::Metrics::Analyzer`.\n\nThe following analyzer demonstrates a simple way to capture commonly used metrics sourced from key parts of your schema\ndefinition, the query document being served, as well as runtime query and resolver timings. In this toy example, all of\nthis data is simply stored on the `GraphQL::Query` context, under a namespace to avoid collisions with other analyzers\netc.\n\nWhat you do with these captured metrics is up to you!\n\n**NOTE**: If any non-`graphql-ruby` gem-related exceptions occur in your application during query document\nparsing and validation, **runtime metrics** for queries (like `query_duration`, etc.) as well as field\nresolver timings (like `resolver_timings`, `lazy_resolver_timings`) **may not be present** in the extracted `metrics` hash,\neven if you opt to collect them.\n\n### Define your own analyzer subclass\n\n```ruby\n  class SimpleAnalyzer \u003c GraphQL::Metrics::Analyzer\n    ANALYZER_NAMESPACE = :simple_analyzer_namespace\n\n    def initialize(query_or_multiplex)\n      super\n\n      # `query` is defined on instances of objects inheriting from GraphQL::Metrics::Analyzer\n      ns = query.context.namespace(ANALYZER_NAMESPACE)\n      ns[:simple_extractor_results] = {}\n    end\n\n    # @param metrics [Hash] Query metrics, including a few details about the query document itself, as well as runtime\n    # timings metrics, intended to be compatible with the Apollo Tracing spec:\n    # https://github.com/apollographql/apollo-tracing#response-format\n    #\n    # {\n    #   operation_type: \"query\",\n    #   operation_name: \"PostDetails\",\n    #   query_start_time: 1573833076.027327,\n    #   query_duration: 2.0207119999686256,\n    #   lexing_duration: 0.0008190000080503523,\n    #   parsing_duration: 0.0008190000080503523,\n    #   validation_duration: 0.01704599999357015,\n    #   analysis_duration: 0.0008190000080503523,\n    # }\n    #\n    # You can use these metrics to track high-level query performance, along with any other details you wish to\n    # manually capture from `query` and/or `query.context`.\n    def query_extracted(metrics)\n      custom_metrics_from_context = {\n        request_id: query.context[:request_id],\n        # ...\n      }\n\n      # You can make use of captured metrics here (logging to Kafka, request logging etc.)\n      # log_metrics(:fields, metrics)\n      #\n      # Or store them on the query context:\n      store_metrics(:queries, metrics.merge(custom_metrics_from_context))\n    end\n\n    # For use after controller:\n    # class GraphQLController \u003c ActionController::Base\n    #   def graphql_query\n    #     query_result = graphql_query.result.to_h\n    #     do_something_with_metrics(query.context[:simple_extractor_results])\n    #     render json: graphql_query.result\n    #   end\n    # end\n\n    # @param metrics [Hash] Field selection metrics, including resolver timings metrics, also adhering to the Apollo\n    # Tracing spec referred to above.\n    #\n    # `resolver_timings` is populated any time a field is resolved (which may be many times, if the field is nested\n    # within a list field e.g. a Relay connection field).\n    #\n    # `lazy_resolver_timings` is only populated by fields that are resolved lazily (for example using the\n    # graphql-batch gem) or that are otherwise resolved with a Promise. Any time spent in the field's resolver to\n    # prepare work to be done \"later\" in a Promise, or batch loader will be captured in `resolver_timings`. The time\n    # spent actually doing lazy field loading, including time spent within a batch loader can be obtained from\n    # `lazy_resolver_timings`.\n    #\n    # {\n    #   field_name: \"id\",\n    #   return_type_name: \"ID\",\n    #   parent_type_name: \"Post\",\n    #   deprecated: false,\n    #   path: [\"post\", \"id\"],\n    #   resolver_timings: [\n    #     5.999987479299307e-06,\n    #   ],\n    #   lazy_resolver_timings: [\n    #     5.999987479299307e-06,\n    #   ],\n    # }\n    def field_extracted(metrics)\n      store_metrics(:fields, metrics)\n    end\n\n    # @param metrics [Hash] Directive metrics\n    # {\n    #   directive_name: \"customDirective\",\n    # }\n    def directive_extracted(metrics)\n      store_metrics(:directives, metrics)\n    end\n\n    # @param metrics [Hash] Argument usage metrics, including a few details about the query document itself, as well\n    # as resolver timings metrics, also ahering to the Apollo Tracing spec referred to above.\n    # {\n    #   argument_name: \"ids\",\n    #   argument_type_name: \"ID\",\n    #   parent_name: \"comments\",\n    #   grandparent_type_name: \"Post\",\n    #   grandparent_node_name: \"post\",\n    #   default_used: false,\n    #   value_is_null: false,\n    #   value: \u003cGraphQL::Query::Arguments::ArgumentValue\u003e,\n    # }\n    #\n    # `value` is exposed here, in case you want to get access to the argument's definition, including the type\n    # class which defines it, e.g. `metrics[:value].definition.metadata[:type_class]`\n    def argument_extracted(metrics)\n      store_metrics(:arguments, metrics)\n    end\n\n    private\n\n    def store_metrics(context_key, metrics)\n      ns = query.context.namespace(ANALYZER_NAMESPACE)\n      ns[:simple_extractor_results][context_key] ||= []\n      ns[:simple_extractor_results][context_key] \u003c\u003c metrics\n    end\n  end\n```\n\nOnce defined, you can opt into capturing all metrics seen above by simply including `GraphQL::Metrics` as a plugin on your\nschema.\n\n#### Metrics that are captured for arguments for fields and directives\n\nExample query:\n```graphql\nquery PostDetails($postId: ID!, $commentsTags: [String!] = null, $val: Int!) @customDirective(val: $val) {\n  post(id: $postId) {\n    title @skip(if: true)\n    comments(ids: [1, 2], tags: $commentsTags) {\n      id\n      body\n    }\n  }\n}\n```\nThese are some of the arguments that are extracted\n\n```ruby\n{\n  argument_name: \"if\",                    # argument name\n  argument_type_name: \"Boolean\",          # argument type\n  parent_name: \"skip\",                    # argument belongs to `skip` directive\n  grandparent_type_name: \"__Directive\",   # argument was applied to directive\n  grandparent_node_name: \"title\",         # directive was applied to field title\n  default_used: false,                    # check if default value was used\n  value_is_null: false,                   # check if value was null\n  value: \u003cGraphQL::Execution::Interpreter::ArgumentValue\u003e\n}, {\n  argument_name: \"id\",\n  argument_name: \"ids\",\n  argument_type_name: \"ID\",\n  parent_name: \"comments\",                # name of the node that argument was applied to\n  grandparent_type_name: \"Post\",          # grandparent node to uniquely identify which node the argument was applied to\n  grandparent_node_name: \"post\",          # name of grandparend node\n  default_used: false,\n  value_is_null: false,\n  value: \u003cGraphQL::Execution::Interpreter::ArgumentValue\u003e\n}, {\n  argument_name: \"id\",\n  argument_type_name: \"ID\",\n  parent_name: \"post\",                   # argument applied to post field\n  grandparent_type_name: \"QueryRoot\",    # post is a QueryRoot\n  grandparent_node_name: \"query\",        # post field is already in the query root\n  parent_input_object_type: nil,\n  default_used: false,\n  value_is_null: false,\n  value: \u003cGraphQL::Execution::Interpreter::ArgumentValue\u003e\n}, {\n  argument_name: \"val\",\n  argument_type_name: \"Int\",\n  parent_name: \"customDirective\",        # argument belongs to `customDirective` directive\n  grandparent_type_name: \"__Directive\",  # argument was applied to directive\n  grandparent_node_name: \"query\",        # directive was applied to query\n  parent_input_object_type: nil,\n  default_used: false,\n  value_is_null: false,\n  value: \u003cGraphQL::Execution::Interpreter::ArgumentValue\u003e\n}\n```\n\n### Make use of your analyzer\n\nAdd the `GraphQL::Metrics` plugin to your schema. This opts you in to capturing all static and runtime metrics seen above.\n\n```ruby\nclass Schema \u003c GraphQL::Schema\n  query QueryRoot\n  mutation MutationRoot\n\n  use GraphQL::Metrics, analyzer: SimpleAnalyzer\nend\n```\n\n### Optionally, only gather static metrics\n\nIf you don't care to capture field timings metrics, they can be disabled with the `capture_field_timings` option:\n\n```ruby\nclass Schema \u003c GraphQL::Schema\n  query QueryRoot\n  mutation MutationRoot\n\n  use GraphQL::Metrics, analyzer: SimpleAnalyzer, capture_field_timings: false\nend\n```\n\nYour analyzer will still be called with `query_extracted`, `field_extracted`, but with timings metrics omitted.\n`argument_extracted` will work exactly the same.\n\nAnd if you want to disable tracing metrics entirely, use the `capture_timings` option:\n\n```ruby\nclass Schema \u003c GraphQL::Schema\n  query QueryRoot\n  mutation MutationRoot\n\n  use GraphQL::Metrics, analyzer: SimpleAnalyzer, capture_timings: false\nend\n```\n\n## Order of execution\n\nBecause of the structure of graphql-ruby's plugin architecture, it may be difficult to build an intuition around the\norder in which methods defined on `GraphQL::Metrics::Instrumentation`, `GraphQL::Metrics::Trace` and subclasses of\n`GraphQL::Metrics::Analyzer` run.\n\nAlthough you ideally will not need to care about these details if you are simply using this gem to gather metrics in\nyour application as intended, here's a breakdown of the order of execution of the methods involved:\n\n When used as instrumentation, an analyzer and tracing, the order of execution is usually:\n\n* Instrumentation.execute_multiplex (context setup)\n* Trace.capture_lexing_time\n* Trace.capture_parsing_time\n* Trace.capture_validation_time\n* Trace.capture_analysis_time\n* Analyzer#initialize (bit more context setup, instance vars setup)\n* Analyzer#result\n* Trace.capture_query_start_time\n* Trace.trace_field (n times)\n* Instrumentation.execute_multiplex (call query and field callbacks, now that we have all static and runtime metrics gathered)\n* Analyzer#extract_query\n* Analyzer#query_extracted\n* Analyzer#extract_fields_with_runtime_metrics\n  * calls Analyzer#field_extracted n times\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/Shopify/graphql-metrics. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the GraphQL::Metrics project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/graphql-metrics/blob/master/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshopify%2Fgraphql-metrics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshopify%2Fgraphql-metrics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshopify%2Fgraphql-metrics/lists"}