8000 Polish api/aql recording stuff by neunhoef · Pull Request #21837 · arangodb/arangodb · GitHub
[go: up one dir, main page]

Skip to content

Polish api/aql recording stuff #21837

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions arangod/RestHandler/RestAdminServerHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,21 +318,21 @@ void RestAdminServerHandler::handleApiCalls() {
// Check if recording API is enabled
if (!apiRecordingFeature.isAPIEnabled()) {
generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN,
"recording API is disabled");
"The recording API has been disabled");
return;
}

// Check permission level
if (apiRecordingFeature.onlySuperUser()) {
if (!ExecContext::current().isSuperuser()) {
generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN,
"you need super user rights for recording API operations");
"You need super user rights for recording API operations");
return;
}
} else {
if (!ExecContext::current().isAdminUser()) {
generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN,
"you need admin rights for recording API operations");
"You need admin rights for recording API operations");
return;
}
}
Expand Down Expand Up @@ -364,7 +364,7 @@ void RestAdminServerHandler::handleAqlRecordedQueries() {
!ServerState::instance()->isSingleServer()) {
generateError(
Result(TRI_ERROR_NOT_IMPLEMENTED,
"API only available on coordinators and single servers"));
"API only available on Coordinators and single servers"));
return;
}

Expand Down
92 changes: 52 additions & 40 deletions arangod/RestServer/ApiRecordingFeature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ size_t ApiCallRecord::memoryUsage() const noexcept {
}

size_t AqlQueryRecord::memoryUsage() const noexcept {
return sizeof(AqlQueryRecord) + queryString.size() + database.size() +
bindParameters.byteSize();
return sizeof(AqlQueryRecord) + query.size() + database.size() +
bindVars.byteSize();
}

ApiRecordingFeature::ApiRecordingFeature(Server& server)
Expand All @@ -65,45 +65,54 @@ void ApiRecordingFeature::collectOptions(
std::shared_ptr<ProgramOptions> options) {
options->addOption(
"--server.api-call-recording",
"Record recent API calls for debugging purposes (default: true).",
new BooleanParameter(&_enabled),
arangodb::options::makeDefaultFlags(arangodb::options::Flags::Uncommon,
arangodb::options::Flags::Command));
"Whether to record recent API calls for debugging purposes.",
new BooleanParameter(&_enabledCalls),
arangodb::options::makeDefaultFlags(arangodb::options::Flags::Uncommon));

options->addOption(
"--server.api-recording-memory-limit",
"Memory limit for the list of ApiCallRecords.",
new UInt64Parameter(&_totalMemoryLimit, 1, 256000, 256000000000),
arangodb::options::makeDefaultFlags(arangodb::options::Flags::Uncommon,
arangodb::options::Flags::Command));
"Size limit for the list of API call records.",
new UInt64Parameter(&_totalMemoryLimitCalls, 1, 256 * 1024,
256 * 1024 * 1024 * 1024),
arangodb::options::makeDefaultFlags(arangodb::options::Flags::Uncommon));

options->addOption(
"--server.aql-query-recording",
"Whether to record recent AQL queries for debugging purposes.",
new BooleanParameter(&_enabledQueries),
arangodb::options::makeDefaultFlags(arangodb::options::Flags::Uncommon));

options->addOption(
"--server.aql-recording-memory-limit",
"Memory limit for the list of AqlCallRecords.",
new UInt64Parameter(&_totalMemoryLimitAql, 1, 256000, 256000000000),
arangodb::options::makeDefaultFlags(arangodb::options::Flags::Uncommon,
arangodb::options::Flags::Command));
"Size limit for the list of AQL query records.",
new UInt64Parameter(&_totalMemoryLimitQueries, 1, 256 * 1024,
256 * 1024 * 1024 * 1024),
arangodb::options::makeDefaultFlags(arangodb::options::Flags::Uncommon));

options
->addOption(
"--log.recording-api-enabled",
"Whether the recording API is enabled (true) or not (false), or "
"only enabled for superuser JWT (jwt).",
"only enabled for the superuser (jwt).",
new StringParameter(&_apiSwitch))
.setLongDescription(R"(The recording API (`/_admin/server/api-calls`
for API calls and `/_admin/server/aql-queries` for AQL queries provides access
to recorded API calls and AQL queries respectively.
Since this data might be sensitive depending on the context of
the deployment, this API needs to be secured properly. By default, the API is
accessible for admin users (administrative access to the `_system` database).
However, you can lock this down further.

The possible values for this option are:

- `true`: The recording API is accessible for admin users.
- `jwt`: The recording API is accessible for the superuser only
(authentication with JWT token and empty username).
- `false`: The recording API is not accessible at all.)");
.setLongDescription(R"(The `/_admin/server/api-calls` and
`/_admin/server/aql-queries` endpoints provide access to recorded API calls
and AQL queries respectively. They are referred to as the recording API.

Since this data might be sensitive depending on the context of the deployment,
these endpoints need to be properly secured. By default, the recording API is
accessible for admin users (users with administrative access to the `_system`
database). However, you can restrict it further to the superuser or disable it
altogether:

- `true`: The recording API is accessible for admin users.
- `jwt`: The recording API is accessible for the superuser only
(authentication with JWT superuser token and empty username).
- `false`: The recording API is not accessible at all.

Whether API calls and AQL queries are recorded is independent of this option.
It is controlled by the `--server.api-call-recording` and
`--server.aql-query-recording` startup options.)");
}

void ApiRecordingFeature::validateOptions(
Expand All @@ -122,20 +131,23 @@ void ApiRecordingFeature::validateOptions(

void ApiRecordingFeature::prepare() {
// Calculate per-list memory limit
_memoryPerApiRecordList = _totalMemoryLimit / NUMBER_OF_API_RECORD_LISTS;
_memoryPerAqlRecordList = _totalMemoryLimitAql / NUMBER_OF_AQL_RECORD_LISTS;
_memoryPerApiRecordList = _totalMemoryLimitCalls / NUMBER_OF_API_RECORD_LISTS;
_memoryPerAqlRecordList =
_totalMemoryLimitQueries / NUMBER_OF_AQL_RECORD_LISTS;

if (_enabled) {
if (_enabledCalls) {
_apiCallRecord = std::make_unique<BoundedList<ApiCallRecord>>(
_memoryPerApiRecordList, NUMBER_OF_API_RECORD_LISTS);
_aqlCallRecord = std::make_unique<BoundedList<AqlQueryRecord>>(
}
if (_enabledQueries) {
_aqlQueryRecord = std::make_unique<BoundedList<AqlQueryRecord>>(
_memoryPerAqlRecordList, NUMBER_OF_AQL_RECORD_LISTS);
}
}

void ApiRecordingFeature::start() {
// Start the cleanup thread if enabled
if (_enabled) {
if (_enabledCalls || _enabledQueries) {
_stopCleanupThread.store(false, std::memory_order_relaxed);
_cleanupThread = std::jthread([this] { cleanupLoop(); });
#ifdef TRI_HAVE_SYS_PRCTL_H
Expand All @@ -155,7 +167,7 @@ void ApiRecordingFeature::stop() {
void ApiRecordingFeature::recordAPICall(arangodb::rest::RequestType requestType,
std::string_view path,
std::string_view database) {
if (!_enabled || !_apiCallRecord) {
if (!_enabledCalls || !_apiCallRecord) {
return;
}

Expand All @@ -177,14 +189,14 @@ void ApiRecordingFeature::recordAPICall(arangodb::rest::RequestType requestType,
void ApiRecordingFeature::recordAQLQuery(
std::string_view queryString, std::string_view database,
velocypack::SharedSlice bindParameters) {
if (!_enabled || !_aqlCallRecord) {
if (!_enabledQueries || !_aqlQueryRecord) {
return;
}

// Start timing
auto start = std::chrono::steady_clock::now();

_aqlCallRecord->prepend(
_aqlQueryRecord->prepend(
AqlQueryRecord(queryString, database, std::move(bindParameters)));

// End timing and record metrics
Expand Down Expand Up @@ -212,8 +224,8 @@ void ApiRecordingFeature::cleanupLoop() {
apiCallCount = _apiCallRecord->clearTrash();
}

if (_aqlCallRecord) {
aqlCallCount = _aqlCallRecord->clearTrash();
if (_aqlQueryRecord) {
aqlCallCount = _aqlQueryRecord->clearTrash();
}

auto duration = std::chrono::steady_clock::now() - start;
Expand All @@ -229,7 +241,7 @@ void ApiRecordingFeature::cleanupLoop() {
}
if (aqlCallCount > 0) {
LOG_TOPIC("53627", TRACE, Logger::MEMORY)
<< "Cleaned up " << aqlCallCount << " AQL call record lists in "
<< "Cleaned up " << aqlCallCount << " AQL query record lists in "
<< nanoseconds.count() << " nanoseconds";
}
// Reset delay to minimum when trash was found
Expand Down
38 changes: 20 additions & 18 deletions arangod/RestServer/ApiRecordingFeature.h
8000
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,16 @@ auto inspect(Inspector& f, ApiCallRecord& record) {

struct AqlQueryRecord {
std::chrono::system_clock::time_point timeStamp;
std::string queryString;
std::string query;
std::string database;
velocypack::SharedSlice bindParameters;
velocypack::SharedSlice bindVars;

AqlQueryRecord(std::string_view queryString, std::string_view database,
velocypack::SharedSlice bindParameters)
AqlQueryRecord(std::string_view query, std::string_view database,
velocypack::SharedSlice bindVars)
: timeStamp(std::chrono::system_clock::now()),
queryString(queryString),
query(query),
database(database),
bindParameters(std::move(bindParameters)) {}
bindVars(std::move(bindVars)) {}

size_t memoryUsage() const noexcept;
};
Expand All @@ -100,9 +100,8 @@ auto inspect(Inspector& f, AqlQueryRecord& record) {
return f.object(record).fields(
f.field("timeStamp", record.timeStamp)
.transformWith(arangodb::inspection::TimeStampTransformer{}),
f.field("queryString", record.queryString),
f.field("database", record.database),
f.field("bindParameters", record.bindParameters));
f.field("query", record.query), f.field("database", record.database),
f.field("bindVars", record.bindVars));
}

class ApiRecordingFeature : public ArangodFeature {
Expand Down Expand Up @@ -138,8 +137,8 @@ class ApiRecordingFeature : public ArangodFeature {
template<typename F>
requires std::is_invocable_v<F, AqlQueryRecord const&>
void doForAqlQueryRecords(F&& callback) const {
if (_aqlCallRecord) {
_aqlCallRecord->forItems(std::forward<F>(callback));
if (_aqlQueryRecord) {
_aqlQueryRecord->forItems(std::forward<F>(callback));
}
}

Expand All @@ -154,27 +153,30 @@ class ApiRecordingFeature : public ArangodFeature {
void cleanupLoop();

// Whether or not to record recent API calls
bool _enabled{true};
bool _enabledCalls{true};

// Whether or not to record recent AQL queries
bool _enabledQueries{true};

// Total memory limit for all ApiCallRecord lists combined
size_t _totalMemoryLimit{25600000}; // Default: ~25MB
size_t _totalMemoryLimitCalls{25 * 1024 * 1024}; // Default: ~25MiB

// Total memory limit for all AqlCallRecord lists combined
size_t _totalMemoryLimitAql{25600000}; // Default: ~25MB
size_t _totalMemoryLimitQueries{25 * 1024 * 1024}; // Default: ~25MiB

// Memory limit for one list of ApiCallRecords (calculated as
// _totalMemoryLimit / NUMBER_OF_API_RECORD_LISTS)
// _totalMemoryLimitCalls / NUMBER_OF_API_RECORD_LISTS)
size_t _memoryPerApiRecordList{100000};

// Memory limit for one list of AqlCallRecords (calculated as
// _totalMemoryLimit / NUMBER_OF_API_RECORD_LISTS)
// Memory limit for one list of AqlQueryRecords (calculated as
// _totalMemoryLimitQueries / NUMBER_OF_AQL_RECORD_LISTS)
size_t _memoryPerAqlRecordList{100000};

/// record of recent api calls:
std::unique_ptr<arangodb::BoundedList<ApiCallRecord>> _apiCallRecord;

// Record of recent AQL calls:
std::unique_ptr<arangodb::BoundedList<AqlQueryRecord>> _aqlCallRecord;
std::u 8000 nique_ptr<arangodb::BoundedList<AqlQueryRecord>> _aqlQueryRecord;

// Flag to control the cleanup thread
std::atomic<bool> _stopCleanupThread{false};
Expand Down
10 changes: 4 additions & 6 deletions lib/Logger/LoggerFeature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ contains a single character with the server's role. The roles are:
options
->addOption("--log.api-enabled",
"Whether the log API is enabled (true) or not (false), or "
"only enabled for superuser JWT (jwt).",
"only enabled for the superuser (jwt).",
new StringParameter(&_apiSwitch))
.setLongDescription(R"(Credentials are not written to log files.
Nevertheless, some logged data might be sensitive depending on the context of
Expand All @@ -404,14 +404,12 @@ with log files is recommended.

Since the database server offers an API to control logging and query logging
data, this API has to be secured properly. By default, the API is accessible
for admin users (administrative access to the `_system` database). However,
you can lock this down further.

The possible values for this option are:
for admin users (administrative access to the `_system` database).
However, you can restrict it further to the superuser or disable it altogether:

- `true`: The `/_admin/log` API is accessible for admin users.
- `jwt`: The `/_admin/log` API is accessible for the superuser only
(authentication with JWT token and empty username).
(authentication with JWT superuser token and empty username).
- `false`: The `/_admin/log` API is not accessible at all.)");
}

Expand Down
Loading
0