appsignal 4.0.3-java → 4.0.5-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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -0
  3. data/ext/agent.rb +27 -27
  4. data/lib/appsignal/check_in/cron.rb +2 -34
  5. data/lib/appsignal/check_in/scheduler.rb +192 -0
  6. data/lib/appsignal/check_in.rb +18 -0
  7. data/lib/appsignal/cli/diagnose.rb +1 -1
  8. data/lib/appsignal/config.rb +7 -0
  9. data/lib/appsignal/hooks/at_exit.rb +3 -1
  10. data/lib/appsignal/hooks/puma.rb +5 -1
  11. data/lib/appsignal/integrations/puma.rb +45 -0
  12. data/lib/appsignal/rack/abstract_middleware.rb +3 -47
  13. data/lib/appsignal/rack/body_wrapper.rb +15 -0
  14. data/lib/appsignal/rack/event_handler.rb +2 -0
  15. data/lib/appsignal/rack/hanami_middleware.rb +5 -1
  16. data/lib/appsignal/rack.rb +68 -0
  17. data/lib/appsignal/transmitter.rb +30 -7
  18. data/lib/appsignal/utils/ndjson.rb +15 -0
  19. data/lib/appsignal/utils.rb +1 -0
  20. data/lib/appsignal/version.rb +1 -1
  21. data/lib/appsignal.rb +1 -0
  22. data/spec/lib/appsignal/check_in/cron_spec.rb +202 -0
  23. data/spec/lib/appsignal/check_in/scheduler_spec.rb +443 -0
  24. data/spec/lib/appsignal/config_spec.rb +13 -0
  25. data/spec/lib/appsignal/environment_spec.rb +1 -1
  26. data/spec/lib/appsignal/hooks/at_exit_spec.rb +22 -0
  27. data/spec/lib/appsignal/hooks/puma_spec.rb +31 -23
  28. data/spec/lib/appsignal/integrations/puma_spec.rb +150 -0
  29. data/spec/lib/appsignal/probes_spec.rb +1 -6
  30. data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +41 -122
  31. data/spec/lib/appsignal/rack/body_wrapper_spec.rb +29 -21
  32. data/spec/lib/appsignal/rack_spec.rb +180 -0
  33. data/spec/lib/appsignal/transmitter_spec.rb +48 -2
  34. data/spec/lib/appsignal_spec.rb +5 -0
  35. data/spec/spec_helper.rb +0 -7
  36. data/spec/support/helpers/config_helpers.rb +2 -1
  37. data/spec/support/helpers/take_at_most_helper.rb +21 -0
  38. data/spec/support/matchers/contains_log.rb +10 -3
  39. data/spec/support/mocks/hash_like.rb +10 -0
  40. data/spec/support/mocks/puma_mock.rb +43 -0
  41. metadata +11 -3
  42. data/spec/lib/appsignal/check_in_spec.rb +0 -136
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ea322f2119a448a34c283e7a5cac3000f0e9acfec8f5d1a406265eb42f859d6
4
- data.tar.gz: 1ab95f249d94eedba7e86e36d3f74d217bd57deed236e6b352b190dc34007eb5
3
+ metadata.gz: 265860dc1cb9e7ef2035d4dcc288d6789d14b6d656c6812ab26f69fc6e3d9e2e
4
+ data.tar.gz: 702e3e82530b79db9d8b26519d14cb9664d728a12f3d6c5999b63bfbda022fd1
5
5
  SHA512:
6
- metadata.gz: e4d10c4f4684675cf908972044fb11be5eafd1041fb6b80d684580285ab9681a746e5fc36fbc79e84816766a33c507a233b22df7ee911b1cc9253abfa14a2a46
7
- data.tar.gz: d25b331756ceafe336d5b430577fb98da0696f5f92613a47d89f30007b910a4057cf981ddb1d841d295cfdb31f48d69bb8fecb1e7071d933dba47b1160d701bf
6
+ metadata.gz: 8e5589a001f66450dd0c2f7250134be2813bcab36a9c4576886cc3997206109e8e6ae53fbd60fb8924ffa9e34bddd225617a92dfec6b442e6565bc2ea3433d53
7
+ data.tar.gz: bf73768bfbbc2db0c042cb84ddc185362707f7b0b622d0565ce7a5142ddd0479daea6e76d7d87d9b7542ff01929b7a24cf566ca9036e3bd66a5fb0f2de1e622a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  # AppSignal for Ruby gem Changelog
2
2
 
3
+ ## 4.0.5
4
+
5
+ _Published on 2024-09-02._
6
+
7
+ ### Added
8
+
9
+ - Report Puma low-level errors using the `lowlevel_error` reporter. This will report errors previously not caught by our instrumentation middleware. (patch [70cc21f4](https://github.com/appsignal/appsignal-ruby/commit/70cc21f49e19faa9fd2d12a051620cd48e036dcb))
10
+
11
+ ### Changed
12
+
13
+ - Log a warning when loader defaults are added after AppSignal has already been configured.
14
+
15
+ ```ruby
16
+ # Bad
17
+ Appsignal.configure # or Appsignal.start
18
+ Appsignal.load(:sinatra)
19
+
20
+ # Good
21
+ Appsignal.load(:sinatra)
22
+ Appsignal.configure # or Appsignal.start
23
+ ```
24
+
25
+ (patch [0997dd9c](https://github.com/appsignal/appsignal-ruby/commit/0997dd9c0430123a697b8100785f5676163e20ef))
26
+ - Rename the `path` and `method` transaction metadata to `request_path` and `request_method` to make it more clear what context this metadata is from. The `path` and `method` metadata will continue to be reported until the next major/minor version. (patch [fa314b5f](https://github.com/appsignal/appsignal-ruby/commit/fa314b5fb6fdfbf3e9746df377b0145cde0cfa36))
27
+ - Internal change to how OpenTelemetry metrics are sent. (patch [e66d1d70](https://github.com/appsignal/appsignal-ruby/commit/e66d1d702d5010cb5b8084ba790b24d9e70a9e08))
28
+
29
+ ### Removed
30
+
31
+ - Drop support for Puma version 2 and lower. (patch [4fab861c](https://github.com/appsignal/appsignal-ruby/commit/4fab861cae74b08aa71bf64e1b134ae4b1df1dff))
32
+
33
+ ### Fixed
34
+
35
+ - Fix the error log message about our `at_exit` hook reporting no error on process exit when the process exits without an error. (patch [b71f3966](https://github.com/appsignal/appsignal-ruby/commit/b71f39661e9b05c10fa78b821ba0e45bde0c941b))
36
+
37
+ ## 4.0.4
38
+
39
+ _Published on 2024-08-29._
40
+
41
+ ### Changed
42
+
43
+ - Send check-ins concurrently. When calling `Appsignal::CheckIn.cron`, instead of blocking the current thread while the check-in events are sent, schedule them to be sent in a separate thread.
44
+
45
+ When shutting down your application manually, call `Appsignal.stop` to block until all scheduled check-ins have been sent.
46
+
47
+ (patch [46d4ca74](https://github.com/appsignal/appsignal-ruby/commit/46d4ca74f4c188cc011653ed23969ad7ec770812))
48
+
49
+ ### Fixed
50
+
51
+ - Make our Rack BodyWrapper behave like a Rack BodyProxy. If a method doesn't exist on our BodyWrapper class, but it does exist on the body, behave like the Rack BodyProxy and call the method on the wrapped body. (patch [e2376305](https://github.com/appsignal/appsignal-ruby/commit/e23763058a3fb980f1054e9c1eaf7e0f25f75666))
52
+ - Do not report `SignalException` errors from our `at_exit` error reporter. (patch [3ba3ce31](https://github.com/appsignal/appsignal-ruby/commit/3ba3ce31ee3f3e84665c9f2f18d488c689cff6c2))
53
+
3
54
  ## 4.0.3
4
55
 
5
56
  _Published on 2024-08-26._
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.35.21",
9
+ "version" => "0.35.22",
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" => "0e7b5bc5bf1e9903276b9fbe105af1128011613f9dc7ac7cedd4d230d92011bd",
17
+ "checksum" => "024cef88b24032d7187488c00682f92095da406d80ae642ac858e6318b840ad6",
18
18
  "filename" => "appsignal-x86_64-darwin-all-static.tar.gz"
19
19
  },
20
20
  "dynamic" => {
21
- "checksum" => "56cc9aed70014b4fda2b30ff34819370cab2eb2bda588f59d7d86816d60a458f",
21
+ "checksum" => "ddddf86a951fe57f50e351a449df65d2c6428d703581b8ef5922fad23a688f1d",
22
22
  "filename" => "appsignal-x86_64-darwin-all-dynamic.tar.gz"
23
23
  }
24
24
  },
25
25
  "universal-darwin" => {
26
26
  "static" => {
27
- "checksum" => "0e7b5bc5bf1e9903276b9fbe105af1128011613f9dc7ac7cedd4d230d92011bd",
27
+ "checksum" => "024cef88b24032d7187488c00682f92095da406d80ae642ac858e6318b840ad6",
28
28
  "filename" => "appsignal-x86_64-darwin-all-static.tar.gz"
29
29
  },
30
30
  "dynamic" => {
31
- "checksum" => "56cc9aed70014b4fda2b30ff34819370cab2eb2bda588f59d7d86816d60a458f",
31
+ "checksum" => "ddddf86a951fe57f50e351a449df65d2c6428d703581b8ef5922fad23a688f1d",
32
32
  "filename" => "appsignal-x86_64-darwin-all-dynamic.tar.gz"
33
33
  }
34
34
  },
35
35
  "aarch64-darwin" => {
36
36
  "static" => {
37
- "checksum" => "423178f0178f15d7f663451e45a318ca92d8f7cca136b5e985e753520b4ad3e4",
37
+ "checksum" => "b8dfebd77322a1f66e0f0acf1717c2583189016e9749286f61956a3ebbc1a16a",
38
38
  "filename" => "appsignal-aarch64-darwin-all-static.tar.gz"
39
39
  },
40
40
  "dynamic" => {
41
- "checksum" => "c2c0aeba5ddb38584c8b1cc8b2ad2407431c71b944cd4dee47d45828b93619ae",
41
+ "checksum" => "d94ae45a9fa69566a30aaacf9f65bdf21b5d0d2a5990e4ba8f98dedc5956f62f",
42
42
  "filename" => "appsignal-aarch64-darwin-all-dynamic.tar.gz"
43
43
  }
44
44
  },
45
45
  "arm64-darwin" => {
46
46
  "static" => {
47
- "checksum" => "423178f0178f15d7f663451e45a318ca92d8f7cca136b5e985e753520b4ad3e4",
47
+ "checksum" => "b8dfebd77322a1f66e0f0acf1717c2583189016e9749286f61956a3ebbc1a16a",
48
48
  "filename" => "appsignal-aarch64-darwin-all-static.tar.gz"
49
49
  },
50
50
  "dynamic" => {
51
- "checksum" => "c2c0aeba5ddb38584c8b1cc8b2ad2407431c71b944cd4dee47d45828b93619ae",
51
+ "checksum" => "d94ae45a9fa69566a30aaacf9f65bdf21b5d0d2a5990e4ba8f98dedc5956f62f",
52
52
  "filename" => "appsignal-aarch64-darwin-all-dynamic.tar.gz"
53
53
  }
54
54
  },
55
55
  "arm-darwin" => {
56
56
  "static" => {
57
- "checksum" => "423178f0178f15d7f663451e45a318ca92d8f7cca136b5e985e753520b4ad3e4",
57
+ "checksum" => "b8dfebd77322a1f66e0f0acf1717c2583189016e9749286f61956a3ebbc1a16a",
58
58
  "filename" => "appsignal-aarch64-darwin-all-static.tar.gz"
59
59
  },
60
60
  "dynamic" => {
61
- "checksum" => "c2c0aeba5ddb38584c8b1cc8b2ad2407431c71b944cd4dee47d45828b93619ae",
61
+ "checksum" => "d94ae45a9fa69566a30aaacf9f65bdf21b5d0d2a5990e4ba8f98dedc5956f62f",
62
62
  "filename" => "appsignal-aarch64-darwin-all-dynamic.tar.gz"
63
63
  }
64
64
  },
65
65
  "aarch64-linux" => {
66
66
  "static" => {
67
- "checksum" => "bf94592c9640f89d0321acd5a0a9bd6951154c37f97f4778ddc2fc536203dc4f",
67
+ "checksum" => "1f21e14f31b9b91f5ce3da0dcb91224bc9f4a0d834ada75e87f396ca9838cf59",
68
68
  "filename" => "appsignal-aarch64-linux-all-static.tar.gz"
69
69
  },
70
70
  "dynamic" => {
71
- "checksum" => "8f8e60daae9ac10bae66fcf9ea33984046b5c92d911eae71ac1a8bdbaa7fa3eb",
71
+ "checksum" => "cf81cabcd216cf41d55ad2ad50f5eccfc1b3edc0dc78159973f3c0687f5ad86d",
72
72
  "filename" => "appsignal-aarch64-linux-all-dynamic.tar.gz"
73
73
  }
74
74
  },
75
75
  "i686-linux" => {
76
76
  "static" => {
77
- "checksum" => "1553674c377a3e6fa56e31754d2af175e734531a44a76c680ff14cc49d4f0031",
77
+ "checksum" => "6111532d1958f9bbbdbea9104c509636d7b6d311090c609a0c134b68e6ef0dd6",
78
78
  "filename" => "appsignal-i686-linux-all-static.tar.gz"
79
79
  },
80
80
  "dynamic" => {
81
- "checksum" => "93e53b3cb2e2fb89cc3dff690da910fccb46503c3d58e4d3fb1a525fb222eea7",
81
+ "checksum" => "705fd88f0e2a4370766f64c93d123476d37f169190550cc3a2c85d34ce1b1893",
82
82
  "filename" => "appsignal-i686-linux-all-dynamic.tar.gz"
83
83
  }
84
84
  },
85
85
  "x86-linux" => {
86
86
  "static" => {
87
- "checksum" => "1553674c377a3e6fa56e31754d2af175e734531a44a76c680ff14cc49d4f0031",
87
+ "checksum" => "6111532d1958f9bbbdbea9104c509636d7b6d311090c609a0c134b68e6ef0dd6",
88
88
  "filename" => "appsignal-i686-linux-all-static.tar.gz"
89
89
  },
90
90
  "dynamic" => {
91
- "checksum" => "93e53b3cb2e2fb89cc3dff690da910fccb46503c3d58e4d3fb1a525fb222eea7",
91
+ "checksum" => "705fd88f0e2a4370766f64c93d123476d37f169190550cc3a2c85d34ce1b1893",
92
92
  "filename" => "appsignal-i686-linux-all-dynamic.tar.gz"
93
93
  }
94
94
  },
95
95
  "x86_64-linux" => {
96
96
  "static" => {
97
- "checksum" => "497d280dff46f7e72875af69f7b3756f3925bcad9e1922f030f818c8c5fa5d67",
97
+ "checksum" => "9e325faf63b7fe9b9804b4e8ce4bac7deefa6c47aec47faf47d90133d44dbaa5",
98
98
  "filename" => "appsignal-x86_64-linux-all-static.tar.gz"
99
99
  },
100
100
  "dynamic" => {
101
- "checksum" => "1b230cc3e80ef4013defc9d6e80713d1d318ed7f673121433d864b47e239ff73",
101
+ "checksum" => "11bac70bee7e620b31a26a01093a0c9ef01f7eb7508ac0c0a0eae27973cc643f",
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" => "2bcbc6b57a0298a97e6549e025ec092f8b3bd2b4e8f430fe7b040f7a2e274527",
107
+ "checksum" => "a76de00904eb40a61a7e68082cb06379aa13f61ec0d25ead828fd2ba022f9be9",
108
108
  "filename" => "appsignal-x86_64-linux-musl-all-static.tar.gz"
109
109
  },
110
110
  "dynamic" => {
111
- "checksum" => "7e89c78e9375d55b035f0e75e13d8dd278c2ddf4618fbe005c26f13d249b303f",
111
+ "checksum" => "b7b34ed9243059259a5fa10daa65adb6c4748e8b9715f64e6e559f88aa7aec03",
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" => "f9bfd06e0107f3a491ff6009a1e8c0d6d2598c128e8ee84bd852a2f8d3e8e16b",
117
+ "checksum" => "2e197b99b7052357e466a7d93450d87cbdc07d0e5749630e98adcf7c7fad8936",
118
118
  "filename" => "appsignal-aarch64-linux-musl-all-static.tar.gz"
119
119
  },
120
120
  "dynamic" => {
121
- "checksum" => "187a9a8e58617a0f78077b412dee18351cc975af41500443d7450fc44f2f65d3",
121
+ "checksum" => "ace9ab040e4c412f9d6c0b7d2209388e81f40882f8719a67cc30c60e0bdc50ed",
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" => "cf8043c78e0e628cf0747dd57327d6c0ffd4147d3bff7b2426110fd2fada0960",
127
+ "checksum" => "0c263f693ba5c06373b9fb4062f14d2b6bf1ff33bc0d737abcccc77bab66a634",
128
128
  "filename" => "appsignal-x86_64-freebsd-all-static.tar.gz"
129
129
  },
130
130
  "dynamic" => {
131
- "checksum" => "072c2312aadf961c3f851552ee255db77f0c4050e47d438dd9f82e536b68bcd6",
131
+ "checksum" => "7545a50f4d127da466e87d9504df4911ed9f8b0ec67b86023e08f8a58ea2b8c2",
132
132
  "filename" => "appsignal-x86_64-freebsd-all-dynamic.tar.gz"
133
133
  }
134
134
  },
135
135
  "amd64-freebsd" => {
136
136
  "static" => {
137
- "checksum" => "cf8043c78e0e628cf0747dd57327d6c0ffd4147d3bff7b2426110fd2fada0960",
137
+ "checksum" => "0c263f693ba5c06373b9fb4062f14d2b6bf1ff33bc0d737abcccc77bab66a634",
138
138
  "filename" => "appsignal-x86_64-freebsd-all-static.tar.gz"
139
139
  },
140
140
  "dynamic" => {
141
- "checksum" => "072c2312aadf961c3f851552ee255db77f0c4050e47d438dd9f82e536b68bcd6",
141
+ "checksum" => "7545a50f4d127da466e87d9504df4911ed9f8b0ec67b86023e08f8a58ea2b8c2",
142
142
  "filename" => "appsignal-x86_64-freebsd-all-dynamic.tar.gz"
143
143
  }
144
144
  }
@@ -3,15 +3,6 @@
3
3
  module Appsignal
4
4
  module CheckIn
5
5
  class Cron
6
- class << self
7
- # @api private
8
- def transmitter
9
- @transmitter ||= Appsignal::Transmitter.new(
10
- "#{Appsignal.config[:logging_endpoint]}/check_ins/json"
11
- )
12
- end
13
- end
14
-
15
6
  # @api private
16
7
  attr_reader :identifier, :digest
17
8
 
@@ -21,11 +12,11 @@ module Appsignal
21
12
  end
22
13
 
23
14
  def start
24
- transmit_event("start")
15
+ CheckIn.scheduler.schedule(event("start"))
25
16
  end
26
17
 
27
18
  def finish
28
- transmit_event("finish")
19
+ CheckIn.scheduler.schedule(event("finish"))
29
20
  end
30
21
 
31
22
  private
@@ -39,29 +30,6 @@ module Appsignal
39
30
  :check_in_type => "cron"
40
31
  }
41
32
  end
42
-
43
- def transmit_event(kind)
44
- unless Appsignal.active?
45
- Appsignal.internal_logger.debug(
46
- "AppSignal not active, not transmitting cron check-in event"
47
- )
48
- return
49
- end
50
-
51
- response = self.class.transmitter.transmit(event(kind))
52
-
53
- if response.code.to_i >= 200 && response.code.to_i < 300
54
- Appsignal.internal_logger.debug(
55
- "Transmitted cron check-in `#{identifier}` (#{digest}) #{kind} event"
56
- )
57
- else
58
- Appsignal.internal_logger.error(
59
- "Failed to transmit cron check-in #{kind} event: status code was #{response.code}"
60
- )
61
- end
62
- rescue => e
63
- Appsignal.internal_logger.error("Failed to transmit cron check-in #{kind} event: #{e}")
64
- end
65
33
  end
66
34
  end
67
35
  end
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module CheckIn
5
+ class Scheduler
6
+ INITIAL_DEBOUNCE_SECONDS = 0.1
7
+ BETWEEN_TRANSMISSIONS_DEBOUNCE_SECONDS = 10
8
+
9
+ def initialize
10
+ # The mutex is used to synchronize access to the events array, the
11
+ # waker thread and the main thread, as well as queue writes
12
+ # (which depend on the events array) and closes (so they do not
13
+ # happen at the same time that an event is added to the scheduler)
14
+ @mutex = Mutex.new
15
+ # The transmitter thread will be started when an event is first added.
16
+ @thread = nil
17
+ @queue = Thread::Queue.new
18
+ # Scheduled events that have not been sent to the transmitter thread
19
+ # yet. A copy of this array is pushed to the queue by the waker thread
20
+ # after it has awaited the debounce period.
21
+ @events = []
22
+ # The waker thread is used to schedule debounces. It will be started
23
+ # when an event is first added.
24
+ @waker = nil
25
+ # For internal testing purposes.
26
+ @transmitted = 0
27
+ end
28
+
29
+ def schedule(event)
30
+ unless Appsignal.active?
31
+ Appsignal.internal_logger.debug(
32
+ "Cannot transmit #{describe([event])}: AppSignal is not active"
33
+ )
34
+ return
35
+ end
36
+
37
+ @mutex.synchronize do
38
+ if @queue.closed?
39
+ Appsignal.internal_logger.debug(
40
+ "Cannot transmit #{describe([event])}: AppSignal is stopped"
41
+ )
42
+ return
43
+ end
44
+ add_event(event)
45
+ # If we're not already waiting to be awakened from a scheduled
46
+ # debounce, schedule a short debounce, which will push the events
47
+ # to the queue and schedule a long debounce.
48
+ start_waker(INITIAL_DEBOUNCE_SECONDS) if @waker.nil?
49
+
50
+ Appsignal.internal_logger.debug(
51
+ "Scheduling #{describe([event])} to be transmitted"
52
+ )
53
+
54
+ # Make sure to start the thread after an event has been added.
55
+ @thread ||= Thread.new(&method(:run))
56
+ end
57
+ end
58
+
59
+ def stop
60
+ @mutex.synchronize do
61
+ # Flush all events before closing the queue.
62
+ push_events
63
+ rescue ClosedQueueError
64
+ # The queue is already closed (by a previous call to `#stop`)
65
+ # so it is not possible to push events to it anymore.
66
+ ensure
67
+ # Ensure calling `#stop` closes the queue and kills
68
+ # the waker thread, disallowing any further events from being
69
+ # scheduled with `#schedule`.
70
+ stop_waker
71
+ @queue.close
72
+
73
+ # Block until the thread has finished.
74
+ @thread&.join
75
+ end
76
+ end
77
+
78
+ # @api private
79
+ # For internal testing purposes.
80
+ attr_reader :thread, :waker, :queue, :events, :transmitted
81
+
82
+ private
83
+
84
+ def run
85
+ loop do
86
+ events = @queue.pop
87
+ break if events.nil?
88
+
89
+ transmit(events)
90
+ @transmitted += 1
91
+ end
92
+ end
93
+
94
+ def transmit(events)
95
+ description = describe(events)
96
+
97
+ begin
98
+ response = CheckIn.transmitter.transmit(events, :format => :ndjson)
99
+
100
+ if (200...300).include?(response.code.to_i)
101
+ Appsignal.internal_logger.debug(
102
+ "Transmitted #{description}"
103
+ )
104
+ else
105
+ Appsignal.internal_logger.error(
106
+ "Failed to transmit #{description}: #{response.code} status code"
107
+ )
108
+ end
109
+ rescue => e
110
+ Appsignal.internal_logger.error("Failed to transmit #{description}: #{e.message}")
111
+ end
112
+ end
113
+
114
+ def describe(events)
115
+ if events.empty?
116
+ # This shouldn't happen.
117
+ "no check-in events"
118
+ elsif events.length > 1
119
+ "#{events.length} check-in events"
120
+ else
121
+ event = events.first
122
+ if event[:check_in_type] == "cron"
123
+ "cron check-in `#{event[:identifier] || "unknown"}` " \
124
+ "#{event[:kind] || "unknown"} event (digest #{event[:digest] || "unknown"})" \
125
+ else
126
+ "unknown check-in event"
127
+ end
128
+ end
129
+ end
130
+
131
+ # Must be called from within a `@mutex.synchronize` block.
132
+ def add_event(event)
133
+ # Remove redundant events, keeping the newly added one, which
134
+ # should be the one with the most recent timestamp.
135
+ if event[:check_in_type] == "cron"
136
+ # Remove any existing cron check-in event with the same identifier,
137
+ # digest and kind as the one we're adding.
138
+ @events.reject! do |existing_event|
139
+ next unless existing_event[:identifier] == event[:identifier] &&
140
+ existing_event[:digest] == event[:digest] &&
141
+ existing_event[:kind] == event[:kind] &&
142
+ existing_event[:check_in_type] == "cron"
143
+
144
+ Appsignal.internal_logger.debug(
145
+ "Replacing previously scheduled #{describe([existing_event])}"
146
+ )
147
+
148
+ true
149
+ end
150
+ end
151
+
152
+ @events << event
153
+ end
154
+
155
+ # Must be called from within a `@mutex.synchronize` block.
156
+ def start_waker(debounce)
157
+ stop_waker
158
+
159
+ @waker = Thread.new do
160
+ sleep(debounce)
161
+
162
+ @mutex.synchronize do
163
+ # Make sure this waker doesn't get killed, so it can push
164
+ # events and schedule a new waker.
165
+ @waker = nil
166
+ push_events
167
+ end
168
+ end
169
+ end
170
+
171
+ # Must be called from within a `@mutex.synchronize` block.
172
+ def stop_waker
173
+ @waker&.kill
174
+ @waker&.join
175
+ @waker = nil
176
+ end
177
+
178
+ # Must be called from within a `@mutex.synchronize` block.
179
+ def push_events
180
+ return if @events.empty?
181
+
182
+ # Push a copy of the events to the queue, and clear the events array.
183
+ # This ensures that `@events` always contains events that have not
184
+ # yet been pushed to the queue.
185
+ @queue.push(@events.dup)
186
+ @events.clear
187
+
188
+ start_waker(BETWEEN_TRANSMISSIONS_DEBOUNCE_SECONDS)
189
+ end
190
+ end
191
+ end
192
+ end
@@ -39,8 +39,26 @@ module Appsignal
39
39
  cron.finish
40
40
  output
41
41
  end
42
+
43
+ # @api private
44
+ def transmitter
45
+ @transmitter ||= Transmitter.new(
46
+ "#{Appsignal.config[:logging_endpoint]}/check_ins/json"
47
+ )
48
+ end
49
+
50
+ # @api private
51
+ def scheduler
52
+ @scheduler ||= Scheduler.new
53
+ end
54
+
55
+ # @api private
56
+ def stop
57
+ scheduler&.stop
58
+ end
42
59
  end
43
60
  end
44
61
  end
45
62
 
63
+ require "appsignal/check_in/scheduler"
46
64
  require "appsignal/check_in/cron"
@@ -150,7 +150,7 @@ module Appsignal
150
150
  ENV.fetch("APPSIGNAL_DIAGNOSE_ENDPOINT", DIAGNOSE_ENDPOINT),
151
151
  Appsignal.config
152
152
  )
153
- response = transmitter.transmit(:diagnose => data)
153
+ response = transmitter.transmit({ :diagnose => data })
154
154
 
155
155
  unless response.code == "200"
156
156
  puts " Error: Something went wrong while submitting the report " \
@@ -15,6 +15,13 @@ module Appsignal
15
15
 
16
16
  # @api private
17
17
  def self.add_loader_defaults(name, env: nil, root_path: nil, **options)
18
+ if Appsignal.config
19
+ Appsignal.internal_logger.warn(
20
+ "The config defaults from the '#{name}' loader are ignored since " \
21
+ "the AppSignal config has already been initialized."
22
+ )
23
+ end
24
+
18
25
  loader_defaults << {
19
26
  :name => name,
20
27
  :env => env,
@@ -24,6 +24,7 @@ module Appsignal
24
24
  class AtExitCallback
25
25
  def self.call
26
26
  error = $! # rubocop:disable Style/SpecialGlobalVars
27
+ return unless error
27
28
  return if ignored_error?(error)
28
29
  return if Appsignal::Transaction.last_errors.include?(error)
29
30
 
@@ -35,7 +36,8 @@ module Appsignal
35
36
 
36
37
  IGNORED_ERRORS = [
37
38
  # Normal exits from the application we do not need to report
38
- SystemExit
39
+ SystemExit,
40
+ SignalException
39
41
  ].freeze
40
42
 
41
43
  def self.ignored_error?(error)
@@ -7,10 +7,14 @@ module Appsignal
7
7
  register :puma
8
8
 
9
9
  def dependencies_present?
10
- defined?(::Puma)
10
+ defined?(::Puma) &&
11
+ Gem::Version.new(Puma::Const::VERSION) >= Gem::Version.new("3.0.0")
11
12
  end
12
13
 
13
14
  def install
15
+ require "appsignal/integrations/puma"
16
+ ::Puma::Server.prepend(Appsignal::Integrations::PumaServer)
17
+
14
18
  return unless defined?(::Puma::Cluster)
15
19
 
16
20
  # For clustered mode with multiple workers
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module Integrations
5
+ # @api private
6
+ module PumaServer
7
+ def lowlevel_error(error, env, response_status = 500)
8
+ response =
9
+ if method(:lowlevel_error).super_method.arity.abs == 3 # Puma >= 5
10
+ super
11
+ else # Puma <= 4
12
+ super(error, env)
13
+ end
14
+
15
+ unless PumaServerHelper.ignored_error?(error)
16
+ Appsignal.report_error(error) do |transaction|
17
+ Appsignal::Rack::ApplyRackRequest
18
+ .new(::Rack::Request.new(env))
19
+ .apply_to(transaction)
20
+ transaction.add_tags(
21
+ :reported_by => :puma_lowlevel_error,
22
+ :response_status => response_status
23
+ )
24
+ end
25
+ end
26
+
27
+ response
28
+ end
29
+ end
30
+
31
+ module PumaServerHelper
32
+ IGNORED_ERRORS = [
33
+ # Ignore internal Puma Client IO errors
34
+ # https://github.com/puma/puma/blob/9ee922d28e1fffd02c1d5480a9e13376f92f46a3/lib/puma/server.rb#L536-L544
35
+ "Puma::MiniSSL::SSLError",
36
+ "Puma::HttpParserError",
37
+ "Puma::HttpParserError501"
38
+ ].freeze
39
+
40
+ def self.ignored_error?(error)
41
+ IGNORED_ERRORS.include?(error.class.to_s)
42
+ end
43
+ end
44
+ end
45
+ end