nurse_andrea 0.1.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0ab519a0523b2ddcd45d545236cb6b29f9d4ed1013d67cd771597838dc091cab
4
+ data.tar.gz: eed1931c5dbe44eeb9ee91ea5d2eb3df391681d6972a593d6c8f0faab0f9b733
5
+ SHA512:
6
+ metadata.gz: 2c01153fec0178129a99b5fad4ce5e680006e40022f35b7f10531f5a7150a5247286239dd598af0129f2eb0af4ecf186e33af823f13f65aec1e6308b8d5b8bb5
7
+ data.tar.gz: 1e90759917304321798cea006be838067d9c5f0668a8363a0a29e672721fd70d10b169f4459615e869df0bc1d39227dfdb8b1d108180f18755ec788e87137f81
data/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ ## [0.1.2] - 2026-04-05
2
+
3
+ ### Added
4
+ - `NurseAndrea::Configuration#host` — configurable base URL for all SDK endpoints. Defaults to `https://nurseandrea.io`.
5
+ - All ingest, metrics, traces, and handshake URLs are now derived from `host`. Nothing is hardcoded.
6
+ - `token` / `token=` aliases for `api_key` / `api_key=`
7
+
8
+ ### Migration
9
+ Add `c.host = ENV.fetch("NURSE_ANDREA_HOST", "https://nurseandrea.io")` to your initializer.
10
+
11
+ ## [0.1.1] - 2026-04-05
12
+
13
+ ### Changed
14
+ - LogInterceptor now captures OpenTelemetry trace_id and span_id in log metadata when an OTel span is active
15
+
16
+ ## [0.1.0] - 2026-04-03
17
+
18
+ ### Added
19
+ - Initial release
20
+ - Log shipping via background thread (thread-safe queue, configurable batch size and flush interval)
21
+ - Request metrics via Rack middleware (duration, status, normalized path)
22
+ - Log backfill on first connection (configurable hours, JSON and plaintext log formats)
23
+ - Handshake/status endpoint at `/nurse_andrea/status`
24
+ - Rails install generator (`rails generate nurse_andrea:install`)
25
+ - Zero runtime dependencies beyond Ruby stdlib
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # NurseAndrea Ruby SDK
2
+
3
+ The official Ruby gem for [NurseAndrea](https://nurseandrea.com) — observability for Rails startups.
4
+
5
+ ## Installation
6
+
7
+ Add to your `Gemfile`:
8
+
9
+ ```ruby
10
+ gem "nurse_andrea"
11
+ ```
12
+
13
+ Then run:
14
+
15
+ ```bash
16
+ bundle install
17
+ rails generate nurse_andrea:install
18
+ ```
19
+
20
+ Set your API key:
21
+
22
+ ```bash
23
+ export NURSE_ANDREA_API_KEY="your_token_from_dashboard"
24
+ ```
25
+
26
+ ## What it does
27
+
28
+ - **Log shipping** — captures all `Rails.logger` calls and ships them to your NurseAndrea dashboard
29
+ - **Request metrics** — measures every HTTP request (duration, status code, path) via Rack middleware
30
+ - **Backfill** — ships the last 24h of your Rails log file on first startup
31
+ - **Health endpoint** — mounts `/nurse_andrea/status` so the dashboard can verify your connection
32
+
33
+ ## Configuration
34
+
35
+ ```ruby
36
+ NurseAndrea.configure do |config|
37
+ config.api_key = ENV["NURSE_ANDREA_API_KEY"]
38
+ config.log_level = :warn
39
+ config.backfill_hours = 48
40
+ config.enabled = !Rails.env.test?
41
+ end
42
+ ```
43
+
44
+ ## Version history
45
+
46
+ See [CHANGELOG.md](CHANGELOG.md).
@@ -0,0 +1,26 @@
1
+ module NurseAndrea
2
+ class StatusController < ActionController::API
3
+ def show
4
+ render json: {
5
+ status: "ok",
6
+ version: NurseAndrea::VERSION,
7
+ rails_version: defined?(Rails) ? Rails::VERSION::STRING : "n/a",
8
+ ruby_version: RUBY_VERSION,
9
+ environment: defined?(Rails) ? Rails.env : "unknown",
10
+ integration_token: masked_token,
11
+ log_shipper_running: NurseAndrea::LogShipper.instance.running?,
12
+ metrics_running: NurseAndrea::MetricsShipper.instance.running?,
13
+ timestamp: Time.now.utc.iso8601,
14
+ capabilities: %w[logs metrics backfill handshake]
15
+ }
16
+ end
17
+
18
+ private
19
+
20
+ def masked_token
21
+ token = NurseAndrea.config.api_key.to_s
22
+ return "not_configured" if token.empty?
23
+ "#{token[0..7]}..."
24
+ end
25
+ end
26
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ NurseAndrea::Engine.routes.draw do
2
+ get "/status", to: "status#show"
3
+ end
@@ -0,0 +1,26 @@
1
+ require "rails/generators"
2
+
3
+ module NurseAndrea
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+ desc "Creates a NurseAndrea initializer and mounts the status endpoint"
8
+
9
+ def create_initializer
10
+ template "nurse_andrea.rb.tt", "config/initializers/nurse_andrea.rb"
11
+ end
12
+
13
+ def mount_engine
14
+ route 'mount NurseAndrea::Engine => "/nurse_andrea"'
15
+ end
16
+
17
+ def show_next_steps
18
+ say "\nNurseAndrea installed!", :green
19
+ say "Next steps:", :bold
20
+ say " 1. Set NURSE_ANDREA_API_KEY in your environment"
21
+ say " 2. Get your API key from: https://app.nurseandrea.com/dashboard/integrations"
22
+ say " 3. Deploy and visit your onboarding wizard to verify the connection\n"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ NurseAndrea.configure do |c|
2
+ # Required: your integration token from the NurseAndrea dashboard
3
+ c.token = ENV["NURSE_ANDREA_TOKEN"]
4
+
5
+ # Required: the NurseAndrea host for this environment
6
+ c.host = ENV.fetch("NURSE_ANDREA_HOST", "https://nurseandrea.io")
7
+
8
+ # Optional: minimum log level to ship (default: :debug — ships everything)
9
+ # c.log_level = :warn
10
+
11
+ # Optional: disable in certain environments
12
+ # c.enabled = !Rails.env.test?
13
+
14
+ # Optional: how many hours of log history to backfill on first connect (default: 24)
15
+ # c.backfill_hours = 24
16
+ end
@@ -0,0 +1,116 @@
1
+ require "securerandom"
2
+ require "json"
3
+
4
+ module NurseAndrea
5
+ class Backfill
6
+ BATCH_SIZE = 500
7
+ MARKER_FILE = ".nurse_andrea_backfill_done"
8
+
9
+ def self.run_async!
10
+ Thread.new { new.run }.tap do |t|
11
+ t.abort_on_exception = false
12
+ t.name = "NurseAndrea::Backfill"
13
+ end
14
+ end
15
+
16
+ def run
17
+ return unless should_run?
18
+
19
+ log_file = resolve_log_file
20
+ return unless log_file && File.exist?(log_file)
21
+
22
+ entries = parse_log_file(log_file)
23
+ ship_in_batches(entries)
24
+ mark_complete!
25
+ rescue => e
26
+ warn "[NurseAndrea] Backfill error: #{e.message}" if NurseAndrea.config.debug
27
+ end
28
+
29
+ private
30
+
31
+ def should_run?
32
+ return false unless NurseAndrea.config.enabled? && NurseAndrea.config.valid?
33
+ !File.exist?(marker_path)
34
+ end
35
+
36
+ def resolve_log_file
37
+ return NurseAndrea.config.log_file_path if NurseAndrea.config.log_file_path
38
+ if defined?(Rails)
39
+ Rails.root.join("log", "#{Rails.env}.log").to_s
40
+ end
41
+ end
42
+
43
+ def parse_log_file(path)
44
+ cutoff = Time.now.utc - (NurseAndrea.config.backfill_hours * 3600)
45
+ entries = []
46
+
47
+ File.foreach(path) do |line|
48
+ line = line.strip
49
+ next if line.empty?
50
+
51
+ entry = parse_line(line)
52
+ begin
53
+ next if entry[:timestamp] && Time.parse(entry[:timestamp]) < cutoff
54
+ rescue
55
+ nil
56
+ end
57
+
58
+ entries << entry
59
+ end
60
+
61
+ entries
62
+ rescue => e
63
+ warn "[NurseAndrea] Log parse error: #{e.message}" if NurseAndrea.config.debug
64
+ []
65
+ end
66
+
67
+ def parse_line(line)
68
+ parsed = JSON.parse(line)
69
+ {
70
+ level: parsed["level"] || parsed["severity"] || "info",
71
+ message: parsed["message"] || parsed["msg"] || line,
72
+ timestamp: parsed["time"] || parsed["timestamp"] || Time.now.utc.iso8601(3),
73
+ metadata: { backfill: true }
74
+ }
75
+ rescue JSON::ParserError
76
+ {
77
+ level: extract_level(line),
78
+ message: line,
79
+ timestamp: Time.now.utc.iso8601(3),
80
+ metadata: { backfill: true }
81
+ }
82
+ end
83
+
84
+ def extract_level(line)
85
+ if line.match?(/\b(ERROR|FATAL)\b/i) then "error"
86
+ elsif line.match?(/\bWARN\b/i) then "warn"
87
+ elsif line.match?(/\bDEBUG\b/i) then "debug"
88
+ else "info"
89
+ end
90
+ end
91
+
92
+ def ship_in_batches(entries)
93
+ client = HttpClient.new
94
+ entries.each_slice(BATCH_SIZE) do |batch|
95
+ client.post(NurseAndrea.config.ingest_url, {
96
+ logs: batch.map { |e|
97
+ { level: e[:level], message: e[:message], occurred_at: e[:timestamp], source: "backfill" }
98
+ }
99
+ })
100
+ sleep 0.1
101
+ end
102
+ end
103
+
104
+ def mark_complete!
105
+ File.write(marker_path, Time.now.utc.iso8601)
106
+ end
107
+
108
+ def marker_path
109
+ if defined?(Rails)
110
+ Rails.root.join(MARKER_FILE).to_s
111
+ else
112
+ File.join(Dir.pwd, MARKER_FILE)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,49 @@
1
+ module NurseAndrea
2
+ class Configuration
3
+ attr_accessor :api_key, :host, :timeout, :log_level, :batch_size,
4
+ :flush_interval, :backfill_hours, :log_file_path,
5
+ :enabled, :debug
6
+
7
+ LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4 }.freeze
8
+ DEFAULT_HOST = "https://nurseandrea.io"
9
+
10
+ def initialize
11
+ @host = DEFAULT_HOST
12
+ @timeout = 5
13
+ @log_level = :debug
14
+ @batch_size = 100
15
+ @flush_interval = 10
16
+ @backfill_hours = 24
17
+ @log_file_path = nil
18
+ @enabled = true
19
+ @debug = false
20
+ end
21
+
22
+ alias_method :token, :api_key
23
+ alias_method :token=, :api_key=
24
+
25
+ # All endpoint URLs derived from host
26
+ def ingest_url = "#{normalised_host}/api/v1/ingest"
27
+ def metrics_url = "#{normalised_host}/api/v1/metrics"
28
+ def traces_url = "#{normalised_host}/api/v1/traces"
29
+ def handshake_url = "#{normalised_host}/api/v1/handshake"
30
+
31
+ def enabled?
32
+ @enabled
33
+ end
34
+
35
+ def min_log_level_int
36
+ LOG_LEVELS.fetch(log_level.to_sym, 0)
37
+ end
38
+
39
+ def valid?
40
+ !api_key.nil? && !api_key.to_s.strip.empty?
41
+ end
42
+
43
+ private
44
+
45
+ def normalised_host
46
+ host.to_s.chomp("/")
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,7 @@
1
+ require "rails/engine"
2
+
3
+ module NurseAndrea
4
+ class Engine < Rails::Engine
5
+ isolate_namespace NurseAndrea
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ require "net/http"
2
+ require "uri"
3
+ require "json"
4
+
5
+ module NurseAndrea
6
+ class HttpClient
7
+ def initialize
8
+ @api_key = NurseAndrea.config.api_key
9
+ @timeout = NurseAndrea.config.timeout
10
+ end
11
+
12
+ def post(url, body)
13
+ uri = URI.parse(url)
14
+ http = Net::HTTP.new(uri.host, uri.port)
15
+ http.use_ssl = uri.scheme == "https"
16
+ http.open_timeout = @timeout
17
+ http.read_timeout = @timeout
18
+
19
+ request = Net::HTTP::Post.new(uri.path)
20
+ request["Content-Type"] = "application/json"
21
+ request["Authorization"] = "Bearer #{@api_key}"
22
+ request["User-Agent"] = "nurse_andrea-ruby/#{NurseAndrea::VERSION}"
23
+ request.body = body.to_json
24
+
25
+ response = http.request(request)
26
+ success = response.code.to_i.between?(200, 299)
27
+
28
+ if NurseAndrea.config.debug && !success
29
+ warn "[NurseAndrea] HTTP #{response.code} from #{uri}: #{response.body.to_s[0..200]}"
30
+ end
31
+
32
+ success
33
+ rescue => e
34
+ warn "[NurseAndrea] HTTP error posting to #{url}: #{e.class}: #{e.message}" if NurseAndrea.config.debug
35
+ false
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,49 @@
1
+ require "logger"
2
+
3
+ module NurseAndrea
4
+ class LogInterceptor < SimpleDelegator
5
+ SEV_LABEL = %w[DEBUG INFO WARN ERROR FATAL ANY].freeze
6
+
7
+ def initialize(original_logger)
8
+ super(original_logger)
9
+ @min_level = NurseAndrea.config.min_log_level_int
10
+ end
11
+
12
+ %w[debug info warn error fatal].each do |level|
13
+ define_method(level) do |progname = nil, &block|
14
+ add(Logger.const_get(level.upcase), nil, progname, &block)
15
+ end
16
+ end
17
+
18
+ def add(severity, message = nil, progname = nil, &block)
19
+ result = super
20
+
21
+ return result unless NurseAndrea.config.enabled? && NurseAndrea.config.valid?
22
+ return result if severity.nil? || severity < @min_level
23
+
24
+ msg = message.nil? ? (block ? block.call : progname) : message
25
+ return result if msg.nil?
26
+
27
+ level_str = SEV_LABEL[severity]&.downcase || "unknown"
28
+
29
+ # Capture OpenTelemetry trace context if available
30
+ trace_metadata = {}
31
+ if defined?(OpenTelemetry) && OpenTelemetry.respond_to?(:tracer_provider)
32
+ span_context = OpenTelemetry::Trace.current_span&.context
33
+ if span_context&.valid?
34
+ trace_metadata[:trace_id] = span_context.hex_trace_id
35
+ trace_metadata[:span_id] = span_context.hex_span_id
36
+ end
37
+ end
38
+
39
+ NurseAndrea::LogShipper.instance.enqueue(
40
+ level: level_str,
41
+ message: msg.to_s.strip,
42
+ timestamp: Time.now.utc.iso8601(3),
43
+ metadata: { progname: progname&.to_s }.merge(trace_metadata).compact
44
+ )
45
+
46
+ result
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,79 @@
1
+ require "singleton"
2
+ require "securerandom"
3
+
4
+ module NurseAndrea
5
+ class LogShipper
6
+ include Singleton
7
+
8
+ MAX_QUEUE_SIZE = 10_000
9
+
10
+ def initialize
11
+ @queue = []
12
+ @mutex = Mutex.new
13
+ @thread = nil
14
+ end
15
+
16
+ def start!
17
+ return if @thread&.alive?
18
+ @thread = Thread.new { flush_loop }
19
+ @thread.abort_on_exception = false
20
+ @thread.name = "NurseAndrea::LogShipper"
21
+ end
22
+
23
+ def stop!
24
+ @thread&.kill
25
+ flush!
26
+ end
27
+
28
+ def running?
29
+ @thread&.alive? || false
30
+ end
31
+
32
+ def enqueue(entry)
33
+ flush_now = @mutex.synchronize do
34
+ @queue.shift if @queue.size >= MAX_QUEUE_SIZE
35
+ @queue << entry
36
+ @queue.size >= NurseAndrea.config.batch_size
37
+ end
38
+ flush! if flush_now
39
+ end
40
+
41
+ def flush!
42
+ entries = @mutex.synchronize do
43
+ return if @queue.empty?
44
+ batch = @queue.dup
45
+ @queue.clear
46
+ batch
47
+ end
48
+ return if entries.nil? || entries.empty?
49
+
50
+ ship(entries)
51
+ end
52
+
53
+ private
54
+
55
+ def flush_loop
56
+ loop do
57
+ sleep NurseAndrea.config.flush_interval
58
+ flush!
59
+ rescue => e
60
+ warn "[NurseAndrea::LogShipper] Error in flush loop: #{e.message}" if NurseAndrea.config.debug
61
+ end
62
+ end
63
+
64
+ def ship(entries)
65
+ HttpClient.new.post(NurseAndrea.config.ingest_url, {
66
+ logs: entries.map { |e|
67
+ {
68
+ level: e[:level],
69
+ message: e[:message],
70
+ occurred_at: e[:timestamp],
71
+ source: "nurse_andrea_gem",
72
+ batch_id: SecureRandom.uuid,
73
+ payload: e[:metadata] || {}
74
+ }
75
+ }
76
+ })
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,40 @@
1
+ module NurseAndrea
2
+ class MetricsMiddleware
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ return @app.call(env) unless NurseAndrea.config.enabled? && NurseAndrea.config.valid?
9
+ return @app.call(env) if env["PATH_INFO"]&.start_with?("/nurse_andrea")
10
+
11
+ started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
12
+ status, headers, body = @app.call(env)
13
+ duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000).round(2)
14
+
15
+ NurseAndrea::MetricsShipper.instance.enqueue(
16
+ name: "http.server.duration",
17
+ value: duration_ms,
18
+ unit: "ms",
19
+ timestamp: Time.now.utc.iso8601(3),
20
+ tags: {
21
+ http_method: env["REQUEST_METHOD"],
22
+ http_status: status.to_s,
23
+ http_path: normalize_path(env["PATH_INFO"])
24
+ }
25
+ )
26
+
27
+ [ status, headers, body ]
28
+ rescue => e
29
+ warn "[NurseAndrea] Middleware error: #{e.message}" if NurseAndrea.config.debug
30
+ raise
31
+ end
32
+
33
+ private
34
+
35
+ def normalize_path(path)
36
+ return "/" if path.nil? || path.empty?
37
+ path.gsub(%r{/\d+(/|$)}, '/:id\1')
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,78 @@
1
+ require "singleton"
2
+ require "securerandom"
3
+
4
+ module NurseAndrea
5
+ class MetricsShipper
6
+ include Singleton
7
+
8
+ BATCH_SIZE = 200
9
+ FLUSH_INTERVAL = 15
10
+
11
+ def initialize
12
+ @queue = []
13
+ @mutex = Mutex.new
14
+ @thread = nil
15
+ end
16
+
17
+ def start!
18
+ return if @thread&.alive?
19
+ @thread = Thread.new { flush_loop }
20
+ @thread.abort_on_exception = false
21
+ @thread.name = "NurseAndrea::MetricsShipper"
22
+ end
23
+
24
+ def stop!
25
+ @thread&.kill
26
+ flush!
27
+ end
28
+
29
+ def running?
30
+ @thread&.alive? || false
31
+ end
32
+
33
+ def enqueue(metric)
34
+ flush_now = @mutex.synchronize do
35
+ @queue << metric
36
+ @queue.size >= BATCH_SIZE
37
+ end
38
+ flush! if flush_now
39
+ end
40
+
41
+ def flush!
42
+ metrics = @mutex.synchronize do
43
+ return if @queue.empty?
44
+ batch = @queue.dup
45
+ @queue.clear
46
+ batch
47
+ end
48
+ return if metrics.nil? || metrics.empty?
49
+
50
+ ship(metrics)
51
+ end
52
+
53
+ private
54
+
55
+ def flush_loop
56
+ loop do
57
+ sleep FLUSH_INTERVAL
58
+ flush!
59
+ rescue => e
60
+ warn "[NurseAndrea::MetricsShipper] #{e.message}" if NurseAndrea.config.debug
61
+ end
62
+ end
63
+
64
+ def ship(metrics)
65
+ HttpClient.new.post(NurseAndrea.config.metrics_url, {
66
+ metrics: metrics.map { |m|
67
+ {
68
+ name: m[:name],
69
+ value: m[:value],
70
+ unit: m[:unit],
71
+ tags: m[:tags] || {},
72
+ occurred_at: m[:timestamp]
73
+ }
74
+ }
75
+ })
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,27 @@
1
+ require "rails/railtie"
2
+
3
+ module NurseAndrea
4
+ class Railtie < Rails::Railtie
5
+ initializer "nurse_andrea.insert_middleware", before: :build_middleware_stack do |app|
6
+ app.config.middleware.insert_before 0, NurseAndrea::MetricsMiddleware
7
+ end
8
+
9
+ initializer "nurse_andrea.wrap_logger", after: :initialize_logger do
10
+ next unless NurseAndrea.config.enabled? && NurseAndrea.config.valid?
11
+
12
+ Rails.logger = NurseAndrea::LogInterceptor.new(Rails.logger)
13
+ NurseAndrea::LogShipper.instance.start!
14
+ NurseAndrea::MetricsShipper.instance.start!
15
+ end
16
+
17
+ config.after_initialize do
18
+ next unless NurseAndrea.config.enabled? && NurseAndrea.config.valid?
19
+ NurseAndrea::Backfill.run_async!
20
+ end
21
+
22
+ at_exit do
23
+ NurseAndrea::LogShipper.instance.flush! rescue nil
24
+ NurseAndrea::MetricsShipper.instance.flush! rescue nil
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module NurseAndrea
2
+ VERSION = "0.1.2"
3
+ end
@@ -0,0 +1,27 @@
1
+ require "nurse_andrea/version"
2
+ require "nurse_andrea/configuration"
3
+ require "nurse_andrea/http_client"
4
+ require "nurse_andrea/log_interceptor"
5
+ require "nurse_andrea/log_shipper"
6
+ require "nurse_andrea/metrics_middleware"
7
+ require "nurse_andrea/metrics_shipper"
8
+ require "nurse_andrea/backfill"
9
+
10
+ require "nurse_andrea/railtie" if defined?(Rails::Railtie)
11
+ require "nurse_andrea/engine" if defined?(Rails::Engine)
12
+
13
+ module NurseAndrea
14
+ class << self
15
+ def configure
16
+ yield(config)
17
+ end
18
+
19
+ def config
20
+ @config ||= Configuration.new
21
+ end
22
+
23
+ def reset_config!
24
+ @config = nil
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ require_relative "lib/nurse_andrea/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "nurse_andrea"
5
+ spec.version = NurseAndrea::VERSION
6
+ spec.authors = [ "Ago AI LLC" ]
7
+ spec.email = [ "hello@nurseandrea.io" ]
8
+ spec.summary = "Observability SDK for Rails — ships logs and metrics to NurseAndrea"
9
+ spec.description = "One-line integration to send your Rails app's logs and metrics to the NurseAndrea observability platform."
10
+ spec.homepage = "https://nurseandrea.io"
11
+ spec.license = "MIT"
12
+
13
+ spec.required_ruby_version = ">= 3.1"
14
+
15
+ spec.metadata = {
16
+ "homepage_uri" => spec.homepage,
17
+ "source_code_uri" => "https://github.com/narteyb/nurse-andrea",
18
+ "changelog_uri" => "https://github.com/narteyb/nurse-andrea/blob/main/gems/nurse_andrea/CHANGELOG.md",
19
+ "rubygems_mfa_required" => "true"
20
+ }
21
+
22
+ spec.files = Dir.chdir(__dir__) do
23
+ Dir["{app,config,lib}/**/*", "README.md", "CHANGELOG.md", "nurse_andrea.gemspec"]
24
+ .reject { |f| File.directory?(f) }
25
+ end
26
+
27
+ spec.require_paths = [ "lib" ]
28
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nurse_andrea
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Ago AI LLC
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: One-line integration to send your Rails app's logs and metrics to the
13
+ NurseAndrea observability platform.
14
+ email:
15
+ - hello@nurseandrea.io
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - CHANGELOG.md
21
+ - README.md
22
+ - app/controllers/nurse_andrea/status_controller.rb
23
+ - config/routes.rb
24
+ - lib/generators/nurse_andrea/install/install_generator.rb
25
+ - lib/generators/nurse_andrea/install/templates/nurse_andrea.rb.tt
26
+ - lib/nurse_andrea.rb
27
+ - lib/nurse_andrea/backfill.rb
28
+ - lib/nurse_andrea/configuration.rb
29
+ - lib/nurse_andrea/engine.rb
30
+ - lib/nurse_andrea/http_client.rb
31
+ - lib/nurse_andrea/log_interceptor.rb
32
+ - lib/nurse_andrea/log_shipper.rb
33
+ - lib/nurse_andrea/metrics_middleware.rb
34
+ - lib/nurse_andrea/metrics_shipper.rb
35
+ - lib/nurse_andrea/railtie.rb
36
+ - lib/nurse_andrea/version.rb
37
+ - nurse_andrea.gemspec
38
+ homepage: https://nurseandrea.io
39
+ licenses:
40
+ - MIT
41
+ metadata:
42
+ homepage_uri: https://nurseandrea.io
43
+ source_code_uri: https://github.com/narteyb/nurse-andrea
44
+ changelog_uri: https://github.com/narteyb/nurse-andrea/blob/main/gems/nurse_andrea/CHANGELOG.md
45
+ rubygems_mfa_required: 'true'
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '3.1'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 4.0.9
61
+ specification_version: 4
62
+ summary: Observability SDK for Rails — ships logs and metrics to NurseAndrea
63
+ test_files: []