skylight-core 2.0.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/lib/skylight/core/config.rb +454 -0
  3. data/lib/skylight/core/errors.rb +6 -0
  4. data/lib/skylight/core/fanout.rb +44 -0
  5. data/lib/skylight/core/formatters/http.rb +23 -0
  6. data/lib/skylight/core/gc.rb +107 -0
  7. data/lib/skylight/core/instrumentable.rb +144 -0
  8. data/lib/skylight/core/instrumenter.rb +249 -0
  9. data/lib/skylight/core/middleware.rb +101 -0
  10. data/lib/skylight/core/normalizers/action_controller/process_action.rb +50 -0
  11. data/lib/skylight/core/normalizers/action_controller/send_file.rb +50 -0
  12. data/lib/skylight/core/normalizers/action_view/render_collection.rb +22 -0
  13. data/lib/skylight/core/normalizers/action_view/render_partial.rb +21 -0
  14. data/lib/skylight/core/normalizers/action_view/render_template.rb +21 -0
  15. data/lib/skylight/core/normalizers/active_job/enqueue_at.rb +21 -0
  16. data/lib/skylight/core/normalizers/active_model_serializers/render.rb +26 -0
  17. data/lib/skylight/core/normalizers/active_record/instantiation.rb +17 -0
  18. data/lib/skylight/core/normalizers/active_record/sql.rb +33 -0
  19. data/lib/skylight/core/normalizers/active_support/cache.rb +20 -0
  20. data/lib/skylight/core/normalizers/active_support/cache_clear.rb +16 -0
  21. data/lib/skylight/core/normalizers/active_support/cache_decrement.rb +16 -0
  22. data/lib/skylight/core/normalizers/active_support/cache_delete.rb +16 -0
  23. data/lib/skylight/core/normalizers/active_support/cache_exist.rb +16 -0
  24. data/lib/skylight/core/normalizers/active_support/cache_fetch_hit.rb +16 -0
  25. data/lib/skylight/core/normalizers/active_support/cache_generate.rb +16 -0
  26. data/lib/skylight/core/normalizers/active_support/cache_increment.rb +16 -0
  27. data/lib/skylight/core/normalizers/active_support/cache_read.rb +16 -0
  28. data/lib/skylight/core/normalizers/active_support/cache_read_multi.rb +16 -0
  29. data/lib/skylight/core/normalizers/active_support/cache_write.rb +16 -0
  30. data/lib/skylight/core/normalizers/coach/handler_finish.rb +36 -0
  31. data/lib/skylight/core/normalizers/coach/middleware_finish.rb +23 -0
  32. data/lib/skylight/core/normalizers/couch_potato/query.rb +20 -0
  33. data/lib/skylight/core/normalizers/data_mapper/sql.rb +12 -0
  34. data/lib/skylight/core/normalizers/default.rb +27 -0
  35. data/lib/skylight/core/normalizers/elasticsearch/request.rb +20 -0
  36. data/lib/skylight/core/normalizers/faraday/request.rb +37 -0
  37. data/lib/skylight/core/normalizers/grape/endpoint.rb +30 -0
  38. data/lib/skylight/core/normalizers/grape/endpoint_render.rb +26 -0
  39. data/lib/skylight/core/normalizers/grape/endpoint_run.rb +33 -0
  40. data/lib/skylight/core/normalizers/grape/endpoint_run_filters.rb +23 -0
  41. data/lib/skylight/core/normalizers/moped/query.rb +100 -0
  42. data/lib/skylight/core/normalizers/sequel/sql.rb +12 -0
  43. data/lib/skylight/core/normalizers/sql.rb +49 -0
  44. data/lib/skylight/core/normalizers.rb +170 -0
  45. data/lib/skylight/core/probes/action_controller.rb +31 -0
  46. data/lib/skylight/core/probes/action_view.rb +37 -0
  47. data/lib/skylight/core/probes/active_model_serializers.rb +55 -0
  48. data/lib/skylight/core/probes/elasticsearch.rb +37 -0
  49. data/lib/skylight/core/probes/excon/middleware.rb +72 -0
  50. data/lib/skylight/core/probes/excon.rb +26 -0
  51. data/lib/skylight/core/probes/faraday.rb +22 -0
  52. data/lib/skylight/core/probes/grape.rb +80 -0
  53. data/lib/skylight/core/probes/httpclient.rb +46 -0
  54. data/lib/skylight/core/probes/middleware.rb +58 -0
  55. data/lib/skylight/core/probes/mongo.rb +171 -0
  56. data/lib/skylight/core/probes/mongoid.rb +21 -0
  57. data/lib/skylight/core/probes/moped.rb +39 -0
  58. data/lib/skylight/core/probes/net_http.rb +64 -0
  59. data/lib/skylight/core/probes/redis.rb +71 -0
  60. data/lib/skylight/core/probes/sequel.rb +33 -0
  61. data/lib/skylight/core/probes/sinatra.rb +69 -0
  62. data/lib/skylight/core/probes/tilt.rb +27 -0
  63. data/lib/skylight/core/probes.rb +129 -0
  64. data/lib/skylight/core/railtie.rb +166 -0
  65. data/lib/skylight/core/subscriber.rb +124 -0
  66. data/lib/skylight/core/test.rb +98 -0
  67. data/lib/skylight/core/trace.rb +190 -0
  68. data/lib/skylight/core/user_config.rb +61 -0
  69. data/lib/skylight/core/util/allocation_free.rb +26 -0
  70. data/lib/skylight/core/util/clock.rb +56 -0
  71. data/lib/skylight/core/util/deploy.rb +132 -0
  72. data/lib/skylight/core/util/gzip.rb +21 -0
  73. data/lib/skylight/core/util/inflector.rb +112 -0
  74. data/lib/skylight/core/util/logging.rb +127 -0
  75. data/lib/skylight/core/util/platform.rb +77 -0
  76. data/lib/skylight/core/util/proxy.rb +13 -0
  77. data/lib/skylight/core/util.rb +14 -0
  78. data/lib/skylight/core/vendor/active_support/notifications.rb +207 -0
  79. data/lib/skylight/core/vendor/active_support/per_thread_registry.rb +52 -0
  80. data/lib/skylight/core/vendor/thread_safe/non_concurrent_cache_backend.rb +133 -0
  81. data/lib/skylight/core/vendor/thread_safe/synchronized_cache_backend.rb +76 -0
  82. data/lib/skylight/core/vendor/thread_safe.rb +126 -0
  83. data/lib/skylight/core/version.rb +6 -0
  84. data/lib/skylight/core/vm/gc.rb +70 -0
  85. data/lib/skylight/core.rb +99 -0
  86. metadata +254 -0
@@ -0,0 +1,49 @@
1
+ require "json"
2
+
3
+ module Skylight::Core
4
+ module Normalizers
5
+ # Normalizer for SQL requests
6
+ class SQL < Normalizer
7
+ CAT = "db.sql.query".freeze
8
+
9
+ # @param trace [Skylight::Messages::Trace::Builder] ignored, only present to match API
10
+ # @param name [String] ignored, only present to match API
11
+ # @param payload [Hash]
12
+ # @option payload [String] [:name] The SQL operation
13
+ # @option payload [Hash] [:binds] The bound parameters
14
+ # @return [Array]
15
+ def normalize(trace, name, payload)
16
+ case payload[:name]
17
+ when "SCHEMA".freeze, "CACHE".freeze
18
+ return :skip
19
+ else
20
+ name = CAT
21
+ title = payload[:name] || "SQL".freeze
22
+ end
23
+
24
+ binds = payload[:binds]
25
+
26
+ if binds && !binds.empty?
27
+ binds = binds.map { |_col, val| val.inspect }
28
+ end
29
+
30
+ begin
31
+ extracted_title, sql = extract_binds(trace.instrumenter, payload, binds)
32
+ [ name, extracted_title || title, sql ]
33
+ rescue => e
34
+ # FIXME: Rust errors get written to STDERR and don't come through here
35
+ if config[:log_sql_parse_errors]
36
+ config.logger.warn "failed to extract binds in SQL; sql=#{payload[:sql].inspect}; exception=#{e.inspect}"
37
+ end
38
+ [ name, title, nil ]
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def extract_binds(instrumenter, payload, _precalculated)
45
+ instrumenter.process_sql(payload[:sql])
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,170 @@
1
+ require 'skylight/core/normalizers/default'
2
+
3
+ module Skylight::Core
4
+ # @api private
5
+ # Convert AS::N events to Skylight events
6
+ module Normalizers
7
+
8
+ DEFAULT = Default.new
9
+
10
+ def self.register(name, klass)
11
+ (@registry ||= {})[name] = klass
12
+ klass
13
+ end
14
+
15
+ def self.build(config)
16
+ normalizers = {}
17
+
18
+ (@registry || {}).each do |k, klass|
19
+ unless klass.method_defined?(:normalize)
20
+ # TODO: Warn
21
+ next
22
+ end
23
+
24
+ normalizers[k] = klass.new(config)
25
+ end
26
+
27
+ Container.new(normalizers)
28
+ end
29
+
30
+ class Normalizer
31
+ def self.register(name)
32
+ Normalizers.register(name, self)
33
+ end
34
+
35
+ attr_reader :config
36
+
37
+ def initialize(config)
38
+ @config = config
39
+ setup if respond_to?(:setup)
40
+ end
41
+
42
+ def normalize(trace, name, payload)
43
+ :skip
44
+ end
45
+
46
+ def normalize_after(trace, span, name, payload)
47
+ end
48
+ end
49
+
50
+ # Base Normalizer for Rails rendering
51
+ class RenderNormalizer < Normalizer
52
+ include Util::AllocationFree
53
+
54
+ def setup
55
+ @paths = config['normalizers.render.view_paths'] || []
56
+ end
57
+
58
+ # Generic normalizer for renders
59
+ # @param category [String]
60
+ # @param payload [Hash]
61
+ # @option payload [String] :identifier
62
+ # @return [Array]
63
+ def normalize_render(category, payload)
64
+ if path = payload[:identifier]
65
+ title = relative_path(path)
66
+ path = nil if path == title
67
+ end
68
+
69
+ [ category, title, nil ]
70
+ end
71
+
72
+ def relative_path(path)
73
+ return path if relative_path?(path)
74
+
75
+ root = array_find(@paths) { |p| path.start_with?(p) }
76
+ type = :project
77
+
78
+ unless root
79
+ root = array_find(Gem.path) { |p| path.start_with?(p) }
80
+ type = :gem
81
+ end
82
+
83
+ if root
84
+ start = root.size
85
+ start += 1 if path.getbyte(start) == SEPARATOR_BYTE
86
+ if type == :gem
87
+ "$GEM_PATH/#{path[start, path.size]}"
88
+ else
89
+ path[start, path.size]
90
+ end
91
+ else
92
+ "Absolute Path"
93
+ end
94
+ end
95
+
96
+ private
97
+ def relative_path?(path)
98
+ !absolute_path?(path)
99
+ end
100
+
101
+ SEPARATOR_BYTE = File::SEPARATOR.ord
102
+
103
+ if File.const_defined?(:NULL) ? File::NULL == "NUL" : RbConfig::CONFIG['host_os'] =~ /mingw|mswin32/
104
+ # This is a DOSish environment
105
+ ALT_SEPARATOR_BYTE = File::ALT_SEPARATOR && File::ALT_SEPARATOR.ord
106
+ COLON_BYTE = ":".ord
107
+ def absolute_path?(path)
108
+ if alpha?(path.getbyte(0)) && path.getbyte(1) == COLON_BYTE
109
+ byte2 = path.getbyte(2)
110
+ byte2 == SEPARATOR_BYTE || byte2 == ALT_SEPARATOR_BYTE
111
+ end
112
+ end
113
+
114
+ def alpha?(byte)
115
+ byte >= 65 and byte <= 90 || byte >= 97 and byte <= 122
116
+ end
117
+ else
118
+ def absolute_path?(path)
119
+ path.getbyte(0) == SEPARATOR_BYTE
120
+ end
121
+ end
122
+ end
123
+
124
+ class Container
125
+ def initialize(normalizers)
126
+ @normalizers = normalizers
127
+ end
128
+
129
+ def keys
130
+ @normalizers.keys
131
+ end
132
+
133
+ def normalize(trace, name, payload)
134
+ normalizer_for(name).normalize(trace, name, payload)
135
+ end
136
+
137
+ def normalize_after(trace, span, name, payload)
138
+ normalizer_for(name).normalize_after(trace, span, name, payload)
139
+ end
140
+
141
+ def normalizer_for(name)
142
+ @normalizers[name] || DEFAULT
143
+ end
144
+ end
145
+
146
+ %w( action_controller/process_action
147
+ action_controller/send_file
148
+ action_view/render_collection
149
+ action_view/render_partial
150
+ action_view/render_template
151
+ active_model_serializers/render
152
+ active_record/instantiation
153
+ active_record/sql
154
+ active_support/cache
155
+ coach/handler_finish
156
+ coach/middleware_finish
157
+ couch_potato/query
158
+ data_mapper/sql
159
+ elasticsearch/request
160
+ faraday/request
161
+ grape/endpoint
162
+ moped/query
163
+ sequel/sql).each do |file|
164
+ require "skylight/core/normalizers/#{file}"
165
+ end
166
+
167
+ # The following are not required by default as they are of dubious usefulness:
168
+ # - active_job/enqueue_at
169
+ end
170
+ end
@@ -0,0 +1,31 @@
1
+ module Skylight::Core
2
+ module Probes
3
+ module ActionController
4
+ class Probe
5
+ def install
6
+ ::ActionController::Instrumentation.class_eval do
7
+ private
8
+ alias append_info_to_payload_without_sk append_info_to_payload
9
+ def append_info_to_payload(payload)
10
+ append_info_to_payload_without_sk(payload)
11
+ rendered_mime = begin
12
+ if respond_to?(:rendered_format)
13
+ rendered_format
14
+ elsif content_type.is_a?(Mime::Type)
15
+ content_type
16
+ elsif content_type.respond_to?(:to_s)
17
+ type_str = content_type.to_s.split(';').first
18
+ Mime::Type.lookup(type_str) unless type_str.blank?
19
+ end
20
+ end
21
+ payload[:rendered_format] = rendered_mime.try(:ref)
22
+ payload[:variant] = request.respond_to?(:variant) ? request.variant : nil
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ register("ActionController::Instrumentation", "action_controller/metal/instrumentation", ActionController::Probe.new)
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ module Skylight::Core
2
+ module Probes
3
+ module ActionView
4
+ class Probe
5
+ def install
6
+ ::ActionView::TemplateRenderer.class_eval do
7
+ alias render_with_layout_without_sk render_with_layout
8
+
9
+ def render_with_layout(path, locals, *args, &block) #:nodoc:
10
+ layout = nil
11
+
12
+ if path
13
+ if method(:find_layout).arity == 3
14
+ # Rails 5
15
+ layout = find_layout(path, locals.keys, [formats.first])
16
+ else
17
+ # Rails 4
18
+ layout = find_layout(path, locals.keys)
19
+ end
20
+ end
21
+
22
+ if layout
23
+ instrument(:template, :identifier => layout.identifier) do
24
+ render_with_layout_without_sk(path, locals, *args, &block)
25
+ end
26
+ else
27
+ render_with_layout_without_sk(path, locals, *args, &block)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ register("ActionView::TemplateRenderer", "action_view", ActionView::Probe.new)
36
+ end
37
+ end
@@ -0,0 +1,55 @@
1
+ module Skylight::Core
2
+ module Probes
3
+ module ActiveModelSerializers
4
+ class Probe
5
+ def install
6
+ version = nil
7
+
8
+ # File moved location between version
9
+ %w(serializer serializers).each do |dir|
10
+ begin
11
+ require "active_model/#{dir}/version"
12
+ rescue LoadError
13
+ end
14
+ end
15
+
16
+ if defined?(::ActiveModel::Serializer::VERSION)
17
+ version = Gem::Version.new(::ActiveModel::Serializer::VERSION)
18
+ end
19
+
20
+ if !version || version < Gem::Version.new("0.5.0")
21
+ # Using $stderr here isn't great, but we don't have a logger accessible
22
+ $stderr.puts "[SKYLIGHT] [#{Skylight::Core::VERSION}] Instrumention is only available for " \
23
+ "ActiveModelSerializers version 0.5.0 and greater."
24
+ return
25
+ end
26
+
27
+ # We don't actually support the RCs correctly, requires
28
+ # a release after 0.10.0.rc3
29
+ if version >= Gem::Version.new("0.10.0.rc1")
30
+ # AS::N is built in to newer versions
31
+ return
32
+ end
33
+
34
+ # End users could override as_json without calling super, but it's likely safer
35
+ # than overriding serializable_array/hash/object.
36
+
37
+ [::ActiveModel::Serializer, ::ActiveModel::ArraySerializer].each do |klass|
38
+ klass.class_eval do
39
+ alias as_json_without_sk as_json
40
+ def as_json(*args)
41
+ payload = { serializer: self.class }
42
+ ActiveSupport::Notifications.instrument('render.active_model_serializers', payload) do
43
+ as_json_without_sk(*args)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+
53
+ register("ActiveModel::Serializer", "active_model/serializer", ActiveModelSerializers::Probe.new)
54
+ end
55
+ end
@@ -0,0 +1,37 @@
1
+ module Skylight::Core
2
+ module Probes
3
+ module Elasticsearch
4
+ class Probe
5
+ def install
6
+ ::Elasticsearch::Transport::Transport::Base.class_eval do
7
+ alias perform_request_without_sk perform_request
8
+ def perform_request(method, path, *args, &block)
9
+ ActiveSupport::Notifications.instrument "request.elasticsearch",
10
+ name: 'Request',
11
+ method: method,
12
+ path: path do
13
+
14
+
15
+ # Prevent HTTP-related probes from firing
16
+ Skylight::Core::Normalizers::Faraday::Request.disable do
17
+ disable_skylight_probe(:NetHTTP) do
18
+ disable_skylight_probe(:HTTPClient) do
19
+ perform_request_without_sk(method, path, *args, &block)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def disable_skylight_probe(class_name, &block)
27
+ klass = Probes.const_get(class_name).const_get(:Probe) rescue nil
28
+ klass ? klass.disable(&block) : block.call
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ register("Elasticsearch", "elasticsearch", Elasticsearch::Probe.new)
36
+ end
37
+ end
@@ -0,0 +1,72 @@
1
+ require 'skylight/core/formatters/http'
2
+
3
+ module Skylight::Core
4
+ module Probes
5
+ module Excon
6
+
7
+ # Middleware for Excon that instruments requests
8
+ class Middleware < ::Excon::Middleware::Base
9
+
10
+ # This probably won't work since config isn't defined
11
+ include Util::Logging
12
+
13
+ def initialize(*)
14
+ @requests = {}
15
+ super
16
+ end
17
+
18
+ # TODO:
19
+ # - Consider whether a LIFO queue would be sufficient
20
+ # - Check that errors can't be called without a request
21
+
22
+ def request_call(datum)
23
+ begin_instrumentation(datum)
24
+ super
25
+ end
26
+
27
+ def response_call(datum)
28
+ super
29
+ ensure
30
+ end_instrumentation(datum)
31
+ end
32
+
33
+ def error_call(datum)
34
+ super
35
+ ensure
36
+ end_instrumentation(datum)
37
+ end
38
+
39
+ private
40
+
41
+ def begin_instrumentation(datum)
42
+ method = datum[:method].to_s
43
+ scheme = datum[:scheme]
44
+ host = datum[:host]
45
+ # TODO: Maybe don't show other default ports like 443
46
+ port = datum[:port] != 80 ? datum[:port] : nil
47
+ path = datum[:path]
48
+ query = datum[:query]
49
+
50
+ opts = Formatters::HTTP.build_opts(method, scheme, host, port, path, query)
51
+
52
+ @requests[datum.object_id] = Skylight::Core::Fanout.instrument(opts)
53
+ rescue Exception => e
54
+ error "failed to begin instrumentation for Excon; msg=%s", e.message
55
+ end
56
+
57
+ def end_instrumentation(datum)
58
+ if request = @requests.delete(datum.object_id)
59
+ meta = { }
60
+ if datum[:error].is_a?(Exception)
61
+ meta[:exception_object] = datum[:error]
62
+ end
63
+ Skylight::Core::Fanout.done(request, meta)
64
+ end
65
+ rescue Exception => e
66
+ error "failed to end instrumentation for Excon; msg=%s", e.message
67
+ end
68
+
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,26 @@
1
+ module Skylight::Core
2
+ module Probes
3
+ module Excon
4
+ # Probe for instrumenting Excon requests. Installs {Excon::Middleware} to achieve this.
5
+ class Probe
6
+ def install
7
+ if defined?(::Excon::Middleware)
8
+ # Don't require until installation since it depends on Excon being loaded
9
+ require 'skylight/core/probes/excon/middleware'
10
+
11
+ idx = ::Excon.defaults[:middlewares].index(::Excon::Middleware::Instrumentor)
12
+
13
+ # TODO: Handle possibility of idx being nil
14
+ ::Excon.defaults[:middlewares].insert(idx, Probes::Excon::Middleware)
15
+ else
16
+ # Using $stderr here isn't great, but we don't have a logger accessible
17
+ $stderr.puts "[SKYLIGHT] [#{Skylight::Core::VERSION}] The installed version of Excon doesn't " \
18
+ "support Middlewares. The Excon probe will be disabled."
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ register("Excon", "excon", Excon::Probe.new)
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ module Skylight::Core
2
+ module Probes
3
+ module Faraday
4
+ class Probe
5
+ def install
6
+ ::Faraday::Connection.class_eval do
7
+ alias initialize_without_sk initialize
8
+
9
+ def initialize(*args, &block)
10
+ initialize_without_sk(*args, &block)
11
+
12
+ @builder.insert 0, ::Faraday::Request::Instrumentation
13
+ end
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+
20
+ register("Faraday", "faraday", Faraday::Probe.new)
21
+ end
22
+ end
@@ -0,0 +1,80 @@
1
+ module Skylight::Core
2
+ module Probes
3
+ module Grape
4
+ class Probe
5
+ def install
6
+ version = Gem::Version.new(::Grape::VERSION)
7
+
8
+ if version > Gem::Version.new("0.12.1")
9
+ # AS::N is built in to newer versions
10
+ return
11
+ end
12
+
13
+ if version < Gem::Version.new("0.10.0")
14
+ # Using $stderr here isn't great, but we don't have a logger accessible
15
+ $stderr.puts "[SKYLIGHT] [#{Skylight::Core::VERSION}] The Grape probe only works with version 0.10.0+ " \
16
+ "and will be disabled."
17
+
18
+ return
19
+ end
20
+
21
+ ::Grape::Endpoint.class_eval do
22
+ alias initialize_without_sk initialize
23
+ def initialize(*args, &block)
24
+ initialize_without_sk(*args, &block)
25
+
26
+ # This solution of wrapping the block is effective, but potentially fragile.
27
+ # A cleaner solution would be to call the original initialize with the already
28
+ # modified block. However, Grape does some odd stuff with the block binding
29
+ # that makes this difficult to reason about.
30
+ if original_block = @block
31
+ @block = lambda do |endpoint_instance|
32
+ ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: endpoint_instance) do
33
+ original_block.call(endpoint_instance)
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ alias run_without_sk run
40
+ def run(*args)
41
+ ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self) do
42
+ run_without_sk(*args)
43
+ end
44
+ end
45
+
46
+ alias run_filters_without_sk run_filters
47
+ def run_filters(filters)
48
+ # Unfortunately, the type isn't provided to the method so we have
49
+ # to try to guess it by looking at the contents. This is only reliable
50
+ # if the filters aren't empty.
51
+ if filters && !filters.empty?
52
+ type = case filters
53
+ when befores then :before
54
+ when before_validations then :before_validation
55
+ when after_validations then :after_validation
56
+ when afters then :after
57
+ else :other
58
+ end
59
+ else
60
+ type = :unknown
61
+ end
62
+
63
+ payload = {
64
+ endpoint: self,
65
+ filters: filters,
66
+ type: type
67
+ }
68
+
69
+ ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', payload) do
70
+ run_filters_without_sk(filters)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ register("Grape::Endpoint", "grape/endpoint", Grape::Probe.new)
79
+ end
80
+ end
@@ -0,0 +1,46 @@
1
+ require 'skylight/core/formatters/http'
2
+
3
+ module Skylight::Core
4
+ module Probes
5
+ module HTTPClient
6
+ class Probe
7
+ DISABLED_KEY = :__skylight_httpclient_disabled
8
+
9
+ def self.disable
10
+ Thread.current[DISABLED_KEY] = true
11
+ yield
12
+ ensure
13
+ Thread.current[DISABLED_KEY] = false
14
+ end
15
+
16
+ def self.disabled?
17
+ !!Thread.current[DISABLED_KEY]
18
+ end
19
+
20
+ def install
21
+ ::HTTPClient.class_eval do
22
+ # HTTPClient has request methods on the class object itself,
23
+ # but the internally instantiate a client and perform the method
24
+ # on that, so this instance method override will cover both
25
+ # `HTTPClient.get(...)` and `HTTPClient.new.get(...)`
26
+
27
+ alias do_request_without_sk do_request
28
+ def do_request(method, uri, query, body, header, &block)
29
+ if Probes::HTTPClient::Probe.disabled?
30
+ return do_request_without_sk(method, uri, query, body, header, &block)
31
+ end
32
+
33
+ opts = Formatters::HTTP.build_opts(method, uri.scheme, uri.host, uri.port, uri.path, uri.query)
34
+
35
+ Skylight::Core::Fanout.instrument(opts) do
36
+ do_request_without_sk(method, uri, query, body, header, &block)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end # class Probe
42
+ end # module Probes::HTTPClient
43
+
44
+ register("HTTPClient", "httpclient", HTTPClient::Probe.new)
45
+ end
46
+ end
@@ -0,0 +1,58 @@
1
+ module Skylight::Core
2
+ module Probes
3
+ module Middleware
4
+ class Probe
5
+ def install
6
+ ActionDispatch::MiddlewareStack::Middleware.class_eval do
7
+ alias build_without_sk build
8
+ def build(*args)
9
+ sk_instrument_middleware(build_without_sk(*args))
10
+ end
11
+
12
+ def sk_instrument_middleware(middleware)
13
+ return middleware if middleware.is_a?(Skylight::Core::Middleware)
14
+
15
+ # Not sure how this would actually happen
16
+ return middleware if middleware.respond_to?(:call_without_sk)
17
+
18
+ # On Rails 3, ActionDispatch::Session::CookieStore is frozen, for one
19
+ return middleware if middleware.frozen?
20
+
21
+ middleware.instance_eval do
22
+ alias call_without_sk call
23
+ def call(*args, &block)
24
+ traces = Skylight::Core::Fanout.registered.map do |r|
25
+ r.instrumenter ? r.instrumenter.current_trace : nil
26
+ end.compact
27
+
28
+ return call_without_sk(*args, &block) if traces.empty?
29
+
30
+ begin
31
+ name = self.class.name || "Anonymous Middleware"
32
+
33
+ traces.each{|t| t.endpoint = name }
34
+
35
+ spans = Skylight::Core::Fanout.instrument(title: name, category: "rack.middleware")
36
+ resp = call_without_sk(*args, &block)
37
+
38
+ Skylight::Core::Middleware.with_after_close(resp) do
39
+ Skylight::Core::Fanout.done(spans)
40
+ end
41
+ rescue Exception => e
42
+ # FIXME: Log this?
43
+ Skylight::Core::Fanout.done(spans, exception_object: e)
44
+ raise
45
+ end
46
+ end
47
+ end
48
+
49
+ middleware
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ register("ActionDispatch::MiddlewareStack::Middleware", "actionpack/action_dispatch", Middleware::Probe.new)
57
+ end
58
+ end