Files
danbooru/app/logical/danbooru_logger.rb
evazion 957076d93b apm: don't record unknown url params in the apm.
Don't record unknown url params that don't come from our app. This
includes typos, url params from userscripts, and weird params from
broken bots, crawlers, or other unknown sources. Indexing too many
params can lead to a mapping explosion.

https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html
2022-04-19 06:59:24 -05:00

120 lines
4.0 KiB
Ruby

# frozen_string_literal: true
# The DanbooruLogger class handles logging messages to the Rails log and to the APM.
#
# @see https://guides.rubyonrails.org/debugging_rails_applications.html#the-logger
class DanbooruLogger
HEADERS = %w[referer sec-fetch-dest sec-fetch-mode sec-fetch-site sec-fetch-user]
# Log a message to the Rails log and to the APM.
#
# @param message [String] the message to log
# @param params [Hash] optional key-value data to log with the message
def self.info(message, params = {})
Rails.logger.info(message)
params = flatten_hash(params).symbolize_keys
log_event(:info, message: message, **params)
end
# Log an exception to the Rails log and to the APM. The `expected` flag is
# used to separate expected exceptions, like search timeouts or auth failures,
# from unexpected exceptions, like runtime errors, in the error logs.
#
# @param message [Exception] the exception to log
# @param expected [Boolean] whether the exception was expected
# @param params [Hash] optional key-value data to log with the exception
def self.log(exception, expected: false, **params)
if expected
Rails.logger.info("#{exception.class}: #{exception.message}")
else
backtrace = Rails.backtrace_cleaner.clean(exception.backtrace).join("\n")
Rails.logger.error("#{exception.class}: #{exception.message}\n#{backtrace}")
end
log_exception(exception, expected: expected, custom_params: params)
end
# Log extra HTTP request data to the APM. Logs the user's IP, user agent,
# request params, and session cookies.
#
# @param request the HTTP request
# @param session the Rails session
# @param user [User] the current user
def self.add_session_attributes(request, session, user)
#add_attributes("request", { path: request.path })
#add_attributes("request.headers", header_params(request))
add_attributes("param", request_params(request))
add_attributes("session", session_params(session))
add_attributes("user", user_params(request, user))
end
# Get logged HTTP headers from request.
def self.header_params(request)
headers = request.headers.to_h.select { |header, value| header.match?(/\AHTTP_/) }
headers = headers.transform_keys { |header| header.delete_prefix("HTTP_").downcase }
headers = headers.select { |header, value| header.in?(HEADERS) }
headers
end
def self.request_params(request)
request.parameters.with_indifferent_access.except(:controller, :action).reject do |key, value|
# exclude strange URL params that don't come from our app.
!key.match?(/\A[a-z._]+\z/) || key.match?(/\A_|_\z/)
end
end
def self.session_params(session)
session.to_h.with_indifferent_access.slice(:session_id, :started_at)
end
def self.user_params(request, user)
{
id: user&.id,
name: user&.name,
level: user&.level_string,
#ip: request.remote_ip,
#country: CurrentUser.country,
#safe_mode: CurrentUser.safe_mode?,
#bot: UserAgent.new(request.headers["HTTP_USER_AGENT"]).is_bot?,
}
end
def self.add_attributes(prefix, hash)
attributes = flatten_hash(hash).transform_keys { |key| "#{prefix}.#{key}" }
attributes.delete_if { |key, value| key.end_with?(*Rails.application.config.filter_parameters.map(&:to_s)) }
log_attributes(attributes)
end
private_class_method
def self.log_attributes(attributes)
attributes.each do |key, value|
ElasticAPM.set_label(key, value)
end
end
def self.log_exception(exception, expected: false, custom_params: {})
ElasticAPM.report(exception, handled: expected)
end
def self.log_event(level, message: nil, **params)
ElasticAPM.set_custom_context(params)
ElasticAPM.report_message(message)
end
# flatten_hash({ foo: { bar: { baz: 42 } } })
# => { "foo.bar.baz" => 42 }
def self.flatten_hash(hash)
hash.each_with_object({}) do |(k, v), h|
if v.is_a?(Hash)
flatten_hash(v).map do |h_k, h_v|
h["#{k}.#{h_k}"] = h_v
end
else
h[k.to_s] = v
end
end
end
end