appsignal 4.10.0-java → 5.0.0.rc.1-java

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -0
  3. data/Rakefile +87 -0
  4. data/appsignal.gemspec +8 -0
  5. data/build_matrix.yml +14 -1
  6. data/ext/appsignal_extension.c +14 -0
  7. data/ext/base.rb +8 -1
  8. data/lib/appsignal/backends.rb +55 -0
  9. data/lib/appsignal/config.rb +140 -1
  10. data/lib/appsignal/demo.rb +1 -1
  11. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +34 -22
  12. data/lib/appsignal/event_formatter/active_job/perform_formatter.rb +35 -0
  13. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +19 -0
  14. data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +27 -0
  15. data/lib/appsignal/event_formatter/recorded_elsewhere.rb +17 -0
  16. data/lib/appsignal/event_formatter/rom/sql_formatter.rb +24 -0
  17. data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +5 -0
  18. data/lib/appsignal/event_formatter/view_component/render_formatter.rb +21 -10
  19. data/lib/appsignal/event_formatter.rb +78 -0
  20. data/lib/appsignal/extension.rb +4 -0
  21. data/lib/appsignal/helpers/instrumentation.rb +236 -20
  22. data/lib/appsignal/helpers/metrics.rb +3 -24
  23. data/lib/appsignal/hooks/action_cable.rb +18 -6
  24. data/lib/appsignal/hooks/active_job.rb +128 -39
  25. data/lib/appsignal/hooks/at_exit.rb +4 -1
  26. data/lib/appsignal/hooks/excon.rb +20 -0
  27. data/lib/appsignal/hooks/faraday.rb +16 -0
  28. data/lib/appsignal/hooks/http.rb +5 -0
  29. data/lib/appsignal/hooks/resque.rb +1 -1
  30. data/lib/appsignal/hooks/sequel.rb +32 -2
  31. data/lib/appsignal/hooks/shoryuken.rb +3 -3
  32. data/lib/appsignal/hooks/sidekiq.rb +1 -1
  33. data/lib/appsignal/integrations/action_cable.rb +5 -2
  34. data/lib/appsignal/integrations/active_support_notifications.rb +59 -14
  35. data/lib/appsignal/integrations/data_mapper.rb +14 -2
  36. data/lib/appsignal/integrations/delayed_job_plugin.rb +45 -5
  37. data/lib/appsignal/integrations/dry_monitor.rb +39 -15
  38. data/lib/appsignal/integrations/excon/appsignal_middleware.rb +21 -0
  39. data/lib/appsignal/integrations/excon.rb +52 -15
  40. data/lib/appsignal/integrations/faraday.rb +47 -12
  41. data/lib/appsignal/integrations/http.rb +43 -1
  42. data/lib/appsignal/integrations/mongo_ruby_driver.rb +73 -4
  43. data/lib/appsignal/integrations/net_http.rb +31 -2
  44. data/lib/appsignal/integrations/puma.rb +4 -1
  45. data/lib/appsignal/integrations/que.rb +224 -37
  46. data/lib/appsignal/integrations/railtie.rb +4 -1
  47. data/lib/appsignal/integrations/rake.rb +9 -3
  48. data/lib/appsignal/integrations/redis.rb +22 -1
  49. data/lib/appsignal/integrations/redis_client.rb +22 -1
  50. data/lib/appsignal/integrations/resque.rb +57 -10
  51. data/lib/appsignal/integrations/shoryuken.rb +140 -12
  52. data/lib/appsignal/integrations/sidekiq.rb +73 -16
  53. data/lib/appsignal/integrations/webmachine.rb +47 -4
  54. data/lib/appsignal/loaders/padrino.rb +2 -1
  55. data/lib/appsignal/logger/extension_backend.rb +24 -0
  56. data/lib/appsignal/logger/opentelemetry_backend.rb +66 -0
  57. data/lib/appsignal/logger.rb +13 -9
  58. data/lib/appsignal/metrics/extension_backend.rb +47 -0
  59. data/lib/appsignal/metrics/opentelemetry_backend.rb +89 -0
  60. data/lib/appsignal/opentelemetry/attributes.rb +31 -0
  61. data/lib/appsignal/opentelemetry/dependencies.rb +35 -0
  62. data/lib/appsignal/opentelemetry/error_type.rb +37 -0
  63. data/lib/appsignal/opentelemetry/http_client_request.rb +83 -0
  64. data/lib/appsignal/opentelemetry/http_method.rb +59 -0
  65. data/lib/appsignal/opentelemetry/http_response.rb +30 -0
  66. data/lib/appsignal/opentelemetry/http_server_request.rb +45 -0
  67. data/lib/appsignal/opentelemetry/messaging.rb +82 -0
  68. data/lib/appsignal/opentelemetry/rendering.rb +29 -0
  69. data/lib/appsignal/opentelemetry/sql_db_system.rb +89 -0
  70. data/lib/appsignal/opentelemetry.rb +337 -0
  71. data/lib/appsignal/rack/abstract_middleware.rb +63 -4
  72. data/lib/appsignal/rack/body_wrapper.rb +18 -5
  73. data/lib/appsignal/rack/event_handler.rb +36 -3
  74. data/lib/appsignal/rack/grape_middleware.rb +1 -0
  75. data/lib/appsignal/rack/hanami_middleware.rb +2 -1
  76. data/lib/appsignal/rack/instrumentation_middleware.rb +1 -0
  77. data/lib/appsignal/rack/rails_instrumentation.rb +1 -0
  78. data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -0
  79. data/lib/appsignal/rack.rb +39 -11
  80. data/lib/appsignal/sample_data.rb +4 -0
  81. data/lib/appsignal/transaction/base_backend.rb +107 -0
  82. data/lib/appsignal/transaction/extension_backend.rb +203 -0
  83. data/lib/appsignal/transaction/opentelemetry_backend.rb +796 -0
  84. data/lib/appsignal/transaction.rb +537 -144
  85. data/lib/appsignal/utils/stdout_and_logger_message.rb +9 -0
  86. data/lib/appsignal/version.rb +1 -1
  87. data/lib/appsignal.rb +9 -0
  88. data/sig/appsignal.rbi +426 -37
  89. data/sig/appsignal.rbs +386 -27
  90. metadata +23 -1
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ class EventFormatter
5
+ # @!visibility private
6
+ module ActiveJob
7
+ # Active Job reports the job it is running as a `perform.active_job`
8
+ # notification. The job's own transaction already carries its title, so
9
+ # this formatter only describes the event as the work of performing a
10
+ # job.
11
+ class PerformFormatter < Appsignal::EventFormatter
12
+ PERFORM_ATTRIBUTES =
13
+ Appsignal::OpenTelemetry::Messaging.perform_attributes("active_job").freeze
14
+
15
+ def opentelemetry_attributes(payload)
16
+ PERFORM_ATTRIBUTES.merge(
17
+ { "messaging.destination.name" => queue_name(payload) }.compact
18
+ )
19
+ end
20
+
21
+ # The queue the job being performed is on, which the notification
22
+ # carries as the job itself.
23
+ def queue_name(payload)
24
+ job = payload[:job]
25
+ job.queue_name if job.respond_to?(:queue_name)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ Appsignal::EventFormatter.register(
33
+ "perform.active_job",
34
+ Appsignal::EventFormatter::ActiveJob::PerformFormatter
35
+ )
@@ -5,6 +5,25 @@ module Appsignal
5
5
  # @!visibility private
6
6
  module ActiveRecord
7
7
  class SqlFormatter < Appsignal::EventFormatter
8
+ # A query is an outgoing call to a datastore.
9
+ def opentelemetry_kind
10
+ :client
11
+ end
12
+
13
+ # The payload carries the connection the query ran on (Rails 6.0+),
14
+ # whose `adapter_name` is each adapter's own name for itself, such as
15
+ # `"PostgreSQL"` or `"Mysql2"`. A name the mapping does not recognise
16
+ # is left to the SQL sentinel, same as an adapter this gem has never
17
+ # heard of.
18
+ def opentelemetry_attributes(payload)
19
+ name = Appsignal::OpenTelemetry::SqlDbSystem.name_for_active_record(
20
+ payload[:connection]&.adapter_name
21
+ )
22
+ return unless name
23
+
24
+ { "db.system.name" => name }
25
+ end
26
+
8
27
  def format(payload)
9
28
  [payload[:name], payload[:sql], SQL_BODY_FORMAT]
10
29
  end
@@ -5,6 +5,21 @@ module Appsignal
5
5
  # @!visibility private
6
6
  module ElasticSearch
7
7
  class SearchFormatter < Appsignal::EventFormatter
8
+ # A search is an outgoing call to an Elasticsearch cluster.
9
+ def opentelemetry_kind
10
+ :client
11
+ end
12
+
13
+ def opentelemetry_attributes(payload)
14
+ {
15
+ "db.system.name" => "elasticsearch",
16
+ # This notification is only emitted for a search, so that is the
17
+ # operation every one of these spans describes.
18
+ "db.operation.name" => "search",
19
+ "db.collection.name" => search_index(payload)
20
+ }.compact
21
+ end
22
+
8
23
  def format(payload)
9
24
  [
10
25
  "#{payload[:name]}: #{payload[:klass]}",
@@ -12,6 +27,18 @@ module Appsignal
12
27
  ]
13
28
  end
14
29
 
30
+ # The index a search ran against, which the notification carries in the
31
+ # search it describes. A search that names more than one index, or none
32
+ # at all, is left without this attribute rather than described with a
33
+ # value that is not an index name.
34
+ def search_index(payload)
35
+ search = payload[:search]
36
+ return unless search.respond_to?(:[])
37
+
38
+ index = search[:index]
39
+ index if index.is_a?(String)
40
+ end
41
+
15
42
  def sanitized_search(search)
16
43
  return unless search.is_a?(Hash)
17
44
 
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ class EventFormatter
5
+ # Registered for an event that a dedicated AppSignal integration already
6
+ # records with richer semantics. The generic instrumentation paths ask the
7
+ # registry whether to record an event, so registering this for an event
8
+ # name is how that integration claims it.
9
+ #
10
+ # @!visibility private
11
+ class RecordedElsewhere < Appsignal::EventFormatter
12
+ def record?
13
+ false
14
+ end
15
+ end
16
+ end
17
+ end
@@ -5,6 +5,18 @@ module Appsignal
5
5
  # @!visibility private
6
6
  module Rom
7
7
  class SqlFormatter < Appsignal::EventFormatter
8
+ # These events arrive over dry-monitor, which is a notification bus
9
+ # rather than a library that queries a database. ROM is what emits
10
+ # them, so that is what the scope names.
11
+ def opentelemetry_scope
12
+ ["appsignal-ruby/rom", Appsignal::VERSION]
13
+ end
14
+
15
+ # A query is an outgoing call to a datastore.
16
+ def opentelemetry_kind
17
+ :client
18
+ end
19
+
8
20
  # dry-monitor reports an event under an id rather than a name, so the
9
21
  # first value here names the event. Naming it after ROM keeps every ROM
10
22
  # query in one group.
@@ -17,6 +29,18 @@ module Appsignal
17
29
  def format(payload)
18
30
  ["query.rom", payload[:query], SQL_BODY_FORMAT]
19
31
  end
32
+
33
+ # The payload's `name` is Sequel's `database_type` symbol for the
34
+ # database ROM is talking to, so this uses Sequel's own lookup
35
+ # rather than a coincidentally similar one. A symbol the mapping does
36
+ # not recognise is left to the SQL sentinel, same as an engine this
37
+ # gem has never heard of.
38
+ def opentelemetry_attributes(payload)
39
+ name = Appsignal::OpenTelemetry::SqlDbSystem.name_for_sequel(payload[:name])
40
+ return unless name
41
+
42
+ { "db.system.name" => name }
43
+ end
20
44
  end
21
45
  end
22
46
  end
@@ -10,6 +10,11 @@ module Appsignal
10
10
  # formatter the sequel-rails events are recorded without the SQL query
11
11
  # that's being executed.
12
12
  class SqlFormatter < Appsignal::EventFormatter
13
+ # A query is an outgoing call to a datastore.
14
+ def opentelemetry_kind
15
+ :client
16
+ end
17
+
13
18
  def format(payload)
14
19
  [payload[:name].to_s, payload[:sql], SQL_BODY_FORMAT]
15
20
  end
@@ -7,11 +7,28 @@ module Appsignal
7
7
  class RenderFormatter < Appsignal::EventFormatter
8
8
  BLANK = ""
9
9
 
10
+ def opentelemetry_attributes(_payload)
11
+ Appsignal::OpenTelemetry::Rendering.attributes
12
+ end
13
+
10
14
  def format(payload)
15
+ # The body is the component's path made relative to the application's
16
+ # root, so a component rendered outside an application gets no title
17
+ # and no body.
18
+ return unless root_path
19
+
11
20
  [payload[:name], payload[:identifier].sub(root_path, BLANK)]
12
21
  end
13
22
 
23
+ # The application's root, which a component's path is made relative to.
24
+ #
25
+ # Whether there is an application is decided here, when the event is
26
+ # formatted, rather than when this file is loaded. AppSignal can be
27
+ # required before Rails is, and deciding it at load time would leave
28
+ # every component render in the application without a title.
14
29
  def root_path
30
+ return unless defined?(Rails)
31
+
15
32
  @root_path ||= "#{Rails.root}/"
16
33
  end
17
34
  end
@@ -19,13 +36,7 @@ module Appsignal
19
36
  end
20
37
  end
21
38
 
22
- if defined?(Rails)
23
- Appsignal::EventFormatter.register(
24
- "render.view_component",
25
- Appsignal::EventFormatter::ViewComponent::RenderFormatter
26
- )
27
- Appsignal::EventFormatter.register(
28
- "!render.view_component",
29
- Appsignal::EventFormatter::ViewComponent::RenderFormatter
30
- )
31
- end
39
+ Appsignal::EventFormatter.register(
40
+ "render.view_component",
41
+ Appsignal::EventFormatter::ViewComponent::RenderFormatter
42
+ )
@@ -102,8 +102,77 @@ module Appsignal
102
102
  formatter&.format(payload)
103
103
  end
104
104
 
105
+ # The OpenTelemetry span kind for an event, which its formatter can
106
+ # declare. An event with no formatter, or whose formatter declares
107
+ # nothing, has no kind of its own and falls back to the default.
108
+ #
109
+ # A formatter written against the documented interface only implements
110
+ # `format`, so ask whether this one answers to the method at all rather
111
+ # than assuming every formatter does.
112
+ #
113
+ # @!visibility private
114
+ def opentelemetry_kind(name)
115
+ formatter = formatter_for(name)
116
+ return unless formatter.respond_to?(:opentelemetry_kind)
117
+
118
+ formatter.opentelemetry_kind
119
+ end
120
+
121
+ # The OpenTelemetry attributes describing an event, which its formatter
122
+ # can build from the event's payload. An event with no formatter, or
123
+ # whose formatter describes nothing, gets no attributes of its own.
124
+ #
125
+ # @!visibility private
126
+ def opentelemetry_attributes(name, payload)
127
+ formatter = formatter_for(name)
128
+ return unless formatter.respond_to?(:opentelemetry_attributes)
129
+
130
+ formatter.opentelemetry_attributes(payload)
131
+ end
132
+
133
+ # The OpenTelemetry instrumentation scope for an event, which its
134
+ # formatter can declare. It names the library the instrumentation is for,
135
+ # which is not always the library the event arrived through.
136
+ #
137
+ # An event with no formatter, or whose formatter declares nothing, is
138
+ # left to the scope the recording path derives for it.
139
+ #
140
+ # @!visibility private
141
+ def opentelemetry_scope(name)
142
+ formatter = formatter_for(name)
143
+ return unless formatter.respond_to?(:opentelemetry_scope)
144
+
145
+ formatter.opentelemetry_scope
146
+ end
147
+
148
+ # Whether the generic instrumentation paths should record an event, which
149
+ # its formatter can answer. An event a dedicated integration already
150
+ # records says no, so that it is not recorded a second time. An event
151
+ # with no formatter, or whose formatter says nothing, is recorded.
152
+ #
153
+ # @!visibility private
154
+ def record?(name)
155
+ formatter = formatter_for(name)
156
+ return true unless formatter.respond_to?(:record?)
157
+
158
+ formatter.record?
159
+ end
160
+
105
161
  private
106
162
 
163
+ # The formatter registered for an event name.
164
+ #
165
+ # A formatter is registered under whatever key was given to `register`,
166
+ # which is a String for every formatter in this gem. An event can be
167
+ # instrumented under a Symbol name, so fall back to the String form of it.
168
+ #
169
+ # `format` does not do this, on purpose. It has always looked a name up
170
+ # exactly as given, so making it match a Symbol name would start giving a
171
+ # title to events that have never had one.
172
+ def formatter_for(name)
173
+ formatters[name] || formatters[name.to_s]
174
+ end
175
+
107
176
  def initialize_formatter(name, formatter)
108
177
  format_method = formatter.instance_method(:format)
109
178
  if !format_method || format_method.arity != 1
@@ -123,6 +192,15 @@ module Appsignal
123
192
  end
124
193
  end
125
194
 
195
+ # The title and the body to show for an event, as an array. A formatter
196
+ # that is registered to describe an event in some other way, rather than to
197
+ # name it, does not have to implement this.
198
+ #
199
+ # @!visibility private
200
+ def format(_payload)
201
+ nil
202
+ end
203
+
126
204
  # @return [Integer]
127
205
  # @api public
128
206
  DEFAULT = 0
@@ -46,6 +46,10 @@ module Appsignal
46
46
  def data_array_new
47
47
  Appsignal::Extension::MockData.new
48
48
  end
49
+
50
+ def allocation_count
51
+ 0
52
+ end
49
53
  end
50
54
  end
51
55