nurse_andrea 0.2.1 → 0.2.3

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: 43221daa0087ee909cc91fd846732ac477aeb2675d56ba02ecd7e3b301e808d7
4
- data.tar.gz: 519daf63d6c80e238fe943d4a08f2bfba4ba365f9d5fa7492512e1df099a29f7
3
+ metadata.gz: ae308e57dd4f7a8bf6182f34e49a8223cc695448e9a0ef0146b37396e6deea19
4
+ data.tar.gz: 7323b870d40f057ac56130b11061ca84bb9d9db9f97f61697c1234d392df296b
5
5
  SHA512:
6
- metadata.gz: cd67cf5a3418cc74ec896ba1df43f62b3add0a2e266ae65aad02c2bb4f85d03c3cad4477352569fdaa568e5cae902829efe2e117fb7389cec3428a9054d429d1
7
- data.tar.gz: 91dc589d97a93d19814cd0ad5b0eb27e9d3c097ef8bc7ecbd130187ea96f9d973577e7009abe2ff9295027fd57aae1bd2102e1422a7941295a50c628bd310d9e
6
+ metadata.gz: 4848f27cff94597a94a21b6fe366da00c547831aad652717e59a41255d25005142bf6a1b8fb9181f37a7358ee88e08e4e597b5e1c83cdff8da052ea716c2b583
7
+ data.tar.gz: f01a8a679ddcd4be389125c43ca2cfeca6703acc5dcf29f53a392896406f6384e87aaba578d24fd86d4a8e474400779c47dd2e02b02e3b751131458a604c7ee2
@@ -43,6 +43,7 @@ module NurseAndrea
43
43
  def metrics_url = "#{normalised_host}/api/v1/metrics"
44
44
  def traces_url = "#{normalised_host}/api/v1/traces"
45
45
  def handshake_url = "#{normalised_host}/api/v1/handshake"
46
+ def deploy_url = "#{normalised_host}/api/v1/deploy"
46
47
 
47
48
  def enabled?
48
49
  @enabled
@@ -0,0 +1,35 @@
1
+ module NurseAndrea
2
+ # Public: ship a deploy event to the NurseAndrea backend so the
3
+ # dashboard can render it as a vertical marker on time-series charts
4
+ # and as a chip in the recent-deploys strip.
5
+ #
6
+ # Fire-and-forget: any failure (no token, network error, non-2xx) is
7
+ # logged in debug mode and swallowed so the host application never
8
+ # crashes from a deploy notification.
9
+ module Deploy
10
+ DESCRIPTION_LIMIT = 500
11
+
12
+ def self.call(version:, deployer: nil, environment: "production", description: nil)
13
+ return false unless NurseAndrea.config.valid?
14
+ return false if version.to_s.strip.empty?
15
+
16
+ payload = {
17
+ version: version.to_s,
18
+ deployer: deployer,
19
+ environment: environment,
20
+ description: description.is_a?(String) ? description[0, DESCRIPTION_LIMIT] : description,
21
+ deployed_at: Time.now.utc.iso8601
22
+ }.compact
23
+
24
+ HttpClient.new.post(NurseAndrea.config.deploy_url, payload)
25
+ rescue => e
26
+ NurseAndrea.debug("[NurseAndrea] deploy() error: #{e.class}: #{e.message}")
27
+ false
28
+ end
29
+ end
30
+
31
+ # Convenience top-level: NurseAndrea.deploy(version: "1.4.2")
32
+ def self.deploy(**kwargs)
33
+ Deploy.call(**kwargs)
34
+ end
35
+ end
@@ -14,7 +14,7 @@ module NurseAndrea
14
14
  "cache_delete.active_support" => :on_cache_delete,
15
15
  "perform.active_job" => :on_job_perform,
16
16
  "enqueue.active_job" => :on_job_enqueue,
17
- "deliver.action_mailer" => :on_mailer,
17
+ "deliver.action_mailer" => :on_mailer
18
18
  }.freeze
19
19
 
20
20
  attr_reader :telemetry, :discovered_components
@@ -48,13 +48,14 @@ module NurseAndrea
48
48
  return if event.payload[:name] == "SCHEMA"
49
49
  return if event.payload[:name]&.start_with?("EXPLAIN")
50
50
 
51
- adapter = event.payload[:connection]&.adapter_name rescue nil
51
+ conn = event.payload[:connection]
52
+ adapter = conn&.adapter_name rescue nil
52
53
 
53
54
  # Skip platform's own ClickHouse queries — not the customer's infrastructure
54
55
  return if adapter&.downcase&.include?("clickhouse")
55
56
 
56
57
  tech = adapter_to_tech(adapter)
57
- register_discovery("database", tech) if tech
58
+ register_discovery("database", tech, connection: conn) if tech
58
59
 
59
60
  table = extract_table(event.payload[:sql])
60
61
  @telemetry.record_query(duration_ms: event.duration, table: table)
@@ -100,8 +101,9 @@ module NurseAndrea
100
101
  register_discovery("external", "email")
101
102
  end
102
103
 
103
- def register_discovery(type, tech)
104
+ def register_discovery(type, tech, connection: nil)
104
105
  return if tech.nil? || tech.empty? || tech == "unknown"
106
+ return if self_referential?(connection)
105
107
 
106
108
  key = "#{type}:#{tech}"
107
109
  return if @discovered_components.include?(key)
@@ -113,6 +115,20 @@ module NurseAndrea
113
115
  )
114
116
  end
115
117
 
118
+ # True when the SDK is running inside NurseAndrea itself, OR when
119
+ # the SQL event's connection points at NurseAndrea's own infra.
120
+ # Either way the discovery would be a self-reference, not a
121
+ # customer component. Process-level + connection-level checks are
122
+ # both consulted; the same module powers the env-scanner filter.
123
+ def self_referential?(connection)
124
+ return true if NurseAndrea::SelfFilter.platform_self?
125
+ return false unless connection
126
+
127
+ db_name = connection.current_database.to_s rescue ""
128
+ host = connection.pool&.db_config&.host.to_s rescue ""
129
+ NurseAndrea::SelfFilter.host_matches?(db_name, host)
130
+ end
131
+
116
132
  def adapter_to_tech(adapter_name)
117
133
  case adapter_name&.downcase
118
134
  when "postgresql", "postgis" then "postgresql"
@@ -15,15 +15,21 @@ module NurseAndrea
15
15
  "MONGODB_URI" => "database",
16
16
  "MONGO_URL" => "database",
17
17
  "ELASTICSEARCH_URL" => "search",
18
- "KAFKA_BROKERS" => "queue",
18
+ "KAFKA_BROKERS" => "queue"
19
19
  }.freeze
20
20
 
21
21
  def self.scan
22
+ # Skip env-based discovery entirely when the SDK is loaded inside
23
+ # NurseAndrea itself — every URL we'd find belongs to the platform's
24
+ # own infrastructure, not a customer component.
25
+ return [] if NurseAndrea::SelfFilter.platform_self?
26
+
22
27
  discoveries = []
23
28
 
24
29
  SCAN_MAP.each do |var_name, component_type|
25
30
  url = ENV[var_name]
26
31
  next if url.nil? || url.strip.empty?
32
+ next if NurseAndrea::SelfFilter.host_matches?(url)
27
33
 
28
34
  tech = Sanitizer.extract_tech(url)
29
35
  provider = Sanitizer.extract_provider(url)
@@ -39,7 +45,7 @@ module NurseAndrea
39
45
  discoveries << Sanitizer.sanitize_discovery(raw)
40
46
  end
41
47
 
42
- discoveries.uniq { |d| [d[:type], d[:tech], d[:provider]] }
48
+ discoveries.uniq { |d| [ d[:type], d[:tech], d[:provider] ] }
43
49
  end
44
50
  end
45
51
  end
@@ -10,7 +10,7 @@ module NurseAndrea
10
10
  fly: -> { ENV.key?("FLY_APP_NAME") },
11
11
  heroku: -> { ENV.key?("DYNO") },
12
12
  digitalocean: -> { ENV.key?("DIGITALOCEAN_APP_PLATFORM_COMPONENT_NAME") },
13
- vercel: -> { ENV.key?("VERCEL") },
13
+ vercel: -> { ENV.key?("VERCEL") }
14
14
  }.freeze
15
15
 
16
16
  def self.detect
@@ -0,0 +1,39 @@
1
+ # Suppresses discovery emission when the SDK is loaded inside
2
+ # NurseAndrea itself. Both the InstrumentationSubscriber (hook-based)
3
+ # and the ManagedServiceScanner (env-based) must consult this filter
4
+ # before adding to NurseAndrea.component_discoveries — otherwise the
5
+ # platform's own infrastructure shows up as proposed components on
6
+ # every workspace dashboard.
7
+
8
+ module NurseAndrea
9
+ module SelfFilter
10
+ SELF_INDICATORS = %w[nurseandrea nurse-andrea nurse_andrea].freeze
11
+
12
+ class << self
13
+ def platform_self?
14
+ return @platform_self if defined?(@platform_self)
15
+ @platform_self = compute_platform_self
16
+ end
17
+
18
+ def reset!
19
+ remove_instance_variable(:@platform_self) if defined?(@platform_self)
20
+ end
21
+
22
+ def host_matches?(*candidates)
23
+ candidates.compact.map(&:to_s).map(&:downcase).any? do |s|
24
+ SELF_INDICATORS.any? { |i| s.include?(i) }
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def compute_platform_self
31
+ return false unless defined?(Rails) && Rails.application
32
+ app_name = Rails.application.class.module_parent_name.to_s.downcase
33
+ SELF_INDICATORS.any? { |i| app_name.include?(i) }
34
+ rescue
35
+ false
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,3 +1,3 @@
1
1
  module NurseAndrea
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.3"
3
3
  end
data/lib/nurse_andrea.rb CHANGED
@@ -15,6 +15,8 @@ require "nurse_andrea/managed_service_scanner"
15
15
  require "nurse_andrea/component_telemetry"
16
16
  require "nurse_andrea/instrumentation_subscriber"
17
17
  require "nurse_andrea/memory_sampler"
18
+ require "nurse_andrea/deploy"
19
+ require "nurse_andrea/self_filter"
18
20
 
19
21
  require "nurse_andrea/railtie" if defined?(Rails::Railtie)
20
22
  require "nurse_andrea/engine" if defined?(Rails::Engine)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nurse_andrea
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ago AI LLC
@@ -28,6 +28,7 @@ files:
28
28
  - lib/nurse_andrea/backfill.rb
29
29
  - lib/nurse_andrea/component_telemetry.rb
30
30
  - lib/nurse_andrea/configuration.rb
31
+ - lib/nurse_andrea/deploy.rb
31
32
  - lib/nurse_andrea/engine.rb
32
33
  - lib/nurse_andrea/http_client.rb
33
34
  - lib/nurse_andrea/instrumentation_subscriber.rb
@@ -43,6 +44,7 @@ files:
43
44
  - lib/nurse_andrea/queue_depth_reporter.rb
44
45
  - lib/nurse_andrea/railtie.rb
45
46
  - lib/nurse_andrea/sanitizer.rb
47
+ - lib/nurse_andrea/self_filter.rb
46
48
  - lib/nurse_andrea/version.rb
47
49
  - nurse_andrea.gemspec
48
50
  homepage: https://nurseandrea.io