appsignal 4.0.9-java → 4.1.1-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbb72b56837756abd3ec4d56855208a44ced0aabac240846bc13e685365ff79e
4
- data.tar.gz: bca5d410c1a2fb91812d2cc63b19a04b0db6cafd18c368a1ece930915bbf0a66
3
+ metadata.gz: 51f686ec6cbdb470c83a24eee9a221026e3d5b4a9c8fd79c1b3e311480bc8da3
4
+ data.tar.gz: ef77980f54715f5b4a05ee218df8a0e51b3f867643ca793ef790dda3f324e05c
5
5
  SHA512:
6
- metadata.gz: cd24c802a14d24e154f99493a242c815b85ab96c7049d589d0d1e26eb097d53e91eecc7732e97b8f2ea7415edef21df79bfbcbe1015f7cb5823f292ff30f9939
7
- data.tar.gz: 5e3d0d285a6ebf6a7b8beae27fdaceec6365aa016fa1181e1572f42054fced494771eac6fd62434efb5cd4dae7ee7ca542aa1b6ac5d680139ec7d24dfd238c55
6
+ metadata.gz: 91c3f646a11d9899994281b529d7f0d6c6ffd31fd4444704ea01528877815fc6b44faf808b95dca6e1a8b12048c532e81447b0dddd47d2f7363a56d1d772777c
7
+ data.tar.gz: 0cf285924ec8dd63cbcdb89a0354f76dfdc86d6737dc32c80f2538ae136b8653763b67a3507238db866751869d7c17f817eb2e766b4b7860584f4618be550d58
data/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # AppSignal for Ruby gem Changelog
2
2
 
3
+ ## 4.1.1
4
+
5
+ _Published on 2024-09-28._
6
+
7
+ ### Changed
8
+
9
+ - Add the `reported_by` tag to errors reported by the Rails error reporter so the source of the error is easier to identify. (patch [ff98ed67](https://github.com/appsignal/appsignal-ruby/commit/ff98ed677bf30242c51261bf7e44c9b6ba2f33ac))
10
+
11
+ ### Fixed
12
+
13
+ - Fix no AppSignal internal logs being logged from Capistrano tasks. (patch [089d0325](https://github.com/appsignal/appsignal-ruby/commit/089d03251c3dc8b83658a4ebfade51ab6bed1771))
14
+ - Report all the config options set via `Appsignal.config` in the DSL config source in the diagnose report. Previously, it would only report the options from the last time `Appsignal.configure` was called. (patch [27b9aff7](https://github.com/appsignal/appsignal-ruby/commit/27b9aff7776646dfef6c55fa589024a71052e70b))
15
+ - Fix 'no implicit conversion of Pathname into String' error when parsing backtrace lines of error causes in Rails apps. (patch [b767f269](https://github.com/appsignal/appsignal-ruby/commit/b767f269c41cb7625d6869d8e8acb9b288292d19))
16
+
17
+ ## 4.1.0
18
+
19
+ _Published on 2024-09-26._
20
+
21
+ ### Added
22
+
23
+ - Add support for heartbeat check-ins.
24
+
25
+ Use the `Appsignal::CheckIn.heartbeat` method to send a single heartbeat check-in event from your application. This can be used, for example, in your application's main loop:
26
+
27
+ ```ruby
28
+ loop do
29
+ Appsignal::CheckIn.heartbeat("job_processor")
30
+ process_job
31
+ end
32
+ ```
33
+
34
+ Heartbeats are deduplicated and sent asynchronously, without blocking the current thread. Regardless of how often the `.heartbeat` method is called, at most one heartbeat with the same identifier will be sent every ten seconds.
35
+
36
+ Pass `continuous: true` as the second argument to send heartbeats continuously during the entire lifetime of the current process. This can be used, for example, after your application has finished its boot process:
37
+
38
+ ```ruby
39
+ def main
40
+ start_app
41
+ Appsignal::CheckIn.heartbeat("my_app", continuous: true)
42
+ end
43
+ ```
44
+
45
+ (minor [7ae7152c](https://github.com/appsignal/appsignal-ruby/commit/7ae7152cddae7c257e9d62d3bf2433cce1f4287d))
46
+ - Include the first backtrace line from error causes to show where each cause originated in the interface. (patch [496b035a](https://github.com/appsignal/appsignal-ruby/commit/496b035a3510dbb6dc47c7c59172f488ec55c986))
47
+
3
48
  ## 4.0.9
4
49
 
5
50
  _Published on 2024-09-17._
@@ -22,13 +22,11 @@ module Appsignal
22
22
  private
23
23
 
24
24
  def event(kind)
25
- {
25
+ Event.cron(
26
26
  :identifier => @identifier,
27
27
  :digest => @digest,
28
- :kind => kind,
29
- :timestamp => Time.now.utc.to_i,
30
- :check_in_type => "cron"
31
- }
28
+ :kind => kind
29
+ )
32
30
  end
33
31
  end
34
32
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module CheckIn
5
+ # @api private
6
+ class Event
7
+ class << self
8
+ def new(check_in_type:, identifier:, digest: nil, kind: nil)
9
+ {
10
+ :identifier => identifier,
11
+ :digest => digest,
12
+ :kind => kind,
13
+ :timestamp => Time.now.utc.to_i,
14
+ :check_in_type => check_in_type
15
+ }.compact
16
+ end
17
+
18
+ def cron(identifier:, digest:, kind:)
19
+ new(
20
+ :check_in_type => "cron",
21
+ :identifier => identifier,
22
+ :digest => digest,
23
+ :kind => kind
24
+ )
25
+ end
26
+
27
+ def heartbeat(identifier:)
28
+ new(
29
+ :check_in_type => "heartbeat",
30
+ :identifier => identifier
31
+ )
32
+ end
33
+
34
+ def redundant?(event, other)
35
+ return false if
36
+ other[:check_in_type] != event[:check_in_type] ||
37
+ other[:identifier] != event[:identifier]
38
+
39
+ return false if event[:check_in_type] == "cron" && (
40
+ other[:digest] != event[:digest] ||
41
+ other[:kind] != event[:kind]
42
+ )
43
+
44
+ return false if
45
+ event[:check_in_type] != "cron" &&
46
+ event[:check_in_type] != "heartbeat"
47
+
48
+ true
49
+ end
50
+
51
+ def describe(events)
52
+ if events.empty?
53
+ # This shouldn't happen.
54
+ "no check-in events"
55
+ elsif events.length > 1
56
+ "#{events.length} check-in events"
57
+ else
58
+ event = events.first
59
+ if event[:check_in_type] == "cron"
60
+ "cron check-in `#{event[:identifier] || "unknown"}` " \
61
+ "#{event[:kind] || "unknown"} event (digest #{event[:digest] || "unknown"})"
62
+ elsif event[:check_in_type] == "heartbeat"
63
+ "heartbeat check-in `#{event[:identifier] || "unknown"}` event"
64
+ else
65
+ "unknown check-in event"
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -29,7 +29,7 @@ module Appsignal
29
29
  def schedule(event)
30
30
  unless Appsignal.active?
31
31
  Appsignal.internal_logger.debug(
32
- "Cannot transmit #{describe([event])}: AppSignal is not active"
32
+ "Cannot transmit #{Event.describe([event])}: AppSignal is not active"
33
33
  )
34
34
  return
35
35
  end
@@ -37,7 +37,7 @@ module Appsignal
37
37
  @mutex.synchronize do
38
38
  if @queue.closed?
39
39
  Appsignal.internal_logger.debug(
40
- "Cannot transmit #{describe([event])}: AppSignal is stopped"
40
+ "Cannot transmit #{Event.describe([event])}: AppSignal is stopped"
41
41
  )
42
42
  return
43
43
  end
@@ -48,7 +48,7 @@ module Appsignal
48
48
  start_waker(INITIAL_DEBOUNCE_SECONDS) if @waker.nil?
49
49
 
50
50
  Appsignal.internal_logger.debug(
51
- "Scheduling #{describe([event])} to be transmitted"
51
+ "Scheduling #{Event.describe([event])} to be transmitted"
52
52
  )
53
53
 
54
54
  # Make sure to start the thread after an event has been added.
@@ -92,7 +92,7 @@ module Appsignal
92
92
  end
93
93
 
94
94
  def transmit(events)
95
- description = describe(events)
95
+ description = Event.describe(events)
96
96
 
97
97
  begin
98
98
  response = CheckIn.transmitter.transmit(events, :format => :ndjson)
@@ -110,42 +110,18 @@ module Appsignal
110
110
  end
111
111
  end
112
112
 
113
- def describe(events)
114
- if events.empty?
115
- # This shouldn't happen.
116
- "no check-in events"
117
- elsif events.length > 1
118
- "#{events.length} check-in events"
119
- else
120
- event = events.first
121
- if event[:check_in_type] == "cron"
122
- "cron check-in `#{event[:identifier] || "unknown"}` " \
123
- "#{event[:kind] || "unknown"} event (digest #{event[:digest] || "unknown"})" \
124
- else
125
- "unknown check-in event"
126
- end
127
- end
128
- end
129
-
130
113
  # Must be called from within a `@mutex.synchronize` block.
131
114
  def add_event(event)
132
115
  # Remove redundant events, keeping the newly added one, which
133
116
  # should be the one with the most recent timestamp.
134
- if event[:check_in_type] == "cron"
135
- # Remove any existing cron check-in event with the same identifier,
136
- # digest and kind as the one we're adding.
137
- @events.reject! do |existing_event|
138
- next unless existing_event[:identifier] == event[:identifier] &&
139
- existing_event[:digest] == event[:digest] &&
140
- existing_event[:kind] == event[:kind] &&
141
- existing_event[:check_in_type] == "cron"
117
+ @events.reject! do |existing_event|
118
+ next unless Event.redundant?(event, existing_event)
142
119
 
143
- Appsignal.internal_logger.debug(
144
- "Replacing previously scheduled #{describe([existing_event])}"
145
- )
120
+ Appsignal.internal_logger.debug(
121
+ "Replacing previously scheduled #{Event.describe([existing_event])}"
122
+ )
146
123
 
147
- true
148
- end
124
+ true
149
125
  end
150
126
 
151
127
  @events << event
@@ -2,10 +2,21 @@
2
2
 
3
3
  module Appsignal
4
4
  module CheckIn
5
+ HEARTBEAT_CONTINUOUS_INTERVAL_SECONDS = 30
5
6
  class << self
7
+ # @api private
8
+ def continuous_heartbeats
9
+ @continuous_heartbeats ||= []
10
+ end
11
+
12
+ # @api private
13
+ def kill_continuous_heartbeats
14
+ continuous_heartbeats.each(&:kill)
15
+ end
16
+
6
17
  # Track cron check-ins.
7
18
  #
8
- # Track the execution of certain processes by sending a cron check-in.
19
+ # Track the execution of scheduled processes by sending a cron check-in.
9
20
  #
10
21
  # To track the duration of a piece of code, pass a block to {.cron}
11
22
  # to report both when the process starts, and when it finishes.
@@ -40,6 +51,37 @@ module Appsignal
40
51
  output
41
52
  end
42
53
 
54
+ # Track heartbeat check-ins.
55
+ #
56
+ # Track the execution of long-lived processes by sending a heartbeat
57
+ # check-in.
58
+ #
59
+ # @example Send a heartbeat check-in
60
+ # Appsignal::CheckIn.heartbeat("main_loop")
61
+ #
62
+ # @param identifier [String] identifier of the heartbeat check-in to report.
63
+ # @param continuous [Boolean] whether the heartbeats should be sent continuously
64
+ # during the lifetime of the process. Defaults to `false`.
65
+ # @yield the block to monitor.
66
+ # @return [void]
67
+ # @since 4.1.0
68
+ # @see https://docs.appsignal.com/check-ins/heartbeat
69
+ def heartbeat(identifier, continuous: false)
70
+ if continuous
71
+ continuous_heartbeats << Thread.new do
72
+ loop do
73
+ heartbeat(identifier)
74
+ sleep HEARTBEAT_CONTINUOUS_INTERVAL_SECONDS
75
+ end
76
+ end
77
+
78
+ return
79
+ end
80
+
81
+ event = Event.heartbeat(:identifier => identifier)
82
+ scheduler.schedule(event)
83
+ end
84
+
43
85
  # @api private
44
86
  def transmitter
45
87
  @transmitter ||= Transmitter.new(
@@ -60,5 +102,6 @@ module Appsignal
60
102
  end
61
103
  end
62
104
 
105
+ require "appsignal/check_in/event"
63
106
  require "appsignal/check_in/scheduler"
64
107
  require "appsignal/check_in/cron"
@@ -180,36 +180,19 @@ module Appsignal
180
180
  "APPSIGNAL_CPU_COUNT" => :cpu_count
181
181
  }.freeze
182
182
 
183
- # @attribute [r] system_config
184
- # Config detected on the system level.
185
- # Used in diagnose report.
186
- # @api private
187
- # @return [Hash]
188
- # @!attribute [r] file_config
189
- # Config loaded from `config/appsignal.yml` config file.
190
- # Used in diagnose report.
191
- # @api private
192
- # @return [Hash]
193
- # @!attribute [r] env_config
194
- # Config loaded from the system environment.
195
- # Used in diagnose report.
196
- # @api private
197
- # @return [Hash]
198
- # @!attribute [r] config_hash
199
- # Config used by the AppSignal gem.
200
- # Combined Hash of the {system_config}, {file_config}, {env_config}
201
- # attributes.
202
- # @see #[]
203
- # @see #[]=
204
- # @api private
205
- # @return [Hash]
183
+ # @api private
184
+ attr_reader :root_path, :env, :config_hash
206
185
 
186
+ # List of config option sources. If a config option was set by a source,
187
+ # it's listed in that source's config options hash.
188
+ #
189
+ # These options are merged as the config is initialized.
190
+ # Their values cannot be changed after the config is initialized.
191
+ #
192
+ # Used by the diagnose report to list which value was read from which source.
207
193
  # @api private
208
- attr_accessor :root_path, :env, :config_hash
209
194
  attr_reader :system_config, :loaders_config, :initial_config, :file_config,
210
195
  :env_config, :override_config, :dsl_config
211
- # @api private
212
- attr_accessor :logger
213
196
 
214
197
  # Initialize a new configuration object for AppSignal.
215
198
  #
@@ -217,9 +200,6 @@ module Appsignal
217
200
  # @param env [String] The environment to load when AppSignal is started. It
218
201
  # will look for an environment with this name in the `config/appsignal.yml`
219
202
  # config file.
220
- # @param logger [Logger] The logger to use for the AppSignal gem. This is
221
- # used by the configuration class only. Default:
222
- # {Appsignal.internal_logger}. See also {Appsignal.start}.
223
203
  #
224
204
  # @api private
225
205
  # @see https://docs.appsignal.com/ruby/configuration/
@@ -230,13 +210,11 @@ module Appsignal
230
210
  # How to integrate AppSignal manually
231
211
  def initialize(
232
212
  root_path,
233
- env,
234
- logger = Appsignal.internal_logger
213
+ env
235
214
  )
236
- @root_path = root_path
215
+ @root_path = root_path.to_s
237
216
  @config_file_error = false
238
217
  @config_file = config_file
239
- @logger = logger
240
218
  @valid = false
241
219
 
242
220
  @env = env.to_s
@@ -402,7 +380,7 @@ module Appsignal
402
380
 
403
381
  # @api private
404
382
  def merge_dsl_options(options)
405
- @dsl_config = options
383
+ @dsl_config.merge!(options)
406
384
  merge(options)
407
385
  end
408
386
 
@@ -426,7 +404,7 @@ module Appsignal
426
404
  push_api_key = config_hash[:push_api_key] || ""
427
405
  if push_api_key.strip.empty?
428
406
  @valid = false
429
- @logger.error "Push API key not set after loading config"
407
+ logger.error "Push API key not set after loading config"
430
408
  else
431
409
  @valid = true
432
410
  end
@@ -445,6 +423,10 @@ module Appsignal
445
423
 
446
424
  private
447
425
 
426
+ def logger
427
+ Appsignal.internal_logger
428
+ end
429
+
448
430
  def config_file
449
431
  @config_file ||=
450
432
  root_path.nil? ? nil : File.join(root_path, "config", "appsignal.yml")
@@ -546,7 +528,7 @@ module Appsignal
546
528
 
547
529
  def merge(new_config)
548
530
  new_config.each do |key, value|
549
- @logger.debug("Config key '#{key}' is being overwritten") unless config_hash[key].nil?
531
+ logger.debug("Config key '#{key}' is being overwritten") unless config_hash[key].nil?
550
532
  config_hash[key] = value
551
533
  end
552
534
  end
@@ -10,12 +10,12 @@ namespace :appsignal do
10
10
 
11
11
  appsignal_config = Appsignal::Config.new(
12
12
  Dir.pwd,
13
- appsignal_env,
14
- Logger.new(StringIO.new)
13
+ appsignal_env
15
14
  ).tap do |c|
16
15
  c.merge_dsl_options(fetch(:appsignal_config, {}))
17
16
  c.validate
18
17
  end
18
+ Appsignal._start_logger
19
19
 
20
20
  if appsignal_config&.active?
21
21
  marker_data = {
@@ -18,12 +18,12 @@ module Appsignal
18
18
 
19
19
  appsignal_config = Appsignal::Config.new(
20
20
  ENV.fetch("PWD", nil),
21
- env,
22
- Appsignal::Utils::IntegrationLogger.new(StringIO.new)
21
+ env
23
22
  ).tap do |c|
24
23
  c.merge_dsl_options(fetch(:appsignal_config, {}))
25
24
  c.validate
26
25
  end
26
+ Appsignal._start_logger
27
27
 
28
28
  if appsignal_config&.active?
29
29
  marker_data = {
@@ -94,6 +94,7 @@ module Appsignal
94
94
  transaction.set_action(action_name) if action_name
95
95
  transaction.add_custom_data(custom_data) if custom_data
96
96
 
97
+ tags[:reported_by] = :rails_error_reporter
97
98
  tags[:severity] = severity
98
99
  tags[:source] = source.to_s if source
99
100
  transaction.add_tags(tags)
@@ -627,7 +627,8 @@ module Appsignal
627
627
  causes_sample_data = causes.map do |e|
628
628
  {
629
629
  :name => e.class.name,
630
- :message => cleaned_error_message(e)
630
+ :message => cleaned_error_message(e),
631
+ :first_line => first_formatted_backtrace_line(e)
631
632
  }
632
633
  end
633
634
 
@@ -639,6 +640,36 @@ module Appsignal
639
640
  )
640
641
  end
641
642
 
643
+ BACKTRACE_REGEX =
644
+ %r{(?<gem>[\w-]+ \(.+\) )?(?<path>:?/?\w+?.+?):(?<line>:?\d+)(?<group>:in `(?<method>.+)')?$}.freeze # rubocop:disable Layout/LineLength
645
+
646
+ def first_formatted_backtrace_line(error)
647
+ backtrace = cleaned_backtrace(error.backtrace)
648
+ first_line = backtrace&.first
649
+ return unless first_line
650
+
651
+ captures = BACKTRACE_REGEX.match(first_line)
652
+ return unless captures
653
+
654
+ captures.named_captures
655
+ .merge("original" => first_line)
656
+ .tap do |c|
657
+ config = Appsignal.config
658
+ c.delete("group") # Unused key, only for easier matching
659
+ # Strip of whitespace at the end of the gem name
660
+ c["gem"] = c["gem"]&.strip
661
+ # Strip the app path from the path if present
662
+ root_path = config.root_path
663
+ if c["path"].start_with?(root_path)
664
+ c["path"].delete_prefix!(root_path)
665
+ # Relative paths shouldn't start with a slash
666
+ c["path"].delete_prefix!("/")
667
+ end
668
+ # Add revision for linking to the repository from the UI
669
+ c["revision"] = config[:revision]
670
+ end
671
+ end
672
+
642
673
  def set_sample_data(key, data)
643
674
  return unless key && data
644
675
 
@@ -107,7 +107,7 @@ module Appsignal
107
107
  if ca_file && File.exist?(ca_file) && File.readable?(ca_file)
108
108
  http.ca_file = ca_file
109
109
  else
110
- config.logger.warn "Ignoring non-existing or unreadable " \
110
+ Appsignal.internal_logger.warn "Ignoring non-existing or unreadable " \
111
111
  "`ca_file_path`: #{ca_file}"
112
112
  end
113
113
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "4.0.9"
4
+ VERSION = "4.1.1"
5
5
  end
data/lib/appsignal.rb CHANGED
@@ -244,8 +244,7 @@ module Appsignal
244
244
  else
245
245
  @config = Config.new(
246
246
  root_path || Config.determine_root_path,
247
- Config.determine_env(env),
248
- Appsignal.internal_logger
247
+ Config.determine_env(env)
249
248
  )
250
249
  end
251
250
 
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: 4.0.9
4
+ version: 4.1.1
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: 2024-09-17 00:00:00.000000000 Z
13
+ date: 2024-09-28 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: logger
@@ -181,6 +181,7 @@ files:
181
181
  - lib/appsignal/capistrano.rb
182
182
  - lib/appsignal/check_in.rb
183
183
  - lib/appsignal/check_in/cron.rb
184
+ - lib/appsignal/check_in/event.rb
184
185
  - lib/appsignal/check_in/scheduler.rb
185
186
  - lib/appsignal/cli.rb
186
187
  - lib/appsignal/cli/demo.rb