module Sapience
Stream appender
Writes log messages to a file or open IO stream
Send log messages to any standard Ruby logging class.
Forwards logging call to loggers such as Logger, log4r, etc.
Send log messages to sentry
Example:
Sapience.add_appender(:stream, {io: STDOUT, formatter: :color})
rubocop:disable ClassVars, Style/SafeNavigation
Sapience::Loggers::Concurrent
is a class wrapping all methods necessary for integration with concurrent-ruby gem .
Example:
Sapience.add_appender(:datadog, {url: "udp://localhost:2222"})
rubocop:disable ClassVars
Abstract Subscriber
Abstract base class for appender and metrics subscribers.
Constants
- APPENDER_NAMESPACE
- APP_NAME
Logging levels in order of most detailed to most severe
- AppNameMissing
- DEFAULT_ENV
- DEFAULT_STATSD_URL
- ERROR_HANDLER_NAMESPACE
- InvalidLogExecutor
- LEVELS
- Log
rubocop:disable LineLength
- METRICS_NAMESPACE
- MissingConfiguration
- RACK_ENV
- RAILS_ENV
- SAPIENCE_ENV
- TestException
- UnknownClass
- UnkownLogLevel
- VERSION
Public Class Methods
Return a logger for the supplied class or class_name
# File lib/sapience/sapience.rb, line 131 def self.[](klass) Sapience::Logger.new(klass) end
Add a new logging appender as a new destination for all log messages emitted from Sapience
Appenders will be written to in the order that they are added
If a block is supplied then it will be used to customize the format of the messages sent to that appender. See Sapience::Logger.new
for more information on custom formatters
Parameters
file_name: [String] File name to write log messages to. Or, io: [IO] An IO Stream to log to. For example STDOUT, STDERR, etc. Or, appender: [Symbol|Sapience::Subscriber] A symbol identifying the appender to create. For example: :bugsnag, :elasticsearch, :graylog, :http, :mongodb, :new_relic, :splunk_http, :syslog, :wrapper Or, An instance of an appender derived from Sapience::Subscriber For example: Sapience::Appender::Http.new(url: 'http://localhost:8088/path') Or, logger: [Logger|Log4r] An instance of a Logger or a Log4r logger. level: [:trace | :debug | :info | :warn | :error | :fatal] Override the log level for this appender. Default: Sapience.config.default_level formatter: [Symbol|Object|Proc] Any of the following symbol values: :default, :color, :json Or, An instance of a class that implements #call Or, A Proc to be used to format the output from this appender Default: :default filter: [Regexp|Proc] RegExp: Only include log messages where the class name matches the supplied. regular expression. All other messages will be ignored. Proc: Only include log messages where the supplied Proc returns true The Proc must return true or false.
Examples:
# Send all logging output to Standard Out (Screen) Sapience.add_appender(:stream, io: STDOUT) # Send all logging output to a file Sapience.add_appender(:stream, file_name: 'logfile.log') # Send all logging output to a file and only :info and above to standard output Sapience.add_appender(:stream, file_name: 'logfile.log') Sapience.add_appender(:stream, io: STDOUT, level: :info)
# Send logging output to an existing logger require 'logger' require 'sapience' # Built-in Ruby logger log = Logger.new(STDOUT) log.level = Logger::DEBUG Sapience.config.default_level = :debug Sapience.add_appender(:wrapper, logger: log) logger = Sapience['Example'] logger.info "Hello World" logger.debug("Login time", user: 'Joe', duration: 100, ip_address: '127.0.0.1')
# File lib/sapience/sapience.rb, line 213 def self.add_appender(appender_class_name, options = {}, _deprecated_level = nil, &_block) fail ArgumentError, "options should be a hash" unless options.is_a?(Hash) options = options.dup.deep_symbolize_keyz! appender_class = constantize_symbol(appender_class_name) validate_appender_class!(appender_class) appender = appender_class.new(options) warn "appender #{appender} with (#{options.inspect}) is not valid" unless appender.valid? @@appenders << appender # Start appender thread if it is not already running Sapience::Logger.start_appender_thread Sapience::Logger.start_invalid_appenders_task appender end
Examples:
Sapience.add_appenders( { file: { io: STDOUT } }, { sentry: { dsn: "https://app.getsentry.com/" } }, )
# File lib/sapience/sapience.rb, line 245 def self.add_appenders(*appenders) appenders.flatten.compact.each do |appender| appender.each do |name, options| add_appender(name, options) end end end
# File lib/sapience/sapience.rb, line 107 def self.app_name config.app_name ||= app_name_builder fail AppNameMissing, "app_name is not configured. See documentation for more information" unless config.app_name config.app_name end
# File lib/sapience/sapience.rb, line 470 def self.app_name_builder config_hash.fetch(environment) { {} }["app_name"] || config_hash.fetch(DEFAULT_ENV) { {} }["app_name"] || ENV[APP_NAME] end
Returns [Sapience::Subscriber] a copy of the list of active appenders for debugging etc. Use Sapience.add_appender
and Sapience.remove_appender
to manipulate the active appenders list
# File lib/sapience/sapience.rb, line 272 def self.appenders @@appenders.clone end
# File lib/sapience/sapience.rb, line 292 def self.capture_exception(exception, payload = {}) error_handler.capture_exception(exception, payload) end
# File lib/sapience/sapience.rb, line 296 def self.capture_message(message, payload = {}) error_handler.capture_message(message, payload) end
Close and flush all appenders
# File lib/sapience/sapience.rb, line 337 def self.close Sapience::Logger.close end
# File lib/sapience/sapience.rb, line 59 def self.config @@config ||= begin options = config_hash[environment] options ||= default_options(config_hash) Configuration.new(options) end end
# File lib/sapience/sapience.rb, line 55 def self.config_hash @@config_hash ||= ConfigLoader.load_from_file end
# File lib/sapience/sapience.rb, line 45 def self.configure(force: false) yield config if block_given? return config if configured? && force == false reset_appenders! add_appenders(*config.appenders) @@configured = true config end
# File lib/sapience/sapience.rb, line 67 def self.configured? @@configured end
# File lib/sapience/sapience.rb, line 455 def self.constantize(class_name) return class_name unless class_name.is_a?(String) if RUBY_VERSION.to_i >= 2 Object.const_get(class_name) else class_name.split("::").inject(Object) { |o, name| o.const_get(name) } end rescue NameError raise UnknownClass, "Could not find class: #{class_name}." end
# File lib/sapience/sapience.rb, line 450 def self.constantize_symbol(symbol, namespace = APPENDER_NAMESPACE) class_name = "#{namespace}::#{symbol.to_sym.camelize}" constantize(class_name) end
# File lib/sapience/sapience.rb, line 300 def self.create_class(config_section, namespace) namespace_string = namespace.to_s.split("::").last fail MissingConfiguration, "No #{namespace_string} configured" unless config_section klass_name = config_section.keys.first options = config_section.values.first klass = constantize_symbol(klass_name, namespace) if namespace.descendants.include?(klass) klass.new(options) else fail NotImplementedError, "Unknown #{namespace_string} '#{klass_name}'" end end
# File lib/sapience/sapience.rb, line 71 def self.default_options(options = {}) warn "No configuration for environment #{environment}. Using 'default'" unless environment =~ /default|rspec/ options[DEFAULT_ENV] end
# File lib/sapience/sapience.rb, line 92 def self.environment @@environment ||= ENV.fetch(SAPIENCE_ENV) do ENV.fetch(RAILS_ENV) do ENV.fetch(RACK_ENV) do if defined?(::Rails) && ::Rails.respond_to?(:env) ::Rails.env else DEFAULT_ENV end end end end end
# File lib/sapience/sapience.rb, line 288 def self.error_handler @@error_handler ||= create_class(config.error_handler, ERROR_HANDLER_NAMESPACE) end
# File lib/sapience/sapience.rb, line 284 def self.error_handler=(error_handler) @@error_handler = error_handler end
If the tag being supplied is definitely a string then this fast tag api can be used for short lived tags
# File lib/sapience/sapience.rb, line 353 def self.fast_tag(tag) (Thread.current[:sapience_tags] ||= []) << tag yield ensure Thread.current[:sapience_tags].pop end
Wait until all queued log messages have been written and flush all active appenders
# File lib/sapience/sapience.rb, line 332 def self.flush Sapience::Logger.flush end
# File lib/sapience/sapience.rb, line 236 def self.known_appenders @known_appenders ||= Sapience::Subscriber.descendants end
# File lib/sapience/sapience.rb, line 446 def self.log_executor_class constantize_symbol(config.log_executor, "Concurrent") end
# File lib/sapience/sapience.rb, line 320 def self.logger @@logger ||= Sapience::Logger.logger end
# File lib/sapience/sapience.rb, line 316 def self.logger=(logger) @@logger = Sapience::Logger.logger = logger end
# File lib/sapience/sapience.rb, line 280 def self.metrics @@metrics ||= create_class(config.metrics, METRICS_NAMESPACE) end
# File lib/sapience/sapience.rb, line 276 def self.metrics=(metrics) @@metrics = metrics end
# File lib/sapience/sapience.rb, line 113 def self.namify(appname, sep = "_") return unless appname.is_a?(String) return if appname.empty? # Turn unwanted chars into the separator appname = appname.dup appname.gsub!(/[^a-z0-9\-_]+/i, sep) unless sep.nil? || sep.empty? re_sep = Regexp.escape(sep) # No more than one of the separator in a row. appname.gsub!(/#{re_sep}{2,}/, sep) # Remove leading/trailing separator. appname.gsub!(/^#{re_sep}|#{re_sep}$/, "") end appname.downcase end
Remove an existing appender Currently only supports appender instances TODO: Make it possible to remove appenders by type Maybe create a concurrent collection that allows this by inheriting from concurrent array.
# File lib/sapience/sapience.rb, line 257 def self.remove_appender(appender) @@appenders.delete(appender) end
Remove specific appenders or all existing
# File lib/sapience/sapience.rb, line 262 def self.remove_appenders(appenders = @@appenders) appenders.each do |appender| remove_appender(appender) end end
After forking an active process call Sapience.reopen
to re-open any open file handles etc to resources
Note: Only appenders that implement the reopen method will be called
# File lib/sapience/sapience.rb, line 345 def self.reopen @@appenders.each { |appender| appender.reopen if appender.respond_to?(:reopen) } # After a fork the appender thread is not running, start it if it is not running Sapience::Logger.start_appender_thread end
# File lib/sapience/sapience.rb, line 76 def self.reset! @@config = nil @@logger = nil @@metrics = nil @@error_handler = nil @@environment = nil @@configured = false @@config_hash = nil clear_tags! reset_appenders! end
# File lib/sapience/sapience.rb, line 88 def self.reset_appenders! @@appenders = Concurrent::Array.new end
# File lib/sapience/sapience.rb, line 466 def self.root @root ||= Gem::Specification.find_by_name("sapience").gem_dir end
Silence noisy log levels by changing the default_level within the block
This setting is thread-safe and only applies to the current thread
Any threads spawned within the block will not be affected by this setting
silence can be used to both raise and lower the log level within the supplied block.
Example:
# Perform trace level logging within the block when the default is higher Sapience.config.default_level = :info logger.debug 'this will _not_ be logged' Sapience.silence(:trace) do logger.debug "this will be logged" end
Parameters
new_level The new log level to apply within the block Default: :error
Example:
# Silence all logging for this thread below :error level Sapience.silence do logger.info "this will _not_ be logged" logger.warn "this neither" logger.error "but errors will be logged" end
Note:
#silence does not affect any loggers which have had their log level set explicitly. I.e. That do not rely on the global default level
# File lib/sapience/sapience.rb, line 436 def self.silence(new_level = :error) current_index = Thread.current[:sapience_silence] Thread.current[:sapience_silence] = Sapience.config.level_to_index(new_level) yield ensure Thread.current[:sapience_silence] = current_index end
Add the supplied tags to the list of tags to log for this thread whilst the supplied block is active. Returns result of block
# File lib/sapience/sapience.rb, line 363 def self.tagged(*tags) new_tags = push_tags(*tags) yield self ensure pop_tags(new_tags.size) end
# File lib/sapience/sapience.rb, line 324 def self.test_exception(_level = :error) fail Sapience::TestException, "Sapience Test Exception" rescue Sapience::TestException => ex Sapience.capture_exception(ex, test_exception: true) end
# File lib/sapience/sapience.rb, line 229 def self.validate_appender_class!(appender_class) return if known_appenders.include?(appender_class) fail NotImplementedError, "Unknown appender '#{appender_class}'. Supported appenders are (#{known_appenders.join(", ")})" end