Class: RailsInformant::ContextBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_informant/context_builder.rb

Constant Summary collapse

MAX_CAUSE_DEPTH =
5
SKIP_HEADERS =
Set.new(%w[
  HTTP_AUTHORIZATION
  HTTP_COOKIE
  HTTP_PROXY_AUTHORIZATION
  HTTP_X_API_KEY
  HTTP_X_AUTH_TOKEN
  HTTP_X_CSRF_TOKEN
]).freeze

Class Method Summary collapse

Class Method Details

.build_custom_context(error) ⇒ Object



95
96
97
98
99
# File 'lib/rails_informant/context_builder.rb', line 95

def build_custom_context(error)
  ctx = RailsInformant::Current.custom_context || {}
  ctx = ctx.merge(error.to_informant_context) if error.respond_to?(:to_informant_context)
  ctx.presence
end

.build_environment_contextObject



52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/rails_informant/context_builder.rb', line 52

def build_environment_context
  @_static_env ||= begin
    hostname = Socket.gethostname
    env = {
      rails_env: Rails.env.to_s,
      ruby_version: RUBY_VERSION,
      rails_version: Rails::VERSION::STRING
    }
    env[:hostname] = hostname unless hostname == "localhost"
    env.freeze
  end
  @_static_env.merge(pid: Process.pid)
end

.build_exception_chain(error) ⇒ Object



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/rails_informant/context_builder.rb', line 14

def build_exception_chain(error)
  chain = []
  current = error.cause
  depth = 0

  while current && depth < MAX_CAUSE_DEPTH
    chain << {
      class: current.class.name,
      message: ContextFilter.filter_message(current.message),
      backtrace: ContextFilter.filter_backtrace(current.backtrace)
    }
    current = current.cause
    depth += 1
  end

  chain.presence
end

.build_request_context(env) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/rails_informant/context_builder.rb', line 32

def build_request_context(env)
  return unless env

  request = ActionDispatch::Request.new(env)
  ctx = {
    url: filtered_url(request.original_url),
    method: request.request_method,
    params: request.filtered_parameters,
    headers: extract_headers(request),
    ip: request.remote_ip
  }

  ContextFilter.filter(ctx)
end

.build_user_context(env) ⇒ Object



47
48
49
50
# File 'lib/rails_informant/context_builder.rb', line 47

def build_user_context(env)
  ctx = RailsInformant::Current.user_context || detect_current_user(env)
  ContextFilter.filter(ctx)
end

.extract_controller_action(env, context) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/rails_informant/context_builder.rb', line 101

def extract_controller_action(env, context)
  if env
    params = env["action_dispatch.request.parameters"]
    "#{params["controller"]}##{params["action"]}" if params&.key?("controller") && params&.key?("action")
  else
    case context
    in { controller: String => controller, action: String => action }
      "#{controller}##{action}"
    else
      nil
    end
  end
end

.extract_job_class(context) ⇒ Object



115
116
117
# File 'lib/rails_informant/context_builder.rb', line 115

def extract_job_class(context)
  context.dig(:job, :class) || context[:job_class]
end

.filtered_url(url) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
# File 'lib/rails_informant/context_builder.rb', line 119

def filtered_url(url)
  uri = URI.parse(url)
  if uri.query.present?
    params = Rack::Utils.parse_query(uri.query)
    filtered = ContextFilter.filter(params)
    uri.query = Rack::Utils.build_query(filtered)
  end
  uri.to_s
rescue URI::InvalidURIError
  url.split("?").first
end

.group_attributes(error, severity:, context:, env:, now:) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
# File 'lib/rails_informant/context_builder.rb', line 70

def group_attributes(error, severity:, context:, env:, now:)
  {
    error_class: error.class.name, severity:,
    message: ContextFilter.filter_message(error.message),
    first_backtrace_line: Fingerprint.first_app_frame(error),
    controller_action: extract_controller_action(env, context),
    job_class: extract_job_class(context),
    first_seen_at: now, last_seen_at: now,
    total_occurrences: 1, created_at: now, updated_at: now
  }
end

.occurrence_attributes(error, env:, context:) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/rails_informant/context_builder.rb', line 82

def occurrence_attributes(error, env:, context:)
  {
    backtrace: ContextFilter.filter_backtrace(error.backtrace),
    exception_chain: build_exception_chain(error),
    request_context: build_request_context(env),
    user_context: build_user_context(env),
    custom_context: ContextFilter.filter(build_custom_context(error)),
    environment_context: build_environment_context,
    breadcrumbs: BreadcrumbBuffer.current.flush,
    git_sha: RailsInformant.current_git_sha
  }
end

.reset!Object



66
67
68
# File 'lib/rails_informant/context_builder.rb', line 66

def reset!
  @_static_env = nil
end