2019-05-03 06:17:27 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2024-07-08 14:03:11 +08:00
|
|
|
require "json"
|
|
|
|
require "socket"
|
2024-07-10 10:36:22 +08:00
|
|
|
require_relative "git_utils"
|
2024-07-08 14:03:11 +08:00
|
|
|
|
|
|
|
class DiscourseLogstashLogger < Logger
|
|
|
|
PROCESS_PID = Process.pid
|
|
|
|
HOST = Socket.gethostname
|
2024-07-10 10:36:22 +08:00
|
|
|
GIT_VERSION = GitUtils.git_version
|
2024-07-08 14:03:11 +08:00
|
|
|
|
|
|
|
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,
|
2024-07-10 10:36:22 +08:00
|
|
|
"git_version" => GitUtils.git_version,
|
2024-07-08 14:03:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
2024-07-10 08:54:39 +08:00
|
|
|
# `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.
|
2024-07-10 11:04:17 +08:00
|
|
|
if progname == "web-exception" && message =~ /\A([^\(\)]+)\s{1}\(([\s\S]+)\)/
|
2024-07-10 08:54:39 +08:00
|
|
|
event["exception.class"] = $1
|
2024-07-10 11:04:17 +08:00
|
|
|
event["exception.message"] = $2.strip
|
2024-07-10 08:54:39 +08:00
|
|
|
end
|
|
|
|
|
2024-07-08 14:03:11 +08:00
|
|
|
if (env = opts&.dig(:env)).present?
|
|
|
|
ALLOWED_HEADERS_FROM_ENV.each do |header|
|
|
|
|
event["request.headers.#{header.downcase}"] = opts[:env][header]
|
|
|
|
end
|
|
|
|
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")}"
|
2017-11-14 12:50:26 +08:00
|
|
|
end
|
|
|
|
end
|