Class: Honeybadger::Config

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Logging::Helper
Defined in:
lib/honeybadger/config.rb,
lib/honeybadger/config/env.rb,
lib/honeybadger/config/yaml.rb,
lib/honeybadger/config/defaults.rb,
lib/honeybadger/config/callbacks.rb

Defined Under Namespace

Classes: Boolean, Callbacks, ConfigError, Env, Yaml

Constant Summary collapse

KEY_REPLACEMENT =
Regexp.new('[^a-z\d_]', Regexp::IGNORECASE).freeze
DOTTED_KEY =
Regexp.new('\A([^\.]+)\.(.+)\z').freeze
NOT_BLANK =
Regexp.new('\S').freeze
FEATURES =
[:notices, :local_variables, :metrics, :traces].freeze
MERGE_DEFAULT =
[:'exceptions.ignore'].freeze
OVERRIDE =
{
  :'exceptions.ignore' => :'exceptions.ignore_only'
}.freeze
DEFAULT_REQUEST_HASH =
{}.freeze
IGNORE_DEFAULT =
['ActionController::RoutingError',
'AbstractController::ActionNotFound',
'ActionController::MethodNotAllowed',
'ActionController::UnknownHttpMethod',
'ActionController::NotImplemented',
'ActionController::UnknownFormat',
'ActionController::InvalidAuthenticityToken',
'ActionController::InvalidCrossOriginRequest',
'ActionDispatch::ParamsParser::ParseError',
'ActionController::BadRequest',
'ActionController::ParameterMissing',
'ActiveRecord::RecordNotFound',
'ActionController::UnknownAction',
'CGI::Session::CookieStore::TamperedWithCookie',
'Mongoid::Errors::DocumentNotFound',
'Sinatra::NotFound'].map(&:freeze).freeze
DEVELOPMENT_ENVIRONMENTS =
['development', 'test', 'cucumber'].map(&:freeze).freeze
DEFAULT_PATHS =
['honeybadger.yml', 'config/honeybadger.yml'].map(&:freeze).freeze
OPTIONS =
{
  api_key: {
    description: 'The API key for your Honeybadger project.',
    default: nil,
    type: String
  },
  env: {
    description: 'The current application\'s environment name.',
    default: ENV['HONEYBADGER_ENV'] || ENV['RACK_ENV'],
    type: String
  },
  report_data: {
    description: 'Enable/disable reporting of data. Defaults to true for non-development environments.',
    default: nil,
    type: Boolean
  },
  root: {
    description: 'The project\'s absolute root path.',
    default: Dir.pwd,
    type: String
  },
  hostname: {
    description: 'The hostname of the current box.',
    default: Socket.gethostname,
    type: String
  },
  backend: {
    description: 'An alternate backend to use for reporting data.',
    default: nil,
    type: String
  },
  debug: {
    description: 'Forces metrics and traces to be reported every 10 seconds rather than 60.',
    default: false,
    type: Boolean
  },
  disabled: {
    description: 'Prevents Honeybadger from starting entirely.',
    default: false,
    type: Boolean
  },
  development_environments: {
    description: 'Environments which will not report data by default (use report_data to enable/disable explicitly).',
    default: DEVELOPMENT_ENVIRONMENTS,
    type: Array
  },
  :'send_data_at_exit' => {
    description: 'Send remaining data when Ruby exits.',
    default: true,
    type: Boolean
  },
  plugins: {
    description: 'An optional list of plugins to load. Default is to load all plugins.',
    default: nil,
    type: Array
  },
  :'plugins.skip' => {
    description: 'An optional list of plugins to skip.',
    default: nil,
    type: Array
  },
  :'config.path' => {
    description: 'The path (absolute, or relative from config.root) to the project\'s YAML configuration file.',
    default: DEFAULT_PATHS,
    type: String
  },
  :'logging.path' => {
    description: 'The path (absolute, or relative from config.root) to the log file.',
    default: nil,
    type: String
  },
  :'logging.level' => {
    description: 'The log level.',
    default: 'INFO',
    type: String
  },
  :'logging.debug' => {
    description: 'Override debug logging.',
    default: nil,
    type: Boolean
  },
  :'logging.tty_level' => {
    description: 'Level to log when attached to a terminal (anything < logging.level will always be ignored).',
    default: 'DEBUG',
    type: String
  },
  :'connection.secure' => {
    description: 'Use SSL when sending data.',
    default: true,
    type: Boolean
  },
  :'connection.host' => {
    description: 'The host to use when sending data.',
    default: 'api.honeybadger.io'.freeze,
    type: String
  },
  :'connection.port' => {
    description: 'The port to use when sending data.',
    default: nil,
    type: Integer
  },
  :'connection.system_ssl_cert_chain' => {
    description: 'Use the system\'s SSL certificate chain (if available).',
    default: false,
    type: Boolean
  },
  :'connection.http_open_timeout' => {
    description: 'The HTTP open timeout when connecting to the server.',
    default: 2,
    type: Integer
  },
  :'connection.http_read_timeout' => {
    description: 'The HTTP read timeout when connecting to the server.',
    default: 5,
    type: Integer
  },
  :'connection.proxy_host' => {
    description: 'The proxy host to use when sending data.',
    default: nil,
    type: String
  },
  :'connection.proxy_port' => {
    description: 'The proxy port to use when sending data.',
    default: nil,
    type: Integer
  },
  :'connection.proxy_user' => {
    description: 'The proxy user to use when sending data.',
    default: nil,
    type: String
  },
  :'connection.proxy_pass' => {
    description: 'The proxy password to use when sending data.',
    default: nil,
    type: String
  },
  :'request.filter_keys' => {
    description: 'A list of keys to filter when sending request data.',
    default: ['password'.freeze, 'password_confirmation'.freeze].freeze,
    type: Array
  },
  :'request.disable_session' => {
    description: 'Prevent session from being sent with request data.',
    default: false,
    type: Boolean
  },
  :'request.disable_params' => {
    description: 'Prevent params from being sent with request data.',
    default: false,
    type: Boolean
  },
  :'request.disable_environment' => {
    description: 'Prevent Rack environment from being sent with request data.',
    default: false,
    type: Boolean
  },
  :'request.disable_url' => {
    description: 'Prevent url from being sent with request data (Rack environment may still contain it in some cases).',
    default: false,
    type: Boolean
  },
  :'user_informer.enabled' => {
    description: 'Enable the UserInformer middleware.',
    default: true,
    type: Boolean
  },
  :'user_informer.info' => {
    description: 'Replacement string for HTML comment in templates.',
    default: 'Honeybadger Error {{error_id}}'.freeze,
    type: String
  },
  :'feedback.enabled' => {
    description: 'Enable the UserFeedback middleware.',
    default: true,
    type: Boolean
  },
  :'exceptions.enabled' => {
    description: 'Enable automatic reporting of exceptions.',
    default: true,
    type: Boolean
  },
  :'exceptions.ignore' => {
    description: 'A list of additional exceptions to ignore (includes default ignored exceptions).',
    default: IGNORE_DEFAULT,
    type: Array
  },
  :'exceptions.ignore_only' => {
    description: 'A list of exceptions to ignore (overrides the default ignored exceptions).',
    default: [].freeze,
    type: Array
  },
  :'exceptions.ignored_user_agents' => {
    description: 'A list of user agents to ignore.',
    default: [].freeze,
    type: Array
  },
  :'exceptions.rescue_rake' => {
    description: 'Enable rescuing exceptions in rake tasks.',
    default: true,
    type: Boolean
  },
  :'exceptions.source_radius' => {
    description: 'The number of lines before and after the source when reporting snippets.',
    default: 2,
    type: Integer
  },
  :'exceptions.local_variables' => {
    description: 'Enable sending local variables. Requires binding_of_caller to be loaded.',
    default: false,
    type: Boolean
  },
  :'exceptions.unwrap' => {
    description: 'Reports #original_exception or #cause one level up from rescued exception when available.',
    default: false,
    type: Boolean
  },
  :'metrics.enabled' => {
    description: 'Enable sending metrics.',
    default: true,
    type: Boolean
  },
  :'metrics.gc_profiler' => {
    description: 'Enable sending GC metrics (GC::Profiler must be enabled)',
    default: false,
    type: Boolean
  },
  :'traces.enabled' => {
    description: 'Enable sending traces.',
    default: true,
    type: Boolean
  },
  :'traces.threshold' => {
    description: 'The threshold in seconds to send traces.',
    default: 2000,
    type: Integer
  },
  :'delayed_job.attempt_threshold' => {
    description: 'The number of attempts before notifications will be sent.',
    default: 0,
    type: Integer
  },
  :'sidekiq.attempt_threshold' => {
    description: 'The number of attempts before notifications will be sent.',
    default: 0,
    type: Integer
  },
  :'sinatra.enabled' => {
    description: 'Enable Sinatra auto-initialization.',
    default: true,
    type: Boolean
  }
}.freeze
DEFAULTS =
Hash[OPTIONS.map{|k,v| [k, v[:default]] }].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Config

Returns a new instance of Config.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/honeybadger/config.rb', line 43

def initialize(opts = {})
  l = opts.delete(:logger)

  @values = opts

  priority = {}
  priority.update(opts)
  load_config_from_disk {|yml| priority.update(yml) }
  priority.update(Env.new(ENV))
  update(merge_defaults!(priority))

  @logger = Logging::ConfigLogger.new(self, build_logger(l))
  Logging::BootLogger.instance.flush(@logger)

  @features = Hash[FEATURES.map{|f| [f, true] }]
end

Instance Attribute Details

#featuresObject (readonly)

Returns the value of attribute features.



62
63
64
# File 'lib/honeybadger/config.rb', line 62

def features
  @features
end

Instance Method Details

#backendObject



95
96
97
# File 'lib/honeybadger/config.rb', line 95

def backend
  Backend.for((self[:backend] || default_backend).to_sym).new(self)
end

#ca_bundle_pathObject



154
155
156
157
158
159
160
# File 'lib/honeybadger/config.rb', line 154

def ca_bundle_path
  if self[:'connection.system_ssl_cert_chain'] && File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE)
    OpenSSL::X509::DEFAULT_CERT_FILE
  else
    local_cert_path
  end
end

#config_pathObject

Internal: Path to honeybadger.yml configuration file; this should be the root directory if no path was specified.

Returns the Pathname configuration path.



144
145
146
# File 'lib/honeybadger/config.rb', line 144

def config_path
  config_paths.first
end

#config_pathsObject



148
149
150
151
152
# File 'lib/honeybadger/config.rb', line 148

def config_paths
  Array(ENV['HONEYBADGER_CONFIG_PATH'] || get(:'config.path')).map do |c|
    locate_absolute_path(c, self[:root])
  end
end

#connection_portObject



166
167
168
169
170
171
172
173
174
# File 'lib/honeybadger/config.rb', line 166

def connection_port
  if self[:'connection.port']
    self[:'connection.port']
  elsif self[:'connection.secure']
    443
  else
    80
  end
end

#connection_protocolObject



176
177
178
179
180
181
182
# File 'lib/honeybadger/config.rb', line 176

def connection_protocol
  if self[:'connection.secure']
    'https'
  else
    'http'
  end
end

#debug?Boolean

Returns:



121
122
123
# File 'lib/honeybadger/config.rb', line 121

def debug?
  !!self[:debug]
end

#default_backendObject



109
110
111
112
113
114
115
# File 'lib/honeybadger/config.rb', line 109

def default_backend
  if public?
    :server
  else
    :null
  end
end

#dev?Boolean

Returns:



99
100
101
# File 'lib/honeybadger/config.rb', line 99

def dev?
  self[:env] && Array(self[:development_environments]).include?(self[:env])
end

#excluded_request_keysObject



208
209
210
211
212
213
214
215
# File 'lib/honeybadger/config.rb', line 208

def excluded_request_keys
  [].tap do |keys|
    keys << :session  if self[:'request.disable_session']
    keys << :params   if self[:'request.disable_params']
    keys << :cgi_data if self[:'request.disable_environment']
    keys << :url      if self[:'request.disable_url']
  end
end

#feature?(feature) ⇒ Boolean

Returns:



87
88
89
# File 'lib/honeybadger/config.rb', line 87

def feature?(feature)
  !!features[feature.to_sym]
end

#frameworkObject



260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/honeybadger/config.rb', line 260

def framework
  if self[:framework] =~ NOT_BLANK
    self[:framework].to_sym
  elsif defined?(::Rails::VERSION) && ::Rails::VERSION::STRING > '3.0'
    :rails
  elsif defined?(::Sinatra::VERSION)
    :sinatra
  elsif defined?(::Rack.release)
    :rack
  else
    :ruby
  end
end

#framework_nameObject



274
275
276
277
278
279
280
281
282
# File 'lib/honeybadger/config.rb', line 274

def framework_name
  case framework
  when :rails then "Rails #{::Rails::VERSION::STRING}"
  when :sinatra then "Sinatra #{::Sinatra::VERSION}"
  when :rack then "Rack #{::Rack.release}"
  else
    "Ruby #{RUBY_VERSION}"
  end
end

#get(key) ⇒ Object Also known as: []



64
65
66
67
68
69
70
71
72
73
# File 'lib/honeybadger/config.rb', line 64

def get(key)
  key = key.to_sym
  if OVERRIDE.has_key?(key) && @values.has_key?(OVERRIDE[key])
    @values[OVERRIDE[key]]
  elsif @values.has_key?(key)
    @values[key]
  else
    DEFAULTS[key]
  end
end

#load_plugin?(name) ⇒ Boolean

Returns:



245
246
247
248
249
# File 'lib/honeybadger/config.rb', line 245

def load_plugin?(name)
  return false if Array(self[:'plugins.skip']).include?(name)
  return true  if self[:plugins].nil?
  Array(self[:plugins]).include?(name)
end

#local_cert_pathObject



162
163
164
# File 'lib/honeybadger/config.rb', line 162

def local_cert_path
  File.expand_path(File.join('..', '..', '..', 'resources', 'ca-bundle.crt'), __FILE__)
end

#log_debug?Boolean

Returns:



125
126
127
128
# File 'lib/honeybadger/config.rb', line 125

def log_debug?
  return debug? if self[:'logging.debug'].nil?
  !!self[:'logging.debug']
end

#log_level(key = :'logging.level') ⇒ Object



234
235
236
237
238
239
240
241
242
243
# File 'lib/honeybadger/config.rb', line 234

def log_level(key = :'logging.level')
  case self[key].to_s
  when /\A(0|debug)\z/i then Logger::DEBUG
  when /\A(1|info)\z/i  then Logger::INFO
  when /\A(2|warn)\z/i  then Logger::WARN
  when /\A(3|error)\z/i then Logger::ERROR
  else
    Logger::INFO
  end
end

#log_pathObject

Internal: Optional path to honeybadger.log log file. If nil, STDOUT will be used instead.

Returns the Pathname log path if a log path was specified.



134
135
136
137
138
# File 'lib/honeybadger/config.rb', line 134

def log_path
  if self[:'logging.path'] && self[:'logging.path'] != 'STDOUT'
    locate_absolute_path(self[:'logging.path'], self[:root])
  end
end

#loggerObject



91
92
93
# File 'lib/honeybadger/config.rb', line 91

def logger
  @logger || Logging::BootLogger.instance
end

#params_filtersObject



200
201
202
# File 'lib/honeybadger/config.rb', line 200

def params_filters
  self[:'request.filter_keys'] + rails_params_filters
end

#pingObject



251
252
253
254
255
256
257
258
# File 'lib/honeybadger/config.rb', line 251

def ping
  if result = send_ping
    @features = symbolize_keys(result['features']) if result['features']
    return true
  end

  false
end

#public?Boolean

Returns:



103
104
105
106
107
# File 'lib/honeybadger/config.rb', line 103

def public?
  return true if self[:report_data]
  return false if self[:report_data] == false
  !self[:env] || !dev?
end

#rails_params_filtersObject



204
205
206
# File 'lib/honeybadger/config.rb', line 204

def rails_params_filters
  request && request.env['action_dispatch.parameter_filter'] or []
end

#requestObject



184
185
186
# File 'lib/honeybadger/config.rb', line 184

def request
  Thread.current[:__honeybadger_request]
end

#request_hashObject



195
196
197
198
# File 'lib/honeybadger/config.rb', line 195

def request_hash
  return DEFAULT_REQUEST_HASH unless request
  Rack::RequestHash.new(request)
end

#root_regexpObject

Internal: Match the project root.

Returns Regexp matching the project root in a file string.



287
288
289
290
291
292
293
294
295
# File 'lib/honeybadger/config.rb', line 287

def root_regexp
  return @root_regexp if @root_regexp
  return nil if @no_root

  root = get(:root).to_s
  @no_root = true and return nil unless root =~ NOT_BLANK

  @root_regexp = Regexp.new("^#{ Regexp.escape(root) }")
end

#set(key, value) ⇒ Object Also known as: []=



76
77
78
# File 'lib/honeybadger/config.rb', line 76

def set(key, value)
  @values[key] = value
end

#to_hash(defaults = false) ⇒ Object Also known as: to_h



81
82
83
84
# File 'lib/honeybadger/config.rb', line 81

def to_hash(defaults = false)
  hash = defaults ? DEFAULTS.merge(@values) : @values
  undotify_keys(hash.select {|k,v| DEFAULTS.has_key?(k) })
end

#valid?Boolean

Returns:



117
118
119
# File 'lib/honeybadger/config.rb', line 117

def valid?
  self[:api_key].to_s =~ /\S/
end

#with_request(request, &block) ⇒ Object



188
189
190
191
192
193
# File 'lib/honeybadger/config.rb', line 188

def with_request(request, &block)
  Thread.current[:__honeybadger_request] = request
  yield
ensure
  Thread.current[:__honeybadger_request] = nil
end

#writeObject



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/honeybadger/config.rb', line 217

def write
  path = config_path

  if path.exist?
    raise ConfigError, "The configuration file #{path} already exists."
  elsif !path.dirname.writable?
    raise ConfigError, "The configuration path #{path.dirname} is not writable."
  end

  File.open(path, 'w+') do |file|
    file.write(<<-CONFIG)
---
api_key: '#{self[:api_key]}'
               CONFIG
  end
end