From 90c0384ac67e0c31bb9beef40572cc88a37db7a3 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Wed, 3 Sep 2025 16:25:11 +0530 Subject: [PATCH 1/7] Monitoring: Add endpoint ti get the global server logs --- v2/arangodb/client_admin.go | 53 ++++++++++++++++++++ v2/arangodb/client_admin_log_impl.go | 73 ++++++++++++++++++++++++++++ v2/tests/admin_log_test.go | 31 ++++++++++++ 3 files changed, 157 insertions(+) diff --git a/v2/arangodb/client_admin.go b/v2/arangodb/client_admin.go index 8925f5a0..8166bb19 100644 --- a/v2/arangodb/client_admin.go +++ b/v2/arangodb/client_admin.go @@ -51,6 +51,9 @@ type ClientAdminLog interface { // SetLogLevels sets log levels for a given topics. SetLogLevels(ctx context.Context, logLevels LogLevels, opts *LogLevelsSetOptions) error + + // Logs retrieve logs from server in ArangoDB 3.8.0+ format + Logs(ctx context.Context, queryParams *AdminLogEntriesOptions) (AdminLogEntriesResponse, error) } type ClientAdminLicense interface { @@ -61,3 +64,53 @@ type ClientAdminLicense interface { // Can be called on single servers, Coordinators, and DB-Servers. SetLicense(ctx context.Context, license string, force bool) error } + +type AdminLogEntriesOptions struct { + // Upto log level + Upto string `json:"upto"` // (default: "info") + + // Returns all log entries of log level level. + // Note that the query parameters upto and level are mutually exclusive. + Level *string `json:"level,omitempty"` + + // Start position + Start int `json:"start"` // (default: 0) + + // Restricts the result to at most size log entries. + Size *int `json:"size,omitempty"` + + // Offset position + Offset int `json:"offset"` // (default: 0) + + // Only return the log entries containing the text specified in search. + Search *string `json:"search,omitempty"` + + // Sort the log entries either ascending (if sort is asc) or + // descending (if sort is desc) according to their id values. + Sort string `json:"sort,omitempty"` // (default: "asc") + + // Returns all log entries of the specified server. + // If no serverId is given, the asked server will reply. + // This parameter is only meaningful on Coordinators. + ServerId *string `json:"serverId,omitempty"` +} + +type AdminLogEntriesResponse struct { + // Total number of log entries + Total int `json:"total"` + + // List of log messages + Messages []MessageObject `json:"messages"` +} + +type MessageObject struct { + Id int `json:"id"` + // Log topic + Topic string `json:"topic"` + // Log level + Level string `json:"level"` + // Current date and time + Date string `json:"date"` + // Log message + Message string `json:"message"` +} diff --git a/v2/arangodb/client_admin_log_impl.go b/v2/arangodb/client_admin_log_impl.go index 061e5060..44ef9f9b 100644 --- a/v2/arangodb/client_admin_log_impl.go +++ b/v2/arangodb/client_admin_log_impl.go @@ -22,7 +22,9 @@ package arangodb import ( "context" + "fmt" "net/http" + "strconv" "github.com/pkg/errors" @@ -98,3 +100,74 @@ func (c *clientAdmin) SetLogLevels(ctx context.Context, logLevels LogLevels, opt return response.AsArangoErrorWithCode(code) } } + +func defaultAdminLogEntriesOptions() *AdminLogEntriesOptions { + return &AdminLogEntriesOptions{ + Start: 0, + Offset: 0, + Upto: "info", + Sort: "asc", + } +} + +func (c *clientAdmin) formServerLogEntriesParams(opts *AdminLogEntriesOptions) ([]connection.RequestModifier, error) { + var mods []connection.RequestModifier + if opts == nil { + opts = defaultAdminLogEntriesOptions() + } + + if opts.Level != nil && opts.Upto != "" { + return nil, errors.New("parameters 'level' and 'upto' cannot be used together") + } + + if opts.Upto != "" { + mods = append(mods, connection.WithQuery("upto", opts.Upto)) + } + if opts.Level != nil && *opts.Level != "" { + mods = append(mods, connection.WithQuery("level", *opts.Level)) + } + if opts.Size != nil { + mods = append(mods, connection.WithQuery("size", fmt.Sprintf("%d", *opts.Size))) + } + if opts.Search != nil && *opts.Search != "" { + mods = append(mods, connection.WithQuery("search", *opts.Search)) + } + if opts.Sort != "" { + mods = append(mods, connection.WithQuery("sort", opts.Sort)) + } + if opts.ServerId != nil && *opts.ServerId != "" { + mods = append(mods, connection.WithQuery("serverId", *opts.ServerId)) + } + if opts.Start >= 0 { + mods = append(mods, connection.WithQuery("start", strconv.Itoa(opts.Start))) + } + if opts.Offset >= 0 { + mods = append(mods, connection.WithQuery("offset", strconv.Itoa(opts.Offset))) + } + return mods, nil +} + +// Logs retrieve logs from server in ArangoDB 3.8.0+ format +func (c *clientAdmin) Logs(ctx context.Context, queryParams *AdminLogEntriesOptions) (AdminLogEntriesResponse, error) { + url := connection.NewUrl("_admin", "log", "entries") + + var response struct { + shared.ResponseStruct `json:",inline"` + AdminLogEntriesResponse `json:",inline"` + } + mods, err := c.formServerLogEntriesParams(queryParams) + if err != nil { + return AdminLogEntriesResponse{}, err + } + resp, err := connection.CallGet(ctx, c.client.connection, url, &response, mods...) + if err != nil { + return AdminLogEntriesResponse{}, errors.WithStack(err) + } + + switch code := resp.Code(); code { + case http.StatusOK: + return response.AdminLogEntriesResponse, nil + default: + return AdminLogEntriesResponse{}, response.AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/admin_log_test.go b/v2/tests/admin_log_test.go index 10ff5015..765fede4 100644 --- a/v2/tests/admin_log_test.go +++ b/v2/tests/admin_log_test.go @@ -145,3 +145,34 @@ func changeLogLevel(l string) string { return "INFO" } + +func Test_Logs(t *testing.T) { + // This test cannot run subtests parallel, because it changes admin settings. + wrapOpts := WrapOptions{ + Parallel: utils.NewType(false), + } + + Wrap(t, func(t *testing.T, client arangodb.Client) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + skipBelowVersion(client, ctx, "3.8.0", t) + + logsResp, err := client.Logs(ctx, &arangodb.AdminLogEntriesOptions{ + Start: 0, + Offset: 0, + Upto: "3", + Sort: "asc", + }) + require.NoError(t, err) + require.NotNil(t, logsResp) + + _, err = client.Logs(ctx, &arangodb.AdminLogEntriesOptions{ + Start: 0, + Offset: 0, + Upto: "3", + Sort: "asc", + Level: utils.NewType("DEBUG"), + }) + require.Error(t, err) + }) + }, wrapOpts) +} From 57deddfa63b06bbda76aeafc6c1a5d195d006229 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Wed, 3 Sep 2025 21:38:13 +0530 Subject: [PATCH 2/7] Monitoring: Add endpoint to reset the server log levels --- v2/arangodb/client_admin.go | 115 +++++++++++++++++++++++++++ v2/arangodb/client_admin_log_impl.go | 27 +++++++ v2/tests/admin_log_test.go | 17 ++++ 3 files changed, 159 insertions(+) diff --git a/v2/arangodb/client_admin.go b/v2/arangodb/client_admin.go index 8166bb19..a59eab93 100644 --- a/v2/arangodb/client_admin.go +++ b/v2/arangodb/client_admin.go @@ -63,6 +63,9 @@ type ClientAdminLicense interface { // SetLicense Set a new license for an Enterprise Edition instance. // Can be called on single servers, Coordinators, and DB-Servers. SetLicense(ctx context.Context, license string, force bool) error + + // DeleteLogLevels removes log levels for a specific server. + DeleteLogLevels(ctx context.Context, serverId *string) (LogLevelResponse, error) } type AdminLogEntriesOptions struct { @@ -114,3 +117,115 @@ type MessageObject struct { // Log message Message string `json:"message"` } + +type LogLevelResponse struct { + Agency string `json:"agency"` + // Communication between Agency instances. + AgencyComm string `json:"agencycomm"` + // Agency's Raft store operations. + AgencyStore string `json:"agencystore"` + // Backup and restore processes. + Backup string `json:"backup"` + // Benchmarking and performance test logs. + Bench string `json:"bench"` + // General cluster-level logs. + Cluster string `json:"cluster"` + // Network communication between servers. + Communication string `json:"communication"` + // User authentication activities. + Authentication string `json:"authentication"` + // Configuration-related logs. + Config string `json:"config"` + // Crash handling. + Crash string `json:"crash"` + // Data export (dump) operations. + Dump string `json:"dump"` + // Storage engines (RocksDB) + Engines string `json:"engines"` + // General server logs not tied to a specific topic. + General string `json:"general"` + // Cluster heartbeat monitoring. + Heartbeat string `json:"heartbeat"` + // AQL query execution and planning. + Aql string `json:"aql"` + // Graph operations and traversals. + Graphs string `json:"graphs"` + // Maintenance operations in cluster. + Maintenance string `json:"maintenance"` + // User authorization and permissions. + Authorization string `json:"authorization"` + // Query execution and lifecycle. + Queries string `json:"queries"` + // Development/debugging logs. + Development string `json:"development"` + // Replication processes (followers, leaders). + Replication string `json:"replication"` + // V8 JavaScript engine logs. + V8 string `json:"v8"` + // Usage of deprecated features. + Deprecation string `json:"deprecation"` + // RocksDB storage engine-specific logs + RocksDB string `json:"rocksdb"` + // Audit logs for database operations. + AuditDatabase string `json:"audit-database"` + // Data validation errors/warnings. + Validation string `json:"validation"` + // RocksDB flush operations. + Flush string `json:"flush"` + // Audit logs for authorization events. + AuditAuthorization string `json:"audit-authorization"` + // System calls made by the server. + Syscall string `json:"syscall"` + // In-memory cache usage and performance. + Cache string `json:"cache"` + // Security-related logs. + Security string `json:"security"` + // Memory allocation and usage. + Memory string `json:"memory"` + // Restore operations from backup. + Restore string `json:"restore"` + // HTTP client communication logs. + HTTPClient string `json:"httpclient"` + // Audit logs for view operations. + AuditView string `json:"audit-view"` + // Audit logs for document operations. + AuditDocument string `json:"audit-document"` + // Audit logs for hot backup. + AuditHotBackup string `json:"audit-hotbackup"` + // Audit logs for collection operations. + AuditCollection string `json:"audit-collection"` + // Server statistics collection. + Statistics string `json:"statistics"` + // Incoming client requests. + Requests string `json:"requests"` + // Audit logs for service-level actions. + AuditService string `json:"audit-service"` + // TTL (Time-to-Live) expiration logs. + TTL string `json:"ttl"` + // Next-gen replication subsystem logs. + Replication2 string `json:"replication2"` + // SSL/TLS communication logs. + SSL string `json:"ssl"` + // Thread management logs. + Threads string `json:"threads"` + // License-related logs. + License string `json:"license"` + // IResearch (ArangoSearch) library logs. + Libiresearch string `json:"libiresearch"` + // Transactions. + Trx string `json:"trx"` + // Supervision process in the cluster. + Supervision string `json:"supervision"` + // Server startup sequence. + Startup string `json:"startup"` + // Audit logs for authentication events. + AuditAuthentication string `json:"audit-authentication"` + // Replication Write-Ahead Log. + RepWal string `json:"rep-wal"` + // View-related logs. + Views string `json:"views"` + // ArangoSearch engine logs. + ArangoSearch string `json:"arangosearch"` + // Replication state machine logs. + RepState string `json:"rep-state"` +} diff --git a/v2/arangodb/client_admin_log_impl.go b/v2/arangodb/client_admin_log_impl.go index 44ef9f9b..51015c4d 100644 --- a/v2/arangodb/client_admin_log_impl.go +++ b/v2/arangodb/client_admin_log_impl.go @@ -171,3 +171,30 @@ func (c *clientAdmin) Logs(ctx context.Context, queryParams *AdminLogEntriesOpti return AdminLogEntriesResponse{}, response.AsArangoErrorWithCode(code) } } + +// DeleteLogLevels is for reset the server log levels from server in ArangoDB 3.12.1+ format +func (c *clientAdmin) DeleteLogLevels(ctx context.Context, serverId *string) (LogLevelResponse, error) { + url := connection.NewUrl("_admin", "log", "level") + + var response struct { + shared.ResponseStruct `json:",inline"` + LogLevelResponse `json:",inline"` + } + + var mods []connection.RequestModifier + if serverId != nil { + mods = append(mods, connection.WithQuery("serverId", *serverId)) + } + + resp, err := connection.CallDelete(ctx, c.client.connection, url, &response, mods...) + if err != nil { + return LogLevelResponse{}, errors.WithStack(err) + } + + switch code := resp.Code(); code { + case http.StatusOK: + return response.LogLevelResponse, nil + default: + return LogLevelResponse{}, response.AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/admin_log_test.go b/v2/tests/admin_log_test.go index 765fede4..ff0856cc 100644 --- a/v2/tests/admin_log_test.go +++ b/v2/tests/admin_log_test.go @@ -176,3 +176,20 @@ func Test_Logs(t *testing.T) { }) }, wrapOpts) } + +func Test_DeleteLogLevels(t *testing.T) { + // This test cannot run subtests parallel, because it changes admin settings. + wrapOpts := WrapOptions{ + Parallel: utils.NewType(false), + } + + Wrap(t, func(t *testing.T, client arangodb.Client) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + skipBelowVersion(client, ctx, "3.12.1", t) + + logsResp, err := client.DeleteLogLevels(ctx, nil) + require.NoError(t, err) + require.NotNil(t, logsResp) + }) + }, wrapOpts) +} From e46d74db5e8fccb83e538051ef88fbc738c914ae Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Thu, 4 Sep 2025 12:18:14 +0530 Subject: [PATCH 3/7] Modified test case for reset the server log levels --- v2/tests/admin_log_test.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/v2/tests/admin_log_test.go b/v2/tests/admin_log_test.go index ff0856cc..b5b2e146 100644 --- a/v2/tests/admin_log_test.go +++ b/v2/tests/admin_log_test.go @@ -186,8 +186,19 @@ func Test_DeleteLogLevels(t *testing.T) { Wrap(t, func(t *testing.T, client arangodb.Client) { withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { skipBelowVersion(client, ctx, "3.12.1", t) + // Role check + serverRole, err := client.ServerRole(ctx) + require.NoError(t, err) + t.Logf("ServerRole: %s", serverRole) + + var serverId *string + if serverRole == arangodb.ServerRoleCoordinator { + serverID, err := client.ServerID(ctx) + require.NoError(t, err) + serverId = &serverID + } - logsResp, err := client.DeleteLogLevels(ctx, nil) + logsResp, err := client.DeleteLogLevels(ctx, serverId) require.NoError(t, err) require.NotNil(t, logsResp) }) From e85878f7cd9ec3678da5c7f5f80ea58940889d7a Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Thu, 4 Sep 2025 13:59:51 +0530 Subject: [PATCH 4/7] Monitoring: Add endpoints for update and get log settings --- v2/arangodb/client_admin.go | 24 ++++++++++++ v2/arangodb/client_admin_log_impl.go | 57 ++++++++++++++++++++++++++++ v2/tests/admin_log_test.go | 23 +++++++++++ 3 files changed, 104 insertions(+) diff --git a/v2/arangodb/client_admin.go b/v2/arangodb/client_admin.go index a59eab93..1060fe6e 100644 --- a/v2/arangodb/client_admin.go +++ b/v2/arangodb/client_admin.go @@ -66,6 +66,12 @@ type ClientAdminLicense interface { // DeleteLogLevels removes log levels for a specific server. DeleteLogLevels(ctx context.Context, serverId *string) (LogLevelResponse, error) + + // GetStructuredLogSettings returns the server's current structured log settings. + GetStructuredLogSettings(ctx context.Context) (LogSettingsOptions, error) + + // UpdateStructuredLogSettings modifies and returns the server's current structured log settings. + UpdateStructuredLogSettings(ctx context.Context, opts *LogSettingsOptions) (LogSettingsOptions, error) } type AdminLogEntriesOptions struct { @@ -229,3 +235,21 @@ type LogLevelResponse struct { // Replication state machine logs. RepState string `json:"rep-state"` } + +// LogSettingsOptions represents configurable flags for including +// specific fields in structured log output. It is used both in +// requests (to configure log behavior) and responses (to indicate +// which fields are currently enabled). +type LogSettingsOptions struct { + // Database indicates whether the database name should be included + // in structured log entries. + Database *bool `json:"database,omitempty"` + + // Url indicates whether the request URL should be included + // in structured log entries. + Url *bool `json:"url,omitempty"` + + // Username indicates whether the authenticated username should be included + // in structured log entries. + Username *bool `json:"username,omitempty"` +} diff --git a/v2/arangodb/client_admin_log_impl.go b/v2/arangodb/client_admin_log_impl.go index 51015c4d..703383af 100644 --- a/v2/arangodb/client_admin_log_impl.go +++ b/v2/arangodb/client_admin_log_impl.go @@ -198,3 +198,60 @@ func (c *clientAdmin) DeleteLogLevels(ctx context.Context, serverId *string) (Lo return LogLevelResponse{}, response.AsArangoErrorWithCode(code) } } + +// GetStructuredLogSettings returns the server's current structured log settings. +func (c *clientAdmin) GetStructuredLogSettings(ctx context.Context) (LogSettingsOptions, error) { + url := connection.NewUrl("_admin", "log", "structured") + + var response struct { + shared.ResponseStruct `json:",inline"` + LogSettingsOptions `json:",inline"` + } + resp, err := connection.CallGet(ctx, c.client.connection, url, &response) + if err != nil { + return LogSettingsOptions{}, errors.WithStack(err) + } + + switch code := resp.Code(); code { + case http.StatusOK: + return response.LogSettingsOptions, nil + default: + return LogSettingsOptions{}, response.AsArangoErrorWithCode(code) + } +} + +func formStructuredLogParams(opt *LogSettingsOptions) map[string]bool { + params := map[string]bool{} + if opt.Database != nil { + params["database"] = *opt.Database + } + if opt.Url != nil { + params["url"] = *opt.Url + } + if opt.Username != nil { + params["username"] = *opt.Username + } + return params +} + +// UpdateStructuredLogSettings modifies and returns the server's current structured log settings. +func (c *clientAdmin) UpdateStructuredLogSettings(ctx context.Context, opts *LogSettingsOptions) (LogSettingsOptions, error) { + url := connection.NewUrl("_admin", "log", "structured") + + var response struct { + shared.ResponseStruct `json:",inline"` + LogSettingsOptions `json:",inline"` + } + + resp, err := connection.CallPut(ctx, c.client.connection, url, &response, formStructuredLogParams(opts)) + if err != nil { + return LogSettingsOptions{}, errors.WithStack(err) + } + + switch code := resp.Code(); code { + case http.StatusOK: + return response.LogSettingsOptions, nil + default: + return LogSettingsOptions{}, response.AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/admin_log_test.go b/v2/tests/admin_log_test.go index b5b2e146..9869d1bb 100644 --- a/v2/tests/admin_log_test.go +++ b/v2/tests/admin_log_test.go @@ -204,3 +204,26 @@ func Test_DeleteLogLevels(t *testing.T) { }) }, wrapOpts) } + +func Test_StructuredLogSettings(t *testing.T) { + Wrap(t, func(t *testing.T, client arangodb.Client) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + skipBelowVersion(client, ctx, "3.12.0", t) + + opts := arangodb.LogSettingsOptions{ + Database: utils.NewType(true), + } + modifiedResp, err := client.UpdateStructuredLogSettings(ctx, &opts) + require.NoError(t, err) + require.NotEmpty(t, modifiedResp) + require.NotNil(t, modifiedResp.Database) + require.Equal(t, *modifiedResp.Database, *opts.Database) + + getResp, err := client.GetStructuredLogSettings(ctx) + require.NoError(t, err) + require.NotEmpty(t, getResp) + require.NotNil(t, getResp.Database) + require.Equal(t, *getResp.Database, *opts.Database) + }) + }) +} From 402cb24466460036aed54fa4822289934474e000 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Thu, 4 Sep 2025 17:40:52 +0530 Subject: [PATCH 5/7] Monitoring: Add endpoint to fetch recent API calls --- Makefile | 2 +- v2/arangodb/client_admin.go | 39 +++++++++++++++++++++------- v2/arangodb/client_admin_log_impl.go | 26 +++++++++++++++++-- v2/tests/admin_log_test.go | 19 ++++++++++++++ 4 files changed, 74 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 49468594..777a2437 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ DOCKER_CMD:=docker run GOBUILDTAGS:=$(TAGS) GOBUILDTAGSOPT=-tags "$(GOBUILDTAGS)" -ARANGODB ?= arangodb/arangodb:latest +ARANGODB ?= arangodb/enterprise:latest STARTER ?= arangodb/arangodb-starter:latest ifndef TESTOPTIONS diff --git a/v2/arangodb/client_admin.go b/v2/arangodb/client_admin.go index 1060fe6e..d176304e 100644 --- a/v2/arangodb/client_admin.go +++ b/v2/arangodb/client_admin.go @@ -54,15 +54,6 @@ type ClientAdminLog interface { // Logs retrieve logs from server in ArangoDB 3.8.0+ format Logs(ctx context.Context, queryParams *AdminLogEntriesOptions) (AdminLogEntriesResponse, error) -} - -type ClientAdminLicense interface { - // GetLicense returns license of an ArangoDB deployment. - GetLicense(ctx context.Context) (License, error) - - // SetLicense Set a new license for an Enterprise Edition instance. - // Can be called on single servers, Coordinators, and DB-Servers. - SetLicense(ctx context.Context, license string, force bool) error // DeleteLogLevels removes log levels for a specific server. DeleteLogLevels(ctx context.Context, serverId *string) (LogLevelResponse, error) @@ -72,6 +63,18 @@ type ClientAdminLicense interface { // UpdateStructuredLogSettings modifies and returns the server's current structured log settings. UpdateStructuredLogSettings(ctx context.Context, opts *LogSettingsOptions) (LogSettingsOptions, error) + + // Get a list of the most recent requests with a timestamp and the endpoint + GetRecentAPICalls(ctx context.Context, dbName string) (ApiCallsResponse, error) +} + +type ClientAdminLicense interface { + // GetLicense returns license of an ArangoDB deployment. + GetLicense(ctx context.Context) (License, error) + + // SetLicense Set a new license for an Enterprise Edition instance. + // Can be called on single servers, Coordinators, and DB-Servers. + SetLicense(ctx context.Context, license string, force bool) error } type AdminLogEntriesOptions struct { @@ -253,3 +256,21 @@ type LogSettingsOptions struct { // in structured log entries. Username *bool `json:"username,omitempty"` } + +type ApiCallsObject struct { + // TimeStamp is the UTC timestamp when the API call was executed. + TimeStamp string `json:"timeStamp"` + + // RequestType is the HTTP method used for the call (e.g., GET, POST). + RequestType string `json:"requestType"` + + // Path is the HTTP request path that was accessed. + Path string `json:"path"` + + // Database is the name of the database the API call was executed against. + Database string `json:"database"` +} + +type ApiCallsResponse struct { + Calls []ApiCallsObject `json:"calls"` +} diff --git a/v2/arangodb/client_admin_log_impl.go b/v2/arangodb/client_admin_log_impl.go index 703383af..c5648df8 100644 --- a/v2/arangodb/client_admin_log_impl.go +++ b/v2/arangodb/client_admin_log_impl.go @@ -24,6 +24,7 @@ import ( "context" "fmt" "net/http" + "net/url" "strconv" "github.com/pkg/errors" @@ -199,7 +200,7 @@ func (c *clientAdmin) DeleteLogLevels(ctx context.Context, serverId *string) (Lo } } -// GetStructuredLogSettings returns the server's current structured log settings. +// GetStructuredLogSettings returns the server's current structured log settings in ArangoDB 3.12.0+ format. func (c *clientAdmin) GetStructuredLogSettings(ctx context.Context) (LogSettingsOptions, error) { url := connection.NewUrl("_admin", "log", "structured") @@ -234,7 +235,7 @@ func formStructuredLogParams(opt *LogSettingsOptions) map[string]bool { return params } -// UpdateStructuredLogSettings modifies and returns the server's current structured log settings. +// UpdateStructuredLogSettings modifies and returns the server's current structured log settings in ArangoDB 3.12.0+ format. func (c *clientAdmin) UpdateStructuredLogSettings(ctx context.Context, opts *LogSettingsOptions) (LogSettingsOptions, error) { url := connection.NewUrl("_admin", "log", "structured") @@ -255,3 +256,24 @@ func (c *clientAdmin) UpdateStructuredLogSettings(ctx context.Context, opts *Log return LogSettingsOptions{}, response.AsArangoErrorWithCode(code) } } + +// Get a list of the most recent requests with a timestamp and the endpoint in ArangoDB 3.12.5+ format. +func (c *clientAdmin) GetRecentAPICalls(ctx context.Context, dbName string) (ApiCallsResponse, error) { + url := connection.NewUrl("_db", url.PathEscape(dbName), "_admin", "server", "api-calls") + + var response struct { + shared.ResponseStruct `json:",inline"` + Result ApiCallsResponse `json:"result"` + } + resp, err := connection.CallGet(ctx, c.client.connection, url, &response) + if err != nil { + return ApiCallsResponse{}, errors.WithStack(err) + } + + switch code := resp.Code(); code { + case http.StatusOK: + return response.Result, nil + default: + return ApiCallsResponse{}, response.AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/admin_log_test.go b/v2/tests/admin_log_test.go index 9869d1bb..fa560cc2 100644 --- a/v2/tests/admin_log_test.go +++ b/v2/tests/admin_log_test.go @@ -227,3 +227,22 @@ func Test_StructuredLogSettings(t *testing.T) { }) }) } + +func Test_GetRecentAPICalls(t *testing.T) { + Wrap(t, func(t *testing.T, client arangodb.Client) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + skipBelowVersion(client, ctx, "3.12.5-2", t) + + resp, err := client.Version(ctx) + require.NoError(t, err) + require.NotEmpty(t, resp) + db, err := client.GetDatabase(ctx, "_system", nil) + require.NoError(t, err) + require.NotEmpty(t, db) + + recentApisResp, err := client.GetRecentAPICalls(ctx, db.Name()) + require.NoError(t, err) + require.NotEmpty(t, recentApisResp) + }) + }) +} From 50e026094507ab18fdb48512d7d64152e88bdbac Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Thu, 4 Sep 2025 20:36:42 +0530 Subject: [PATCH 6/7] Monitoring: Add endpoint to get the metrics --- v2/arangodb/client_admin.go | 3 +++ v2/arangodb/client_admin_log_impl.go | 23 +++++++++++++++++++++++ v2/tests/admin_log_test.go | 26 ++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/v2/arangodb/client_admin.go b/v2/arangodb/client_admin.go index d176304e..15eef49c 100644 --- a/v2/arangodb/client_admin.go +++ b/v2/arangodb/client_admin.go @@ -66,6 +66,9 @@ type ClientAdminLog interface { // Get a list of the most recent requests with a timestamp and the endpoint GetRecentAPICalls(ctx context.Context, dbName string) (ApiCallsResponse, error) + + //GetMetrics returns the instance's current metrics in Prometheus format + GetMetrics(ctx context.Context, dbName string, serverId *string) ([]byte, error) } type ClientAdminLicense interface { diff --git a/v2/arangodb/client_admin_log_impl.go b/v2/arangodb/client_admin_log_impl.go index c5648df8..81558e30 100644 --- a/v2/arangodb/client_admin_log_impl.go +++ b/v2/arangodb/client_admin_log_impl.go @@ -277,3 +277,26 @@ func (c *clientAdmin) GetRecentAPICalls(ctx context.Context, dbName string) (Api return ApiCallsResponse{}, response.AsArangoErrorWithCode(code) } } + +// GetMetrics returns the instance's current metrics in Prometheus format +func (c *clientAdmin) GetMetrics(ctx context.Context, dbName string, serverId *string) ([]byte, error) { + url := connection.NewUrl("_db", url.PathEscape(dbName), "_admin", "metrics", "v2") + + var mods []connection.RequestModifier + if serverId != nil { + mods = append(mods, connection.WithQuery("serverId", *serverId)) + } + var output []byte + + resp, err := connection.CallGet(ctx, c.client.connection, url, &output, mods...) + if err != nil { + return nil, errors.WithStack(err) + } + + switch code := resp.Code(); code { + case http.StatusOK: + return output, nil + default: + return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/admin_log_test.go b/v2/tests/admin_log_test.go index fa560cc2..e2591eca 100644 --- a/v2/tests/admin_log_test.go +++ b/v2/tests/admin_log_test.go @@ -246,3 +246,29 @@ func Test_GetRecentAPICalls(t *testing.T) { }) }) } + +func Test_GetMetrics(t *testing.T) { + Wrap(t, func(t *testing.T, client arangodb.Client) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + + db, err := client.GetDatabase(ctx, "_system", nil) + require.NoError(t, err) + require.NotEmpty(t, db) + // Role check + serverRole, err := client.ServerRole(ctx) + require.NoError(t, err) + t.Logf("ServerRole: %s", serverRole) + + var serverId *string + if serverRole == arangodb.ServerRoleCoordinator { + serverID, err := client.ServerID(ctx) + require.NoError(t, err) + serverId = &serverID + } + + metricsResp, err := client.GetMetrics(ctx, db.Name(), serverId) + require.NoError(t, err) + require.NotEmpty(t, metricsResp) + }) + }) +} From f2d0664483ff016aa1fb059506ff7eb53d4216f0 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Fri, 5 Sep 2025 13:56:49 +0530 Subject: [PATCH 7/7] Resolved merge conflicts --- v2/CHANGELOG.md | 1 + v2/arangodb/client_admin.go | 4 ++-- v2/arangodb/client_admin_log_impl.go | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/v2/CHANGELOG.md b/v2/CHANGELOG.md index 1068504e..b3edc97c 100644 --- a/v2/CHANGELOG.md +++ b/v2/CHANGELOG.md @@ -2,6 +2,7 @@ ## [master](https://github.com/arangodb/go-driver/tree/master) (N/A) - Add missing endpoints from replication to v2 +- Add missing endpoints from monitoring to v2 ## [2.1.5](https://github.com/arangodb/go-driver/tree/v2.1.5) (2025-08-31) - Add tasks endpoints to v2 diff --git a/v2/arangodb/client_admin.go b/v2/arangodb/client_admin.go index 15eef49c..14c79b66 100644 --- a/v2/arangodb/client_admin.go +++ b/v2/arangodb/client_admin.go @@ -64,10 +64,10 @@ type ClientAdminLog interface { // UpdateStructuredLogSettings modifies and returns the server's current structured log settings. UpdateStructuredLogSettings(ctx context.Context, opts *LogSettingsOptions) (LogSettingsOptions, error) - // Get a list of the most recent requests with a timestamp and the endpoint + // GetRecentAPICalls gets a list of the most recent requests with a timestamp and the endpoint GetRecentAPICalls(ctx context.Context, dbName string) (ApiCallsResponse, error) - //GetMetrics returns the instance's current metrics in Prometheus format + // GetMetrics returns the instance's current metrics in Prometheus format GetMetrics(ctx context.Context, dbName string, serverId *string) ([]byte, error) } diff --git a/v2/arangodb/client_admin_log_impl.go b/v2/arangodb/client_admin_log_impl.go index 81558e30..eb526e4c 100644 --- a/v2/arangodb/client_admin_log_impl.go +++ b/v2/arangodb/client_admin_log_impl.go @@ -118,7 +118,7 @@ func (c *clientAdmin) formServerLogEntriesParams(opts *AdminLogEntriesOptions) ( } if opts.Level != nil && opts.Upto != "" { - return nil, errors.New("parameters 'level' and 'upto' cannot be used together") + return nil, errors.New("level and upto parameters are mutually exclusive") } if opts.Upto != "" { @@ -257,7 +257,7 @@ func (c *clientAdmin) UpdateStructuredLogSettings(ctx context.Context, opts *Log } } -// Get a list of the most recent requests with a timestamp and the endpoint in ArangoDB 3.12.5+ format. +// GetRecentAPICalls gets a list of the most recent requests with a timestamp and the endpoint in ArangoDB 3.12.5+ format. func (c *clientAdmin) GetRecentAPICalls(ctx context.Context, dbName string) (ApiCallsResponse, error) { url := connection.NewUrl("_db", url.PathEscape(dbName), "_admin", "server", "api-calls")