fleet/server/contexts/ctxerr/statistics.go
Victor Lyuboslavsky c88cc953fb
Refactor endpoint_utils for modularization (#36484)
Resolves #37192

Separating generic endpoint_utils middleware logic from domain-specific
business logic. New bounded contexts would share the generic logic and
implement their own domain-specific logic. The two approaches used in
this PR are:
- Use common `platform` types
- Use interfaces

In the next PR we will move `endpointer_utils`, `authzcheck` and
`ratelimit` into `platform` directory.

# Checklist for submitter

- [x] Added changes file

## Testing

- [x] Added/updated tests
- [x] QA'd all new/changed functionality manually



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Restructured internal error handling and context management to support
bounded context architecture.
* Improved error context collection and telemetry observability through
a provider-based mechanism.
* Decoupled licensing and authentication concerns into interfaces for
better modularity.

* **Chores**
* Updated internal package dependencies to align with new architectural
boundaries.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-31 09:12:00 -06:00

97 lines
2.6 KiB
Go

package ctxerr
import (
"context"
"encoding/json"
)
type ErrorAgg struct {
Count int `json:"count"`
Loc []string `json:"loc"`
Metadata json.RawMessage `json:"metadata,omitempty"`
}
// Aggregate retrieves all errors in the store and returns an aggregated,
// json-formatted summary containing:
// - The number of occurrences of each error
// - A reduced stack trace used for debugging the error
// - Additional metadata present for vital errors
func Aggregate(ctx context.Context) (json.RawMessage, error) {
const maxTraceLen = 3
empty := json.RawMessage("[]")
storedErrs, err := Retrieve(ctx)
if err != nil {
return empty, Wrap(ctx, err, "retrieve on aggregation")
}
aggs := make([]ErrorAgg, len(storedErrs))
for i, stored := range storedErrs {
var ferr []fleetErrorJSON
if err = json.Unmarshal(stored.Chain, &ferr); err != nil {
return empty, Wrap(ctx, err, "unmarshal on aggregation")
}
stack := aggregateStack(ferr, maxTraceLen)
meta := getVitalMetadata(ferr)
aggs[i] = ErrorAgg{stored.Count, stack, meta}
}
return json.Marshal(aggs)
}
// aggregateStack creates a single stack trace by joining all the stack traces in
// an error chain
func aggregateStack(chain []fleetErrorJSON, maxStack int) []string {
stack := make([]string, maxStack)
stackIdx := 0
out:
for _, e := range chain {
for _, m := range e.Stack {
if stackIdx >= maxStack {
break out
}
stack[stackIdx] = m
stackIdx++
}
}
return stack[:stackIdx]
}
// vitalErrorData represents the structure of vital fleetd error data.
type vitalErrorData struct {
ErrorSource string `json:"error_source"`
ErrorSourceVersion string `json:"error_source_version"`
ErrorMessage string `json:"error_message"`
ErrorAdditionalInfo map[string]any `json:"error_additional_info"`
Vital bool `json:"vital"`
}
func getVitalMetadata(chain []fleetErrorJSON) json.RawMessage {
for _, e := range chain {
if len(e.Data) > 0 {
// Currently, only vital fleetd errors contain metadata.
// Note: vital errors should not contain any sensitive info
var fleetdErr vitalErrorData
var err error
if err = json.Unmarshal(e.Data, &fleetdErr); err != nil || !fleetdErr.Vital {
continue
}
export := map[string]interface{}{
"error_source": fleetdErr.ErrorSource,
"error_source_version": fleetdErr.ErrorSourceVersion,
"error_message": fleetdErr.ErrorMessage,
"error_additional_info": fleetdErr.ErrorAdditionalInfo,
}
var meta json.RawMessage
if meta, err = json.Marshal(export); err != nil {
return nil
}
return meta
}
}
return nil
}