appsignal 3.10.0 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +88 -0
  4. data/Gemfile +1 -0
  5. data/benchmark.rake +99 -42
  6. data/lib/appsignal/cli/demo.rb +0 -1
  7. data/lib/appsignal/config.rb +54 -98
  8. data/lib/appsignal/demo.rb +15 -20
  9. data/lib/appsignal/event_formatter/rom/sql_formatter.rb +1 -0
  10. data/lib/appsignal/event_formatter.rb +3 -2
  11. data/lib/appsignal/helpers/instrumentation.rb +331 -19
  12. data/lib/appsignal/hooks/action_cable.rb +21 -16
  13. data/lib/appsignal/hooks/active_job.rb +14 -8
  14. data/lib/appsignal/hooks/delayed_job.rb +1 -1
  15. data/lib/appsignal/hooks/shoryuken.rb +3 -63
  16. data/lib/appsignal/integrations/action_cable.rb +5 -7
  17. data/lib/appsignal/integrations/active_support_notifications.rb +1 -0
  18. data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +36 -35
  19. data/lib/appsignal/integrations/data_mapper.rb +1 -0
  20. data/lib/appsignal/integrations/delayed_job_plugin.rb +27 -33
  21. data/lib/appsignal/integrations/dry_monitor.rb +1 -0
  22. data/lib/appsignal/integrations/excon.rb +1 -0
  23. data/lib/appsignal/integrations/http.rb +1 -0
  24. data/lib/appsignal/integrations/net_http.rb +1 -0
  25. data/lib/appsignal/integrations/object.rb +6 -0
  26. data/lib/appsignal/integrations/que.rb +13 -20
  27. data/lib/appsignal/integrations/railtie.rb +1 -1
  28. data/lib/appsignal/integrations/rake.rb +1 -5
  29. data/lib/appsignal/integrations/redis.rb +1 -0
  30. data/lib/appsignal/integrations/redis_client.rb +1 -0
  31. data/lib/appsignal/integrations/resque.rb +2 -5
  32. data/lib/appsignal/integrations/shoryuken.rb +75 -0
  33. data/lib/appsignal/integrations/sidekiq.rb +7 -15
  34. data/lib/appsignal/integrations/unicorn.rb +1 -0
  35. data/lib/appsignal/integrations/webmachine.rb +2 -5
  36. data/lib/appsignal/logger.rb +7 -3
  37. data/lib/appsignal/probes/helpers.rb +1 -0
  38. data/lib/appsignal/probes/mri.rb +1 -0
  39. data/lib/appsignal/probes/sidekiq.rb +1 -0
  40. data/lib/appsignal/probes.rb +3 -0
  41. data/lib/appsignal/rack/abstract_middleware.rb +18 -12
  42. data/lib/appsignal/rack/event_handler.rb +39 -8
  43. data/lib/appsignal/rack/generic_instrumentation.rb +1 -0
  44. data/lib/appsignal/rack/grape_middleware.rb +2 -1
  45. data/lib/appsignal/rack/streaming_listener.rb +1 -0
  46. data/lib/appsignal/rack.rb +29 -0
  47. data/lib/appsignal/span.rb +1 -0
  48. data/lib/appsignal/transaction.rb +308 -101
  49. data/lib/appsignal/utils/data.rb +0 -1
  50. data/lib/appsignal/utils/hash_sanitizer.rb +0 -1
  51. data/lib/appsignal/utils/integration_logger.rb +0 -13
  52. data/lib/appsignal/utils/integration_memory_logger.rb +0 -13
  53. data/lib/appsignal/utils/json.rb +0 -1
  54. data/lib/appsignal/utils/query_params_sanitizer.rb +0 -1
  55. data/lib/appsignal/utils/stdout_and_logger_message.rb +0 -1
  56. data/lib/appsignal/utils.rb +6 -0
  57. data/lib/appsignal/version.rb +1 -1
  58. data/lib/appsignal.rb +6 -5
  59. data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
  60. data/spec/lib/appsignal/config_spec.rb +138 -43
  61. data/spec/lib/appsignal/hooks/action_cable_spec.rb +43 -74
  62. data/spec/lib/appsignal/hooks/activejob_spec.rb +9 -0
  63. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +2 -443
  64. data/spec/lib/appsignal/hooks/shoryuken_spec.rb +0 -171
  65. data/spec/lib/appsignal/integrations/delayed_job_plugin_spec.rb +459 -0
  66. data/spec/lib/appsignal/integrations/que_spec.rb +3 -4
  67. data/spec/lib/appsignal/integrations/shoryuken_spec.rb +167 -0
  68. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +4 -4
  69. data/spec/lib/appsignal/integrations/webmachine_spec.rb +13 -1
  70. data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +48 -3
  71. data/spec/lib/appsignal/rack/event_handler_spec.rb +81 -10
  72. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +4 -2
  73. data/spec/lib/appsignal/rack_spec.rb +63 -0
  74. data/spec/lib/appsignal/transaction_spec.rb +1634 -1071
  75. data/spec/lib/appsignal/utils/integration_logger_spec.rb +12 -16
  76. data/spec/lib/appsignal/utils/integration_memory_logger_spec.rb +0 -10
  77. data/spec/lib/appsignal_spec.rb +323 -10
  78. data/spec/support/helpers/transaction_helpers.rb +44 -20
  79. data/spec/support/matchers/transaction.rb +15 -1
  80. data/spec/support/testing.rb +1 -1
  81. metadata +6 -2
@@ -5,6 +5,161 @@ module Appsignal
5
5
  module Instrumentation
6
6
  include Appsignal::Utils::StdoutAndLoggerMessage
7
7
 
8
+ # Monitor a block of code with AppSignal.
9
+ #
10
+ # This is a helper to create an AppSignal transaction, track any errors
11
+ # that may occur and complete the transaction.
12
+ #
13
+ # This helper is recommended to be used in Ruby scripts and parts of an
14
+ # app not already instrumented by AppSignal's automatic instrumentations.
15
+ #
16
+ # Use this helper in combination with our {.instrument} helper to track
17
+ # instrumentation events.
18
+ #
19
+ # If AppSignal is not active ({Appsignal.active?}) it will still execute
20
+ # the block, but not create a transaction for it.
21
+ #
22
+ # @example Instrument a block of code
23
+ # Appsignal.monitor(
24
+ # :namespace => "my_namespace",
25
+ # :action => "MyClass#my_method"
26
+ # ) do
27
+ # # Some code
28
+ # end
29
+ #
30
+ # @example Instrument a block of code using the default namespace
31
+ # Appsignal.monitor(
32
+ # :action => "MyClass#my_method"
33
+ # ) do
34
+ # # Some code
35
+ # end
36
+ #
37
+ # @example Instrument a block of code with an instrumentation event
38
+ # Appsignal.monitor(
39
+ # :namespace => "my_namespace",
40
+ # :action => "MyClass#my_method"
41
+ # ) do
42
+ # Appsignal.instrument("some_event.some_group") do
43
+ # # Some code
44
+ # end
45
+ # end
46
+ #
47
+ # @example Set the action name in the monitor block
48
+ # Appsignal.monitor(
49
+ # :action => nil
50
+ # ) do
51
+ # # Some code
52
+ #
53
+ # Appsignal.set_action("GET /resource/:id")
54
+ # end
55
+ #
56
+ # @example Set the action name in the monitor block
57
+ # Appsignal.monitor(
58
+ # :action => :set_later # Explicit placeholder
59
+ # ) do
60
+ # # Some code
61
+ #
62
+ # Appsignal.set_action("GET /resource/:id")
63
+ # end
64
+ #
65
+ # @example Set custom metadata on the transaction
66
+ # Appsignal.monitor(
67
+ # :namespace => "my_namespace",
68
+ # :action => "MyClass#my_method"
69
+ # ) do
70
+ # # Some code
71
+ #
72
+ # Appsignal.set_tags(:tag1 => "value1", :tag2 => "value2")
73
+ # Appsignal.set_params(:param1 => "value1", :param2 => "value2")
74
+ # end
75
+ #
76
+ # @example Call monitor within monitor will do nothing
77
+ # Appsignal.monitor(
78
+ # :namespace => "my_namespace",
79
+ # :action => "MyClass#my_method"
80
+ # ) do
81
+ # # This will _not_ update the namespace and action name
82
+ # Appsignal.monitor(
83
+ # :namespace => "my_other_namespace",
84
+ # :action => "MyOtherClass#my_other_method"
85
+ # ) do
86
+ # # Some code
87
+ #
88
+ # # The reported namespace will be "my_namespace"
89
+ # # The reported action will be "MyClass#my_method"
90
+ # end
91
+ # end
92
+ #
93
+ # @param namespace [String/Symbol] The namespace to set on the new
94
+ # transaction.
95
+ # Defaults to the 'web' namespace.
96
+ # This will not update the active transaction's namespace if
97
+ # {.monitor} is called when another transaction is already active.
98
+ # @param action [String, Symbol, NilClass]
99
+ # The action name for the transaction.
100
+ # The action name is required to be set for the transaction to be
101
+ # reported.
102
+ # The argument can be set to `nil` or `:set_later` if the action is set
103
+ # within the block with {#set_action}.
104
+ # This will not update the active transaction's action if
105
+ # {.monitor} is called when another transaction is already active.
106
+ # @yield The block to monitor.
107
+ # @raise [Exception] Any exception that occurs within the given block is
108
+ # re-raised by this method.
109
+ # @return [Object] The value of the given block is returned.
110
+ # @since 3.11.0
111
+ def monitor(
112
+ action:, namespace: nil
113
+ )
114
+ return yield unless active?
115
+
116
+ has_parent_transaction = Appsignal::Transaction.current?
117
+ if has_parent_transaction
118
+ callers = caller
119
+ Appsignal::Utils::StdoutAndLoggerMessage.warning \
120
+ "An active transaction around this 'Appsignal.monitor' call. " \
121
+ "Calling `Appsignal.monitor` in another `Appsignal.monitor` block has no effect. " \
122
+ "The namespace and action are not updated for the active transaction." \
123
+ "Did you mean to use `Appsignal.instrument`? " \
124
+ "Update the 'Appsignal.monitor' call in: #{callers.first}"
125
+ return yield if block_given?
126
+
127
+ return
128
+ end
129
+
130
+ transaction =
131
+ if has_parent_transaction
132
+ Appsignal::Transaction.current
133
+ else
134
+ Appsignal::Transaction.create(namespace || Appsignal::Transaction::HTTP_REQUEST)
135
+ end
136
+
137
+ begin
138
+ yield if block_given?
139
+ rescue Exception => error # rubocop:disable Lint/RescueException
140
+ transaction.set_error(error)
141
+ raise error
142
+ ensure
143
+ transaction.set_action_if_nil(action.to_s) if action && action != :set_later
144
+ Appsignal::Transaction.complete_current!
145
+ end
146
+ end
147
+
148
+ # Instrument a block of code and stop AppSignal.
149
+ #
150
+ # Useful for cases such as one-off scripts where there is no long running
151
+ # process active and the data needs to be sent after the process exists.
152
+ #
153
+ # Acts the same way as {.monitor}. See that method for more
154
+ # documentation.
155
+ #
156
+ # @see monitor
157
+ def monitor_and_stop(action:, namespace: nil)
158
+ monitor(:namespace => namespace, :action => action)
159
+ ensure
160
+ Appsignal.stop("monitor_and_stop")
161
+ end
162
+
8
163
  # Creates an AppSignal transaction for the given block.
9
164
  #
10
165
  # If AppSignal is not {Appsignal.active?} it will still execute the
@@ -58,12 +213,22 @@ module Appsignal
58
213
  # @return [Object] the value of the given block is returned.
59
214
  # @since 0.10.0
60
215
  def monitor_transaction(name, env = {}, &block)
216
+ stdout_and_logger_warning \
217
+ "The `Appsignal.monitor_transaction` helper is deprecated. " \
218
+ "Please use `Appsignal.monitor` and `Appsignal.instrument` instead. " \
219
+ "Read our instrumentation documentation: " \
220
+ "https://docs.appsignal.com/ruby/instrumentation/instrumentation.html"
221
+ _monitor_transaction(name, env, &block)
222
+ end
223
+
224
+ # @api private
225
+ def _monitor_transaction(name, env = {}, &block)
61
226
  # Always verify input, even when Appsignal is not active.
62
227
  # This makes it more likely invalid arguments get flagged in test/dev
63
228
  # environments.
64
229
  if name.start_with?("perform_job")
65
230
  namespace = Appsignal::Transaction::BACKGROUND_JOB
66
- request = Appsignal::Transaction::GenericRequest.new(env)
231
+ request = Appsignal::Transaction::InternalGenericRequest.new(env)
67
232
  elsif name.start_with?("process_action")
68
233
  namespace = Appsignal::Transaction::HTTP_REQUEST
69
234
  request = ::Rack::Request.new(env)
@@ -88,7 +253,9 @@ module Appsignal
88
253
  raise error
89
254
  ensure
90
255
  transaction.set_http_or_background_action(request.env)
91
- transaction.set_http_or_background_queue_start
256
+ queue_start = Appsignal::Rack::Utils.queue_start_from(request.env) ||
257
+ (env[:queue_start]&.to_i&.* 1_000)
258
+ transaction.set_queue_start(queue_start) if queue_start
92
259
  Appsignal::Transaction.complete_current!
93
260
  end
94
261
  end
@@ -101,6 +268,11 @@ module Appsignal
101
268
  #
102
269
  # @see monitor_transaction
103
270
  def monitor_single_transaction(name, env = {}, &block)
271
+ stdout_and_logger_warning \
272
+ "The `Appsignal.monitor_single_transaction` helper is deprecated. " \
273
+ "Please use `Appsignal.monitor_and_stop` and `Appsignal.instrument` instead. " \
274
+ "Read our instrumentation documentation: " \
275
+ "https://docs.appsignal.com/ruby/instrumentation/instrumentation.html"
104
276
  monitor_transaction(name, env, &block)
105
277
  ensure
106
278
  stop("monitor_single_transaction")
@@ -235,8 +407,7 @@ module Appsignal
235
407
  end
236
408
  transaction = Appsignal::Transaction.new(
237
409
  SecureRandom.uuid,
238
- namespace || Appsignal::Transaction::HTTP_REQUEST,
239
- Appsignal::Transaction::GenericRequest.new({})
410
+ namespace || Appsignal::Transaction::HTTP_REQUEST
240
411
  )
241
412
  transaction.set_tags(tags) if tags
242
413
  transaction.set_error(error)
@@ -390,8 +561,7 @@ module Appsignal
390
561
  else
391
562
  Appsignal::Transaction.new(
392
563
  SecureRandom.uuid,
393
- Appsignal::Transaction::HTTP_REQUEST,
394
- Appsignal::Transaction::GenericRequest.new({})
564
+ Appsignal::Transaction::HTTP_REQUEST
395
565
  )
396
566
  end
397
567
 
@@ -565,8 +735,8 @@ module Appsignal
565
735
  # A block can be given to this method to defer the fetching and parsing
566
736
  # of the parameters until and only when the transaction is sampled.
567
737
  #
568
- # When both the `params` and a block is given to this method, the
569
- # `params` argument is leading and the block will _not_ be called.
738
+ # When both the `params` argument and a block is given to this method,
739
+ # the `params` argument is leading and the block will _not_ be called.
570
740
  #
571
741
  # @example Set parameters
572
742
  # Appsignal.set_params("param1" => "value1")
@@ -608,6 +778,125 @@ module Appsignal
608
778
  transaction.set_params(params, &block)
609
779
  end
610
780
 
781
+ # Set session data on the current transaction.
782
+ #
783
+ # Session data is automatically set by most of our integrations. It
784
+ # should not be necessary to call this method unless you want to report
785
+ # different session data.
786
+ #
787
+ # To filter session data, see our session data filtering guide.
788
+ #
789
+ # When this method is called multiple times, it will overwrite the
790
+ # previously set value.
791
+ #
792
+ # A block can be given to this method to defer the fetching and parsing
793
+ # of the session data until and only when the transaction is sampled.
794
+ #
795
+ # When both the `session_data` argument and a block is given to this
796
+ # method, the `session_data` argument is leading and the block will _not_
797
+ # be called.
798
+ #
799
+ # @example Set session data
800
+ # Appsignal.set_session_data("data" => "value")
801
+ #
802
+ # @example Calling `set_session_data` multiple times will only keep the last call
803
+ # Appsignal.set_session_data("data1" => "value1")
804
+ # Appsignal.set_session_data("data2" => "value2")
805
+ # # The session data is: { "data2" => "value2" }
806
+ #
807
+ # @example Calling `set_session_data` with a block
808
+ # Appsignal.set_session_data do
809
+ # # Some slow code to parse session data
810
+ # JSON.parse('{"data": "value"}')
811
+ # end
812
+ # # The session data is: { "data" => "value" }
813
+ #
814
+ # @example Calling `set_session_data` with a session_data argument and a block
815
+ # Appsignal.set_session_data("argument" => "argument value") do
816
+ # # Some slow code to parse session data
817
+ # JSON.parse('{"data": "value"}')
818
+ # end
819
+ # # The session data is: { "argument" => "argument value" }
820
+ #
821
+ # @since 3.11.0
822
+ # @param session_data [Hash] The session data to set on the transaction.
823
+ # @yield This block is called when the transaction is sampled. The block's
824
+ # return value will become the new session data.
825
+ # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
826
+ # Sample data guide
827
+ # @see https://docs.appsignal.com/guides/filter-data/filter-session-data.html
828
+ # Session data filtering guide
829
+ # @see Transaction#set_session_data
830
+ # @return [void]
831
+ def set_session_data(session_data = nil, &block)
832
+ return unless active?
833
+ return unless Appsignal::Transaction.current?
834
+
835
+ transaction = Appsignal::Transaction.current
836
+ transaction.set_session_data(session_data, &block)
837
+ end
838
+
839
+ # Set request headers on the current transaction.
840
+ #
841
+ # Request headers are automatically set by most of our integrations. It
842
+ # should not be necessary to call this method unless you want to report
843
+ # different request headers.
844
+ #
845
+ # To filter request headers, see our session data filtering guide.
846
+ #
847
+ # When this method is called multiple times, it will overwrite the
848
+ # previously set value.
849
+ #
850
+ # A block can be given to this method to defer the fetching and parsing
851
+ # of the request headers until and only when the transaction is sampled.
852
+ #
853
+ # When both the `request_headers` argument and a block is given to this
854
+ # method, the `request_headers` argument is leading and the block will
855
+ # _not_ be called.
856
+ #
857
+ # @example Set request headers
858
+ # Appsignal.set_headers(
859
+ # "PATH_INFO" => "/some-path",
860
+ # "HTTP_USER_AGENT" => "Firefox"
861
+ # )
862
+ #
863
+ # @example Calling `set_headers` multiple times will only keep the last call
864
+ # Appsignal.set_headers("PATH_INFO" => "/some-path")
865
+ # Appsignal.set_headers("HTTP_USER_AGENT" => "Firefox")
866
+ # # The request headers are: { "HTTP_USER_AGENT" => "Firefox" }
867
+ #
868
+ # @example Calling `set_headers` with a block
869
+ # Appsignal.set_headers do
870
+ # # Some slow code to parse request headers
871
+ # JSON.parse('{"PATH_INFO": "/some-path"}')
872
+ # end
873
+ # # The session data is: { "PATH_INFO" => "/some-path" }
874
+ #
875
+ # @example Calling `set_headers` with a headers argument and a block
876
+ # Appsignal.set_headers("PATH_INFO" => "/some-path") do
877
+ # # Some slow code to parse session data
878
+ # JSON.parse('{"PATH_INFO": "/block-path"}')
879
+ # end
880
+ # # The session data is: { "PATH_INFO" => "/some-path" }
881
+ #
882
+ # @since 3.11.0
883
+ # @param headers [Hash] The request headers to set on the transaction.
884
+ # @yield This block is called when the transaction is sampled. The block's
885
+ # return value will become the new request headers.
886
+ # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
887
+ # Sample data guide
888
+ # @see https://docs.appsignal.com/guides/filter-data/filter-headers.html
889
+ # Request headers filtering guide
890
+ # @see Transaction#set_headers
891
+ # @return [void]
892
+ def set_headers(headers = nil, &block)
893
+ return unless active?
894
+ return unless Appsignal::Transaction.current?
895
+
896
+ transaction = Appsignal::Transaction.current
897
+ transaction.set_headers(headers, &block)
898
+ end
899
+
611
900
  # Add breadcrumbs to the transaction.
612
901
  #
613
902
  # Breadcrumbs can be used to trace what path a user has taken
@@ -699,14 +988,11 @@ module Appsignal
699
988
  name,
700
989
  title = nil,
701
990
  body = nil,
702
- body_format = Appsignal::EventFormatter::DEFAULT
991
+ body_format = Appsignal::EventFormatter::DEFAULT,
992
+ &block
703
993
  )
704
- Appsignal::Transaction.current.start_event
705
- yield if block_given?
706
- ensure
707
- Appsignal::Transaction
708
- .current
709
- .finish_event(name, title, body, body_format)
994
+ Appsignal::Transaction.current
995
+ .instrument(name, title, body, body_format, &block)
710
996
  end
711
997
 
712
998
  # Instrumentation helper for SQL queries.
@@ -749,22 +1035,48 @@ module Appsignal
749
1035
  )
750
1036
  end
751
1037
 
752
- # Convenience method for skipping instrumentations around a block of code.
1038
+ # Convenience method for ignoring instrumentation events in a block of
1039
+ # code.
1040
+ #
1041
+ # - This helper ignores events, like those created
1042
+ # `Appsignal.instrument`, within this block.
1043
+ # This includes custom instrumentation and events recorded by AppSignal
1044
+ # integrations for requests, database queries, view rendering, etc.
1045
+ # - The time spent in the block is still reported on the transaction.
1046
+ # - Errors and metrics are reported from within this block.
753
1047
  #
754
1048
  # @example
755
- # Appsignal.without_instrumentation do
1049
+ # Appsignal.instrument "my_event.my_group" do
756
1050
  # # Complex code here
757
1051
  # end
1052
+ # Appsignal.ignore_instrumentation_events do
1053
+ # Appsignal.instrument "my_ignored_event.my_ignored_group" do
1054
+ # # Complex code here
1055
+ # end
1056
+ # end
1057
+ #
1058
+ # # Only the "my_event.my_group" instrumentation event is reported.
758
1059
  #
759
1060
  # @yield block of code that shouldn't be instrumented.
760
1061
  # @return [Object] Returns the return value of the block.
761
- # @since 0.8.7
762
- def without_instrumentation
1062
+ # @since 3.10.0
1063
+ # @see https://docs.appsignal.com/ruby/instrumentation/ignore-instrumentation.html
1064
+ # Ignore instrumentation guide
1065
+ def ignore_instrumentation_events
763
1066
  Appsignal::Transaction.current&.pause!
764
1067
  yield
765
1068
  ensure
766
1069
  Appsignal::Transaction.current&.resume!
767
1070
  end
1071
+
1072
+ # @deprecated Use {.ignore_instrumentation_events} instead.
1073
+ # @since 0.8.7
1074
+ def without_instrumentation(&block)
1075
+ stdout_and_logger_warning \
1076
+ "The `Appsignal.without_instrumentation` helper is deprecated. " \
1077
+ "Please use `Appsignal.ignore_instrumentation_events` instead."
1078
+ ignore_instrumentation_events(&block)
1079
+ end
768
1080
  end
769
1081
  end
770
1082
  end
@@ -17,12 +17,13 @@ module Appsignal
17
17
  require "appsignal/integrations/action_cable"
18
18
  ActionCable::Channel::Base.prepend Appsignal::Integrations::ActionCableIntegration
19
19
 
20
- install_callbacks
20
+ install_subscribe_callback
21
+ install_unsubscribe_callback
21
22
  end
22
23
 
23
24
  private
24
25
 
25
- def install_callbacks
26
+ def install_subscribe_callback
26
27
  ActionCable::Channel::Base.set_callback :subscribe, :around,
27
28
  :prepend => true do |channel, inner|
28
29
  # The request is only the original websocket request
@@ -32,14 +33,11 @@ module Appsignal
32
33
  # in apps' test suites.
33
34
  env = connection.respond_to?(:env) ? connection.env : {}
34
35
  request = ActionDispatch::Request.new(env)
35
- env[Appsignal::Hooks::ActionCableHook::REQUEST_ID] ||=
36
- request.request_id || SecureRandom.uuid
36
+ request_id = request.request_id || SecureRandom.uuid
37
+ env[Appsignal::Hooks::ActionCableHook::REQUEST_ID] ||= request_id
37
38
 
38
- transaction = Appsignal::Transaction.create(
39
- env[Appsignal::Hooks::ActionCableHook::REQUEST_ID],
40
- Appsignal::Transaction::ACTION_CABLE,
41
- request
42
- )
39
+ transaction =
40
+ Appsignal::Transaction.create(Appsignal::Transaction::ACTION_CABLE)
43
41
 
44
42
  begin
45
43
  Appsignal.instrument "subscribed.action_cable" do
@@ -52,10 +50,16 @@ module Appsignal
52
50
  transaction.set_action_if_nil("#{channel.class}#subscribed")
53
51
  transaction.set_metadata("path", request.path)
54
52
  transaction.set_metadata("method", "websocket")
53
+ transaction.set_params_if_nil { request.params }
54
+ transaction.set_headers_if_nil { request.env }
55
+ transaction.set_session_data { request.session if request.respond_to? :session }
56
+ transaction.set_tags(:request_id => request_id) if request_id
55
57
  Appsignal::Transaction.complete_current!
56
58
  end
57
59
  end
60
+ end
58
61
 
62
+ def install_unsubscribe_callback
59
63
  ActionCable::Channel::Base.set_callback :unsubscribe, :around,
60
64
  :prepend => true do |channel, inner|
61
65
  # The request is only the original websocket request
@@ -65,14 +69,11 @@ module Appsignal
65
69
  # in apps' test suites.
66
70
  env = connection.respond_to?(:env) ? connection.env : {}
67
71
  request = ActionDispatch::Request.new(env)
68
- env[Appsignal::Hooks::ActionCableHook::REQUEST_ID] ||=
69
- request.request_id || SecureRandom.uuid
72
+ request_id = request.request_id || SecureRandom.uuid
73
+ env[Appsignal::Hooks::ActionCableHook::REQUEST_ID] ||= request_id
70
74
 
71
- transaction = Appsignal::Transaction.create(
72
- env[Appsignal::Hooks::ActionCableHook::REQUEST_ID],
73
- Appsignal::Transaction::ACTION_CABLE,
74
- request
75
- )
75
+ transaction =
76
+ Appsignal::Transaction.create(Appsignal::Transaction::ACTION_CABLE)
76
77
 
77
78
  begin
78
79
  Appsignal.instrument "unsubscribed.action_cable" do
@@ -85,6 +86,10 @@ module Appsignal
85
86
  transaction.set_action_if_nil("#{channel.class}#unsubscribed")
86
87
  transaction.set_metadata("path", request.path)
87
88
  transaction.set_metadata("method", "websocket")
89
+ transaction.set_params_if_nil { request.params }
90
+ transaction.set_headers_if_nil { request.env }
91
+ transaction.set_session_data { request.session if request.respond_to? :session }
92
+ transaction.set_tags(:request_id => request_id) if request_id
88
93
  Appsignal::Transaction.complete_current!
89
94
  end
90
95
  end
@@ -54,20 +54,13 @@ module Appsignal
54
54
  # we do for Sidekiq.
55
55
  #
56
56
  # Prefer job_id from provider, instead of ActiveJob's internal ID.
57
- Appsignal::Transaction.create(
58
- job["provider_job_id"] || job["job_id"],
59
- Appsignal::Transaction::BACKGROUND_JOB,
60
- Appsignal::Transaction::GenericRequest.new({})
61
- )
57
+ Appsignal::Transaction.create(Appsignal::Transaction::BACKGROUND_JOB)
62
58
  end
63
59
 
64
60
  if transaction
65
61
  transaction.set_params_if_nil(job["arguments"])
66
62
 
67
63
  transaction_tags = ActiveJobHelpers.transaction_tags_for(job)
68
- transaction_tags["active_job_id"] = job["job_id"]
69
- provider_job_id = job["provider_job_id"]
70
- transaction_tags[:provider_job_id] = provider_job_id if provider_job_id
71
64
  transaction.set_tags(transaction_tags)
72
65
 
73
66
  transaction.set_action(ActiveJobHelpers.action_name(job))
@@ -154,12 +147,25 @@ module Appsignal
154
147
 
155
148
  def self.transaction_tags_for(job)
156
149
  tags = {}
150
+
157
151
  queue = job["queue_name"]
158
152
  tags[:queue] = queue if queue
153
+
159
154
  priority = job["priority"]
160
155
  tags[:priority] = priority if priority
156
+
161
157
  executions = job["executions"]
162
158
  tags[:executions] = executions.to_i + 1 if executions
159
+
160
+ job_id = job["job_id"]
161
+ tags[:active_job_id] = job_id
162
+
163
+ provider_job_id = job["provider_job_id"]
164
+ tags[:provider_job_id] = provider_job_id if provider_job_id
165
+
166
+ request_id = provider_job_id || job_id
167
+ tags[:request_id] = request_id if request_id
168
+
163
169
  tags
164
170
  end
165
171
 
@@ -14,7 +14,7 @@ module Appsignal
14
14
  # The DJ plugin is a subclass of Delayed::Plugin, so we can only
15
15
  # require this code if we're actually installing.
16
16
  require "appsignal/integrations/delayed_job_plugin"
17
- ::Delayed::Worker.plugins << Appsignal::Hooks::DelayedJobPlugin
17
+ ::Delayed::Worker.plugins << Appsignal::Integrations::DelayedJobPlugin
18
18
  end
19
19
  end
20
20
  end
@@ -3,68 +3,6 @@
3
3
  module Appsignal
4
4
  class Hooks
5
5
  # @api private
6
- class ShoryukenMiddleware
7
- def call(worker_instance, queue, sqs_msg, body, &block)
8
- batch = sqs_msg.is_a?(Array)
9
- attributes =
10
- if batch
11
- # We can't instrument batched message separately, the `yield` will
12
- # perform all the batched messages.
13
- # To provide somewhat useful metadata, Get first message based on
14
- # SentTimestamp, and use its attributes as metadata for the
15
- # transaction. We can't combine them all because then they would
16
- # overwrite each other and the last message (in an sorted order)
17
- # would be used as the source of the metadata. With the
18
- # oldest/first message at least some useful information is stored
19
- # such as the first received time and the number of retries for the
20
- # first message. The newer message should have lower values and
21
- # timestamps in their metadata.
22
- first_msg = sqs_msg.min do |a, b|
23
- a.attributes["SentTimestamp"].to_i <=> b.attributes["SentTimestamp"].to_i
24
- end
25
- # Add batch => true metadata so people can recognize when a
26
- # transaction is about a batch of messages.
27
- first_msg.attributes.merge(:batch => true)
28
- else
29
- sqs_msg.attributes.merge(:message_id => sqs_msg.message_id)
30
- end
31
- metadata = { :queue => queue }.merge(attributes)
32
- options = {
33
- :class => worker_instance.class.name,
34
- :method => "perform",
35
- :metadata => metadata
36
- }
37
-
38
- args =
39
- if batch
40
- bodies = {}
41
- sqs_msg.each_with_index do |msg, index|
42
- # Store all separate bodies on a hash with the key being the
43
- # message_id
44
- bodies[msg.message_id] = body[index]
45
- end
46
- bodies
47
- else
48
- case body
49
- when Hash
50
- body
51
- else
52
- { :params => body }
53
- end
54
- end
55
- options[:params] = Appsignal::Utils::HashSanitizer.sanitize(
56
- args,
57
- Appsignal.config[:filter_parameters]
58
- )
59
-
60
- if attributes.key?("SentTimestamp")
61
- options[:queue_start] = Time.at(attributes["SentTimestamp"].to_i / 1000)
62
- end
63
-
64
- Appsignal.monitor_transaction("perform_job.shoryuken", options, &block)
65
- end
66
- end
67
-
68
6
  class ShoryukenHook < Appsignal::Hooks::Hook
69
7
  register :shoryuken
70
8
 
@@ -73,9 +11,11 @@ module Appsignal
73
11
  end
74
12
 
75
13
  def install
14
+ require "appsignal/integrations/shoryuken"
15
+
76
16
  ::Shoryuken.configure_server do |config|
77
17
  config.server_middleware do |chain|
78
- chain.add Appsignal::Hooks::ShoryukenMiddleware
18
+ chain.add Appsignal::Integrations::ShoryukenMiddleware
79
19
  end
80
20
  end
81
21
  end
@@ -2,19 +2,16 @@
2
2
 
3
3
  module Appsignal
4
4
  module Integrations
5
+ # @api private
5
6
  module ActionCableIntegration
6
7
  def perform_action(*args, &block)
7
8
  # The request is only the original websocket request
8
9
  env = connection.env
9
10
  request = ActionDispatch::Request.new(env)
10
- env[Appsignal::Hooks::ActionCableHook::REQUEST_ID] ||=
11
- request.request_id || SecureRandom.uuid
11
+ request_id = request.request_id || SecureRandom.uuid
12
+ env[Appsignal::Hooks::ActionCableHook::REQUEST_ID] ||= request_id
12
13
 
13
- transaction = Appsignal::Transaction.create(
14
- env[Appsignal::Hooks::ActionCableHook::REQUEST_ID],
15
- Appsignal::Transaction::ACTION_CABLE,
16
- request
17
- )
14
+ transaction = Appsignal::Transaction.create(Appsignal::Transaction::ACTION_CABLE)
18
15
 
19
16
  begin
20
17
  super
@@ -26,6 +23,7 @@ module Appsignal
26
23
  transaction.set_action_if_nil("#{self.class}##{args.first["action"]}")
27
24
  transaction.set_metadata("path", request.path)
28
25
  transaction.set_metadata("method", "websocket")
26
+ transaction.set_tags(:request_id => request_id) if request_id
29
27
  Appsignal::Transaction.complete_current!
30
28
  end
31
29
  end