mirror of
https://github.com/discourse/discourse.git
synced 2024-12-13 16:43:45 +08:00
2d59795e28
This ensures that elasticsearch doesn't parse it as an object. There are too many combination of job opts so we don't want elasticsearch to be parsing and indexing this field as an object.
127 lines
4.3 KiB
Ruby
127 lines
4.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "json"
|
|
require "socket"
|
|
require_relative "git_utils"
|
|
|
|
class DiscourseLogstashLogger < Logger
|
|
PROCESS_PID = Process.pid
|
|
HOST = Socket.gethostname
|
|
GIT_VERSION = GitUtils.git_version
|
|
|
|
attr_accessor :customize_event, :type
|
|
|
|
# Creates a new logger instance.
|
|
#
|
|
# @param logdev [String, IO, nil] The log device. This can be one of:
|
|
# - A string filepath: entries are written to the file at that path. If the file exists, new entries are appended.
|
|
# - An IO stream (typically +$stdout+, +$stderr+, or an open file): entries are written to the given stream.
|
|
# - nil or File::NULL: no entries are written.
|
|
# @param type [String] The type of log messages. This will add a `type` field to all log messages.
|
|
# @param customize_event [Proc, nil] A proc that customizes the log event before it is written to the log device.
|
|
# The proc is called with a hash of log event data and can be modified in place.
|
|
#
|
|
# @return [Logger] A new logger instance with the specified log device and type.
|
|
def self.logger(logdev:, type:, customize_event: nil)
|
|
logger = self.new(logdev)
|
|
logger.type = type
|
|
logger.customize_event = customize_event if customize_event
|
|
logger
|
|
end
|
|
|
|
# :nodoc:
|
|
def add(*args, &block)
|
|
add_with_opts(*args, &block)
|
|
end
|
|
|
|
ALLOWED_HEADERS_FROM_ENV = %w[
|
|
REQUEST_URI
|
|
REQUEST_METHOD
|
|
HTTP_HOST
|
|
HTTP_USER_AGENT
|
|
HTTP_ACCEPT
|
|
HTTP_REFERER
|
|
HTTP_X_FORWARDED_FOR
|
|
HTTP_X_REAL_IP
|
|
]
|
|
|
|
# :nodoc:
|
|
def add_with_opts(severity, message = nil, progname = nil, opts = {}, &block)
|
|
return true if @logdev.nil? || severity < @level
|
|
|
|
progname = @progname if progname.nil?
|
|
|
|
if message.nil?
|
|
if block_given?
|
|
message = yield
|
|
else
|
|
message = progname
|
|
progname = @progname
|
|
end
|
|
end
|
|
|
|
event = {
|
|
"message" => message.to_s,
|
|
"severity" => severity,
|
|
"severity_name" => Logger::SEV_LABEL[severity],
|
|
"pid" => PROCESS_PID,
|
|
"type" => @type.to_s,
|
|
"host" => HOST,
|
|
"git_version" => GitUtils.git_version,
|
|
}
|
|
|
|
# Only log backtrace and env for Logger::WARN and above.
|
|
# Backtrace is just noise for anything below that.
|
|
if severity >= Logger::WARN
|
|
if (backtrace = opts&.dig(:backtrace)).present?
|
|
event["backtrace"] = backtrace
|
|
end
|
|
|
|
# `web-exception` is a log message triggered by logster.
|
|
# The exception class and message are extracted from the message based on the format logged by logster in
|
|
# https://github.com/discourse/logster/blob/25375250fb8a5c312e9c55a75f6048637aad2c69/lib/logster/middleware/debug_exceptions.rb#L22.
|
|
#
|
|
# In theory we could get logster to include the exception class and message in opts but logster currently does not
|
|
# need those options so we are parsing it from the message for now and not making a change in logster.
|
|
if progname == "web-exception"
|
|
# `Logster.store.ignore` is set in the logster initializer and is an array of regex patterns.
|
|
return if Logster.store&.ignore&.any? { |pattern| pattern.match(message) }
|
|
|
|
if message =~ /\A([^\(\)]+)\s{1}\(([\s\S]+)\)/
|
|
event["exception.class"] = $1
|
|
event["exception.message"] = $2.strip
|
|
end
|
|
|
|
ALLOWED_HEADERS_FROM_ENV.each do |header|
|
|
event["request.headers.#{header.downcase}"] = opts.dig(:env, header)
|
|
end
|
|
end
|
|
|
|
if progname == "sidekiq-exception"
|
|
event["job.class"] = opts.dig(:context, :job)
|
|
event["job.opts"] = opts.dig(:context, :opts)&.stringify_keys&.to_s
|
|
event["job.problem_db"] = opts.dig(:context, :problem_db)
|
|
event["exception.class"] = opts[:exception_class]
|
|
event["exception.message"] = opts[:exception_message]
|
|
end
|
|
end
|
|
|
|
if message.is_a?(String) && message.start_with?("{") && message.end_with?("}")
|
|
begin
|
|
parsed = JSON.parse(message)
|
|
event["message"] = parsed.delete("message") if parsed["message"]
|
|
event.merge!(parsed)
|
|
event
|
|
rescue JSON::ParserError
|
|
# Do nothing
|
|
end
|
|
end
|
|
|
|
@customize_event.call(event) if @customize_event
|
|
|
|
@logdev.write("#{event.to_json}\n")
|
|
rescue Exception => e
|
|
STDERR.puts "Error logging message `#{message}` in DiscourseLogstashLogger: #{e.class} (#{e.message})\n#{e.backtrace.join("\n")}"
|
|
end
|
|
end
|