gitlab-labkit 0.13.5 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab/CODEOWNERS +1 -0
  3. data/.rubocop.yml +3 -0
  4. data/gitlab-labkit.gemspec +8 -3
  5. data/lib/gitlab-labkit.rb +29 -0
  6. data/lib/labkit/excon_publisher.rb +130 -0
  7. data/lib/labkit/httpclient_publisher.rb +66 -0
  8. data/lib/labkit/logging/grpc/server_interceptor.rb +1 -1
  9. data/lib/labkit/middleware/sidekiq/tracing/client.rb +1 -1
  10. data/lib/labkit/middleware/sidekiq/tracing/server.rb +1 -1
  11. data/lib/labkit/middleware/sidekiq/tracing/sidekiq_common.rb +14 -1
  12. data/lib/labkit/net_http_publisher.rb +88 -0
  13. data/lib/labkit/system.rb +13 -0
  14. data/lib/labkit/tracing.rb +4 -1
  15. data/lib/labkit/tracing/abstract_instrumenter.rb +47 -0
  16. data/lib/labkit/tracing/external_http.rb +26 -0
  17. data/lib/labkit/tracing/external_http/request_instrumenter.rb +33 -0
  18. data/lib/labkit/tracing/rails.rb +0 -2
  19. data/lib/labkit/tracing/rails/action_view.rb +14 -0
  20. data/lib/labkit/tracing/rails/action_view/render_collection_instrumenter.rb +7 -2
  21. data/lib/labkit/tracing/rails/action_view/render_partial_instrumenter.rb +7 -2
  22. data/lib/labkit/tracing/rails/action_view/render_template_instrumenter.rb +7 -2
  23. data/lib/labkit/tracing/rails/action_view/subscriber.rb +1 -1
  24. data/lib/labkit/tracing/rails/active_record/sql_instrumenter.rb +1 -1
  25. data/lib/labkit/tracing/rails/active_record/subscriber.rb +1 -1
  26. data/lib/labkit/tracing/rails/active_support/cache_delete_instrumenter.rb +1 -1
  27. data/lib/labkit/tracing/rails/active_support/cache_fetch_hit_instrumenter.rb +1 -1
  28. data/lib/labkit/tracing/rails/active_support/cache_generate_instrumenter.rb +1 -1
  29. data/lib/labkit/tracing/rails/active_support/cache_read_instrumenter.rb +1 -1
  30. data/lib/labkit/tracing/rails/active_support/cache_write_instrumenter.rb +1 -1
  31. data/lib/labkit/tracing/rails/active_support/subscriber.rb +1 -1
  32. data/lib/labkit/tracing/tracing_common.rb +20 -0
  33. data/lib/labkit/tracing/tracing_utils.rb +2 -0
  34. metadata +89 -12
  35. data/lib/labkit/tracing/rails/abstract_instrumenter.rb +0 -46
  36. data/lib/labkit/tracing/rails/rails_common.rb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 386875226d2cd983130f209da6204b633bc564dc7cdf749e4fd51aeb27ea44b7
4
- data.tar.gz: b07771c72a4dc8385bba052ac9524dde00cce55023ad9c327db040535faeed31
3
+ metadata.gz: 2b2b166e9a540a6314a2c5e863e7f109f6c3763f2649aa459dfec41a5018e57f
4
+ data.tar.gz: 7dc9c77305e855d930420dca4e59cba0e18bd990fae24a7a44e871a7e03e84ed
5
5
  SHA512:
6
- metadata.gz: 73dcbe4d30d4706da4299dce292b7cd7271be8c10ab555fa4bbd46c53f3cdbb2d47e1c92743f4f914a95b78d3035d43e38003344f7afc399b5507dab84690bb1
7
- data.tar.gz: 571c2df46e971b9696c5e23b5bdba9ab6df56d8253e998fa9a8797b2ebfb4d0e1b2371ac1626fd8e02d11641b17fb5270e23a7c927e28fddf03b17f4b3b21e7a
6
+ metadata.gz: 81819b7e4fd168c805e286f9315dd5a757423367938619021dcb7e81d5ad41a6f057224bfc046d96a132354688b33b0b4ed2c3e2196dfa09725176bb32b6ca3e
7
+ data.tar.gz: 35d67adc6f7561609f617df26af9a3942e77cd1b66000523584308bd160c88062dc0fcd1ed6e0d9662f1af6a5c6436c80fe80b53d5b6b4c86ba1e880b24f5a61
@@ -0,0 +1 @@
1
+ * @andrewn @ayufan @reprazent
@@ -28,6 +28,9 @@ Style/StringLiterals:
28
28
  Style/StringLiteralsInInterpolation:
29
29
  EnforcedStyle: double_quotes
30
30
 
31
+ Style/UseLambda:
32
+ Enabled: false
33
+
31
34
  Layout/MultilineMethodCallIndentation:
32
35
  Enabled: No
33
36
 
@@ -19,19 +19,24 @@ Gem::Specification.new do |spec|
19
19
  spec.required_ruby_version = ">= 2.4.0"
20
20
 
21
21
  # Please maintain alphabetical order for dependencies
22
- spec.add_runtime_dependency "actionpack", ">= 5.0.0", "< 6.1.0"
23
- spec.add_runtime_dependency "activesupport", ">= 5.0.0", "< 6.1.0"
22
+ spec.add_runtime_dependency "actionpack", ">= 5.0.0", "< 7.0.0"
23
+ spec.add_runtime_dependency "activesupport", ">= 5.0.0", "< 7.0.0"
24
+ spec.add_runtime_dependency "gitlab-pg_query", "~> 1.3"
24
25
  spec.add_runtime_dependency "grpc", "~> 1.19" # Be sure to update the "grpc-tools" dev_depenency too
25
26
  spec.add_runtime_dependency "jaeger-client", "~> 1.1"
26
27
  spec.add_runtime_dependency "opentracing", "~> 0.4"
27
28
  spec.add_runtime_dependency "redis", ">3.0.0", "<5.0.0"
28
- spec.add_runtime_dependency "gitlab-pg_query", "~> 1.3"
29
29
 
30
30
  # Please maintain alphabetical order for dev dependencies
31
+ spec.add_development_dependency "excon", "~> 0.78.1"
32
+ spec.add_development_dependency "faraday", "~> 1.2.0"
31
33
  spec.add_development_dependency "grpc-tools", "~> 1.19"
34
+ spec.add_development_dependency "httparty", "~> 0.17.3"
35
+ spec.add_development_dependency "httpclient", "~> 2.8.3"
32
36
  spec.add_development_dependency "pry", "~> 0.12"
33
37
  spec.add_development_dependency "rack", "~> 2.0"
34
38
  spec.add_development_dependency "rake", "~> 12.3"
39
+ spec.add_development_dependency "rest-client", "~> 2.1.0"
35
40
  spec.add_development_dependency "rspec", "~> 3.8.0"
36
41
  spec.add_development_dependency "rspec-parameterized", "~> 0.4"
37
42
  spec.add_development_dependency "rubocop", "~> 0.65.0"
@@ -7,11 +7,40 @@ require "active_support/all"
7
7
  # infrastructural concerns, partcularly related to
8
8
  # observability.
9
9
  module Labkit
10
+ autoload :System, "labkit/system"
11
+
10
12
  autoload :Correlation, "labkit/correlation"
11
13
  autoload :Context, "labkit/context"
12
14
  autoload :Tracing, "labkit/tracing"
13
15
  autoload :Logging, "labkit/logging"
14
16
  autoload :Middleware, "labkit/middleware"
17
+
18
+ # Publishers to publish notifications whenever a HTTP reqeust is made.
19
+ # A broadcasted notification's payload in topic "request.external_http" includes:
20
+ # + method (String): "GET"
21
+ # + code (String): "200" # This is the status code read directly from HTTP response
22
+ # + duration (Float - seconds): 0.234
23
+ # + host (String): "gitlab.com"
24
+ # + port (Integer): 80,
25
+ # + path (String): "/gitlab-org/gitlab"
26
+ # + scheme (String): "https"
27
+ # + query (String): "field_a=1&field_b=2"
28
+ # + fragment (String): "issue-number-1"
29
+ # + proxy_host (String - Optional): "proxy.gitlab.com"
30
+ # + proxy_port (Integer - Optional): 80
31
+ # + exception (Array<String> - Optional): ["Net::ReadTimeout", "Net::ReadTimeout with #<TCPSocket:(closed)>"]
32
+ # + exception_object (Error Object - Optional): #<Net::ReadTimeout: Net::ReadTimeout>
33
+ #
34
+ # Usage:
35
+ #
36
+ # ActiveSupport::Notifications.subscribe "request.external_http" do |name, started, finished, unique_id, data|
37
+ # puts "#{name} | #{started} | #{finished} | #{unique_id} | #{data.inspect}"
38
+ # end
39
+ #
40
+ EXTERNAL_HTTP_NOTIFICATION_TOPIC = "request.external_http"
41
+ autoload :NetHttpPublisher, "labkit/net_http_publisher"
42
+ autoload :ExconPublisher, "labkit/excon_publisher"
43
+ autoload :HTTPClientPublisher, "labkit/httpclient_publisher"
15
44
  end
16
45
 
17
46
  # rubocop:enable Naming/FileName
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Labkit
4
+ ##
5
+ # A middleware for Excon HTTP library to publish a notification
6
+ # whenever a HTTP request is triggered.
7
+ #
8
+ # Excon supports a middleware system that allows request/response
9
+ # interception freely. Whenever a new Excon connection is created, a list of
10
+ # default middlewares is injected. This list of middlewares can be altered
11
+ # thanks to Excon.defaults accessor. ExconPublisher is inserted into this
12
+ # list. It affects all connections created in future. There is a limitation
13
+ # that this approach doesn't work if a user decides to override the default
14
+ # middleware list. It is unlikely though, at least in the dependency tree of
15
+ # GitLab.
16
+ #
17
+ # ExconPublisher instance is created once and shared between all Excon
18
+ # connections later. Each connection may be triggered by different threads in
19
+ # parallel. In such cases, a connection objects creates multiple sockets for
20
+ # each thread. Therfore in the implementation of this middleware, the
21
+ # instrumation payload for each connection is stored inside a thread-isolated
22
+ # storage.
23
+ #
24
+ # For more information:
25
+ # https://github.com/excon/excon/blob/81a0130537f2f8cd00d6daafb05d02d9a90dc9f7/lib/excon/middlewares/base.rb
26
+ # https://github.com/excon/excon/blob/fa3ec51e9bb062a12846a1cfff09534e76c99f4b/lib/excon/constants.rb#L146
27
+ # https://github.com/excon/excon/blob/fa3ec51e9bb062a12846a1cfff09534e76c99f4b/lib/excon/connection.rb#L474
28
+ class ExconPublisher
29
+ @prepend_mutex = Mutex.new
30
+
31
+ def self.labkit_prepend!
32
+ @prepend_mutex.synchronize do
33
+ return if !defined?(Excon) || @prepended
34
+
35
+ defaults = Excon.defaults
36
+ defaults[:middlewares] << ExconPublisher
37
+
38
+ @prepended = true
39
+ end
40
+ end
41
+
42
+ def initialize(stack)
43
+ @stack = stack
44
+ @instrumenter = ActiveSupport::Notifications.instrumenter
45
+ end
46
+
47
+ def request_call(datum)
48
+ payload = start_payload(datum)
49
+ store_connection_payload(datum, payload)
50
+ @instrumenter.start(::Labkit::EXTERNAL_HTTP_NOTIFICATION_TOPIC, payload)
51
+ @stack.request_call(datum)
52
+ end
53
+
54
+ def response_call(datum)
55
+ payload = fetch_connection_payload(datum)
56
+
57
+ return @stack.response_call(datum) if payload.nil?
58
+
59
+ calculate_duration(payload)
60
+ payload[:code] = datum[:response][:status].to_s
61
+
62
+ @instrumenter.finish(::Labkit::EXTERNAL_HTTP_NOTIFICATION_TOPIC, payload)
63
+ @stack.response_call(datum)
64
+ ensure
65
+ remove_connection_payload(datum)
66
+ end
67
+
68
+ def error_call(datum)
69
+ payload = fetch_connection_payload(datum)
70
+
71
+ return @stack.error_call(datum) if payload.nil?
72
+
73
+ calculate_duration(payload)
74
+
75
+ if datum[:error].is_a?(Exception)
76
+ payload[:exception] = [datum[:error].class.name, datum[:error].message]
77
+ payload[:exception_object] = datum[:error]
78
+ elsif datum[:error].is_a?(String)
79
+ exception = StandardError.new(datum[:error])
80
+ payload[:exception] = [exception.class.name, exception.message]
81
+ payload[:exception_object] = exception
82
+ end
83
+
84
+ @instrumenter.finish(::Labkit::EXTERNAL_HTTP_NOTIFICATION_TOPIC, payload)
85
+ @stack.error_call(datum)
86
+ ensure
87
+ remove_connection_payload(datum)
88
+ end
89
+
90
+ private
91
+
92
+ def start_payload(datum)
93
+ payload = {
94
+ method: datum[:method].to_s.upcase,
95
+ host: datum[:host],
96
+ path: datum[:path],
97
+ port: datum[:port],
98
+ scheme: datum[:scheme],
99
+ query: datum[:query],
100
+ start_time: ::Labkit::System.monotonic_time,
101
+ }
102
+ unless datum[:proxy].nil?
103
+ payload[:proxy_host] = datum[:proxy][:host]
104
+ payload[:proxy_port] = datum[:proxy][:port]
105
+ end
106
+ payload
107
+ end
108
+
109
+ def calculate_duration(payload)
110
+ start_time = payload.delete(:start_time) || ::Labkit::System.monotonic_time
111
+ payload[:duration] = (::Labkit::System.monotonic_time - start_time).to_f
112
+ end
113
+
114
+ def connection_payload
115
+ Thread.current[:__labkit_http_excon_payload] ||= {}
116
+ end
117
+
118
+ def store_connection_payload(datum, payload)
119
+ connection_payload[datum[:connection]] = payload
120
+ end
121
+
122
+ def fetch_connection_payload(datum)
123
+ connection_payload.fetch(datum[:connection], nil)
124
+ end
125
+
126
+ def remove_connection_payload(datum)
127
+ connection_payload.delete(datum[:connection])
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Labkit
4
+ ##
5
+ # Prepend to HTTPClient class to publish an ActiveSupport::Notifcation
6
+ # whenever a HTTP request is triggered.
7
+ #
8
+ # Similar to Net::HTTP, this HTTP client redirects all calls to
9
+ # HTTPClient#do_get_block. HTTPClient is prepended with HTTPClientPublisher.
10
+ # Although HTTPClient supports request filter (a kind of middleware), its
11
+ # support is strictly limited. The request and response passed into the
12
+ # filter don't contain connection information. The response doesn't even
13
+ # contain any link to the request object. It's impossible to fit this filter
14
+ # mechanism into our subscribing model.
15
+ #
16
+ # For more information;
17
+ # https://github.com/nahi/httpclient/blob/d3091b095a1b29f65f4531a70a8e581e75be035e/lib/httpclient.rb#L1233
18
+ module HTTPClientPublisher
19
+ @prepend_mutex = Mutex.new
20
+
21
+ def self.labkit_prepend!
22
+ @prepend_mutex.synchronize do
23
+ return if !defined?(HTTPClient) || @prepended
24
+
25
+ HTTPClient.prepend(self)
26
+ @prepended = true
27
+ end
28
+ end
29
+
30
+ def do_get_block(req, proxy, conn, &block)
31
+ start_time = ::Labkit::System.monotonic_time
32
+ ActiveSupport::Notifications.instrument ::Labkit::EXTERNAL_HTTP_NOTIFICATION_TOPIC, create_request_payload(req, proxy) do |payload|
33
+ response =
34
+ begin
35
+ super
36
+ ensure
37
+ payload[:duration] = (::Labkit::System.monotonic_time - start_time).to_f
38
+ end
39
+ payload[:code] = response.status_code.to_s
40
+ response
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def create_request_payload(request, proxy)
47
+ http_header = request.http_header
48
+ payload = {
49
+ method: http_header.request_method,
50
+ host: http_header.request_uri.host,
51
+ path: http_header.request_uri.path,
52
+ port: http_header.request_uri.port,
53
+ scheme: http_header.request_uri.scheme,
54
+ query: http_header.request_uri.query,
55
+ fragment: http_header.request_uri.fragment,
56
+ }
57
+
58
+ unless proxy.nil?
59
+ payload[:proxy_host] = proxy.host
60
+ payload[:proxy_port] = proxy.port
61
+ end
62
+
63
+ payload
64
+ end
65
+ end
66
+ end
@@ -55,7 +55,7 @@ module Labkit
55
55
 
56
56
  private
57
57
 
58
- def log_request(method, call)
58
+ def log_request(method, _call)
59
59
  start = Time.now
60
60
  code = ::GRPC::Core::StatusCodes::OK
61
61
 
@@ -15,7 +15,7 @@ module Labkit
15
15
  SPAN_KIND = "client"
16
16
 
17
17
  def call(_worker_class, job, _queue, _redis_pool)
18
- Labkit::Tracing::TracingUtils.with_tracing(operation_name: "sidekiq:#{job["class"]}", tags: tags_from_job(job, SPAN_KIND)) do |span|
18
+ Labkit::Tracing::TracingUtils.with_tracing(operation_name: "sidekiq:#{job_class(job)}", tags: tags_from_job(job, SPAN_KIND)) do |span|
19
19
  # Inject the details directly into the job
20
20
  Labkit::Tracing::TracingUtils.tracer.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, job)
21
21
 
@@ -17,7 +17,7 @@ module Labkit
17
17
  def call(_worker, job, _queue)
18
18
  context = Labkit::Tracing::TracingUtils.tracer.extract(OpenTracing::FORMAT_TEXT_MAP, job)
19
19
 
20
- Labkit::Tracing::TracingUtils.with_tracing(operation_name: "sidekiq:#{job["class"]}", child_of: context, tags: tags_from_job(job, SPAN_KIND)) { |_span| yield }
20
+ Labkit::Tracing::TracingUtils.with_tracing(operation_name: "sidekiq:#{job_class(job)}", child_of: context, tags: tags_from_job(job, SPAN_KIND)) { |_span| yield }
21
21
  end
22
22
  end
23
23
  end
@@ -6,15 +6,28 @@ module Labkit
6
6
  module Tracing
7
7
  # SidekiqCommon is a mixin for the sidekiq middleware components
8
8
  module SidekiqCommon
9
+ def job_class(job)
10
+ # Active Job wrapping can be found at
11
+ # https://github.com/rails/rails/blob/v6.0.3.1/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
12
+ job["wrapped"].presence || job["class"].presence || "undefined"
13
+ end
14
+
15
+ def wrapped?(job)
16
+ job["wrapped"].present?
17
+ end
18
+
9
19
  def tags_from_job(job, kind)
10
- {
20
+ tags = {
11
21
  "component" => "sidekiq",
12
22
  "span.kind" => kind,
23
+ "sidekiq.wrapped" => wrapped?(job),
13
24
  "sidekiq.queue" => job["queue"],
14
25
  "sidekiq.jid" => job["jid"],
15
26
  "sidekiq.retry" => job["retry"].to_s,
16
27
  "sidekiq.args" => job["args"]&.join(", "),
17
28
  }
29
+ tags["sidekiq.at"] = job["at"] if job["at"]
30
+ tags
18
31
  end
19
32
  end
20
33
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Labkit
4
+ ##
5
+ # Prepend to Ruby's Net/HTTP standard HTTP library to publish a notification
6
+ # whenever a HTTP request is triggered. Net::HTTP has different class methods
7
+ # for each http method. Those methods are delegated to corresponding instance
8
+ # methods. Eventually, `request` method is call to dispatch the HTTP request.
9
+ # Therefore, a prepender that override `request` method covers all HTTP
10
+ # calls.
11
+ #
12
+ # For more information:
13
+ # https://github.com/ruby/ruby/blob/9b9cbbbc17bb5840581c7da37fd0feb0a7d4c1f3/lib/net/http.rb#L1510
14
+ #
15
+ # Note: some use cases to take care of
16
+ # - Create a request from input URI
17
+ # - Create a request from input host, port, and path string
18
+ # - Create a singular request and closes the connection immediately
19
+ # - Create a persistent connection and perform multiple HTTP requests
20
+ # - Notification payload must separate URI components
21
+ # - Create a post request with a body
22
+ # - Create a post request with form data
23
+ # - Create a request with basic authentication
24
+ # - Make a request via a proxy server
25
+ # - Streaming
26
+ module NetHttpPublisher
27
+ @prepend_mutex = Mutex.new
28
+
29
+ def self.labkit_prepend!
30
+ @prepend_mutex.synchronize do
31
+ return if @prepended
32
+
33
+ require "net/http"
34
+ Net::HTTP.prepend(self)
35
+ @prepended = true
36
+ end
37
+ end
38
+
39
+ def request(request, *args, &block)
40
+ return super unless started?
41
+
42
+ start_time = ::Labkit::System.monotonic_time
43
+
44
+ ActiveSupport::Notifications.instrument ::Labkit::EXTERNAL_HTTP_NOTIFICATION_TOPIC, create_request_payload(request) do |payload|
45
+ response =
46
+ begin
47
+ super
48
+ ensure
49
+ payload[:duration] = (::Labkit::System.monotonic_time - start_time).to_f
50
+ end
51
+ payload[:code] = response.code
52
+ response
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def create_request_payload(request)
59
+ payload = {
60
+ method: request.method,
61
+ }
62
+
63
+ if request.uri.nil?
64
+ path_uri = URI(request.path)
65
+ payload[:host] = address
66
+ payload[:path] = path_uri.path
67
+ payload[:port] = port
68
+ payload[:scheme] = use_ssl? ? "https" : "http"
69
+ payload[:query] = path_uri.query
70
+ payload[:fragment] = path_uri.fragment
71
+ else
72
+ payload[:host] = request.uri.host
73
+ payload[:path] = request.uri.path
74
+ payload[:port] = request.uri.port
75
+ payload[:scheme] = request.uri.scheme
76
+ payload[:query] = request.uri.query
77
+ payload[:fragment] = request.uri.fragment
78
+ end
79
+
80
+ if proxy?
81
+ payload[:proxy_host] = proxy_address
82
+ payload[:proxy_port] = proxy_port
83
+ end
84
+
85
+ payload
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Labkit
4
+ # A helper class to store system-related methods used in metrics, tracing, and logging
5
+ module System
6
+ # Returns the current monotonic clock time as seconds with microseconds precision.
7
+ #
8
+ # Returns the time as a Float.
9
+ def self.monotonic_time
10
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second)
11
+ end
12
+ end
13
+ end
@@ -5,6 +5,8 @@ require "active_support/all"
5
5
  module Labkit
6
6
  # Tracing provides distributed tracing functionality
7
7
  module Tracing
8
+ autoload :AbstractInstrumenter, "labkit/tracing/abstract_instrumenter"
9
+ autoload :TracingCommon, "labkit/tracing/tracing_common"
8
10
  autoload :Factory, "labkit/tracing/factory"
9
11
  autoload :GRPC, "labkit/tracing/grpc"
10
12
  autoload :GRPCInterceptor, "labkit/tracing/grpc_interceptor" # Deprecated
@@ -12,6 +14,7 @@ module Labkit
12
14
  autoload :RackMiddleware, "labkit/tracing/rack_middleware"
13
15
  autoload :Rails, "labkit/tracing/rails"
14
16
  autoload :Redis, "labkit/tracing/redis"
17
+ autoload :ExternalHttp, "labkit/tracing/external_http"
15
18
  autoload :Sidekiq, "labkit/tracing/sidekiq"
16
19
  autoload :TracingUtils, "labkit/tracing/tracing_utils"
17
20
 
@@ -31,7 +34,7 @@ module Labkit
31
34
  # Check if the current request is being traced.
32
35
  def self.sampled?
33
36
  context = OpenTracing.active_span&.context
34
- context && context.respond_to?(:sampled?) && context.sampled?
37
+ context&.respond_to?(:sampled?) && context&.sampled?
35
38
  end
36
39
 
37
40
  def self.stacktrace_operations
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "opentracing"
4
+ require "active_support/all"
5
+
6
+ module Labkit
7
+ module Tracing
8
+ # https://edgeapi.rubyonrails.org/classes/ActiveSupport/Notifications/Instrumenter.html#method-c-new
9
+ class AbstractInstrumenter
10
+ def start(_name, _id, payload)
11
+ scope = OpenTracing.start_active_span(span_name(payload))
12
+
13
+ scope_stack.push scope
14
+ end
15
+
16
+ def finish(_name, _id, payload)
17
+ scope = scope_stack.pop
18
+ span = scope.span
19
+
20
+ Labkit::Tracing::TracingUtils.log_common_fields_on_span(span, span_name(payload))
21
+
22
+ # exception_object is the standard exception payload from ActiveSupport::Notifications
23
+ # https://github.com/rails/rails/blob/v6.0.3.1/activesupport/lib/active_support/notifications/instrumenter.rb#L26
24
+ exception = payload[:exception_object].presence || payload[:exception].presence
25
+ Labkit::Tracing::TracingUtils.log_exception_on_span(span, exception)
26
+
27
+ tags(payload).each do |k, v|
28
+ span.set_tag(k, v)
29
+ end
30
+
31
+ scope.close
32
+ end
33
+
34
+ def scope_stack
35
+ Thread.current[:_labkit_trace_scope_stack] ||= []
36
+ end
37
+
38
+ def span_name(_payload)
39
+ raise "span_name not implemented"
40
+ end
41
+
42
+ def tags(_payload)
43
+ {}
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Labkit
4
+ module Tracing
5
+ # Instrument external HTTP calls made by the HTTP client libraries. This
6
+ # tracing instrumenter listens to the events broadcasted from the
7
+ # publishers injected into the libraries whenever there is a request.
8
+ module ExternalHttp
9
+ include Labkit::Tracing::TracingCommon
10
+
11
+ autoload :RequestInstrumenter, "labkit/tracing/external_http/request_instrumenter"
12
+
13
+ def self.instrument
14
+ Labkit::NetHttpPublisher.labkit_prepend!
15
+ Labkit::ExconPublisher.labkit_prepend!
16
+ Labkit::HTTPClientPublisher.labkit_prepend!
17
+
18
+ subscriptions = [
19
+ ::ActiveSupport::Notifications.subscribe(::Labkit::EXTERNAL_HTTP_NOTIFICATION_TOPIC, RequestInstrumenter.new),
20
+ ]
21
+
22
+ create_unsubscriber subscriptions
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Labkit
4
+ module Tracing
5
+ module ExternalHttp
6
+ # For more information on the payloads: lib/labkit/net_http_publisher.rb
7
+ class RequestInstrumenter < Labkit::Tracing::AbstractInstrumenter
8
+ def span_name(_payload)
9
+ "external_http:request"
10
+ end
11
+
12
+ def tags(payload)
13
+ # Duration is calculated by start and end time
14
+ # Exception is already captured in lib/labkit/tracing/tracing_utils.rb
15
+ tags = {
16
+ "component" => "external_http",
17
+ "method" => payload[:method],
18
+ "code" => payload[:code],
19
+ "host" => payload[:host],
20
+ "port" => payload[:port],
21
+ "path" => payload[:path],
22
+ "scheme" => payload[:scheme],
23
+ }
24
+ unless payload[:proxy_host].nil?
25
+ tags["proxy_host"] = payload[:proxy_host]
26
+ tags["proxy_port"] = payload[:proxy_port]
27
+ end
28
+ tags
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -4,11 +4,9 @@ module Labkit
4
4
  module Tracing
5
5
  # Rails provides classes for instrumenting Rails events
6
6
  module Rails
7
- autoload :AbstractInstrumenter, "labkit/tracing/rails/abstract_instrumenter"
8
7
  autoload :ActionView, "labkit/tracing/rails/action_view"
9
8
  autoload :ActiveRecord, "labkit/tracing/rails/active_record"
10
9
  autoload :ActiveSupport, "labkit/tracing/rails/active_support"
11
- autoload :RailsCommon, "labkit/tracing/rails/rails_common"
12
10
 
13
11
  ActionViewSubscriber = ActionView::Subscriber
14
12
  ActiveRecordSubscriber = ActiveRecord::Subscriber
@@ -10,6 +10,20 @@ module Labkit
10
10
  autoload :Subscriber, "labkit/tracing/rails/action_view/subscriber"
11
11
 
12
12
  COMPONENT_TAG = "ActionView"
13
+
14
+ # Returns identifier relative to Rails.root. Rails supports different template types and returns corresponding identifiers:
15
+ # - Text template: the identifier is "text template"
16
+ # - Html template: the identifier is "html template"
17
+ # - Inline template: the identifier is "inline template"
18
+ # - Raw template: the identifier is the file path of the template
19
+ # Therefore, the amount of returned identifiers is static.
20
+ def self.template_identifier(payload)
21
+ return if !defined?(::Rails.root) || payload[:identifier].nil?
22
+
23
+ # Rails.root returns a Pathname object, whose `to_s` methods returns an absolute path without ending "/"
24
+ # Source: https://github.com/rails/rails/blob/v6.0.3.1/railties/lib/rails.rb#L64
25
+ payload[:identifier].sub("#{::Rails.root}/", "")
26
+ end
13
27
  end
14
28
  end
15
29
  end
@@ -5,9 +5,14 @@ module Labkit
5
5
  module Rails
6
6
  module ActionView
7
7
  # For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
8
- class RenderCollectionInstrumenter < AbstractInstrumenter
8
+ class RenderCollectionInstrumenter < Labkit::Tracing::AbstractInstrumenter
9
9
  def span_name(payload)
10
- "render_collection"
10
+ identifier = ActionView.template_identifier(payload)
11
+ if identifier.nil?
12
+ "render_collection"
13
+ else
14
+ "render_collection:#{identifier}"
15
+ end
11
16
  end
12
17
 
13
18
  def tags(payload)
@@ -5,9 +5,14 @@ module Labkit
5
5
  module Rails
6
6
  module ActionView
7
7
  # For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
8
- class RenderPartialInstrumenter < AbstractInstrumenter
8
+ class RenderPartialInstrumenter < Labkit::Tracing::AbstractInstrumenter
9
9
  def span_name(payload)
10
- "render_partial"
10
+ identifier = ActionView.template_identifier(payload)
11
+ if identifier.nil?
12
+ "render_partial"
13
+ else
14
+ "render_partial:#{identifier}"
15
+ end
11
16
  end
12
17
 
13
18
  def tags(payload)
@@ -5,9 +5,14 @@ module Labkit
5
5
  module Rails
6
6
  module ActionView
7
7
  # For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
8
- class RenderTemplateInstrumenter < AbstractInstrumenter
8
+ class RenderTemplateInstrumenter < Labkit::Tracing::AbstractInstrumenter
9
9
  def span_name(payload)
10
- "render_template"
10
+ identifier = ActionView.template_identifier(payload)
11
+ if identifier.nil?
12
+ "render_template"
13
+ else
14
+ "render_template:#{identifier}"
15
+ end
11
16
  end
12
17
 
13
18
  def tags(payload)
@@ -7,7 +7,7 @@ module Labkit
7
7
  # ActionView bridges action view notifications to
8
8
  # the distributed tracing subsystem
9
9
  class Subscriber
10
- include RailsCommon
10
+ include Labkit::Tracing::TracingCommon
11
11
 
12
12
  RENDER_TEMPLATE_NOTIFICATION_TOPIC = "render_template.action_view"
13
13
  RENDER_COLLECTION_NOTIFICATION_TOPIC = "render_collection.action_view"
@@ -5,7 +5,7 @@ module Labkit
5
5
  module Rails
6
6
  module ActiveRecord
7
7
  # For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
8
- class SqlInstrumenter < AbstractInstrumenter
8
+ class SqlInstrumenter < Labkit::Tracing::AbstractInstrumenter
9
9
  OPERATION_NAME_PREFIX = "active_record:"
10
10
  DEFAULT_OPERATION_NAME = "sqlquery"
11
11
 
@@ -7,7 +7,7 @@ module Labkit
7
7
  # ActiveRecord bridges active record notifications to
8
8
  # the distributed tracing subsystem
9
9
  class Subscriber
10
- include RailsCommon
10
+ include Labkit::Tracing::TracingCommon
11
11
 
12
12
  ACTIVE_RECORD_NOTIFICATION_TOPIC = "sql.active_record"
13
13
 
@@ -5,7 +5,7 @@ module Labkit
5
5
  module Rails
6
6
  module ActiveSupport
7
7
  # For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
8
- class CacheDeleteInstrumenter < AbstractInstrumenter
8
+ class CacheDeleteInstrumenter < Labkit::Tracing::AbstractInstrumenter
9
9
  def span_name(payload)
10
10
  "cache_delete"
11
11
  end
@@ -5,7 +5,7 @@ module Labkit
5
5
  module Rails
6
6
  module ActiveSupport
7
7
  # For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
8
- class CacheFetchHitInstrumenter < AbstractInstrumenter
8
+ class CacheFetchHitInstrumenter < Labkit::Tracing::AbstractInstrumenter
9
9
  def span_name(payload)
10
10
  "cache_fetch_hit"
11
11
  end
@@ -5,7 +5,7 @@ module Labkit
5
5
  module Rails
6
6
  module ActiveSupport
7
7
  # For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
8
- class CacheGenerateInstrumenter < AbstractInstrumenter
8
+ class CacheGenerateInstrumenter < Labkit::Tracing::AbstractInstrumenter
9
9
  def span_name(payload)
10
10
  "cache_generate"
11
11
  end
@@ -5,7 +5,7 @@ module Labkit
5
5
  module Rails
6
6
  module ActiveSupport
7
7
  # For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
8
- class CacheReadInstrumenter < AbstractInstrumenter
8
+ class CacheReadInstrumenter < Labkit::Tracing::AbstractInstrumenter
9
9
  def span_name(payload)
10
10
  "cache_read"
11
11
  end
@@ -5,7 +5,7 @@ module Labkit
5
5
  module Rails
6
6
  module ActiveSupport
7
7
  # For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
8
- class CacheWriteInstrumenter < AbstractInstrumenter
8
+ class CacheWriteInstrumenter < Labkit::Tracing::AbstractInstrumenter
9
9
  def span_name(payload)
10
10
  "cache_write"
11
11
  end
@@ -7,7 +7,7 @@ module Labkit
7
7
  # ActiveSupport bridges action active support notifications to
8
8
  # the distributed tracing subsystem
9
9
  class Subscriber
10
- include RailsCommon
10
+ include Labkit::Tracing::TracingCommon
11
11
 
12
12
  CACHE_READ_TOPIC = "cache_read.active_support"
13
13
  CACHE_GENERATE_TOPIC = "cache_generate.active_support"
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/all"
4
+
5
+ module Labkit
6
+ module Tracing
7
+ # TracingCommon is a mixin for providing instrumentation
8
+ # functionality for the instrumentation classes based on
9
+ # ActiveSupport::Notifications
10
+ module TracingCommon
11
+ extend ::ActiveSupport::Concern
12
+
13
+ class_methods do
14
+ def create_unsubscriber(subscriptions)
15
+ -> { subscriptions.each { |subscriber| ::ActiveSupport::Notifications.unsubscribe(subscriber) } }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -48,6 +48,8 @@ module Labkit
48
48
 
49
49
  # Add exception logging to a span
50
50
  def self.log_exception_on_span(span, exception)
51
+ return if exception.blank?
52
+
51
53
  span.set_tag("error", true)
52
54
  span.log_kv(**kv_tags_for_exception(exception))
53
55
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-labkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.5
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Newdigate
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-21 00:00:00.000000000 Z
11
+ date: 2021-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: 5.0.0
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: 6.1.0
22
+ version: 7.0.0
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: 5.0.0
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: 6.1.0
32
+ version: 7.0.0
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: activesupport
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -39,7 +39,7 @@ dependencies:
39
39
  version: 5.0.0
40
40
  - - "<"
41
41
  - !ruby/object:Gem::Version
42
- version: 6.1.0
42
+ version: 7.0.0
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
@@ -49,7 +49,21 @@ dependencies:
49
49
  version: 5.0.0
50
50
  - - "<"
51
51
  - !ruby/object:Gem::Version
52
- version: 6.1.0
52
+ version: 7.0.0
53
+ - !ruby/object:Gem::Dependency
54
+ name: gitlab-pg_query
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '1.3'
60
+ type: :runtime
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '1.3'
53
67
  - !ruby/object:Gem::Dependency
54
68
  name: grpc
55
69
  requirement: !ruby/object:Gem::Requirement
@@ -113,19 +127,33 @@ dependencies:
113
127
  - !ruby/object:Gem::Version
114
128
  version: 5.0.0
115
129
  - !ruby/object:Gem::Dependency
116
- name: gitlab-pg_query
130
+ name: excon
117
131
  requirement: !ruby/object:Gem::Requirement
118
132
  requirements:
119
133
  - - "~>"
120
134
  - !ruby/object:Gem::Version
121
- version: '1.3'
122
- type: :runtime
135
+ version: 0.78.1
136
+ type: :development
123
137
  prerelease: false
124
138
  version_requirements: !ruby/object:Gem::Requirement
125
139
  requirements:
126
140
  - - "~>"
127
141
  - !ruby/object:Gem::Version
128
- version: '1.3'
142
+ version: 0.78.1
143
+ - !ruby/object:Gem::Dependency
144
+ name: faraday
145
+ requirement: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - "~>"
148
+ - !ruby/object:Gem::Version
149
+ version: 1.2.0
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - "~>"
155
+ - !ruby/object:Gem::Version
156
+ version: 1.2.0
129
157
  - !ruby/object:Gem::Dependency
130
158
  name: grpc-tools
131
159
  requirement: !ruby/object:Gem::Requirement
@@ -140,6 +168,34 @@ dependencies:
140
168
  - - "~>"
141
169
  - !ruby/object:Gem::Version
142
170
  version: '1.19'
171
+ - !ruby/object:Gem::Dependency
172
+ name: httparty
173
+ requirement: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - "~>"
176
+ - !ruby/object:Gem::Version
177
+ version: 0.17.3
178
+ type: :development
179
+ prerelease: false
180
+ version_requirements: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - "~>"
183
+ - !ruby/object:Gem::Version
184
+ version: 0.17.3
185
+ - !ruby/object:Gem::Dependency
186
+ name: httpclient
187
+ requirement: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - "~>"
190
+ - !ruby/object:Gem::Version
191
+ version: 2.8.3
192
+ type: :development
193
+ prerelease: false
194
+ version_requirements: !ruby/object:Gem::Requirement
195
+ requirements:
196
+ - - "~>"
197
+ - !ruby/object:Gem::Version
198
+ version: 2.8.3
143
199
  - !ruby/object:Gem::Dependency
144
200
  name: pry
145
201
  requirement: !ruby/object:Gem::Requirement
@@ -182,6 +238,20 @@ dependencies:
182
238
  - - "~>"
183
239
  - !ruby/object:Gem::Version
184
240
  version: '12.3'
241
+ - !ruby/object:Gem::Dependency
242
+ name: rest-client
243
+ requirement: !ruby/object:Gem::Requirement
244
+ requirements:
245
+ - - "~>"
246
+ - !ruby/object:Gem::Version
247
+ version: 2.1.0
248
+ type: :development
249
+ prerelease: false
250
+ version_requirements: !ruby/object:Gem::Requirement
251
+ requirements:
252
+ - - "~>"
253
+ - !ruby/object:Gem::Version
254
+ version: 2.1.0
185
255
  - !ruby/object:Gem::Dependency
186
256
  name: rspec
187
257
  requirement: !ruby/object:Gem::Requirement
@@ -275,6 +345,7 @@ extra_rdoc_files: []
275
345
  files:
276
346
  - ".gitignore"
277
347
  - ".gitlab-ci.yml"
348
+ - ".gitlab/CODEOWNERS"
278
349
  - ".rspec"
279
350
  - ".rubocop.yml"
280
351
  - ".ruby-version"
@@ -292,6 +363,8 @@ files:
292
363
  - lib/labkit/correlation/grpc/client_interceptor.rb
293
364
  - lib/labkit/correlation/grpc/grpc_common.rb
294
365
  - lib/labkit/correlation/grpc/server_interceptor.rb
366
+ - lib/labkit/excon_publisher.rb
367
+ - lib/labkit/httpclient_publisher.rb
295
368
  - lib/labkit/logging.rb
296
369
  - lib/labkit/logging/grpc.rb
297
370
  - lib/labkit/logging/grpc/server_interceptor.rb
@@ -308,7 +381,12 @@ files:
308
381
  - lib/labkit/middleware/sidekiq/tracing/client.rb
309
382
  - lib/labkit/middleware/sidekiq/tracing/server.rb
310
383
  - lib/labkit/middleware/sidekiq/tracing/sidekiq_common.rb
384
+ - lib/labkit/net_http_publisher.rb
385
+ - lib/labkit/system.rb
311
386
  - lib/labkit/tracing.rb
387
+ - lib/labkit/tracing/abstract_instrumenter.rb
388
+ - lib/labkit/tracing/external_http.rb
389
+ - lib/labkit/tracing/external_http/request_instrumenter.rb
312
390
  - lib/labkit/tracing/factory.rb
313
391
  - lib/labkit/tracing/grpc.rb
314
392
  - lib/labkit/tracing/grpc/client_interceptor.rb
@@ -317,7 +395,6 @@ files:
317
395
  - lib/labkit/tracing/jaeger_factory.rb
318
396
  - lib/labkit/tracing/rack_middleware.rb
319
397
  - lib/labkit/tracing/rails.rb
320
- - lib/labkit/tracing/rails/abstract_instrumenter.rb
321
398
  - lib/labkit/tracing/rails/action_view.rb
322
399
  - lib/labkit/tracing/rails/action_view/render_collection_instrumenter.rb
323
400
  - lib/labkit/tracing/rails/action_view/render_partial_instrumenter.rb
@@ -333,10 +410,10 @@ files:
333
410
  - lib/labkit/tracing/rails/active_support/cache_read_instrumenter.rb
334
411
  - lib/labkit/tracing/rails/active_support/cache_write_instrumenter.rb
335
412
  - lib/labkit/tracing/rails/active_support/subscriber.rb
336
- - lib/labkit/tracing/rails/rails_common.rb
337
413
  - lib/labkit/tracing/redis.rb
338
414
  - lib/labkit/tracing/redis/redis_interceptor.rb
339
415
  - lib/labkit/tracing/redis/redis_interceptor_helper.rb
416
+ - lib/labkit/tracing/tracing_common.rb
340
417
  - lib/labkit/tracing/tracing_utils.rb
341
418
  homepage: https://gitlab.com/gitlab-org/labkit-ruby
342
419
  licenses:
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_support/all"
4
-
5
- module Labkit
6
- module Tracing
7
- module Rails
8
- # https://edgeapi.rubyonrails.org/classes/ActiveSupport/Notifications/Instrumenter.html#method-c-new
9
- class AbstractInstrumenter
10
- def start(name, id, payload)
11
- scope = OpenTracing.start_active_span(span_name(payload))
12
-
13
- scope_stack.push scope
14
- end
15
-
16
- def finish(name, id, payload)
17
- scope = scope_stack.pop
18
- span = scope.span
19
-
20
- Labkit::Tracing::TracingUtils.log_common_fields_on_span(span, span_name(payload))
21
-
22
- exception = payload[:exception]
23
- Labkit::Tracing::TracingUtils.log_exception_on_span(span, exception) if exception
24
-
25
- tags(payload).each do |k, v|
26
- span.set_tag(k, v)
27
- end
28
-
29
- scope.close
30
- end
31
-
32
- def scope_stack
33
- Thread.current[:_labkit_trace_scope_stack] ||= []
34
- end
35
-
36
- def span_name(payload)
37
- raise "span_name not implemented"
38
- end
39
-
40
- def tags(payload)
41
- {}
42
- end
43
- end
44
- end
45
- end
46
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_support/all"
4
-
5
- module Labkit
6
- module Tracing
7
- module Rails
8
- # RailsCommon is a mixin for providing instrumentation
9
- # functionality for the rails instrumentation classes
10
- module RailsCommon
11
- extend ::ActiveSupport::Concern
12
-
13
- class_methods do
14
- def create_unsubscriber(subscriptions)
15
- -> { subscriptions.each { |subscriber| ::ActiveSupport::Notifications.unsubscribe(subscriber) } }
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end