mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-16 18:05:35 +08:00
Browser page view (BPV) are tracked currently via specific HTTP request headers which we attach on AJAX/MessageBus polling requests and refer to as the "piggyback" method or via the new dedicated `/srv/pv` endpoint when the `use_beacon_for_browser_page_views` site setting is enabled. For the "piggyback" method, details about the BPV request headers are never logged while the `/srv/pv` endpoint is handled by the `Middleware::RequestTracker` and never hits the Rails controllers. As a result, any attempt to debug errors with regards to BPV is hard because we have no visibility into how the actual params of these BPV related requests arrived at the server. This commit updates `Middleware::RequestTracker` to fire `process_action.action_controller` notification whenever a request results in BPV counters being incremented so that a log line lands in the configured Rails log file where the request's params field contains the attributes that was sent from the client. "piggyback" BPV requests are logged using a fake controller#action reference of "PageviewController#piggyback" while the beacon BPV requests are logged using a fake controller#action reference of "PageviewController#beacon".
184 lines
6.3 KiB
Ruby
184 lines
6.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "discourse_lograge"
|
|
|
|
if DiscourseLograge.enabled?
|
|
require "lograge"
|
|
|
|
Rails.application.config.after_initialize do
|
|
def unsubscribe(component_name, subscriber)
|
|
subscriber
|
|
.public_methods(false)
|
|
.reject { |method| method.to_s == "call" }
|
|
.each do |event|
|
|
ActiveSupport::Notifications
|
|
.notifier
|
|
.all_listeners_for("#{event}.#{component_name}")
|
|
.each do |listener|
|
|
if listener
|
|
.instance_variable_get("@delegate")
|
|
.class
|
|
.to_s
|
|
.start_with?("#{component_name.to_s.classify}::LogSubscriber")
|
|
ActiveSupport::Notifications.unsubscribe listener
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# This is doing what the `lograge` gem is doing but has stopped working after we upgraded to Rails 7.1 and the `lograge`
|
|
# gem does not seem to be maintained anymore so we're shipping our own fix. In the near term, we are considering
|
|
# dropping the lograge gem and just port the relevant code to our codebase.
|
|
#
|
|
# Basically, this code silences log events coming from `ActionView::Logsubscriber` and `ActionController::LogSubscriber`
|
|
# since those are just noise.
|
|
ActiveSupport::LogSubscriber.log_subscribers.each do |subscriber|
|
|
case subscriber
|
|
when ActionView::LogSubscriber
|
|
unsubscribe(:action_view, subscriber)
|
|
when ActionController::LogSubscriber
|
|
unsubscribe(:action_controller, subscriber)
|
|
end
|
|
end
|
|
end
|
|
|
|
Rails.application.config.to_prepare do
|
|
if Rails.configuration.multisite
|
|
Rails.logger.formatter =
|
|
ActiveSupport::Logger::SimpleFormatter.new.extend(ActiveSupport::TaggedLogging::Formatter)
|
|
end
|
|
|
|
Rails.application.configure do
|
|
config.lograge.enabled = true
|
|
|
|
# Monkey patch Rails::Rack::Logger#logger to silence its logs.
|
|
# The `lograge` gem is supposed to do this but it broke after we upgraded to Rails 7.1.
|
|
# This patch is a temporary workaround until we find a proper fix.
|
|
Rails::Rack::Logger.prepend(Module.new { def logger = (@logger ||= Logger.new(IO::NULL)) })
|
|
|
|
Lograge.ignore(
|
|
lambda do |event|
|
|
# this is our hijack magic status,
|
|
# no point logging this cause we log again
|
|
# direct from hijack
|
|
event.payload[:status] == 418
|
|
end,
|
|
)
|
|
|
|
config.lograge.custom_payload do |controller|
|
|
begin
|
|
username =
|
|
begin
|
|
controller.current_user&.username if controller.respond_to?(:current_user)
|
|
rescue Discourse::InvalidAccess, Discourse::ReadOnly, ActiveRecord::ReadOnlyError
|
|
nil
|
|
end
|
|
|
|
ip =
|
|
begin
|
|
controller.request.remote_ip
|
|
rescue ActionDispatch::RemoteIp::IpSpoofAttackError
|
|
nil
|
|
end
|
|
|
|
DiscourseLograge.custom_payload(ip: ip, username: username)
|
|
rescue => e
|
|
Rails.logger.warn(
|
|
"Failed to append custom payload: #{e.message}\n#{e.backtrace.join("\n")}",
|
|
)
|
|
{}
|
|
end
|
|
end
|
|
|
|
config.lograge.custom_options =
|
|
lambda do |event|
|
|
begin
|
|
exceptions = %w[controller action format id]
|
|
|
|
params = event.payload[:params].except(*exceptions)
|
|
|
|
if (file = params[:file]) && file.respond_to?(:headers)
|
|
params[:file] = file.headers
|
|
end
|
|
|
|
if (files = params[:files]) && files.respond_to?(:map)
|
|
params[:files] = files.map { |f| f.respond_to?(:headers) ? f.headers : f }
|
|
end
|
|
|
|
output = {
|
|
params: params.to_query,
|
|
database: RailsMultisite::ConnectionManagement.current_db,
|
|
}
|
|
|
|
if ENV["LOGRAGE_INCLUDE_HTTP_ACCEPT_LANGUAGE_HEADER"] == "1" &&
|
|
(
|
|
http_accept_language_request_header =
|
|
event.payload[:headers]["HTTP_ACCEPT_LANGUAGE"]
|
|
).present?
|
|
output["request.headers.http_accept_language"] = http_accept_language_request_header
|
|
end
|
|
|
|
if data = (Thread.current[:_method_profiler] || event.payload[:timings])
|
|
if sql = data[:sql]
|
|
output[:db] = sql[:duration] * 1000
|
|
output[:db_calls] = sql[:calls]
|
|
end
|
|
|
|
if redis = data[:redis]
|
|
output[:redis] = redis[:duration] * 1000
|
|
output[:redis_calls] = redis[:calls]
|
|
end
|
|
|
|
if net = data[:net]
|
|
output[:net] = net[:duration] * 1000
|
|
output[:net_calls] = net[:calls]
|
|
end
|
|
|
|
# MethodProfiler.stop is called after this lambda, so the delta
|
|
# must be computed here.
|
|
if data[:__start_gc_heap_live_slots]
|
|
output[:heap_live_slots] = GC.stat[:heap_live_slots] -
|
|
data[:__start_gc_heap_live_slots]
|
|
end
|
|
end
|
|
|
|
output
|
|
rescue RateLimiter::LimitExceeded
|
|
# no idea who this is, but they are limited
|
|
{}
|
|
rescue => e
|
|
Rails.logger.warn(
|
|
"Failed to append custom options: #{e.message}\n#{e.backtrace.join("\n")}",
|
|
)
|
|
{}
|
|
end
|
|
end
|
|
|
|
config.lograge.formatter = Lograge::Formatters::Logstash.new
|
|
|
|
require "discourse_logstash_logger"
|
|
|
|
config.lograge.logger =
|
|
DiscourseLogstashLogger.logger(
|
|
logdev: Rails.root.join("log", "#{Rails.env}.log"),
|
|
type: :rails,
|
|
customize_event:
|
|
lambda { |event| event["database"] = RailsMultisite::ConnectionManagement.current_db },
|
|
level: Rails.application.config.log_level,
|
|
)
|
|
|
|
config.lograge.log_level = Rails.application.config.log_level
|
|
|
|
# Stop broadcasting to Rails' default logger
|
|
Rails.logger.stop_broadcasting_to(
|
|
Rails.logger.broadcasts.find { |logger| logger.is_a?(ActiveSupport::Logger) },
|
|
)
|
|
|
|
if Logster.logger
|
|
Logster.logger.subscribe do |severity, message, progname, opts, &block|
|
|
config.lograge.logger.add_with_opts(severity, message, progname, opts, &block)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|