skylight 4.3.2 → 5.0.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -3
- data/CONTRIBUTING.md +2 -8
- data/ext/extconf.rb +6 -5
- data/ext/libskylight.yml +7 -6
- data/ext/skylight_native.c +22 -99
- data/lib/skylight.rb +211 -14
- data/lib/skylight/api.rb +10 -3
- data/lib/skylight/cli.rb +4 -3
- data/lib/skylight/cli/doctor.rb +13 -14
- data/lib/skylight/cli/merger.rb +6 -4
- data/lib/skylight/config.rb +597 -127
- data/lib/skylight/deprecation.rb +17 -0
- data/lib/skylight/errors.rb +21 -6
- data/lib/skylight/extensions.rb +107 -0
- data/lib/skylight/extensions/source_location.rb +291 -0
- data/lib/skylight/formatters/http.rb +20 -0
- data/lib/skylight/gc.rb +109 -0
- data/lib/skylight/helpers.rb +69 -26
- data/lib/skylight/instrumenter.rb +326 -15
- data/lib/skylight/middleware.rb +138 -1
- data/lib/skylight/native.rb +52 -2
- data/lib/skylight/native_ext_fetcher.rb +4 -3
- data/lib/skylight/normalizers.rb +153 -0
- data/lib/skylight/normalizers/action_controller/process_action.rb +69 -0
- data/lib/skylight/normalizers/action_controller/send_file.rb +50 -0
- data/lib/skylight/normalizers/action_dispatch/process_middleware.rb +22 -0
- data/lib/skylight/normalizers/action_dispatch/route_set.rb +27 -0
- data/lib/skylight/normalizers/action_view/render_collection.rb +24 -0
- data/lib/skylight/normalizers/action_view/render_layout.rb +25 -0
- data/lib/skylight/normalizers/action_view/render_partial.rb +23 -0
- data/lib/skylight/normalizers/action_view/render_template.rb +23 -0
- data/lib/skylight/normalizers/active_job/perform.rb +86 -0
- data/lib/skylight/normalizers/active_model_serializers/render.rb +28 -0
- data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
- data/lib/skylight/normalizers/active_record/sql.rb +12 -0
- data/lib/skylight/normalizers/active_storage.rb +30 -0
- data/lib/skylight/normalizers/active_support/cache.rb +22 -0
- data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
- data/lib/skylight/normalizers/coach/handler_finish.rb +46 -0
- data/lib/skylight/normalizers/coach/middleware_finish.rb +33 -0
- data/lib/skylight/normalizers/couch_potato/query.rb +20 -0
- data/lib/skylight/normalizers/data_mapper/sql.rb +12 -0
- data/lib/skylight/normalizers/default.rb +32 -0
- data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
- data/lib/skylight/normalizers/faraday/request.rb +40 -0
- data/lib/skylight/normalizers/grape/endpoint.rb +34 -0
- data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
- data/lib/skylight/normalizers/grape/endpoint_run.rb +41 -0
- data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +22 -0
- data/lib/skylight/normalizers/grape/format_response.rb +20 -0
- data/lib/skylight/normalizers/graphiti/render.rb +22 -0
- data/lib/skylight/normalizers/graphiti/resolve.rb +31 -0
- data/lib/skylight/normalizers/graphql/base.rb +132 -0
- data/lib/skylight/normalizers/render.rb +81 -0
- data/lib/skylight/normalizers/sequel/sql.rb +12 -0
- data/lib/skylight/normalizers/shrine.rb +34 -0
- data/lib/skylight/normalizers/sql.rb +45 -0
- data/lib/skylight/probes.rb +181 -0
- data/lib/skylight/probes/action_controller.rb +48 -0
- data/lib/skylight/probes/action_dispatch.rb +2 -0
- data/lib/skylight/probes/action_dispatch/request_id.rb +29 -0
- data/lib/skylight/probes/action_dispatch/routing/route_set.rb +28 -0
- data/lib/skylight/probes/action_view.rb +43 -0
- data/lib/skylight/probes/active_job.rb +27 -0
- data/lib/skylight/probes/active_job_enqueue.rb +41 -0
- data/lib/skylight/probes/active_model_serializers.rb +50 -0
- data/lib/skylight/probes/delayed_job.rb +149 -0
- data/lib/skylight/probes/elasticsearch.rb +38 -0
- data/lib/skylight/probes/excon.rb +25 -0
- data/lib/skylight/probes/excon/middleware.rb +66 -0
- data/lib/skylight/probes/faraday.rb +23 -0
- data/lib/skylight/probes/graphql.rb +43 -0
- data/lib/skylight/probes/httpclient.rb +44 -0
- data/lib/skylight/probes/middleware.rb +126 -0
- data/lib/skylight/probes/mongo.rb +164 -0
- data/lib/skylight/probes/mongoid.rb +13 -0
- data/lib/skylight/probes/net_http.rb +54 -0
- data/lib/skylight/probes/redis.rb +63 -0
- data/lib/skylight/probes/sequel.rb +33 -0
- data/lib/skylight/probes/sinatra.rb +63 -0
- data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
- data/lib/skylight/probes/tilt.rb +27 -0
- data/lib/skylight/railtie.rb +162 -18
- data/lib/skylight/sidekiq.rb +48 -0
- data/lib/skylight/subscriber.rb +110 -0
- data/lib/skylight/test.rb +146 -0
- data/lib/skylight/trace.rb +307 -10
- data/lib/skylight/user_config.rb +61 -0
- data/lib/skylight/util.rb +12 -0
- data/lib/skylight/util/allocation_free.rb +26 -0
- data/lib/skylight/util/clock.rb +56 -0
- data/lib/skylight/util/component.rb +5 -2
- data/lib/skylight/util/deploy.rb +7 -10
- data/lib/skylight/util/gzip.rb +20 -0
- data/lib/skylight/util/http.rb +4 -10
- data/lib/skylight/util/instrumenter_method.rb +26 -0
- data/lib/skylight/util/logging.rb +138 -0
- data/lib/skylight/util/lru_cache.rb +40 -0
- data/lib/skylight/util/platform.rb +1 -1
- data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
- data/lib/skylight/version.rb +5 -1
- data/lib/skylight/vm/gc.rb +68 -0
- metadata +126 -13
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
module Normalizers
|
|
3
|
+
module Grape
|
|
4
|
+
class EndpointRunFilters < Endpoint
|
|
5
|
+
register "endpoint_run_filters.grape"
|
|
6
|
+
|
|
7
|
+
CAT = "app.grape.filters".freeze
|
|
8
|
+
|
|
9
|
+
def normalize(_trace, _name, payload)
|
|
10
|
+
filters = payload[:filters]
|
|
11
|
+
type = payload[:type]
|
|
12
|
+
|
|
13
|
+
if (!filters || filters.empty?) || !type
|
|
14
|
+
return :skip
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
[CAT, "#{type.to_s.capitalize} Filters", nil]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
module Normalizers
|
|
3
|
+
module Grape
|
|
4
|
+
class FormatResponse < Normalizer
|
|
5
|
+
register "format_response.grape"
|
|
6
|
+
|
|
7
|
+
CAT = "view.grape.format_response".freeze
|
|
8
|
+
|
|
9
|
+
def normalize(_trace, _name, payload)
|
|
10
|
+
if (formatter = payload[:formatter])
|
|
11
|
+
title = formatter.is_a?(Module) ? formatter.to_s : formatter.class.to_s
|
|
12
|
+
[CAT, title, nil]
|
|
13
|
+
else
|
|
14
|
+
:skip
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skylight
|
|
4
|
+
module Normalizers
|
|
5
|
+
module Graphiti
|
|
6
|
+
class Render < Normalizer
|
|
7
|
+
register "render.graphiti"
|
|
8
|
+
|
|
9
|
+
CAT = "view.render.graphiti"
|
|
10
|
+
ANONYMOUS = "<Anonymous Resource>"
|
|
11
|
+
|
|
12
|
+
def normalize(_trace, _name, payload)
|
|
13
|
+
resource_class = payload[:proxy]&.resource&.class
|
|
14
|
+
title = "Render #{resource_class&.name || ANONYMOUS}"
|
|
15
|
+
desc = nil
|
|
16
|
+
|
|
17
|
+
[CAT, title, desc]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skylight
|
|
4
|
+
module Normalizers
|
|
5
|
+
module Graphiti
|
|
6
|
+
class Resolve < Normalizer
|
|
7
|
+
register "resolve.graphiti"
|
|
8
|
+
|
|
9
|
+
CAT = "app.resolve.graphiti"
|
|
10
|
+
|
|
11
|
+
ANONYMOUS_RESOURCE = "<Anonymous Resource>"
|
|
12
|
+
ANONYMOUS_ADAPTER = "<Anonymous Adapter>"
|
|
13
|
+
|
|
14
|
+
def normalize(_trace, _name, payload)
|
|
15
|
+
resource = payload[:resource]
|
|
16
|
+
|
|
17
|
+
if (sideload = payload[:sideload])
|
|
18
|
+
type = sideload.type.to_s.split("_").map(&:capitalize).join(" ")
|
|
19
|
+
desc = "Custom Scope" if sideload.class.scope_proc
|
|
20
|
+
else
|
|
21
|
+
type = "Primary"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
title = "Resolve #{type} #{resource.class.name || ANONYMOUS_RESOURCE}"
|
|
25
|
+
|
|
26
|
+
[CAT, title, desc]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/inflector"
|
|
4
|
+
|
|
5
|
+
module Skylight::Normalizers::GraphQL
|
|
6
|
+
# Some AS::N events in GraphQL are not super useful.
|
|
7
|
+
# We are purposefully ignoring the following keys (and you probably shouldn't add them):
|
|
8
|
+
# - "graphql.analyze_multiplex"
|
|
9
|
+
# - "graphql.execute_field" (very frequently called)
|
|
10
|
+
# - "graphql.execute_field_lazy"
|
|
11
|
+
|
|
12
|
+
class Base < Skylight::Normalizers::Normalizer
|
|
13
|
+
ANONYMOUS = "[anonymous]"
|
|
14
|
+
CAT = "app.graphql"
|
|
15
|
+
|
|
16
|
+
if defined?(::GraphQL::VERSION) && Gem::Version.new(::GraphQL::VERSION) >= Gem::Version.new("1.10")
|
|
17
|
+
def self.register_graphql
|
|
18
|
+
register("#{key}.graphql")
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
def self.register_graphql
|
|
22
|
+
register("graphql.#{key}")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.inherited(klass)
|
|
27
|
+
super
|
|
28
|
+
klass.const_set(
|
|
29
|
+
:KEY,
|
|
30
|
+
ActiveSupport::Inflector.underscore(
|
|
31
|
+
ActiveSupport::Inflector.demodulize(klass.name)
|
|
32
|
+
).freeze
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.key
|
|
37
|
+
self::KEY
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def normalize(_trace, _name, _payload)
|
|
41
|
+
[CAT, "graphql.#{key}", nil]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def key
|
|
47
|
+
self.class.key
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def extract_query_name(query)
|
|
51
|
+
query&.context&.[](:skylight_endpoint) ||
|
|
52
|
+
query&.operation_name ||
|
|
53
|
+
ANONYMOUS
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class Lex < Base
|
|
58
|
+
register_graphql
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class Parse < Base
|
|
62
|
+
register_graphql
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class Validate < Base
|
|
66
|
+
register_graphql
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class ExecuteMultiplex < Base
|
|
70
|
+
register_graphql
|
|
71
|
+
|
|
72
|
+
def normalize_after(trace, _span, _name, payload)
|
|
73
|
+
# This is in normalize_after because the queries may not have
|
|
74
|
+
# an assigned operation name before they are executed.
|
|
75
|
+
# For example, if you send a single query with a defined operation name, e.g.:
|
|
76
|
+
# ```graphql
|
|
77
|
+
# query MyNamedQuery { user(id: 1) { name } }
|
|
78
|
+
# ```
|
|
79
|
+
# ... but do _not_ send the operationName request param, the GraphQL docs[1]
|
|
80
|
+
# specify that the executor should use the operation name from the definition.
|
|
81
|
+
#
|
|
82
|
+
# In graphql-ruby's case, the calculation of the operation name is lazy, and
|
|
83
|
+
# has not been done yet at the point where execute_multiplex starts.
|
|
84
|
+
# [1] https://graphql.org/learn/serving-over-http/#post-request
|
|
85
|
+
queries, has_errors = payload[:multiplex].queries.each_with_object([Set.new, Set.new]) do |query, (names, errors)|
|
|
86
|
+
names << extract_query_name(query)
|
|
87
|
+
errors << query.static_errors.any?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
trace.endpoint = "graphql:#{queries.sort.join('+')}"
|
|
91
|
+
trace.compound_response_error_status = if has_errors.all?
|
|
92
|
+
:all
|
|
93
|
+
elsif has_errors.any?
|
|
94
|
+
:partial
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class AnalyzeQuery < Base
|
|
100
|
+
register_graphql
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
class ExecuteQuery < Base
|
|
104
|
+
register_graphql
|
|
105
|
+
|
|
106
|
+
def normalize(trace, _name, payload)
|
|
107
|
+
query_name = extract_query_name(payload[:query])
|
|
108
|
+
|
|
109
|
+
if query_name == ANONYMOUS
|
|
110
|
+
meta = { mute_children: true }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# This is probably always overriden by execute_multiplex#normalize_after,
|
|
114
|
+
# but in the case of a single query, it will be the same value anyway.
|
|
115
|
+
trace.endpoint = "graphql:#{query_name}"
|
|
116
|
+
|
|
117
|
+
[CAT, "graphql.#{key}: #{query_name}", nil, meta]
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
class ExecuteQueryLazy < ExecuteQuery
|
|
122
|
+
register_graphql
|
|
123
|
+
|
|
124
|
+
def normalize(trace, _name, payload)
|
|
125
|
+
if payload[:query]
|
|
126
|
+
super
|
|
127
|
+
elsif payload[:multiplex]
|
|
128
|
+
[CAT, "graphql.#{key}.multiplex", nil]
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
module Normalizers
|
|
3
|
+
# Base Normalizer for Rails rendering
|
|
4
|
+
class RenderNormalizer < Normalizer
|
|
5
|
+
include Skylight::Util::AllocationFree
|
|
6
|
+
|
|
7
|
+
def setup
|
|
8
|
+
@paths = []
|
|
9
|
+
|
|
10
|
+
Gem.path.each do |path|
|
|
11
|
+
@paths << "#{path}/bundler/gems".freeze
|
|
12
|
+
@paths << "#{path}/gems".freeze
|
|
13
|
+
@paths << path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
@paths.concat(Array(config["normalizers.render.view_paths"]))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Generic normalizer for renders
|
|
20
|
+
# @param category [String]
|
|
21
|
+
# @param payload [Hash]
|
|
22
|
+
# @option payload [String] :identifier
|
|
23
|
+
# @return [Array]
|
|
24
|
+
def normalize_render(category, payload)
|
|
25
|
+
if (path = payload[:identifier])
|
|
26
|
+
title = relative_path(path)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
[category, title, nil]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def relative_path(path)
|
|
33
|
+
return path if relative_path?(path)
|
|
34
|
+
|
|
35
|
+
if (root = array_find(@paths) { |p| path.start_with?(p) })
|
|
36
|
+
start = root.size
|
|
37
|
+
start += 1 if path.getbyte(start) == SEPARATOR_BYTE
|
|
38
|
+
|
|
39
|
+
path[start, path.size].sub(
|
|
40
|
+
# Matches a Gem Version or 12-digit hex (sha)
|
|
41
|
+
# that is preceeded by a `-` and followed by `/`
|
|
42
|
+
# Also matches 'app/views/' if it exists
|
|
43
|
+
%r{-(?:#{Gem::Version::VERSION_PATTERN}|[0-9a-f]{12})/(?:app/views/)*},
|
|
44
|
+
": ".freeze
|
|
45
|
+
)
|
|
46
|
+
else
|
|
47
|
+
"Absolute Path".freeze
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def relative_path?(path)
|
|
54
|
+
!absolute_path?(path)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
SEPARATOR_BYTE = File::SEPARATOR.ord
|
|
58
|
+
|
|
59
|
+
if File.const_defined?(:NULL) ? File::NULL == "NUL" : RbConfig::CONFIG["host_os"] =~ /mingw|mswin32/
|
|
60
|
+
# This is a DOSish environment
|
|
61
|
+
ALT_SEPARATOR_BYTE = File::ALT_SEPARATOR&.ord
|
|
62
|
+
COLON_BYTE = ":".ord
|
|
63
|
+
SEPARATOR_BYTES = [SEPARATOR_BYTE, ALT_SEPARATOR_BYTE].freeze
|
|
64
|
+
|
|
65
|
+
def absolute_path?(path)
|
|
66
|
+
if alpha?(path.getbyte(0)) && path.getbyte(1) == COLON_BYTE
|
|
67
|
+
SEPARATOR_BYTES.include?(path.getbyte(2))
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def alpha?(byte)
|
|
72
|
+
(byte >= 65 && byte <= 90) || (byte >= 97 && byte <= 122)
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
def absolute_path?(path)
|
|
76
|
+
path.getbyte(0) == SEPARATOR_BYTE
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
module Normalizers
|
|
3
|
+
class Shrine < Normalizer
|
|
4
|
+
TITLES = {
|
|
5
|
+
"upload.shrine" => "Upload",
|
|
6
|
+
"download.shrine" => "Download",
|
|
7
|
+
"open.shrine" => "Open",
|
|
8
|
+
"exists.shrine" => "Exists",
|
|
9
|
+
"delete.shrine" => "Delete",
|
|
10
|
+
"metadata.shrine" => "Metadata",
|
|
11
|
+
"mime_type.shrine" => "MIME Type",
|
|
12
|
+
"image_dimensions.shrine" => "Image Dimensions",
|
|
13
|
+
"signature.shrine" => "Signature",
|
|
14
|
+
"extension.shrine" => "Extension",
|
|
15
|
+
"derivation.shrine" => "Derivation",
|
|
16
|
+
"derivatives.shrine" => "Derivatives",
|
|
17
|
+
"data_uri.shrine" => "Data URI",
|
|
18
|
+
"remote_url.shrine" => "Remote URL"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
TITLES.each_key do |key|
|
|
22
|
+
register key
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def normalize(_trace, name, _payload)
|
|
26
|
+
title = ["Shrine", TITLES[name]].join(" ")
|
|
27
|
+
|
|
28
|
+
cat = "app.#{name.split('.').reverse.join('.')}"
|
|
29
|
+
|
|
30
|
+
[cat, title, nil]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Skylight
|
|
6
|
+
module Normalizers
|
|
7
|
+
# Normalizer for SQL requests
|
|
8
|
+
class SQL < Normalizer
|
|
9
|
+
CAT = "db.sql.query"
|
|
10
|
+
|
|
11
|
+
# @param trace [Skylight::Messages::Trace::Builder] ignored, only present to match API
|
|
12
|
+
# @param name [String] ignored, only present to match API
|
|
13
|
+
# @param payload [Hash]
|
|
14
|
+
# @option payload [String] [:name] The SQL operation
|
|
15
|
+
# @option payload [Hash] [:binds] The bound parameters
|
|
16
|
+
# @return [Array]
|
|
17
|
+
def normalize(_trace, name, payload)
|
|
18
|
+
case payload[:name]
|
|
19
|
+
when "SCHEMA", "CACHE"
|
|
20
|
+
return :skip
|
|
21
|
+
else
|
|
22
|
+
name = CAT
|
|
23
|
+
title = payload[:name] || "SQL"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# We can only handle UTF-8 encoded strings.
|
|
27
|
+
# (Construction method here avoids extra allocations)
|
|
28
|
+
sql = String.new.concat("<sk-sql>", payload[:sql], "</sk-sql>").force_encoding(Encoding::UTF_8)
|
|
29
|
+
|
|
30
|
+
unless sql.valid_encoding?
|
|
31
|
+
if config[:log_sql_parse_errors]
|
|
32
|
+
config.logger.error "[#{Skylight::SqlLexError.formatted_code}] Unable to extract binds from non-UTF-8 " \
|
|
33
|
+
"query. " \
|
|
34
|
+
"encoding=#{payload[:sql].encoding.name} " \
|
|
35
|
+
"sql=#{payload[:sql].inspect} "
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
sql = nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
[name, title, sql]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
require "pathname"
|
|
2
|
+
require "active_support/inflector"
|
|
3
|
+
|
|
4
|
+
module Skylight
|
|
5
|
+
# @api private
|
|
6
|
+
module Probes
|
|
7
|
+
class ProbeRegistration
|
|
8
|
+
attr_reader :name, :const_name, :require_paths, :probe
|
|
9
|
+
|
|
10
|
+
def initialize(name, const_name, require_paths, probe)
|
|
11
|
+
@name = name
|
|
12
|
+
@const_name = const_name
|
|
13
|
+
@require_paths = Array(require_paths)
|
|
14
|
+
@probe = probe
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def install
|
|
18
|
+
probe.install
|
|
19
|
+
rescue StandardError, LoadError => e
|
|
20
|
+
log_install_exception(e)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def constant_available?
|
|
24
|
+
Skylight::Probes.constant_available?(const_name)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def log_install_exception(err)
|
|
30
|
+
description = err.class.to_s
|
|
31
|
+
description << ": #{err.message}" unless err.message.empty?
|
|
32
|
+
|
|
33
|
+
backtrace = err.backtrace.map { |l| " #{l}" }.join("\n")
|
|
34
|
+
|
|
35
|
+
gems =
|
|
36
|
+
begin
|
|
37
|
+
Bundler.locked_gems.dependencies.map { |d| [d.name, d.requirement.to_s] }
|
|
38
|
+
rescue # rubocop:disable Lint/SuppressedException
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
error = "[SKYLIGHT] [#{Skylight::VERSION}] Encountered an error while installing the " \
|
|
42
|
+
"probe for #{const_name}. Please notify support@skylight.io with the debugging " \
|
|
43
|
+
"information below. It's recommended that you disable this probe until the " \
|
|
44
|
+
"issue is resolved." \
|
|
45
|
+
"\n\nERROR: #{description}\n\n#{backtrace}\n\n"
|
|
46
|
+
|
|
47
|
+
if gems
|
|
48
|
+
gems_string = gems.map { |g| " #{g[0]} #{g[1]}" }.join("\n")
|
|
49
|
+
error << "GEMS:\n\n#{gems_string}\n\n"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
$stderr.puts(error)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class << self
|
|
57
|
+
def constant_available?(const_name)
|
|
58
|
+
::ActiveSupport::Inflector.safe_constantize(const_name).present?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def install!
|
|
62
|
+
pending = registered.values - installed.values
|
|
63
|
+
|
|
64
|
+
pending.each do |registration|
|
|
65
|
+
if registration.constant_available?
|
|
66
|
+
install_probe(registration)
|
|
67
|
+
else
|
|
68
|
+
register_require_hook(registration)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def install_probe(registration)
|
|
74
|
+
return if installed.key?(registration.name)
|
|
75
|
+
|
|
76
|
+
installed[registration.name] = registration
|
|
77
|
+
registration.install
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def add_path(path)
|
|
81
|
+
root = Pathname.new(path)
|
|
82
|
+
Pathname.glob(root.join("./**/*.rb")).each do |f|
|
|
83
|
+
name = f.relative_path_from(root).sub_ext("").to_s
|
|
84
|
+
if available.key?(name)
|
|
85
|
+
raise "duplicate probe name: #{name}; original=#{available[name]}; new=#{f}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
available[name] = f
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def available
|
|
93
|
+
@available ||= {}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def probe(*probes)
|
|
97
|
+
unknown = probes.map(&:to_s) - available.keys
|
|
98
|
+
unless unknown.empty?
|
|
99
|
+
raise ArgumentError, "unknown probes: #{unknown.join(', ')}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
probes.each do |p|
|
|
103
|
+
require available[p.to_s]
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def registered
|
|
108
|
+
@registered ||= {}
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def require_hooks
|
|
112
|
+
@require_hooks ||= {}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def installed
|
|
116
|
+
@installed ||= {}
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def register(name, *args)
|
|
120
|
+
if registered.key?(name)
|
|
121
|
+
raise "already registered: #{name}"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
registered[name] = ProbeRegistration.new(name, *args)
|
|
125
|
+
|
|
126
|
+
true
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def require_hook(require_path)
|
|
130
|
+
each_by_require_path(require_path) do |registration|
|
|
131
|
+
# Double check constant is available
|
|
132
|
+
next unless registration.constant_available?
|
|
133
|
+
|
|
134
|
+
install_probe(registration)
|
|
135
|
+
|
|
136
|
+
# Don't need this to be called again
|
|
137
|
+
unregister_require_hook(registration)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def register_require_hook(registration)
|
|
142
|
+
registration.require_paths.each do |p|
|
|
143
|
+
require_hooks[p] ||= []
|
|
144
|
+
require_hooks[p] << registration
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def unregister_require_hook(registration)
|
|
149
|
+
registration.require_paths.each do |p|
|
|
150
|
+
require_hooks[p].delete(registration)
|
|
151
|
+
require_hooks.delete(p) if require_hooks[p].empty?
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def each_by_require_path(require_path)
|
|
156
|
+
return unless require_hooks.key?(require_path)
|
|
157
|
+
|
|
158
|
+
# dup because we may be mutating the array
|
|
159
|
+
require_hooks[require_path].dup.each do |registration|
|
|
160
|
+
yield registration
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
add_path(File.expand_path("./probes", __dir__))
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# @api private
|
|
170
|
+
module Kernel
|
|
171
|
+
# Unfortunately, we can't use prepend here, in part because RubyGems changes require with an alias
|
|
172
|
+
alias require_without_sk require
|
|
173
|
+
|
|
174
|
+
def require(name)
|
|
175
|
+
require_without_sk(name).tap do
|
|
176
|
+
Skylight::Probes.require_hook(name)
|
|
177
|
+
rescue Exception => e
|
|
178
|
+
warn("[SKYLIGHT] Rescued exception in require hook", e)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|