Module: Lapsoss
- Defined in:
- lib/lapsoss.rb,
lib/lapsoss/event.rb,
lib/lapsoss/scope.rb,
lib/lapsoss/client.rb,
lib/lapsoss/router.rb,
lib/lapsoss/current.rb,
lib/lapsoss/railtie.rb,
lib/lapsoss/version.rb,
lib/lapsoss/pipeline.rb,
lib/lapsoss/registry.rb,
lib/lapsoss/scrubber.rb,
lib/lapsoss/breadcrumb.rb,
lib/lapsoss/validators.rb,
lib/lapsoss/http_client.rb,
lib/lapsoss/merged_scope.rb,
lib/lapsoss/adapters/base.rb,
lib/lapsoss/configuration.rb,
lib/lapsoss/fingerprinter.rb,
lib/lapsoss/sampling/base.rb,
lib/lapsoss/backtrace_frame.rb,
lib/lapsoss/middleware/base.rb,
lib/lapsoss/release_tracker.rb,
lib/lapsoss/runtime_context.rb,
lib/lapsoss/exclusion_filter.rb,
lib/lapsoss/pipeline_builder.rb,
lib/lapsoss/backtrace_processor.rb,
lib/lapsoss/sampling/rate_limiter.rb,
lib/lapsoss/rails_error_subscriber.rb,
lib/lapsoss/adapters/logger_adapter.rb,
lib/lapsoss/adapters/sentry_adapter.rb,
lib/lapsoss/backtrace_frame_factory.rb,
lib/lapsoss/middleware/rate_limiter.rb,
lib/lapsoss/adapters/bugsnag_adapter.rb,
lib/lapsoss/adapters/rollbar_adapter.rb,
lib/lapsoss/rails_controller_context.rb,
lib/lapsoss/sampling/uniform_sampler.rb,
lib/lapsoss/adapters/telebugs_adapter.rb,
lib/lapsoss/exception_backtrace_frame.rb,
lib/lapsoss/middleware/event_enricher.rb,
lib/lapsoss/adapters/appsignal_adapter.rb,
lib/lapsoss/middleware/release_tracker.rb,
lib/lapsoss/middleware/exception_filter.rb,
lib/lapsoss/adapters/insight_hub_adapter.rb,
lib/lapsoss/middleware/event_transformer.rb,
lib/lapsoss/middleware/metrics_collector.rb,
lib/lapsoss/rails_controller_transaction.rb,
lib/lapsoss/middleware/conditional_filter.rb,
lib/lapsoss/adapters/concerns/http_delivery.rb,
lib/lapsoss/adapters/concerns/level_mapping.rb,
lib/lapsoss/adapters/concerns/envelope_builder.rb
Defined Under Namespace
Modules: Adapters, Breadcrumb, Middleware, RailsControllerContext, RailsControllerTransaction, Sampling, Validators Classes: BacktraceFrameFactory, BacktraceProcessor, Client, Configuration, Current, DeliveryError, Error, ExceptionBacktraceFrame, ExclusionFilter, Fingerprinter, HttpClient, MergedScope, Pipeline, PipelineBuilder, RailsErrorSubscriber, Railtie, Registry, ReleaseTracker, Router, Scope, Scrubber
Constant Summary collapse
- Event =
Immutable event structure using Ruby 3.3 Data class
Data.define( :type, # :exception, :message, :transaction :level, # :debug, :info, :warning, :error, :fatal :timestamp, :message, :exception, :context, :environment, :fingerprint, :backtrace_frames, :transaction # Controller#action or task name where event occurred ) do # Factory method with smart defaults def self.build(type:, level: :info, **attributes) = attributes[:timestamp] || Time.now environment = attributes[:environment].presence || Lapsoss.configuration.environment context = attributes[:context] || {} # Process exception if present exception = attributes[:exception] = attributes[:message].presence || exception&. backtrace_frames = process_backtrace(exception) if exception # Generate fingerprint fingerprint = attributes.fetch(:fingerprint) { generate_fingerprint(type, , exception, environment) } new( type: type, level: level, timestamp: , message: , exception: exception, context: context, environment: environment, fingerprint: fingerprint, backtrace_frames: backtrace_frames, transaction: attributes[:transaction] ) end # ActiveSupport::JSON serialization def as_json( = nil) to_h.compact_blank.as_json() end def to_json( = nil) ActiveSupport::JSON.encode(as_json()) end # Helper methods def exception_type = exception&.class&.name def = exception&. def has_exception? = exception.present? def has_backtrace? = backtrace_frames.present? def backtrace = exception&.backtrace def request_context = context.dig(:extra, :request) || context.dig(:extra, "request") def user_context = context[:user] def = context[:tags] || {} def extra = context[:extra] || {} def = context[:breadcrumbs] || [] # Apply data scrubbing def scrubbed scrubber = Scrubber.new( scrub_fields: Lapsoss.configuration.scrub_fields, scrub_all: Lapsoss.configuration.scrub_all, whitelist_fields: Lapsoss.configuration.whitelist_fields, randomize_scrub_length: Lapsoss.configuration.randomize_scrub_length ) with(context: scrubber.scrub(context)) end private def self.process_backtrace(exception) return nil unless exception&.backtrace.present? config = Lapsoss.configuration processor = BacktraceProcessor.new(config) processor.process_exception_backtrace(exception) end def self.generate_fingerprint(type, , exception, environment) return nil unless Lapsoss.configuration.fingerprint_callback || Lapsoss.configuration.fingerprint_patterns.present? fingerprinter = Fingerprinter.new( custom_callback: Lapsoss.configuration.fingerprint_callback, patterns: Lapsoss.configuration.fingerprint_patterns, normalize_paths: Lapsoss.configuration.normalize_fingerprint_paths, normalize_ids: Lapsoss.configuration.normalize_fingerprint_ids, include_environment: Lapsoss.configuration.fingerprint_include_environment ) # Create a temporary event-like object for fingerprinting temp_event = Struct.new(:type, :message, :exception, :environment).new( type, , exception, environment ) fingerprinter.generate_fingerprint(temp_event) end end
- VERSION =
"0.4.10"- BacktraceFrame =
Data.define( :filename, :absolute_path, :line_number, :method_name, :in_app, :raw_line, :function, :module_name, :code_context, :block_info ) do # Backward compatibility aliases alias_method :lineno, :line_number alias_method :raw, :raw_line def to_h { filename: filename, absolute_path: absolute_path, line_number: line_number, method: method_name, function: function, module: module_name, in_app: in_app, code_context: code_context, raw: raw_line }.compact end def add_code_context(processor, context_lines = 3) return unless line_number # Use absolute path if available, otherwise try filename path_to_read = absolute_path || filename return unless path_to_read with(code_context: processor.get_code_context(path_to_read, line_number, context_lines)) end def valid? filename && (line_number.nil? || line_number >= 0) end def library_frame? !in_app end def app_frame? in_app end def excluded?(exclude_patterns = []) return false if exclude_patterns.empty? exclude_patterns.any? do |pattern| case pattern when Regexp raw_line.match?(pattern) when String raw_line.include?(pattern) else false end end end def relative_filename(load_paths = []) return filename unless filename && load_paths.any? # Try to make path relative to load paths load_paths.each do |load_path| if filename.start_with?(load_path) relative = filename.sub(%r{^#{Regexp.escape(load_path)}/?}, "") return relative unless relative.empty? end end filename end end
- RuntimeContext =
Boot-time context collection using Data class
Data.define(:os, :runtime, :modules, :server_name, :release) do def self.current @current ||= new( os: collect_os_context, runtime: collect_runtime_context, modules: collect_modules, server_name: collect_server_name, release: collect_release ) end def self.collect_os_context { name: RbConfig::CONFIG["host_os"], version: `uname -r 2>/dev/null`.strip.presence, build: `uname -v 2>/dev/null`.strip.presence, kernel_version: `uname -a 2>/dev/null`.strip.presence, machine: RbConfig::CONFIG["host_cpu"] }.compact rescue { name: RbConfig::CONFIG["host_os"] } end def self.collect_runtime_context { name: "ruby", version: RUBY_DESCRIPTION } end def self.collect_modules return {} unless defined?(Bundler) Bundler.load.specs.each_with_object({}) do |spec, h| h[spec.name] = spec.version.to_s end rescue {} end def self.collect_server_name Socket.gethostname rescue "unknown" end def self.collect_release # Try to get from git if available if File.exist?(".git") `git rev-parse HEAD 2>/dev/null`.strip.presence end rescue nil end def to_contexts { os: os, runtime: runtime } end end
Class Attribute Summary collapse
-
.client ⇒ Object
readonly
Returns the value of attribute client.
Class Method Summary collapse
- .add_breadcrumb(message, type: :default, **metadata) ⇒ Object
- .capture_exception(exception, **context) ⇒ Object
- .capture_message(message, level: :info, **context) ⇒ Object
- .configuration ⇒ Object
- .configure {|configuration| ... } ⇒ Object
- .flush(timeout: 2) ⇒ Object
-
.handle(error_class = StandardError, fallback: nil, **context) ⇒ Object
Handle errors and swallow them (like Rails.error.handle).
-
.record(error_class = StandardError, **context) ⇒ Object
Record errors and re-raise them (like Rails.error.record).
-
.report(exception, handled: true, **context) ⇒ Object
Report an error manually (like Rails.error.report).
- .with_scope(context = {}) ⇒ Object
Class Attribute Details
.client ⇒ Object (readonly)
Returns the value of attribute client.
26 27 28 |
# File 'lib/lapsoss.rb', line 26 def client @client end |
Class Method Details
.add_breadcrumb(message, type: :default, **metadata) ⇒ Object
72 73 74 |
# File 'lib/lapsoss.rb', line 72 def (, type: :default, **) client.(, type: type, **) end |
.capture_exception(exception, **context) ⇒ Object
39 40 41 42 43 |
# File 'lib/lapsoss.rb', line 39 def capture_exception(exception, **context) configuration.logger.debug "[LAPSOSS] capture_exception called for #{exception.class}" return unless client client.capture_exception(exception, **context) end |
.capture_message(message, level: :info, **context) ⇒ Object
45 46 47 |
# File 'lib/lapsoss.rb', line 45 def (, level: :info, **context) client.(, level: level, **context) end |
.configuration ⇒ Object
28 29 30 |
# File 'lib/lapsoss.rb', line 28 def configuration @configuration ||= Configuration.new end |
.configure {|configuration| ... } ⇒ Object
32 33 34 35 36 37 |
# File 'lib/lapsoss.rb', line 32 def configure yield(configuration) configuration.validate! configuration.apply! @client = Client.new(configuration) end |
.flush(timeout: 2) ⇒ Object
82 83 84 |
# File 'lib/lapsoss.rb', line 82 def flush(timeout: 2) client.flush(timeout: timeout) end |
.handle(error_class = StandardError, fallback: nil, **context) ⇒ Object
Handle errors and swallow them (like Rails.error.handle)
52 53 54 55 56 57 |
# File 'lib/lapsoss.rb', line 52 def handle(error_class = StandardError, fallback: nil, **context) yield rescue error_class => e capture_exception(e, **context.merge(handled: true)) fallback.respond_to?(:call) ? fallback.call : fallback end |
.record(error_class = StandardError, **context) ⇒ Object
Record errors and re-raise them (like Rails.error.record)
60 61 62 63 64 65 |
# File 'lib/lapsoss.rb', line 60 def record(error_class = StandardError, **context) yield rescue error_class => e capture_exception(e, **context.merge(handled: false)) raise end |
.report(exception, handled: true, **context) ⇒ Object
Report an error manually (like Rails.error.report)
68 69 70 |
# File 'lib/lapsoss.rb', line 68 def report(exception, handled: true, **context) capture_exception(exception, **context.merge(handled: handled)) end |
.with_scope(context = {}) ⇒ Object
76 77 78 |
# File 'lib/lapsoss.rb', line 76 def with_scope(context = {}, &) client.with_scope(context, &) end |