Commit 6b5b3d21 authored by Igor's avatar Igor
Browse files

tracing: nest rails spans properly

parent fd429bdd
Loading
Loading
Loading
Loading
Loading
+21 −22
Original line number Diff line number Diff line
@@ -19,40 +19,32 @@ module Labkit
          subscriber = new

          subscriptions = [
            ActiveSupport::Notifications.subscribe(RENDER_TEMPLATE_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
              subscriber.notify_render_template(start, finish, payload)
            end,
            ActiveSupport::Notifications.subscribe(RENDER_COLLECTION_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
              subscriber.notify_render_collection(start, finish, payload)
            end,
            ActiveSupport::Notifications.subscribe(RENDER_PARTIAL_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
              subscriber.notify_render_partial(start, finish, payload)
            end,
            ActiveSupport::Notifications.subscribe(RENDER_TEMPLATE_NOTIFICATION_TOPIC, ActionViewRenderTemplateSubscriber.new),
            ActiveSupport::Notifications.subscribe(RENDER_COLLECTION_NOTIFICATION_TOPIC, ActionViewRenderCollectionSubscriber.new),
            ActiveSupport::Notifications.subscribe(RENDER_PARTIAL_NOTIFICATION_TOPIC, ActionViewRenderPartialSubscriber.new),
          ]

          create_unsubscriber subscriptions
        end

        # For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
        def notify_render_template(start, finish, payload)
          generate_span_for_notification("render_template", start, finish, payload, tags_for_render_template(payload))
        end

        def notify_render_collection(start, finish, payload)
          generate_span_for_notification("render_collection", start, finish, payload, tags_for_render_collection(payload))
        class ActionViewRenderTemplateSubscriber < AbstractSubscriber
          def span_name(payload)
            "render_template"
          end

        def notify_render_partial(start, finish, payload)
          generate_span_for_notification("render_partial", start, finish, payload, tags_for_render_partial(payload))
          def tags(payload)
            { "component" => COMPONENT_TAG, "template.id" => payload[:identifier], "template.layout" => payload[:layout] }
          end
        end

        private

        def tags_for_render_template(payload)
          { "component" => COMPONENT_TAG, "template.id" => payload[:identifier], "template.layout" => payload[:layout] }
        class ActionViewRenderCollectionSubscriber < AbstractSubscriber
          def span_name(payload)
            "render_collection"
          end

        def tags_for_render_collection(payload)
          def tags(payload)
            {
              "component" => COMPONENT_TAG,
              "template.id" => payload[:identifier],
@@ -60,11 +52,18 @@ module Labkit
              "template.cache.hits" => payload[:cache_hits] || 0,
            }
          end
        end

        class ActionViewRenderPartialSubscriber < AbstractSubscriber
          def span_name(payload)
            "render_partial"
          end

        def tags_for_render_partial(payload)
          def tags(payload)
            { "component" => COMPONENT_TAG, "template.id" => payload[:identifier] }
          end
        end
      end
    end
  end
end
+5 −11
Original line number Diff line number Diff line
@@ -17,26 +17,19 @@ module Labkit
        def self.instrument
          subscriber = new

          subscription =
            ActiveSupport::Notifications.subscribe(ACTIVE_RECORD_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
              subscriber.notify(start, finish, payload)
            end
          subscription = ActiveSupport::Notifications.subscribe(ACTIVE_RECORD_NOTIFICATION_TOPIC, ActiveRecordSqlSubscriber.new)

          create_unsubscriber [subscription]
        end

        # For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
        def notify(start, finish, payload)
          generate_span_for_notification(notification_name(payload), start, finish, payload, tags_for_notification(payload))
        end

        private

        def notification_name(payload)
        class ActiveRecordSqlSubscriber < AbstractSubscriber
          def span_name(payload)
            OPERATION_NAME_PREFIX + (payload[:name].presence || DEFAULT_OPERATION_NAME)
          end

        def tags_for_notification(payload)
          def tags(payload)
            {
              "component" => "ActiveRecord",
              "span.kind" => "client",
@@ -50,3 +43,4 @@ module Labkit
      end
    end
  end
end
+46 −38
Original line number Diff line number Diff line
@@ -22,63 +22,71 @@ module Labkit
          subscriber = new

          subscriptions = [
            ActiveSupport::Notifications.subscribe(CACHE_READ_TOPIC) do |_, start, finish, _, payload|
              subscriber.notify_cache_read(start, finish, payload)
            end,
            ActiveSupport::Notifications.subscribe(CACHE_GENERATE_TOPIC) do |_, start, finish, _, payload|
              subscriber.notify_cache_generate(start, finish, payload)
            end,
            ActiveSupport::Notifications.subscribe(CACHE_FETCH_HIT_TOPIC) do |_, start, finish, _, payload|
              subscriber.notify_cache_fetch_hit(start, finish, payload)
            end,
            ActiveSupport::Notifications.subscribe(CACHE_WRITE_TOPIC) do |_, start, finish, _, payload|
              subscriber.notify_cache_write(start, finish, payload)
            end,
            ActiveSupport::Notifications.subscribe(CACHE_DELETE_TOPIC) do |_, start, finish, _, payload|
              subscriber.notify_cache_delete(start, finish, payload)
            end,
            ActiveSupportCacheReadSubscriber.new,
            ActiveSupportCacheGenerateSubscriber.new,
            ActiveSupportCacheFetchHitSubscriber.new,
            ActiveSupportCacheWriteSubscriber.new,
            ActiveSupportCacheDeleteSubscriber.new,
          ]

          create_unsubscriber subscriptions
        end

        # For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html#active-support
        def notify_cache_read(start, finish, payload)
          generate_span_for_notification("cache_read", start, finish, payload, tags_for_cache_read(payload))

        class ActiveSupportCacheReadSubscriber < AbstractSubscriber
          def span_name(payload)
            "cache_read"
          end

        def notify_cache_generate(start, finish, payload)
          generate_span_for_notification("cache_generate", start, finish, payload, tags_for_key(payload))
          def tags(payload)
            {
              "component" => COMPONENT_TAG,
              "cache.key" => payload[:key],
              "cache.hit" => payload[:hit],
              "cache.super_operation" => payload[:super_operation],
            }
          end
        end

        def notify_cache_fetch_hit(start, finish, payload)
          generate_span_for_notification("cache_fetch_hit", start, finish, payload, tags_for_key(payload))
        class ActiveSupportCacheGenerateSubscriber < AbstractSubscriber
          def span_name(payload)
            "cache_generate"
          end

        def notify_cache_write(start, finish, payload)
          generate_span_for_notification("cache_write", start, finish, payload, tags_for_key(payload))
          def tags(payload)
            { "component" => COMPONENT_TAG, "cache.key" => payload[:key] }
          end
        end

        def notify_cache_delete(start, finish, payload)
          generate_span_for_notification("cache_delete", start, finish, payload, tags_for_key(payload))
        class ActiveSupportCacheFetchHitSubscriber < AbstractSubscriber
          def span_name(payload)
            "cache_fetch_hit"
          end

        private
          def tags(payload)
            { "component" => COMPONENT_TAG, "cache.key" => payload[:key] }
          end
        end

        def tags_for_cache_read(payload)
          {
            "component" => COMPONENT_TAG,
            "cache.key" => payload[:key],
            "cache.hit" => payload[:hit],
            "cache.super_operation" => payload[:super_operation],
          }
        class ActiveSupportCacheWriteSubscriber < AbstractSubscriber
          def span_name(payload)
            "cache_write"
          end

        def tags_for_key(payload)
          {
            "component" => COMPONENT_TAG,
            "cache.key" => payload[:key],
          }
          def tags(payload)
            { "component" => COMPONENT_TAG, "cache.key" => payload[:key] }
          end
        end

        class ActiveSupportCacheDeleteSubscriber < AbstractSubscriber
          def span_name(payload)
            "cache_delete"
          end

          def tags(payload)
            { "component" => COMPONENT_TAG, "cache.key" => payload[:key] }
          end
        end
      end
    end
+32 −6
Original line number Diff line number Diff line
@@ -10,16 +10,42 @@ module Labkit
      module RailsCommon
        extend ActiveSupport::Concern

        class_methods do
          def create_unsubscriber(subscriptions)
            -> { subscriptions.each { |subscriber| ActiveSupport::Notifications.unsubscribe(subscriber) } }
          end
        class AbstractSubscriber
          def start(name, id, payload)
            scope = OpenTracing.start_active_span(span_name(payload))

            stack = Thread.current[:_labkit_span_stack] ||= []
            stack.push scope
          end

        def generate_span_for_notification(operation_name, start, finish, payload, tags)
          def finish(name, id, payload)
            stack = Thread.current[:_labkit_span_stack]
            scope = stack.pop
            span = scope.span

            exception = payload[:exception]
            Labkit::Tracing::TracingUtils.log_exception_on_span(span, exception) if exception

            tags(payload).each do |k,v|
              span.set_tag(k, v)
            end

            scope.close
          end

          TracingUtils.postnotify_span(operation_name, start, finish, tags: tags, exception: exception)
          def span_name(payload)
            raise 'span_name not implemented'
          end

          def tags(payload)
            {}
          end
        end

        class_methods do
          def create_unsubscriber(subscriptions)
            -> { subscriptions.each { |subscriber| ActiveSupport::Notifications.unsubscribe(subscriber) } }
          end
        end
      end
    end