appsignal 3.4.4-java → 3.4.6-java

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d424b2fb153945a0ead961e5c02ec4ee58bababe8135403c49c990ba2f2eb1f1
4
- data.tar.gz: b633f608930f213d0b83e0903334e96a17fa19fa2c7aff6d2485db1b9aa0d648
3
+ metadata.gz: af3d35d071f1564b0cbdaf41dbc8d28578aa4bef90ac880a3731b227fb067643
4
+ data.tar.gz: 69d319fa4870d64c3e54bb78f12c643934bb0a17c20145dcc373b52defcea554
5
5
  SHA512:
6
- metadata.gz: a313ef38d4cee93e9c0b8dfe503d2bf4dae2410b69fd8d6f78210fe1779d7d9644bc973ebcc000ae61691faacd89c56853862b8b1970dc6c8d69026ed8263f55
7
- data.tar.gz: 87aac29579b855242d002fd12645716879af8eb4c8ec2c4afe79227a3225923b9e44a5a85f701add17bcdba74a6fadadc78b0f501ec9f42e4acbd1ae936ee51a
6
+ metadata.gz: a977d7886759656795f443e7488420708c54e1086701f55a3d17aec8f22c1a50a54068b2a63b0e804433913f79aee7927e02bd97890cb055ef0abd7015ece32f
7
+ data.tar.gz: e23e971908c575908983bd7f16ed6321f559799af5e6442766208532a117722dc51d245b11d723a08a22e348b18cbec6f2911f52abe598df87be24cc2aea1743
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # AppSignal for Ruby gem Changelog
2
2
 
3
+ ## 3.4.6
4
+
5
+ ### Changed
6
+
7
+ - [85c155a0](https://github.com/appsignal/appsignal-ruby/commit/85c155a0a4b2b618c04db52c34ee7f0adba8f3c5) patch - When sanitizing an array or hash, replace recursively nested values with a placeholder string. This fixes a SystemStackError issue when sanitising arrays and hashes.
8
+
9
+ ## 3.4.5
10
+
11
+ ### Added
12
+
13
+ - [e5e79d9a](https://github.com/appsignal/appsignal-ruby/commit/e5e79d9aa17006a6995e9ea18fabdc14a2356c82) patch - Add `filter_metadata` config option to filter metadata set on Transactions set by default. Metadata like `path`, (request) `method`, `request_id`, `hostname`, etc. This can be useful if there's PII or other sensitive data in any of the app's metadata.
14
+
15
+ ### Fixed
16
+
17
+ - [5a4797c8](https://github.com/appsignal/appsignal-ruby/commit/5a4797c8560c2d1e60b4f1a750136c906505746c) patch - Fix Sinatra request custom request parameters method. If the Sinatra option `params_method` is set, a different method than `params` will be called on the request object to fetch the request parameters. This can be used to add custom filtering to parameters recorded by AppSignal.
18
+ - [9cdee8aa](https://github.com/appsignal/appsignal-ruby/commit/9cdee8aae3cb7b969583493440469ac0dfea764f) patch - Log error when the argument type of the breadcrumb metadata is invalid. This metadata argument should be a Hash, and other values are not supported. More information can be found in the [Ruby gem breadcrumb documentation](https://docs.appsignal.com/ruby/instrumentation/breadcrumbs.html).
19
+
20
+ ```ruby
21
+ Appsignal.add_breadcrumb(
22
+ "breadcrumb category",
23
+ "breadcrumb action",
24
+ "some message",
25
+ { :metadata_key => "some value" } # This needs to be a Hash object
26
+ )
27
+ ```
28
+
3
29
  ## 3.4.4
4
30
 
5
31
  ### Fixed
@@ -24,6 +24,7 @@ module Appsignal
24
24
  :enable_rails_error_reporter => true,
25
25
  :endpoint => "https://push.appsignal.com",
26
26
  :files_world_accessible => true,
27
+ :filter_metadata => [],
27
28
  :filter_parameters => [],
28
29
  :filter_session_data => [],
29
30
  :ignore_actions => [],
@@ -77,6 +78,7 @@ module Appsignal
77
78
  "APPSIGNAL_ENABLE_GVL_WAITING_THREADS" => :enable_gvl_waiting_threads,
78
79
  "APPSIGNAL_ENABLE_RAILS_ERROR_REPORTER" => :enable_rails_error_reporter,
79
80
  "APPSIGNAL_FILES_WORLD_ACCESSIBLE" => :files_world_accessible,
81
+ "APPSIGNAL_FILTER_METADATA" => :filter_metadata,
80
82
  "APPSIGNAL_FILTER_PARAMETERS" => :filter_parameters,
81
83
  "APPSIGNAL_FILTER_SESSION_DATA" => :filter_session_data,
82
84
  "APPSIGNAL_HOSTNAME" => :hostname,
@@ -150,6 +152,7 @@ module Appsignal
150
152
  # @api private
151
153
  ENV_ARRAY_KEYS = %w[
152
154
  APPSIGNAL_DNS_SERVERS
155
+ APPSIGNAL_FILTER_METADATA
153
156
  APPSIGNAL_FILTER_PARAMETERS
154
157
  APPSIGNAL_FILTER_SESSION_DATA
155
158
  APPSIGNAL_IGNORE_ACTIONS
@@ -47,13 +47,14 @@ module Appsignal
47
47
  end
48
48
 
49
49
  def call_with_appsignal_monitoring(env)
50
- env[:params_method] = @options[:params_method] if @options[:params_method]
50
+ options = { :force => @options.include?(:force) && @options[:force] }
51
+ options.merge!(:params_method => @options[:params_method]) if @options[:params_method]
51
52
  request = @options.fetch(:request_class, Sinatra::Request).new(env)
52
53
  transaction = Appsignal::Transaction.create(
53
54
  SecureRandom.uuid,
54
55
  Appsignal::Transaction::HTTP_REQUEST,
55
56
  request,
56
- :force => @options.include?(:force) && @options[:force]
57
+ options
57
58
  )
58
59
  begin
59
60
  Appsignal.instrument("process_action.sinatra") do
@@ -186,6 +186,12 @@ module Appsignal
186
186
  # @see https://docs.appsignal.com/ruby/instrumentation/breadcrumbs.html
187
187
  # Breadcrumb reference
188
188
  def add_breadcrumb(category, action, message = "", metadata = {}, time = Time.now.utc)
189
+ unless metadata.is_a? Hash
190
+ Appsignal.logger.error "add_breadcrumb: Cannot add breadcrumb. " \
191
+ "The given metadata argument is not a Hash."
192
+ return
193
+ end
194
+
189
195
  @breadcrumbs.push(
190
196
  :time => time.to_i,
191
197
  :category => category,
@@ -308,6 +314,7 @@ module Appsignal
308
314
 
309
315
  def set_metadata(key, value)
310
316
  return unless key && value
317
+ return if Appsignal.config[:filter_metadata].include?(key.to_s)
311
318
 
312
319
  @ext.set_metadata(key, value)
313
320
  end
@@ -337,7 +344,7 @@ module Appsignal
337
344
  :params => sanitized_params,
338
345
  :environment => sanitized_environment,
339
346
  :session_data => sanitized_session_data,
340
- :metadata => metadata,
347
+ :metadata => sanitized_metadata,
341
348
  :tags => sanitized_tags,
342
349
  :breadcrumbs => breadcrumbs
343
350
  }.each do |key, data|
@@ -522,12 +529,17 @@ module Appsignal
522
529
  )
523
530
  end
524
531
 
525
- # Returns metadata from the environment.
532
+ # Returns sanitized metadata set by {#set_metadata} and from the
533
+ # {#environment}.
526
534
  #
527
- # @return [nil] if no `:metadata` key is present in the {#environment}.
528
535
  # @return [Hash<String, Object>]
529
- def metadata
530
- environment[:metadata]
536
+ def sanitized_metadata
537
+ metadata = environment[:metadata]
538
+ return unless metadata
539
+
540
+ metadata
541
+ .transform_keys(&:to_s)
542
+ .except(*Appsignal.config[:filter_metadata])
531
543
  end
532
544
 
533
545
  # Returns the environment for a transaction.
@@ -5,20 +5,21 @@ module Appsignal
5
5
  # @api private
6
6
  class HashSanitizer
7
7
  FILTERED = "[FILTERED]"
8
+ RECURSIVE = "[RECURSIVE VALUE]"
8
9
 
9
10
  class << self
10
11
  def sanitize(value, filter_keys = [])
11
- sanitize_value(value, filter_keys)
12
+ sanitize_value(value, filter_keys, [])
12
13
  end
13
14
 
14
15
  private
15
16
 
16
- def sanitize_value(value, filter_keys)
17
+ def sanitize_value(value, filter_keys, seen)
17
18
  case value
18
19
  when Hash
19
- sanitize_hash(value, filter_keys)
20
+ sanitize_hash(value, filter_keys, seen)
20
21
  when Array
21
- sanitize_array(value, filter_keys)
22
+ sanitize_array(value, filter_keys, seen)
22
23
  when TrueClass, FalseClass, NilClass, Integer, String, Symbol, Float
23
24
  unmodified(value)
24
25
  else
@@ -26,23 +27,34 @@ module Appsignal
26
27
  end
27
28
  end
28
29
 
29
- def sanitize_hash(source, filter_keys)
30
+ def sanitize_hash(source, filter_keys, seen)
31
+ seen = seen.clone << source.object_id
32
+
30
33
  {}.tap do |hash|
31
34
  source.each_pair do |key, value|
32
35
  hash[key] =
33
- if filter_keys.include?(key.to_s)
36
+ if seen.include?(value.object_id)
37
+ RECURSIVE
38
+ elsif filter_keys.include?(key.to_s)
34
39
  FILTERED
35
40
  else
36
- sanitize_value(value, filter_keys)
41
+ sanitize_value(value, filter_keys, seen)
37
42
  end
38
43
  end
39
44
  end
40
45
  end
41
46
 
42
- def sanitize_array(source, filter_keys)
47
+ def sanitize_array(source, filter_keys, seen)
48
+ seen = seen.clone << source.object_id
49
+
43
50
  [].tap do |array|
44
51
  source.each_with_index do |item, index|
45
- array[index] = sanitize_value(item, filter_keys)
52
+ array[index] =
53
+ if seen.include?(item.object_id)
54
+ RECURSIVE
55
+ else
56
+ sanitize_value(item, filter_keys, seen)
57
+ end
46
58
  end
47
59
  end
48
60
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "3.4.4"
4
+ VERSION = "3.4.6"
5
5
  end
@@ -165,6 +165,7 @@ describe Appsignal::Config do
165
165
  :enable_rails_error_reporter => true,
166
166
  :endpoint => "https://push.appsignal.com",
167
167
  :files_world_accessible => true,
168
+ :filter_metadata => [],
168
169
  :filter_parameters => [],
169
170
  :filter_session_data => [],
170
171
  :ignore_actions => [],
@@ -1,39 +1,49 @@
1
1
  if DependencyHelper.sinatra_present?
2
2
  require "appsignal/integrations/sinatra"
3
3
 
4
+ module SinatraRequestHelpers
5
+ def make_request(env)
6
+ middleware.call(env)
7
+ end
8
+
9
+ def make_request_with_error(env, error)
10
+ expect { middleware.call(env) }.to raise_error(error)
11
+ end
12
+ end
13
+
4
14
  describe Appsignal::Rack::SinatraInstrumentation do
15
+ include SinatraRequestHelpers
16
+
5
17
  let(:settings) { double(:raise_errors => false) }
6
18
  let(:app) { double(:call => true, :settings => settings) }
7
19
  let(:env) { { "sinatra.route" => "GET /", :path => "/", :method => "GET" } }
8
20
  let(:middleware) { Appsignal::Rack::SinatraInstrumentation.new(app) }
9
21
 
22
+ before(:context) { start_agent }
23
+ around do |example|
24
+ keep_transactions { example.run }
25
+ end
26
+
10
27
  describe "#call" do
11
- before do
12
- start_agent
13
- allow(middleware).to receive(:raw_payload).and_return({})
14
- allow(Appsignal).to receive(:active?).and_return(true)
15
- end
28
+ before { allow(middleware).to receive(:raw_payload).and_return({}) }
16
29
 
17
- it "should call without monitoring" do
18
- expect(Appsignal::Transaction).to_not receive(:create)
30
+ it "doesn't instrument requests" do
31
+ make_request(env)
32
+ expect(created_transactions.count).to eq(0)
19
33
  end
20
-
21
- after { middleware.call(env) }
22
34
  end
23
35
 
24
36
  describe ".settings" do
25
37
  subject { middleware.settings }
26
38
 
27
- it "should return the app's settings" do
39
+ it "returns the app's settings" do
28
40
  expect(subject).to eq(app.settings)
29
41
  end
30
42
  end
31
43
  end
32
44
 
33
45
  describe Appsignal::Rack::SinatraBaseInstrumentation do
34
- before :context do
35
- start_agent
36
- end
46
+ include SinatraRequestHelpers
37
47
 
38
48
  let(:settings) { double(:raise_errors => false) }
39
49
  let(:app) { double(:call => true, :settings => settings) }
@@ -41,11 +51,16 @@ if DependencyHelper.sinatra_present?
41
51
  let(:options) { {} }
42
52
  let(:middleware) { Appsignal::Rack::SinatraBaseInstrumentation.new(app, options) }
43
53
 
54
+ before(:context) { start_agent }
55
+ around do |example|
56
+ keep_transactions { example.run }
57
+ end
58
+
44
59
  describe "#initialize" do
45
60
  context "with no settings method in the Sinatra app" do
46
61
  let(:app) { double(:call => true) }
47
62
 
48
- it "should not raise errors" do
63
+ it "does not raise errors" do
49
64
  expect(middleware.raise_errors_on).to be(false)
50
65
  end
51
66
  end
@@ -53,7 +68,7 @@ if DependencyHelper.sinatra_present?
53
68
  context "with no raise_errors setting in the Sinatra app" do
54
69
  let(:app) { double(:call => true, :settings => double) }
55
70
 
56
- it "should not raise errors" do
71
+ it "does not raise errors" do
57
72
  expect(middleware.raise_errors_on).to be(false)
58
73
  end
59
74
  end
@@ -61,7 +76,7 @@ if DependencyHelper.sinatra_present?
61
76
  context "with raise_errors turned off in the Sinatra app" do
62
77
  let(:app) { double(:call => true, :settings => double(:raise_errors => false)) }
63
78
 
64
- it "should raise errors" do
79
+ it "raises errors" do
65
80
  expect(middleware.raise_errors_on).to be(false)
66
81
  end
67
82
  end
@@ -69,21 +84,17 @@ if DependencyHelper.sinatra_present?
69
84
  context "with raise_errors turned on in the Sinatra app" do
70
85
  let(:app) { double(:call => true, :settings => double(:raise_errors => true)) }
71
86
 
72
- it "should raise errors" do
87
+ it "raises errors" do
73
88
  expect(middleware.raise_errors_on).to be(true)
74
89
  end
75
90
  end
76
91
  end
77
92
 
78
93
  describe "#call" do
79
- before do
80
- allow(middleware).to receive(:raw_payload).and_return({})
81
- end
94
+ before { allow(middleware).to receive(:raw_payload).and_return({}) }
82
95
 
83
96
  context "when appsignal is active" do
84
- before { allow(Appsignal).to receive(:active?).and_return(true) }
85
-
86
- it "should call with monitoring" do
97
+ it "instruments requests" do
87
98
  expect(middleware).to receive(:call_with_appsignal_monitoring).with(env)
88
99
  end
89
100
  end
@@ -91,34 +102,36 @@ if DependencyHelper.sinatra_present?
91
102
  context "when appsignal is not active" do
92
103
  before { allow(Appsignal).to receive(:active?).and_return(false) }
93
104
 
94
- it "should not call with monitoring" do
95
- expect(middleware).to_not receive(:call_with_appsignal_monitoring)
105
+ it "does not instrument requests" do
106
+ expect(created_transactions.count).to eq(0)
96
107
  end
97
108
 
98
- it "should call the stack" do
109
+ it "calls the next middleware in the stack" do
99
110
  expect(app).to receive(:call).with(env)
100
111
  end
101
112
  end
102
113
 
103
- after { middleware.call(env) }
114
+ after { make_request(env) }
104
115
  end
105
116
 
106
- describe "#call_with_appsignal_monitoring", :error => false do
107
- it "should create a transaction" do
108
- expect(Appsignal::Transaction).to receive(:create).with(
109
- kind_of(String),
110
- Appsignal::Transaction::HTTP_REQUEST,
111
- kind_of(Sinatra::Request),
112
- kind_of(Hash)
113
- ).and_return(double(:set_action_if_nil => nil, :set_http_or_background_queue_start => nil,
114
- :set_metadata => nil))
115
- end
117
+ describe "#call_with_appsignal_monitoring" do
118
+ context "without an error" do
119
+ it "creates a transaction" do
120
+ expect(app).to receive(:call).with(env)
121
+
122
+ make_request(env)
116
123
 
117
- it "should call the app" do
118
- expect(app).to receive(:call).with(env)
124
+ expect(created_transactions.count).to eq(1)
125
+ expect(last_transaction.to_h).to include(
126
+ "namespace" => Appsignal::Transaction::HTTP_REQUEST,
127
+ "action" => "GET /",
128
+ "error" => nil,
129
+ "metadata" => { "path" => "" }
130
+ )
131
+ end
119
132
  end
120
133
 
121
- context "with an error", :error => true do
134
+ context "with an error" do
122
135
  let(:error) { ExampleException }
123
136
  let(:app) do
124
137
  double.tap do |d|
@@ -128,23 +141,48 @@ if DependencyHelper.sinatra_present?
128
141
  end
129
142
 
130
143
  it "records the exception" do
131
- expect_any_instance_of(Appsignal::Transaction).to receive(:set_error).with(error)
144
+ make_request_with_error(env, error)
145
+
146
+ expect(created_transactions.count).to eq(1)
147
+ expect(last_transaction.to_h).to include(
148
+ "namespace" => Appsignal::Transaction::HTTP_REQUEST,
149
+ "action" => "GET /",
150
+ "error" => hash_including(
151
+ "name" => "ExampleException",
152
+ "message" => "ExampleException"
153
+ )
154
+ )
132
155
  end
133
156
  end
134
157
 
135
158
  context "with an error in sinatra.error" do
136
- let(:error) { ExampleException }
159
+ let(:error) { ExampleException.new }
137
160
  let(:env) { { "sinatra.error" => error } }
138
161
 
139
- it "records the exception" do
140
- expect_any_instance_of(Appsignal::Transaction).to receive(:set_error).with(error)
162
+ context "when raise_errors is off" do
163
+ let(:settings) { double(:raise_errors => false) }
164
+
165
+ it "record the error" do
166
+ make_request(env)
167
+
168
+ expect(created_transactions.count).to eq(1)
169
+ expect(last_transaction.to_h).to include(
170
+ "error" => hash_including(
171
+ "name" => "ExampleException",
172
+ "message" => "ExampleException"
173
+ )
174
+ )
175
+ end
141
176
  end
142
177
 
143
178
  context "when raise_errors is on" do
144
179
  let(:settings) { double(:raise_errors => true) }
145
180
 
146
181
  it "does not record the error" do
147
- expect_any_instance_of(Appsignal::Transaction).to_not receive(:set_error)
182
+ make_request(env)
183
+
184
+ expect(created_transactions.count).to eq(1)
185
+ expect(last_transaction.to_h).to include("error" => nil)
148
186
  end
149
187
  end
150
188
 
@@ -152,22 +190,30 @@ if DependencyHelper.sinatra_present?
152
190
  let(:env) { { "sinatra.error" => error, "sinatra.skip_appsignal_error" => true } }
153
191
 
154
192
  it "does not record the error" do
155
- expect_any_instance_of(Appsignal::Transaction).to_not receive(:set_error)
193
+ make_request(env)
194
+
195
+ expect(created_transactions.count).to eq(1)
196
+ expect(last_transaction.to_h).to include("error" => nil)
156
197
  end
157
198
  end
158
199
  end
159
200
 
160
201
  describe "action name" do
161
- it "should set the action" do
162
- expect_any_instance_of(Appsignal::Transaction)
163
- .to receive(:set_action_if_nil).with("GET /")
202
+ it "sets the action" do
203
+ make_request(env)
204
+
205
+ expect(created_transactions.count).to eq(1)
206
+ expect(last_transaction.to_h).to include("action" => "GET /")
164
207
  end
165
208
 
166
209
  context "without 'sinatra.route' env" do
167
210
  let(:env) { { :path => "/", :method => "GET" } }
168
211
 
169
- it "returns nil" do
170
- expect_any_instance_of(Appsignal::Transaction).to receive(:set_action_if_nil).with(nil)
212
+ it "doesn't set an action name" do
213
+ make_request(env)
214
+
215
+ expect(created_transactions.count).to eq(1)
216
+ expect(last_transaction.to_h).to include("action" => nil)
171
217
  end
172
218
  end
173
219
 
@@ -175,44 +221,90 @@ if DependencyHelper.sinatra_present?
175
221
  before { env["SCRIPT_NAME"] = "/api" }
176
222
 
177
223
  it "should call set_action with an application prefix path" do
178
- expect_any_instance_of(Appsignal::Transaction)
179
- .to receive(:set_action_if_nil).with("GET /api/")
224
+ make_request(env)
225
+
226
+ expect(created_transactions.count).to eq(1)
227
+ expect(last_transaction.to_h).to include("action" => "GET /api/")
180
228
  end
181
229
 
182
230
  context "without 'sinatra.route' env" do
183
231
  let(:env) { { :path => "/", :method => "GET" } }
184
232
 
185
- it "returns nil" do
186
- expect_any_instance_of(Appsignal::Transaction)
187
- .to receive(:set_action_if_nil).with(nil)
233
+ it "doesn't set an action name" do
234
+ make_request(env)
235
+
236
+ expect(created_transactions.count).to eq(1)
237
+ expect(last_transaction.to_h).to include("action" => nil)
188
238
  end
189
239
  end
190
240
  end
191
241
  end
192
242
 
193
- it "should set metadata" do
194
- expect_any_instance_of(Appsignal::Transaction).to receive(:set_metadata).twice
243
+ context "metadata" do
244
+ let(:env) { { "PATH_INFO" => "/some/path", "REQUEST_METHOD" => "GET" } }
245
+
246
+ it "sets metadata from the environment" do
247
+ make_request(env)
248
+
249
+ expect(created_transactions.count).to eq(1)
250
+ expect(last_transaction.to_h).to include(
251
+ "metadata" => {
252
+ "method" => "GET",
253
+ "path" => "/some/path"
254
+ },
255
+ "sample_data" => hash_including(
256
+ "environment" => hash_including(
257
+ "REQUEST_METHOD" => "GET",
258
+ "PATH_INFO" => "/some/path"
259
+ )
260
+ )
261
+ )
262
+ end
263
+ end
264
+
265
+ context "with queue start" do
266
+ let(:queue_start_time) { fixed_time * 1_000 }
267
+ let(:env) do
268
+ { "HTTP_X_REQUEST_START" => "t=#{queue_start_time.to_i}" } # in milliseconds
269
+ end
270
+
271
+ it "sets the queue start" do
272
+ make_request(env)
273
+ expect(last_transaction.ext.queue_start).to eq(queue_start_time)
274
+ end
195
275
  end
196
276
 
197
- it "should set the queue start" do
198
- expect_any_instance_of(Appsignal::Transaction)
199
- .to receive(:set_http_or_background_queue_start)
277
+ class FilteredRequest
278
+ def initialize(_args) # rubocop:disable Style/RedundantInitialize
279
+ end
280
+
281
+ def path
282
+ "/static/path"
283
+ end
284
+
285
+ def request_method
286
+ "GET"
287
+ end
288
+
289
+ def filtered_params
290
+ { "abc" => "123" }
291
+ end
200
292
  end
201
293
 
202
294
  context "with overridden request class and params method" do
203
- let(:options) { { :request_class => ::Rack::Request, :params_method => :filtered_params } }
295
+ let(:options) { { :request_class => FilteredRequest, :params_method => :filtered_params } }
204
296
 
205
- it "should use the overridden request class and params method" do
206
- request = ::Rack::Request.new(env)
207
- expect(::Rack::Request).to receive(:new)
208
- .with(env.merge(:params_method => :filtered_params))
209
- .at_least(:once)
210
- .and_return(request)
297
+ it "uses the overridden request class and params method to fetch params" do
298
+ make_request(env)
299
+
300
+ expect(created_transactions.count).to eq(1)
301
+ expect(last_transaction.to_h).to include(
302
+ "sample_data" => hash_including(
303
+ "params" => { "abc" => "123" }
304
+ )
305
+ )
211
306
  end
212
307
  end
213
-
214
- after(:error => false) { middleware.call(env) }
215
- after(:error => true) { expect { middleware.call(env) }.to raise_error(error) }
216
308
  end
217
309
  end
218
310
  end
@@ -1,6 +1,7 @@
1
1
  require "appsignal/rack/streaming_listener"
2
2
 
3
3
  describe Appsignal::Rack::StreamingListener do
4
+ before(:context) { start_agent }
4
5
  let(:headers) { {} }
5
6
  let(:env) do
6
7
  {
@@ -432,6 +432,19 @@ describe Appsignal::Transaction do
432
432
  expect(breadcrumb["metadata"]).to eq({})
433
433
  end
434
434
  end
435
+
436
+ context "with metadata argument that's not a Hash" do
437
+ it "does not add the breadcrumb and logs and error" do
438
+ transaction.add_breadcrumb("category", "action", "message", "invalid metadata")
439
+ transaction.sample_data
440
+
441
+ expect(transaction.to_h["sample_data"]["breadcrumbs"]).to be_empty
442
+ expect(log_contents(log)).to contains_log(
443
+ :error,
444
+ "add_breadcrumb: Cannot add breadcrumb. The given metadata argument is not a Hash."
445
+ )
446
+ end
447
+ end
435
448
  end
436
449
 
437
450
  describe "#set_action" do
@@ -623,6 +636,18 @@ describe Appsignal::Transaction do
623
636
  expect(transaction.to_h["metadata"]).to eq("request_method" => "GET")
624
637
  end
625
638
 
639
+ context "when filter_metadata includes metadata key" do
640
+ before { Appsignal.config[:filter_metadata] = ["filter_key"] }
641
+ after { Appsignal.config[:filter_metadata] = [] }
642
+
643
+ it "does not set the metadata on the transaction" do
644
+ transaction.set_metadata(:filter_key, "filtered value")
645
+ transaction.set_metadata("filter_key", "filtered value")
646
+
647
+ expect(transaction.to_h["metadata"].keys).to_not include("filter_key")
648
+ end
649
+ end
650
+
626
651
  context "when the key is nil" do
627
652
  it "does not update the metadata on the transaction" do
628
653
  transaction.set_metadata(nil, "GET")
@@ -1275,8 +1300,8 @@ describe Appsignal::Transaction do
1275
1300
  end
1276
1301
  end
1277
1302
 
1278
- describe "#metadata" do
1279
- subject { transaction.send(:metadata) }
1303
+ describe "#sanitized_metadata" do
1304
+ subject { transaction.send(:sanitized_metadata) }
1280
1305
 
1281
1306
  context "when request is nil" do
1282
1307
  let(:request) { nil }
@@ -1291,9 +1316,18 @@ describe Appsignal::Transaction do
1291
1316
  end
1292
1317
 
1293
1318
  context "when env is present" do
1294
- let(:env) { { :metadata => { :key => "value" } } }
1319
+ let(:env) { { "key" => "value" } }
1320
+
1321
+ it { is_expected.to eq("key" => "value") }
1295
1322
 
1296
- it { is_expected.to eq env[:metadata] }
1323
+ context "with filter_metadata option set" do
1324
+ before { Appsignal.config[:filter_metadata] = ["key"] }
1325
+ after { Appsignal.config[:filter_metadata] = [] }
1326
+
1327
+ it "filters out keys listed in the filter_metadata option" do
1328
+ expect(subject.keys).to_not include("key")
1329
+ end
1330
+ end
1297
1331
  end
1298
1332
  end
1299
1333
 
@@ -1,5 +1,7 @@
1
1
  describe Appsignal::Utils::HashSanitizer do
2
2
  let(:file) { uploaded_file }
3
+ let(:some_array) { [1, 2, 3] }
4
+ let(:some_hash) { { :a => 1, :b => 2 } }
3
5
  let(:params) do
4
6
  {
5
7
  :text => "string",
@@ -8,6 +10,10 @@ describe Appsignal::Utils::HashSanitizer do
8
10
  :float => 0.0,
9
11
  :bool_true => true,
10
12
  :bool_false => false,
13
+ # Non-recursive appearances of the same array instance
14
+ :some_arrays => [some_array, some_array],
15
+ # Non-recursive appearances of the same hash instance
16
+ :some_hashes => { :a => some_hash, :b => some_hash },
11
17
  :nil => nil,
12
18
  :int => 1, # Fixnum
13
19
  :int64 => 1 << 64, # Bignum
@@ -20,9 +26,20 @@ describe Appsignal::Utils::HashSanitizer do
20
26
  {
21
27
  :key => "value",
22
28
  :file => file
23
- }
24
- ]
25
- }
29
+ }.tap do |hsh|
30
+ # Recursive hash-in-hash (should be [:nested_array][3][:recursive_hash])
31
+ hsh[:recursive_hash] = hsh
32
+ end
33
+ ].tap do |ary|
34
+ # Recursive array-in-array (should be [:nested_array][4])
35
+ ary << ary
36
+ # Recursive array-in-hash (should be [:nested_array][3][:recursive_array])
37
+ ary[3][:recursive_array] = ary
38
+ end
39
+ }.tap do |hsh|
40
+ # Recursive hash-in-array (should be [:nested_array][5])
41
+ hsh[:nested_array] << hsh
42
+ end
26
43
  }
27
44
  end
28
45
 
@@ -43,6 +60,9 @@ describe Appsignal::Utils::HashSanitizer do
43
60
  expect(subject[:nil]).to be_nil
44
61
  expect(subject[:int]).to eq(1)
45
62
  expect(subject[:int64]).to eq(1 << 64)
63
+ expect(subject[:some_arrays]).to eq([[1, 2, 3], [1, 2, 3]])
64
+ expect(subject[:some_hashes]).to eq({ :a => { :a => 1, :b => 2 },
65
+ :b => { :a => 1, :b => 2 } })
46
66
  end
47
67
 
48
68
  it "does not change the original params" do
@@ -72,7 +92,7 @@ describe Appsignal::Utils::HashSanitizer do
72
92
  expect(subject[2]).to include "::UploadedFile"
73
93
  end
74
94
 
75
- describe ":nested_hash key" do
95
+ describe "nested hash" do
76
96
  subject { sanitized_params[:hash][:nested_array][3] }
77
97
 
78
98
  it "returns a sanitized Hash" do
@@ -82,6 +102,24 @@ describe Appsignal::Utils::HashSanitizer do
82
102
  expect(subject[:file]).to be_instance_of String
83
103
  expect(subject[:file]).to include "::UploadedFile"
84
104
  end
105
+
106
+ it "replaces a recursive array" do
107
+ expect(subject[:recursive_array]).to eq("[RECURSIVE VALUE]")
108
+ end
109
+
110
+ it "replaces a recursive hash" do
111
+ expect(subject[:recursive_hash]).to eq("[RECURSIVE VALUE]")
112
+ end
113
+ end
114
+
115
+ describe "nested array" do
116
+ it "replaces a recursive array" do
117
+ expect(sanitized_params[:hash][:nested_array][4]).to eq("[RECURSIVE VALUE]")
118
+ end
119
+
120
+ it "replaces a recursive hash" do
121
+ expect(sanitized_params[:hash][:nested_array][5]).to eq("[RECURSIVE VALUE]")
122
+ end
85
123
  end
86
124
  end
87
125
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appsignal
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.4.4
4
+ version: 3.4.6
5
5
  platform: java
6
6
  authors:
7
7
  - Robert Beekman
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-06-02 00:00:00.000000000 Z
13
+ date: 2023-07-03 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rack
@@ -453,7 +453,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
453
453
  - !ruby/object:Gem::Version
454
454
  version: '0'
455
455
  requirements: []
456
- rubygems_version: 3.4.11
456
+ rubygems_version: 3.3.7
457
457
  signing_key:
458
458
  specification_version: 4
459
459
  summary: Logs performance and exception data from your app to appsignal.com