appsignal 3.5.5 → 3.6.0

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: d3267960e538cd5adc0ee8f5eb3262da34e31f73a7e476229986a04530608a2d
4
- data.tar.gz: 8e095d524c562120f15fc8bf983459488dc1e6d537bf20490855057d76eb119e
3
+ metadata.gz: 6402d39c536a0e4fe970163c92d3207cad1af89c279d0bec8494abf90844434c
4
+ data.tar.gz: c989b01ded89a0c2643ba5b4bb02d24b02012ee25383b451fee192eae59ab611
5
5
  SHA512:
6
- metadata.gz: c72a0a85dc9f4c448692c15d3d87ce1d34e858beed3ed3e211d121348fe756f4f3ccae4b1606edc96b74aed0cbf8d789eeb209bc68e5ee1384d724664daca94c
7
- data.tar.gz: cbacece1707e5c1b7ddc473863f5aa9bacbb317979827f1e752957dd9ec72270b1bbd5baf4637fa4ba37f03b471fdbd7c22ebb7bcf63a07d322a91952af1e746
6
+ metadata.gz: 65908eb0b138011eccd79c0fe0de47f3c2b150bc333eb656fde66f541529fe1801d0404a6dadfc875cbbf53b442613970dabe221fc2ae52fafd55927bb408bc8
7
+ data.tar.gz: 2852aeb49f06d45606bc58a81c0e344b62326340aff7c81f363a2f081346b2147fd60eb7c38a0065c1dfed94ca4f148b1b866aafc6bada1734a4d1911746e07a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # AppSignal for Ruby gem Changelog
2
2
 
3
+ ## 3.6.0
4
+
5
+ _Published on 2024-02-26._
6
+
7
+ ### Added
8
+
9
+ - [9984156f](https://github.com/appsignal/appsignal-ruby/commit/9984156faea0a76cb0fe81594e1ddf40d55dabbe) minor - Add instrumentation for all Rack responses, including streaming responses. New `response_body_each.rack`, `response_body_call.rack` and `response_body_to_ary.rack` events will be shown in the event timeline. This will show how long it takes to complete responses, depending on the response implementation.
10
+
11
+ This Sinatra route with a streaming response will be better instrumented, for example:
12
+
13
+ ```ruby
14
+ get "/stream" do
15
+ stream do |out|
16
+ sleep 1
17
+ out << "1"
18
+ sleep 1
19
+ out << "2"
20
+ sleep 1
21
+ out << "3"
22
+ end
23
+ end
24
+ ```
25
+ - [e7706038](https://github.com/appsignal/appsignal-ruby/commit/e7706038d8b2f52ea90441cfa62d5ee867d893a2) patch - Add histogram support to the OpenTelemetry HTTP server. This allows OpenTelemetry-based instrumentations to report histogram data to AppSignal as distribution metrics.
26
+
27
+ ### Changed
28
+
29
+ - [11220302](https://github.com/appsignal/appsignal-ruby/commit/112203023a58e53e607a9fd7d545044fa7d896d5) minor - **Breaking change**: Normalize CPU metrics for cgroups v1 systems. When we can detect how many CPUs are configured in the container's limits, we will normalize the CPU percentages to a maximum of 100%. This is a breaking change. Triggers for CPU percentages that are configured for a CPU percentage higher than 100% will no longer trigger after this update. Please configure triggers to a percentage with a maximum of 100% CPU percentage.
30
+ - [11220302](https://github.com/appsignal/appsignal-ruby/commit/112203023a58e53e607a9fd7d545044fa7d896d5) patch - Support fractional CPUs for cgroups v2 metrics. Previously a CPU count of 0.5 would be interpreted as 1 CPU. Now it will be correctly seen as half a CPU and calculate CPU percentages accordingly.
31
+ - [14aefc35](https://github.com/appsignal/appsignal-ruby/commit/14aefc3594b3f55a4c2ab14ba1259a4f10499467) patch - Update bundled trusted root certificates.
32
+
33
+ ### Fixed
34
+
35
+ - [f2abbd6a](https://github.com/appsignal/appsignal-ruby/commit/f2abbd6aeb2230d79139cbdf82af98557bbe5b54) patch - Fix (sub)traces not being reported in their entirety when the OpenTelemetry exporter sends one trace in multiple export requests. This would be an issue for long running traces, that are exported in several requests.
36
+
37
+ ## 3.5.6
38
+
39
+ ### Changed
40
+
41
+ - [c76a3293](https://github.com/appsignal/appsignal-ruby/commit/c76a329389c7ce55f1a8307d67fca6c0824c7b6f) patch - Default headers don't contain `REQUEST_URI` anymore as query params are not filtered. Now `REQUEST_PATH` is sent instead to avoid any PII filtering.
42
+
3
43
  ## 3.5.5
4
44
 
5
45
  _Published on 2024-02-01._
@@ -25,11 +65,11 @@ _Published on 2024-02-01._
25
65
 
26
66
  ### Deprecated
27
67
 
28
- - [bb98744b](https://github.com/appsignal/appsignal-ruby/commit/bb98744b1b6d34db71b5f46279b1a9b26039bd0f) patch - Deprecate the `Appsignal.set_host_guage` and `Appsignal.set_process_gauge` helper methods in the Ruby gem. These methods would already log deprecation warnings in the `appsignal.log` file, but now also as a Ruby warning. These methods will be removed in the next major version. These methods already did not report any metrics, and still do not.
68
+ - [bb98744b](https://github.com/appsignal/appsignal-ruby/commit/bb98744b1b6d34db71b5f46279b1a9b26039bd0f) patch - Deprecate the `Appsignal.set_host_gauge` and `Appsignal.set_process_gauge` helper methods in the Ruby gem. These methods would already log deprecation warnings in the `appsignal.log` file, but now also as a Ruby warning. These methods will be removed in the next major version. These methods already did not report any metrics, and still do not.
29
69
 
30
70
  ### Removed
31
71
 
32
- - [1a863490](https://github.com/appsignal/appsignal-ruby/commit/1a863490046318b8cee5fff2ac341fb73065f252) patch - Remove the `appsignal_set_host_guage` and `appsignal_set_process_gauge` extension functions. These functions were already deprecated and did not report any metrics.
72
+ - [1a863490](https://github.com/appsignal/appsignal-ruby/commit/1a863490046318b8cee5fff2ac341fb73065f252) patch - Remove the `appsignal_set_host_gauge` and `appsignal_set_process_gauge` extension functions. These functions were already deprecated and did not report any metrics.
33
73
 
34
74
  ### Fixed
35
75
 
data/ext/agent.rb CHANGED
@@ -6,7 +6,7 @@
6
6
  # Modifications to this file will be overwritten with the next agent release.
7
7
 
8
8
  APPSIGNAL_AGENT_CONFIG = {
9
- "version" => "0.31.2",
9
+ "version" => "0.33.2",
10
10
  "mirrors" => [
11
11
  "https://appsignal-agent-releases.global.ssl.fastly.net",
12
12
  "https://d135dj0rjqvssy.cloudfront.net"
@@ -14,131 +14,131 @@ APPSIGNAL_AGENT_CONFIG = {
14
14
  "triples" => {
15
15
  "x86_64-darwin" => {
16
16
  "static" => {
17
- "checksum" => "42cdf814a89e5d6bd6e5cd9ba84103df82b43418012bb4f9251e98d0c3627759",
17
+ "checksum" => "0864691f001133fa479b34b00a682e76f374c40c161e7715756a3c036e3c8798",
18
18
  "filename" => "appsignal-x86_64-darwin-all-static.tar.gz"
19
19
  },
20
20
  "dynamic" => {
21
- "checksum" => "7497d64b125849d306ebca71a2a2ce83e3f76dbfbc809b43b5948e12b7351b96",
21
+ "checksum" => "5141528c4293e4bd619107ae79afc8e07fdc8b33835899c5cf3f82ab3d31de8f",
22
22
  "filename" => "appsignal-x86_64-darwin-all-dynamic.tar.gz"
23
23
  }
24
24
  },
25
25
  "universal-darwin" => {
26
26
  "static" => {
27
- "checksum" => "42cdf814a89e5d6bd6e5cd9ba84103df82b43418012bb4f9251e98d0c3627759",
27
+ "checksum" => "0864691f001133fa479b34b00a682e76f374c40c161e7715756a3c036e3c8798",
28
28
  "filename" => "appsignal-x86_64-darwin-all-static.tar.gz"
29
29
  },
30
30
  "dynamic" => {
31
- "checksum" => "7497d64b125849d306ebca71a2a2ce83e3f76dbfbc809b43b5948e12b7351b96",
31
+ "checksum" => "5141528c4293e4bd619107ae79afc8e07fdc8b33835899c5cf3f82ab3d31de8f",
32
32
  "filename" => "appsignal-x86_64-darwin-all-dynamic.tar.gz"
33
33
  }
34
34
  },
35
35
  "aarch64-darwin" => {
36
36
  "static" => {
37
- "checksum" => "8db9e31e090e767b1157d969521967f322be9dd73eb1b677b6192eb4c987af72",
37
+ "checksum" => "13506e5911523e7107a8cb714e18b3bcb690f3eeef88bf9aff54777ba540fdc4",
38
38
  "filename" => "appsignal-aarch64-darwin-all-static.tar.gz"
39
39
  },
40
40
  "dynamic" => {
41
- "checksum" => "612d68620836b324bc61d8d4fd42630e50641116688ac78f2a7c806e2ee10ad1",
41
+ "checksum" => "9d4deef17f42dc54981344a5af6b872e06dbd3d317be68b6abeb2403ffd65e23",
42
42
  "filename" => "appsignal-aarch64-darwin-all-dynamic.tar.gz"
43
43
  }
44
44
  },
45
45
  "arm64-darwin" => {
46
46
  "static" => {
47
- "checksum" => "8db9e31e090e767b1157d969521967f322be9dd73eb1b677b6192eb4c987af72",
47
+ "checksum" => "13506e5911523e7107a8cb714e18b3bcb690f3eeef88bf9aff54777ba540fdc4",
48
48
  "filename" => "appsignal-aarch64-darwin-all-static.tar.gz"
49
49
  },
50
50
  "dynamic" => {
51
- "checksum" => "612d68620836b324bc61d8d4fd42630e50641116688ac78f2a7c806e2ee10ad1",
51
+ "checksum" => "9d4deef17f42dc54981344a5af6b872e06dbd3d317be68b6abeb2403ffd65e23",
52
52
  "filename" => "appsignal-aarch64-darwin-all-dynamic.tar.gz"
53
53
  }
54
54
  },
55
55
  "arm-darwin" => {
56
56
  "static" => {
57
- "checksum" => "8db9e31e090e767b1157d969521967f322be9dd73eb1b677b6192eb4c987af72",
57
+ "checksum" => "13506e5911523e7107a8cb714e18b3bcb690f3eeef88bf9aff54777ba540fdc4",
58
58
  "filename" => "appsignal-aarch64-darwin-all-static.tar.gz"
59
59
  },
60
60
  "dynamic" => {
61
- "checksum" => "612d68620836b324bc61d8d4fd42630e50641116688ac78f2a7c806e2ee10ad1",
61
+ "checksum" => "9d4deef17f42dc54981344a5af6b872e06dbd3d317be68b6abeb2403ffd65e23",
62
62
  "filename" => "appsignal-aarch64-darwin-all-dynamic.tar.gz"
63
63
  }
64
64
  },
65
65
  "aarch64-linux" => {
66
66
  "static" => {
67
- "checksum" => "72873d1c7ad2d4d744fe3dd4370fb07b4c9d8a4f4d87febb8dbe08c532eebff8",
67
+ "checksum" => "76702b5755d5bb45cc05df17dd38389b7e20e105a52324120a45ae1b481c7881",
68
68
  "filename" => "appsignal-aarch64-linux-all-static.tar.gz"
69
69
  },
70
70
  "dynamic" => {
71
- "checksum" => "b1510e425a0719cc7e7fe08e760c4794ece81108647ef73babffbb1eec93b5c7",
71
+ "checksum" => "bf518ce2cb4a9041fe819b6bf43e1bc793fe52b3e73527687d7812618c8e7407",
72
72
  "filename" => "appsignal-aarch64-linux-all-dynamic.tar.gz"
73
73
  }
74
74
  },
75
75
  "i686-linux" => {
76
76
  "static" => {
77
- "checksum" => "8779fdd2f02b034463900456b5b65a92d3a9165b87ba896b01baded96729685a",
77
+ "checksum" => "22cbda11a8d801d75e9394033f5cf28f0ddcff66a2138720f827441bdcf919c2",
78
78
  "filename" => "appsignal-i686-linux-all-static.tar.gz"
79
79
  },
80
80
  "dynamic" => {
81
- "checksum" => "be30f7ff817a048af9f133ffd0da5c408f50b38e75bb3f25e47cd3916d5e4fac",
81
+ "checksum" => "157492663e434421499f9cc0b510178387c8968e53bdc6e216db374b86d5c3dc",
82
82
  "filename" => "appsignal-i686-linux-all-dynamic.tar.gz"
83
83
  }
84
84
  },
85
85
  "x86-linux" => {
86
86
  "static" => {
87
- "checksum" => "8779fdd2f02b034463900456b5b65a92d3a9165b87ba896b01baded96729685a",
87
+ "checksum" => "22cbda11a8d801d75e9394033f5cf28f0ddcff66a2138720f827441bdcf919c2",
88
88
  "filename" => "appsignal-i686-linux-all-static.tar.gz"
89
89
  },
90
90
  "dynamic" => {
91
- "checksum" => "be30f7ff817a048af9f133ffd0da5c408f50b38e75bb3f25e47cd3916d5e4fac",
91
+ "checksum" => "157492663e434421499f9cc0b510178387c8968e53bdc6e216db374b86d5c3dc",
92
92
  "filename" => "appsignal-i686-linux-all-dynamic.tar.gz"
93
93
  }
94
94
  },
95
95
  "x86_64-linux" => {
96
96
  "static" => {
97
- "checksum" => "36fc29655d13e4dfe7bcbe2c798bfc16d68194610ff354d43e977f3768f31458",
97
+ "checksum" => "8ff0b1d7bf0cfc1c66e918545a9ab5c29be35c371cde48f64a01c725290599ed",
98
98
  "filename" => "appsignal-x86_64-linux-all-static.tar.gz"
99
99
  },
100
100
  "dynamic" => {
101
- "checksum" => "d37e57f55f44ac88a2b803fc17942f60137fbfa819134388941d22f579518170",
101
+ "checksum" => "a186c18536c3b7ec4802e852a62154cc976dcb5f554c3d0d8472d5cd7131b02b",
102
102
  "filename" => "appsignal-x86_64-linux-all-dynamic.tar.gz"
103
103
  }
104
104
  },
105
105
  "x86_64-linux-musl" => {
106
106
  "static" => {
107
- "checksum" => "59b6cef9797746da9d6717effc0892a2f2219767734a0e76f8b3d1578dc0d9e0",
107
+ "checksum" => "a5e0af3e5e1ad908792e79c7c46b59119272e9836e5ea96791c78e3cb12ed132",
108
108
  "filename" => "appsignal-x86_64-linux-musl-all-static.tar.gz"
109
109
  },
110
110
  "dynamic" => {
111
- "checksum" => "703d657ee15b69563c000fe150e368a91a12c9b39f16976f23a4b191284204f3",
111
+ "checksum" => "17c108a83dff86b2531bf7f348481bb31ece53b4cc62615ca0a34332c0df2970",
112
112
  "filename" => "appsignal-x86_64-linux-musl-all-dynamic.tar.gz"
113
113
  }
114
114
  },
115
115
  "aarch64-linux-musl" => {
116
116
  "static" => {
117
- "checksum" => "ec3ab8fcc20d1f31df6003e2ca3dcf257abfeddd1b7912fa6189f1f6905a89ab",
117
+ "checksum" => "92460560115d540a8140cbc360bd98beba8477e8a73eafd20ee611543b4528df",
118
118
  "filename" => "appsignal-aarch64-linux-musl-all-static.tar.gz"
119
119
  },
120
120
  "dynamic" => {
121
- "checksum" => "b7a00a09b7e10f500b54913feefd6a16873849324f0e8ec65b36c58bdc1901b0",
121
+ "checksum" => "d4749b10a1803080e0b1b0d8f95ef9d1fef0aa694fa0fc405df97812937d8e7c",
122
122
  "filename" => "appsignal-aarch64-linux-musl-all-dynamic.tar.gz"
123
123
  }
124
124
  },
125
125
  "x86_64-freebsd" => {
126
126
  "static" => {
127
- "checksum" => "bd654d5c555f6006e4145d76e27f02c5d22b285f411675a520455d6db6c8e165",
127
+ "checksum" => "8d8733c2adc0f750553be11b5e54fd614b13207be67863d95c57e4739021a92f",
128
128
  "filename" => "appsignal-x86_64-freebsd-all-static.tar.gz"
129
129
  },
130
130
  "dynamic" => {
131
- "checksum" => "b06445f38b7b8b3e47f407540fb24f196dbf0a84583a2ad7f25bbba750930108",
131
+ "checksum" => "8a9cbdc645b3833766458a252c2a8fefda76c62fceee8be795b286d65cc513c6",
132
132
  "filename" => "appsignal-x86_64-freebsd-all-dynamic.tar.gz"
133
133
  }
134
134
  },
135
135
  "amd64-freebsd" => {
136
136
  "static" => {
137
- "checksum" => "bd654d5c555f6006e4145d76e27f02c5d22b285f411675a520455d6db6c8e165",
137
+ "checksum" => "8d8733c2adc0f750553be11b5e54fd614b13207be67863d95c57e4739021a92f",
138
138
  "filename" => "appsignal-x86_64-freebsd-all-static.tar.gz"
139
139
  },
140
140
  "dynamic" => {
141
- "checksum" => "b06445f38b7b8b3e47f407540fb24f196dbf0a84583a2ad7f25bbba750930108",
141
+ "checksum" => "8a9cbdc645b3833766458a252c2a8fefda76c62fceee8be795b286d65cc513c6",
142
142
  "filename" => "appsignal-x86_64-freebsd-all-dynamic.tar.gz"
143
143
  }
144
144
  }
@@ -40,7 +40,7 @@ module Appsignal
40
40
  HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
41
41
  HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_CONNECTION
42
42
  CONTENT_LENGTH PATH_INFO HTTP_RANGE
43
- REQUEST_METHOD REQUEST_URI SERVER_NAME SERVER_PORT
43
+ REQUEST_METHOD REQUEST_PATH SERVER_NAME SERVER_PORT
44
44
  SERVER_PROTOCOL
45
45
  ],
46
46
  :send_environment_metadata => true,
@@ -93,7 +93,7 @@ module Appsignal
93
93
  "SERVER_NAME" => "localhost",
94
94
  "SERVER_PORT" => "80",
95
95
  "SERVER_PROTOCOL" => "HTTP/1.1",
96
- "REQUEST_URI" => "/hello",
96
+ "REQUEST_PATH" => "/hello",
97
97
  "PATH_INFO" => "/hello",
98
98
  "HTTP_ACCEPT" => "text/html,application/xhtml+xml",
99
99
  "HTTP_ACCEPT_ENCODING" => "gzip, deflate, sdch",
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ # @api private
5
+ module Rack
6
+ class BodyWrapper
7
+ def self.wrap(original_body, appsignal_transaction)
8
+ # The logic of how Rack treats a response body differs based on which methods
9
+ # the body responds to. This means that to support the Rack 3.x spec in full
10
+ # we need to return a wrapper which matches the API of the wrapped body as closely
11
+ # as possible. Pick the wrapper from the most specific to the least specific.
12
+ # See https://github.com/rack/rack/blob/main/SPEC.rdoc#the-body-
13
+ #
14
+ # What is important is that our Body wrapper responds to the same methods Rack
15
+ # (or a webserver) would be checking and calling, and passes through that functionality
16
+ # to the original body. This can be done using delegation via i.e. SimpleDelegate
17
+ # but we also need "close" to get called correctly so that the Appsignal transaction
18
+ # gets completed - which will not happen, for example, when #to_ary gets called
19
+ # just on the delegated Rack body.
20
+ #
21
+ # This comment https://github.com/rails/rails/pull/49627#issuecomment-1769802573
22
+ # is of particular interest to understand why this has to be somewhat complicated.
23
+ if original_body.respond_to?(:to_path)
24
+ PathableBodyWrapper.new(original_body, appsignal_transaction)
25
+ elsif original_body.respond_to?(:to_ary)
26
+ ArrayableBodyWrapper.new(original_body, appsignal_transaction)
27
+ elsif !original_body.respond_to?(:each) && original_body.respond_to?(:call)
28
+ # This body only supports #call, so we must be running a Rack 3 application
29
+ # It is possible that a body exposes both `each` and `call` in the hopes of
30
+ # being backwards-compatible with both Rack 3.x and Rack 2.x, however
31
+ # this is not going to work since the SPEC says that if both are available,
32
+ # `each` should be used and `call` should be ignored.
33
+ # So for that case we can drop by to our default EnumerableBodyWrapper
34
+ CallableBodyWrapper.new(original_body, appsignal_transaction)
35
+ else
36
+ EnumerableBodyWrapper.new(original_body, appsignal_transaction)
37
+ end
38
+ end
39
+
40
+ def initialize(body, appsignal_transaction)
41
+ @body_already_closed = false
42
+ @body = body
43
+ @transaction = appsignal_transaction
44
+ end
45
+
46
+ # This must be present in all Rack bodies and will be called by the serving adapter
47
+ def close
48
+ # The @body_already_closed check is needed so that if `to_ary`
49
+ # of the body has already closed itself (as prescribed) we do not
50
+ # attempt to close it twice
51
+ if !@body_already_closed && @body.respond_to?(:close)
52
+ Appsignal.instrument("response_body_close.rack") { @body.close }
53
+ end
54
+ @body_already_closed = true
55
+ rescue Exception => error # rubocop:disable Lint/RescueException
56
+ @transaction.set_error(error)
57
+ raise error
58
+ ensure
59
+ complete_transaction!
60
+ end
61
+
62
+ def complete_transaction!
63
+ # We need to call the Transaction class method and not
64
+ # @transaction.complete because the transaction is still
65
+ # thread-local and it needs to remove itself from the
66
+ # thread variables correctly, which does not happen on
67
+ # Transaction#complete.
68
+ #
69
+ # In the future it would be a good idea to ensure
70
+ # that the current transaction is the same as @transaction,
71
+ # or allow @transaction to complete itself and remove
72
+ # itself from Thread.current
73
+ Appsignal::Transaction.complete_current!
74
+ end
75
+ end
76
+
77
+ # The standard Rack body wrapper which exposes "each" for iterating
78
+ # over the response body. This is supported across all 3 major Rack
79
+ # versions.
80
+ #
81
+ # @api private
82
+ class EnumerableBodyWrapper < BodyWrapper
83
+ def each(&blk)
84
+ # This is a workaround for the Rails bug when there was a bit too much
85
+ # eagerness in implementing to_ary, see:
86
+ # https://github.com/rails/rails/pull/44953
87
+ # https://github.com/rails/rails/pull/47092
88
+ # https://github.com/rails/rails/pull/49627
89
+ # https://github.com/rails/rails/issues/49588
90
+ # While the Rack SPEC does not mandate `each` to be callable
91
+ # in a blockless way it is still a good idea to have it in place.
92
+ return enum_for(:each) unless block_given?
93
+
94
+ Appsignal.instrument("process_response_body.rack", "Process Rack response body (#each)") do
95
+ @body.each(&blk)
96
+ end
97
+ rescue Exception => error # rubocop:disable Lint/RescueException
98
+ @transaction.set_error(error)
99
+ raise error
100
+ end
101
+ end
102
+
103
+ # The callable response bodies are a new Rack 3.x feature, and would not work
104
+ # with older Rack versions. They must not respond to `each` because
105
+ # "If it responds to each, you must call each and not call". This is why
106
+ # it inherits from BodyWrapper directly and not from EnumerableBodyWrapper
107
+ #
108
+ # @api private
109
+ class CallableBodyWrapper < BodyWrapper
110
+ def call(stream)
111
+ # `stream` will be closed by the app we are calling, no need for us
112
+ # to close it ourselves
113
+ Appsignal.instrument("process_response_body.rack", "Process Rack response body (#call)") do
114
+ @body.call(stream)
115
+ end
116
+ rescue Exception => error # rubocop:disable Lint/RescueException
117
+ @transaction.set_error(error)
118
+ raise error
119
+ end
120
+ end
121
+
122
+ # "to_ary" takes precedence over "each" and allows the response body
123
+ # to be read eagerly. If the body supports that method, it takes precedence
124
+ # over "each":
125
+ # "Middleware may call to_ary directly on the Body and return a new Body in its place"
126
+ # One could "fold" both the to_ary API and the each() API into one Body object, but
127
+ # to_ary must also call "close" after it executes - and in the Rails implementation
128
+ # this pecularity was not handled properly.
129
+ #
130
+ # @api private
131
+ class ArrayableBodyWrapper < EnumerableBodyWrapper
132
+ def to_ary
133
+ @body_already_closed = true
134
+ Appsignal.instrument(
135
+ "process_response_body.rack",
136
+ "Process Rack response body (#to_ary)"
137
+ ) do
138
+ @body.to_ary
139
+ end
140
+ rescue Exception => error # rubocop:disable Lint/RescueException
141
+ @transaction.set_error(error)
142
+ raise error
143
+ ensure
144
+ # We do not call "close" on ourselves as the only action
145
+ # we need to complete is completing the transaction.
146
+ complete_transaction!
147
+ end
148
+ end
149
+
150
+ # Having "to_path" on a body allows Rack to serve out a static file, or to
151
+ # pass that file to the downstream webserver for sending using X-Sendfile
152
+ class PathableBodyWrapper < EnumerableBodyWrapper
153
+ def to_path
154
+ Appsignal.instrument("response_body_to_path.rack") { @body.to_path }
155
+ rescue Exception => error # rubocop:disable Lint/RescueException
156
+ @transaction.set_error(error)
157
+ raise error
158
+ end
159
+ end
160
+ end
161
+ end
@@ -16,7 +16,9 @@ module Appsignal
16
16
  if Appsignal.active?
17
17
  call_with_appsignal_monitoring(env)
18
18
  else
19
- @app.call(env)
19
+ nil_transaction = Appsignal::Transaction::NilTransaction.new
20
+ status, headers, obody = @app.call(env)
21
+ [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, nil_transaction)]
20
22
  end
21
23
  end
22
24
 
@@ -27,19 +29,30 @@ module Appsignal
27
29
  Appsignal::Transaction::HTTP_REQUEST,
28
30
  request
29
31
  )
32
+ # We need to complete the transaction if there is an exception inside the `call`
33
+ # of the app. If there isn't one and the app returns us a Rack response triplet, we let
34
+ # the BodyWrapper complete the transaction when #close gets called on it
35
+ # (guaranteed by the webserver)
36
+ complete_transaction_without_body = false
30
37
  begin
31
38
  Appsignal.instrument("process_action.generic") do
32
- @app.call(env)
39
+ status, headers, obody = @app.call(env)
40
+ [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, transaction)]
33
41
  end
34
42
  rescue Exception => error # rubocop:disable Lint/RescueException
35
43
  transaction.set_error(error)
44
+ complete_transaction_without_body = true
36
45
  raise error
37
46
  ensure
38
- transaction.set_action_if_nil(env["appsignal.route"] || "unknown")
47
+ default_action = env["appsignal.route"] || env["appsignal.action"] || "unknown"
48
+ transaction.set_action_if_nil(default_action)
39
49
  transaction.set_metadata("path", request.path)
40
50
  transaction.set_metadata("method", request.request_method)
41
51
  transaction.set_http_or_background_queue_start
42
- Appsignal::Transaction.complete_current!
52
+
53
+ # Transaction gets completed when the body gets read out, except in cases when
54
+ # the app failed before returning us the Rack response triplet.
55
+ Appsignal::Transaction.complete_current! if complete_transaction_without_body
43
56
  end
44
57
  end
45
58
  end
@@ -16,7 +16,9 @@ module Appsignal
16
16
  if Appsignal.active?
17
17
  call_with_appsignal_monitoring(env)
18
18
  else
19
- @app.call(env)
19
+ nil_transaction = Appsignal::Transaction::NilTransaction.new
20
+ status, headers, obody = @app.call(env)
21
+ [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, nil_transaction)]
20
22
  end
21
23
  end
22
24
 
@@ -28,10 +30,17 @@ module Appsignal
28
30
  request,
29
31
  :params_method => :filtered_parameters
30
32
  )
33
+ # We need to complete the transaction if there is an exception exception inside the `call`
34
+ # of the app. If there isn't one and the app returns us a Rack response triplet, we let
35
+ # the BodyWrapper complete the transaction when #close gets called on it
36
+ # (guaranteed by the webserver)
37
+ complete_transaction_without_body = false
31
38
  begin
32
- @app.call(env)
39
+ status, headers, obody = @app.call(env)
40
+ [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, transaction)]
33
41
  rescue Exception => error # rubocop:disable Lint/RescueException
34
42
  transaction.set_error(error)
43
+ complete_transaction_without_body = true
35
44
  raise error
36
45
  ensure
37
46
  controller = env["action_controller.instance"]
@@ -45,7 +54,10 @@ module Appsignal
45
54
  rescue => error
46
55
  Appsignal.internal_logger.error("Unable to report HTTP request method: '#{error}'")
47
56
  end
48
- Appsignal::Transaction.complete_current!
57
+
58
+ # Transaction gets completed when the body gets read out, except in cases when
59
+ # the app failed before returning us the Rack response triplet.
60
+ Appsignal::Transaction.complete_current! if complete_transaction_without_body
49
61
  end
50
62
  end
51
63
 
@@ -42,7 +42,9 @@ module Appsignal
42
42
  if Appsignal.active?
43
43
  call_with_appsignal_monitoring(env)
44
44
  else
45
- @app.call(env)
45
+ nil_transaction = Appsignal::Transaction::NilTransaction.new
46
+ status, headers, obody = @app.call(env)
47
+ [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, nil_transaction)]
46
48
  end
47
49
  end
48
50
 
@@ -56,12 +58,19 @@ module Appsignal
56
58
  request,
57
59
  options
58
60
  )
61
+ # We need to complete the transaction if there is an exception exception inside the `call`
62
+ # of the app. If there isn't one and the app returns us a Rack response triplet, we let
63
+ # the BodyWrapper complete the transaction when #close gets called on it
64
+ # (guaranteed by the webserver)
65
+ complete_transaction_without_body = false
59
66
  begin
60
67
  Appsignal.instrument("process_action.sinatra") do
61
- @app.call(env)
68
+ status, headers, obody = @app.call(env)
69
+ [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, transaction)]
62
70
  end
63
71
  rescue Exception => error # rubocop:disable Lint/RescueException
64
72
  transaction.set_error(error)
73
+ complete_transaction_without_body = true
65
74
  raise error
66
75
  ensure
67
76
  # If raise_error is off versions of Sinatra don't raise errors, but store
@@ -73,7 +82,10 @@ module Appsignal
73
82
  transaction.set_metadata("path", request.path)
74
83
  transaction.set_metadata("method", request.request_method)
75
84
  transaction.set_http_or_background_queue_start
76
- Appsignal::Transaction.complete_current!
85
+
86
+ # Transaction gets completed when the body gets read out, except in cases when
87
+ # the app failed before returning us the Rack response triplet.
88
+ Appsignal::Transaction.complete_current! if complete_transaction_without_body
77
89
  end
78
90
  end
79
91
 
@@ -16,7 +16,9 @@ module Appsignal
16
16
  if Appsignal.active?
17
17
  call_with_appsignal_monitoring(env)
18
18
  else
19
- @app.call(env)
19
+ nil_transaction = Appsignal::Transaction::NilTransaction.new
20
+ status, headers, obody = @app.call(env)
21
+ [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, nil_transaction)]
20
22
  end
21
23
  end
22
24
 
@@ -28,46 +30,35 @@ module Appsignal
28
30
  request
29
31
  )
30
32
 
33
+ # We need to complete the transaction if there is an exception exception inside the `call`
34
+ # of the app. If there isn't one and the app returns us a Rack response triplet, we let
35
+ # the BodyWrapper complete the transaction when #close gets called on it
36
+ # (guaranteed by the webserver)
37
+ complete_transaction_without_body = false
38
+
31
39
  # Instrument a `process_action`, to set params/action name
32
- status, headers, body =
40
+ begin
33
41
  Appsignal.instrument("process_action.rack") do
34
- @app.call(env)
35
- rescue Exception => e # rubocop:disable Lint/RescueException
36
- transaction.set_error(e)
37
- raise e
38
- ensure
39
- transaction.set_action_if_nil(env["appsignal.action"])
40
- transaction.set_metadata("path", request.path)
41
- transaction.set_metadata("method", request.request_method)
42
- transaction.set_http_or_background_queue_start
42
+ status, headers, obody = @app.call(env)
43
+ [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, transaction)]
43
44
  end
45
+ rescue Exception => error # rubocop:disable Lint/RescueException
46
+ transaction.set_error(error)
47
+ complete_transaction_without_body = true
48
+ raise error
49
+ ensure
50
+ transaction.set_action_if_nil(env["appsignal.action"])
51
+ transaction.set_metadata("path", request.path)
52
+ transaction.set_metadata("method", request.request_method)
53
+ transaction.set_http_or_background_queue_start
44
54
 
45
- # Wrap the result body with our StreamWrapper
46
- [status, headers, StreamWrapper.new(body, transaction)]
55
+ # Transaction gets completed when the body gets read out, except in cases when
56
+ # the app failed before returning us the Rack response triplet.
57
+ Appsignal::Transaction.complete_current! if complete_transaction_without_body
58
+ end
47
59
  end
48
60
  end
49
61
  end
50
62
 
51
- class StreamWrapper
52
- def initialize(stream, transaction)
53
- @stream = stream
54
- @transaction = transaction
55
- end
56
-
57
- def each(&block)
58
- @stream.each(&block)
59
- rescue Exception => e # rubocop:disable Lint/RescueException
60
- @transaction.set_error(e)
61
- raise e
62
- end
63
-
64
- def close
65
- @stream.close if @stream.respond_to?(:close)
66
- rescue Exception => e # rubocop:disable Lint/RescueException
67
- @transaction.set_error(e)
68
- raise e
69
- ensure
70
- Appsignal::Transaction.complete_current!
71
- end
72
- end
63
+ StreamWrapper = Rack::EnumerableBodyWrapper
73
64
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "3.5.5"
4
+ VERSION = "3.6.0"
5
5
  end
data/lib/appsignal.rb CHANGED
@@ -305,5 +305,6 @@ require "appsignal/garbage_collection"
305
305
  require "appsignal/integrations/railtie" if defined?(::Rails)
306
306
  require "appsignal/transaction"
307
307
  require "appsignal/version"
308
+ require "appsignal/rack/body_wrapper"
308
309
  require "appsignal/rack/generic_instrumentation"
309
310
  require "appsignal/transmitter"