mirror of
https://github.com/discourse/discourse.git
synced 2025-03-06 16:37:55 +08:00
FEATURE: Track API and user API requests (#19186)
Adds stats for API and user API requests similar to regular page views. This comes with a new report to visualize API requests per day like the consolidated page views one.
This commit is contained in:
parent
c139767055
commit
3048d3d07d
@ -11,7 +11,9 @@ class ApplicationRequest < ActiveRecord::Base
|
|||||||
page_view_logged_in
|
page_view_logged_in
|
||||||
page_view_anon
|
page_view_anon
|
||||||
page_view_logged_in_mobile
|
page_view_logged_in_mobile
|
||||||
page_view_anon_mobile)
|
page_view_anon_mobile
|
||||||
|
api
|
||||||
|
user_api)
|
||||||
|
|
||||||
include CachedCounting
|
include CachedCounting
|
||||||
|
|
||||||
|
40
app/models/concerns/reports/consolidated_api_requests.rb
Normal file
40
app/models/concerns/reports/consolidated_api_requests.rb
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Reports::ConsolidatedApiRequests
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
class_methods do
|
||||||
|
def report_consolidated_api_requests(report)
|
||||||
|
filters = %w[
|
||||||
|
api
|
||||||
|
user_api
|
||||||
|
]
|
||||||
|
|
||||||
|
report.modes = [:stacked_chart]
|
||||||
|
|
||||||
|
tertiary = ColorScheme.hex_for_name('tertiary') || '0088cc'
|
||||||
|
danger = ColorScheme.hex_for_name('danger') || 'e45735'
|
||||||
|
|
||||||
|
requests = filters.map do |filter|
|
||||||
|
color = filter == "api" ? report.rgba_color(tertiary) : report.rgba_color(danger)
|
||||||
|
|
||||||
|
{
|
||||||
|
req: filter,
|
||||||
|
label: I18n.t("reports.consolidated_api_requests.xaxis.#{filter}"),
|
||||||
|
color: color,
|
||||||
|
data: ApplicationRequest.where(req_type: ApplicationRequest.req_types[filter])
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
requests.each do |request|
|
||||||
|
request[:data] = request[:data].where('date >= ? AND date <= ?', report.start_date, report.end_date)
|
||||||
|
.order(date: :asc)
|
||||||
|
.group(:date)
|
||||||
|
.sum(:count)
|
||||||
|
.map { |date, count| { x: date, y: count } }
|
||||||
|
end
|
||||||
|
|
||||||
|
report.data = requests
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -40,6 +40,7 @@ class Report
|
|||||||
include Reports::UserFlaggingRatio
|
include Reports::UserFlaggingRatio
|
||||||
include Reports::TrustLevelGrowth
|
include Reports::TrustLevelGrowth
|
||||||
include Reports::ConsolidatedPageViews
|
include Reports::ConsolidatedPageViews
|
||||||
|
include Reports::ConsolidatedApiRequests
|
||||||
include Reports::Visits
|
include Reports::Visits
|
||||||
include Reports::TimeToFirstResponse
|
include Reports::TimeToFirstResponse
|
||||||
include Reports::UsersByTrustLevel
|
include Reports::UsersByTrustLevel
|
||||||
|
@ -1179,6 +1179,13 @@ en:
|
|||||||
editor: Editor
|
editor: Editor
|
||||||
author: Author
|
author: Author
|
||||||
edit_reason: Reason
|
edit_reason: Reason
|
||||||
|
consolidated_api_requests:
|
||||||
|
title: "Consolidated API Requests"
|
||||||
|
xaxis:
|
||||||
|
api: "API"
|
||||||
|
user_api: "User API"
|
||||||
|
yaxis: "Day"
|
||||||
|
description: "API requests for regular API keys and user API keys."
|
||||||
dau_by_mau:
|
dau_by_mau:
|
||||||
title: "DAU/MAU"
|
title: "DAU/MAU"
|
||||||
xaxis: "Day"
|
xaxis: "Day"
|
||||||
|
@ -64,10 +64,11 @@ class Middleware::RequestTracker
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.log_request(data)
|
def self.log_request(data)
|
||||||
status = data[:status]
|
if data[:is_api]
|
||||||
track_view = data[:track_view]
|
ApplicationRequest.increment!(:api)
|
||||||
|
elsif data[:is_user_api]
|
||||||
if track_view
|
ApplicationRequest.increment!(:user_api)
|
||||||
|
elsif data[:track_view]
|
||||||
if data[:is_crawler]
|
if data[:is_crawler]
|
||||||
ApplicationRequest.increment!(:page_view_crawler)
|
ApplicationRequest.increment!(:page_view_crawler)
|
||||||
WebCrawlerRequest.increment!(data[:user_agent])
|
WebCrawlerRequest.increment!(data[:user_agent])
|
||||||
@ -82,6 +83,7 @@ class Middleware::RequestTracker
|
|||||||
|
|
||||||
ApplicationRequest.increment!(:http_total)
|
ApplicationRequest.increment!(:http_total)
|
||||||
|
|
||||||
|
status = data[:status]
|
||||||
if status >= 500
|
if status >= 500
|
||||||
ApplicationRequest.increment!(:http_5xx)
|
ApplicationRequest.increment!(:http_5xx)
|
||||||
elsif data[:is_background]
|
elsif data[:is_background]
|
||||||
@ -110,6 +112,9 @@ class Middleware::RequestTracker
|
|||||||
has_auth_cookie = Auth::DefaultCurrentUserProvider.find_v0_auth_cookie(request).present?
|
has_auth_cookie = Auth::DefaultCurrentUserProvider.find_v0_auth_cookie(request).present?
|
||||||
has_auth_cookie ||= Auth::DefaultCurrentUserProvider.find_v1_auth_cookie(env).present?
|
has_auth_cookie ||= Auth::DefaultCurrentUserProvider.find_v1_auth_cookie(env).present?
|
||||||
|
|
||||||
|
is_api ||= !!env[Auth::DefaultCurrentUserProvider::API_KEY_ENV]
|
||||||
|
is_user_api ||= !!env[Auth::DefaultCurrentUserProvider::USER_API_KEY_ENV]
|
||||||
|
|
||||||
is_message_bus = request.path.start_with?("#{Discourse.base_path}/message-bus/")
|
is_message_bus = request.path.start_with?("#{Discourse.base_path}/message-bus/")
|
||||||
is_topic_timings = request.path.start_with?("#{Discourse.base_path}/topics/timings")
|
is_topic_timings = request.path.start_with?("#{Discourse.base_path}/topics/timings")
|
||||||
|
|
||||||
@ -117,6 +122,8 @@ class Middleware::RequestTracker
|
|||||||
status: status,
|
status: status,
|
||||||
is_crawler: helper.is_crawler?,
|
is_crawler: helper.is_crawler?,
|
||||||
has_auth_cookie: has_auth_cookie,
|
has_auth_cookie: has_auth_cookie,
|
||||||
|
is_api: is_api,
|
||||||
|
is_user_api: is_user_api,
|
||||||
is_background: is_message_bus || is_topic_timings,
|
is_background: is_message_bus || is_topic_timings,
|
||||||
background_type: is_message_bus ? "message-bus" : "topic-timings",
|
background_type: is_message_bus ? "message-bus" : "topic-timings",
|
||||||
is_mobile: helper.is_mobile?,
|
is_mobile: helper.is_mobile?,
|
||||||
|
51
spec/integration/request_tracker_spec.rb
Normal file
51
spec/integration/request_tracker_spec.rb
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe 'request tracker' do
|
||||||
|
let(:api_key) do
|
||||||
|
Fabricate(
|
||||||
|
:api_key,
|
||||||
|
user: Fabricate.build(:user),
|
||||||
|
api_key_scopes: [ApiKeyScope.new(resource: 'users', action: 'show')]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:user_api_key) do
|
||||||
|
Fabricate(:user_api_key, scopes: [Fabricate.build(:user_api_key_scope, name: 'session_info')])
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
CachedCounting.reset
|
||||||
|
CachedCounting.enable
|
||||||
|
ApplicationRequest.enable
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
ApplicationRequest.disable
|
||||||
|
CachedCounting.reset
|
||||||
|
CachedCounting.disable
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when using an api key' do
|
||||||
|
it 'is counted as an API request' do
|
||||||
|
get "/u/#{api_key.user.username}.json", headers: { HTTP_API_KEY: api_key.key }
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
CachedCounting.flush
|
||||||
|
expect(ApplicationRequest.http_total.first.count).to eq(1)
|
||||||
|
expect(ApplicationRequest.http_2xx.first.count).to eq(1)
|
||||||
|
expect(ApplicationRequest.api.first.count).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when using an user api key' do
|
||||||
|
it 'is counted as a user API request' do
|
||||||
|
get '/session/current.json', headers: { HTTP_USER_API_KEY: user_api_key.key }
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
CachedCounting.flush
|
||||||
|
expect(ApplicationRequest.http_total.first.count).to eq(1)
|
||||||
|
expect(ApplicationRequest.http_2xx.first.count).to eq(1)
|
||||||
|
expect(ApplicationRequest.user_api.first.count).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -100,6 +100,33 @@ RSpec.describe Middleware::RequestTracker do
|
|||||||
expect(ApplicationRequest.page_view_crawler.first.count).to eq(1)
|
expect(ApplicationRequest.page_view_crawler.first.count).to eq(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "logs API requests correctly" do
|
||||||
|
data = Middleware::RequestTracker.get_data(
|
||||||
|
env("_DISCOURSE_API" => "1"), ["200", { "Content-Type" => 'text/json' }], 0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
Middleware::RequestTracker.log_request(data)
|
||||||
|
|
||||||
|
data = Middleware::RequestTracker.get_data(
|
||||||
|
env("_DISCOURSE_API" => "1"), ["404", { "Content-Type" => 'text/json' }], 0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
Middleware::RequestTracker.log_request(data)
|
||||||
|
|
||||||
|
data = Middleware::RequestTracker.get_data(
|
||||||
|
env("_DISCOURSE_USER_API" => "1"), ["200", {}], 0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
Middleware::RequestTracker.log_request(data)
|
||||||
|
CachedCounting.flush
|
||||||
|
|
||||||
|
expect(ApplicationRequest.http_total.first.count).to eq(3)
|
||||||
|
expect(ApplicationRequest.http_2xx.first.count).to eq(2)
|
||||||
|
|
||||||
|
expect(ApplicationRequest.api.first.count).to eq(2)
|
||||||
|
expect(ApplicationRequest.user_api.first.count).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
it "can log Discourse user agent requests correctly" do
|
it "can log Discourse user agent requests correctly" do
|
||||||
# log discourse api agents as crawlers for page view stats...
|
# log discourse api agents as crawlers for page view stats...
|
||||||
data = Middleware::RequestTracker.get_data(env(
|
data = Middleware::RequestTracker.get_data(env(
|
||||||
|
@ -1233,6 +1233,53 @@ RSpec.describe Report do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe ".report_consolidated_api_requests" do
|
||||||
|
before do
|
||||||
|
freeze_time(Time.now.at_midnight)
|
||||||
|
Theme.clear_default!
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:reports) { Report.find('consolidated_api_requests') }
|
||||||
|
|
||||||
|
context "with no data" do
|
||||||
|
it "works" do
|
||||||
|
reports.data.each do |report|
|
||||||
|
expect(report[:data]).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with data" do
|
||||||
|
before do
|
||||||
|
CachedCounting.reset
|
||||||
|
CachedCounting.enable
|
||||||
|
ApplicationRequest.enable
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
ApplicationRequest.disable
|
||||||
|
CachedCounting.disable
|
||||||
|
end
|
||||||
|
|
||||||
|
it "works" do
|
||||||
|
2.times { ApplicationRequest.increment!(:api) }
|
||||||
|
ApplicationRequest.increment!(:user_api)
|
||||||
|
|
||||||
|
CachedCounting.flush
|
||||||
|
|
||||||
|
api_report = reports.data.find { |r| r[:req] == "api" }
|
||||||
|
user_api_report = reports.data.find { |r| r[:req] == "user_api" }
|
||||||
|
|
||||||
|
expect(api_report[:color]).to eql("rgba(0,136,204,1)")
|
||||||
|
expect(api_report[:data][0][:y]).to eql(2)
|
||||||
|
|
||||||
|
expect(user_api_report[:color]).to eql("rgba(200,0,1,1)")
|
||||||
|
expect(user_api_report[:data][0][:y]).to eql(1)
|
||||||
|
ensure
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "trust_level_growth" do
|
describe "trust_level_growth" do
|
||||||
before do
|
before do
|
||||||
freeze_time(Time.now.at_midnight)
|
freeze_time(Time.now.at_midnight)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user