mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
b76f707c8b
Result: {"status":"keep","total_issues":74,"golint_canonicalheader":8,"golint_errname":1,"golint_errorlint":12,"golint_forcetypeassert":3,"golint_gosec":0,"golint_intrange":3,"golint_modernize":5,"golint_nilnil":3,"golint_perfsprint":18,"golint_prealloc":3,"golint_recvcheck":7,"golint_usestdlibvars":3,"golint_wastedassign":7,"golint_total":73,"eslint_problems":1,"eslint_errors":0,"eslint_warnings":1,"tsc_errors":0,"measure_s":38}
443 lines
14 KiB
Go
443 lines
14 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package repository
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
analyticsmodel "github.com/Rain-kl/Wavelet/internal/model/analytics"
|
|
|
|
"github.com/Rain-kl/Wavelet/internal/model"
|
|
"github.com/Rain-kl/Wavelet/internal/repository/logstore"
|
|
)
|
|
|
|
const (
|
|
sortOrderAsc = "asc"
|
|
secondsPerMinute = 60
|
|
)
|
|
|
|
// ListOpenFlareAccessLogWAFIPAggregates returns per-IP aggregates for WAF automatic rules.
|
|
func ListOpenFlareAccessLogWAFIPAggregates(ctx context.Context, query model.OpenFlareAccessLogQuery) ([]*model.OpenFlareAccessLogWAFIPAggregate, error) {
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rows, err := s.AccessLogs.WAFIPAggregates(ctx, query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := make([]*model.OpenFlareAccessLogWAFIPAggregate, 0, len(rows))
|
|
for _, row := range rows {
|
|
remoteAddr := strings.TrimSpace(row.RemoteAddr)
|
|
if remoteAddr == "" {
|
|
continue
|
|
}
|
|
statusCounts := make(map[int]int, len(row.StatusCounts))
|
|
for code, count := range row.StatusCounts {
|
|
statusCounts[code] = int(count)
|
|
}
|
|
result = append(result, &model.OpenFlareAccessLogWAFIPAggregate{
|
|
RemoteAddr: remoteAddr,
|
|
RequestCount: int(row.RequestCount),
|
|
Status404Count: int(row.Status404Count),
|
|
ClientErrorCount: int(row.ClientErrorCount),
|
|
ServerErrorCount: int(row.ServerErrorCount),
|
|
IPHostCount: int(row.IPHostCount),
|
|
LastSeenEpoch: row.LastSeenEpoch,
|
|
StatusCounts: statusCounts,
|
|
})
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// InsertOpenFlareAccessLogsBatch inserts access log rows into the active log store.
|
|
func InsertOpenFlareAccessLogsBatch(ctx context.Context, records []*model.OpenFlareAccessLog) error {
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.AccessLogs.InsertBatch(ctx, records)
|
|
}
|
|
|
|
// ListOpenFlareAccessLogs lists access logs matching the query.
|
|
func ListOpenFlareAccessLogs(ctx context.Context, query model.OpenFlareAccessLogQuery) ([]*model.OpenFlareAccessLog, error) {
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.AccessLogs.List(ctx, query)
|
|
}
|
|
|
|
// CountOpenFlareAccessLogs counts access logs, distinct IPs, and total bytes sent matching the query.
|
|
func CountOpenFlareAccessLogs(ctx context.Context, query model.OpenFlareAccessLogQuery) (int64, int64, int64, error) {
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return 0, 0, 0, err
|
|
}
|
|
return s.AccessLogs.Count(ctx, query)
|
|
}
|
|
|
|
// TrafficSummaryOpenFlareAccessLogs returns window-level request/error/UV/bytes summary.
|
|
func TrafficSummaryOpenFlareAccessLogs(ctx context.Context, query model.OpenFlareAccessLogQuery) (model.OpenFlareAccessLogTrafficSummary, error) {
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return model.OpenFlareAccessLogTrafficSummary{}, err
|
|
}
|
|
return s.AccessLogs.TrafficSummary(ctx, query)
|
|
}
|
|
|
|
// ValueCountsOpenFlareAccessLogs groups logs by status_code, host, path, remote_addr, or user_agent.
|
|
func ValueCountsOpenFlareAccessLogs(ctx context.Context, query model.OpenFlareAccessLogQuery, column string, limit int) ([]model.OpenFlareAccessLogValueCount, error) {
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.AccessLogs.ValueCounts(ctx, query, column, limit)
|
|
}
|
|
|
|
// NodeAggregatesOpenFlareAccessLogs returns per-node request/error/UV for the window.
|
|
func NodeAggregatesOpenFlareAccessLogs(ctx context.Context, query model.OpenFlareAccessLogQuery) ([]model.OpenFlareAccessLogNodeAggregate, error) {
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.AccessLogs.NodeAggregates(ctx, query)
|
|
}
|
|
|
|
// ListOpenFlareAccessLogRegionCounts returns region counts for access logs.
|
|
func ListOpenFlareAccessLogRegionCounts(ctx context.Context, nodeID string, since time.Time, limit int) ([]*model.OpenFlareAccessLogRegionCount, error) {
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.AccessLogs.RegionCounts(ctx, nodeID, since, limit)
|
|
}
|
|
|
|
// ListOpenFlareAccessLogBuckets lists folded access log buckets.
|
|
func ListOpenFlareAccessLogBuckets(ctx context.Context, query model.OpenFlareAccessLogBucketQuery) ([]*model.OpenFlareAccessLogBucketRow, error) {
|
|
return buildOpenFlareAccessLogBucketRows(ctx, query)
|
|
}
|
|
|
|
// CountOpenFlareAccessLogBuckets counts folded access log buckets.
|
|
func CountOpenFlareAccessLogBuckets(ctx context.Context, query model.OpenFlareAccessLogBucketQuery) (int64, error) {
|
|
filter := openFlareAccessLogQueryFromBucket(query)
|
|
bucketSeconds := int64(query.FoldMinutes * secondsPerMinute)
|
|
if bucketSeconds <= 0 {
|
|
bucketSeconds = 180
|
|
}
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return s.AccessLogs.CountBuckets(ctx, filter, bucketSeconds)
|
|
}
|
|
|
|
// ListOpenFlareAccessLogBucketIPs lists folded IP rows for a bucket window.
|
|
func ListOpenFlareAccessLogBucketIPs(ctx context.Context, query model.OpenFlareAccessLogBucketIPQuery) ([]*model.OpenFlareAccessLogBucketIPRow, error) {
|
|
rows, err := buildOpenFlareAccessLogBucketIPRows(ctx, query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
start, end := openFlareAccessLogPaginateBounds(len(rows), query.Page, query.PageSize)
|
|
if start >= len(rows) {
|
|
return []*model.OpenFlareAccessLogBucketIPRow{}, nil
|
|
}
|
|
return rows[start:end], nil
|
|
}
|
|
|
|
// CountOpenFlareAccessLogBucketIPs counts folded IP rows for a bucket window.
|
|
func CountOpenFlareAccessLogBucketIPs(ctx context.Context, query model.OpenFlareAccessLogBucketIPQuery) (int64, error) {
|
|
rows, err := buildOpenFlareAccessLogBucketIPRows(ctx, query)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return int64(len(rows)), nil
|
|
}
|
|
|
|
// ListOpenFlareAccessLogIPSummaries lists IP summaries.
|
|
func ListOpenFlareAccessLogIPSummaries(ctx context.Context, query model.OpenFlareAccessLogIPSummaryQuery, recentSince time.Time) ([]*analyticsmodel.NodeAccessLogIPSummary, error) {
|
|
return buildOpenFlareAccessLogIPSummaryRows(ctx, query, recentSince)
|
|
}
|
|
|
|
// CountOpenFlareAccessLogIPSummaries counts IP summaries.
|
|
func CountOpenFlareAccessLogIPSummaries(ctx context.Context, query model.OpenFlareAccessLogIPSummaryQuery) (int64, error) {
|
|
filter := openFlareAccessLogQueryFromIPSummary(query)
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return s.AccessLogs.CountIPSummaries(ctx, filter)
|
|
}
|
|
|
|
// ListOpenFlareAccessLogIPTrend lists IP trend points.
|
|
func ListOpenFlareAccessLogIPTrend(ctx context.Context, query model.OpenFlareAccessLogIPTrendQuery) ([]*analyticsmodel.NodeAccessLogIPTrend, error) {
|
|
remoteAddr := strings.TrimSpace(query.RemoteAddr)
|
|
if remoteAddr == "" {
|
|
return []*analyticsmodel.NodeAccessLogIPTrend{}, nil
|
|
}
|
|
filter := model.OpenFlareAccessLogQuery{
|
|
NodeID: query.NodeID,
|
|
RemoteAddr: remoteAddr,
|
|
Host: query.Host,
|
|
Since: query.Since,
|
|
}
|
|
bucketSeconds := int64(query.BucketMinutes * secondsPerMinute)
|
|
if bucketSeconds <= 0 {
|
|
bucketSeconds = 1800
|
|
}
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rows, err := s.AccessLogs.IPTrend(ctx, filter, bucketSeconds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := make([]*analyticsmodel.NodeAccessLogIPTrend, len(rows))
|
|
for index, row := range rows {
|
|
result[index] = &analyticsmodel.NodeAccessLogIPTrend{
|
|
BucketEpoch: row.BucketEpoch,
|
|
RequestCount: row.RequestCount,
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// DeleteAllOpenFlareAccessLogs deletes all access logs.
|
|
func DeleteAllOpenFlareAccessLogs(ctx context.Context) (int64, error) {
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return s.AccessLogs.DeleteAll(ctx)
|
|
}
|
|
|
|
// DeleteOpenFlareAccessLogsBefore deletes access logs older than cutoff.
|
|
func DeleteOpenFlareAccessLogsBefore(ctx context.Context, cutoff time.Time) (int64, error) {
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return s.AccessLogs.DeleteBefore(ctx, cutoff)
|
|
}
|
|
|
|
// DeleteOpenFlareAccessLogsByNodeBefore deletes access logs for a node older than cutoff.
|
|
func DeleteOpenFlareAccessLogsByNodeBefore(ctx context.Context, nodeID string, cutoff time.Time) (int64, error) {
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return s.AccessLogs.DeleteByNodeBefore(ctx, nodeID, cutoff)
|
|
}
|
|
|
|
func buildOpenFlareAccessLogBucketRows(ctx context.Context, query model.OpenFlareAccessLogBucketQuery) ([]*model.OpenFlareAccessLogBucketRow, error) {
|
|
filter := openFlareAccessLogQueryFromBucket(query)
|
|
bucketSeconds := int64(query.FoldMinutes * secondsPerMinute)
|
|
if bucketSeconds <= 0 {
|
|
bucketSeconds = 180
|
|
}
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
partials, err := s.AccessLogs.BucketAggregates(ctx, filter, bucketSeconds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rows := make([]*model.OpenFlareAccessLogBucketRow, 0, len(partials))
|
|
for _, partial := range partials {
|
|
rows = append(rows, &model.OpenFlareAccessLogBucketRow{
|
|
BucketEpoch: partial.BucketEpoch,
|
|
RequestCount: partial.RequestCount,
|
|
UniqueIPCount: partial.UniqueIPCount,
|
|
UniqueHostCount: partial.UniqueHostCount,
|
|
SuccessCount: partial.SuccessCount,
|
|
ClientErrorCount: partial.ClientErrorCount,
|
|
ServerErrorCount: partial.ServerErrorCount,
|
|
Status2xxCount: partial.Status2xxCount,
|
|
Status4xxCount: partial.Status4xxCount,
|
|
Status5xxCount: partial.Status5xxCount,
|
|
BytesSent: partial.BytesSent,
|
|
RequestLength: partial.RequestLength,
|
|
})
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
func buildOpenFlareAccessLogBucketIPRows(ctx context.Context, query model.OpenFlareAccessLogBucketIPQuery) ([]*model.OpenFlareAccessLogBucketIPRow, error) {
|
|
if query.BucketStartedAt.IsZero() {
|
|
return []*model.OpenFlareAccessLogBucketIPRow{}, nil
|
|
}
|
|
foldMinutes := query.FoldMinutes
|
|
if foldMinutes <= 0 {
|
|
foldMinutes = 3
|
|
}
|
|
bucketStartedAt := query.BucketStartedAt.UTC()
|
|
filter := model.OpenFlareAccessLogQuery{
|
|
NodeID: query.NodeID,
|
|
RemoteAddr: query.RemoteAddr,
|
|
Host: query.Host,
|
|
Path: query.Path,
|
|
Since: bucketStartedAt,
|
|
Until: bucketStartedAt.Add(time.Duration(foldMinutes) * time.Minute),
|
|
}
|
|
rows, err := queryOpenFlareAccessLogIPAggregateRows(ctx, filter, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sortOpenFlareAccessLogBucketIPRows(rows, query.SortBy, query.SortOrder)
|
|
return rows, nil
|
|
}
|
|
|
|
func buildOpenFlareAccessLogIPSummaryRows(ctx context.Context, query model.OpenFlareAccessLogIPSummaryQuery, recentSince time.Time) ([]*analyticsmodel.NodeAccessLogIPSummary, error) {
|
|
filter := openFlareAccessLogQueryFromIPSummary(query)
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
partials, err := s.AccessLogs.IPSummaries(ctx, filter, recentSince)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rows := make([]*analyticsmodel.NodeAccessLogIPSummary, 0, len(partials))
|
|
for _, partial := range partials {
|
|
remoteAddr := strings.TrimSpace(partial.RemoteAddr)
|
|
if remoteAddr == "" {
|
|
continue
|
|
}
|
|
rows = append(rows, &analyticsmodel.NodeAccessLogIPSummary{
|
|
RemoteAddr: remoteAddr,
|
|
Region: strings.TrimSpace(partial.Region),
|
|
TotalRequests: partial.TotalRequests,
|
|
Success2xxCount: partial.Success2xxCount,
|
|
SuccessRatio: partial.SuccessRatio,
|
|
BytesReceived: partial.BytesReceived,
|
|
BytesSent: partial.BytesSent,
|
|
RecentRequests: 0,
|
|
LastSeenEpoch: partial.LastSeenEpoch,
|
|
})
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
func queryOpenFlareAccessLogIPAggregateRows(ctx context.Context, filter model.OpenFlareAccessLogQuery, exactRemoteAddr bool) ([]*model.OpenFlareAccessLogBucketIPRow, error) {
|
|
s, err := logstore.Active(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
partials, err := s.AccessLogs.IPAggregates(ctx, filter, exactRemoteAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rows := make([]*model.OpenFlareAccessLogBucketIPRow, 0, len(partials))
|
|
for _, partial := range partials {
|
|
remoteAddr := strings.TrimSpace(partial.RemoteAddr)
|
|
if remoteAddr == "" {
|
|
continue
|
|
}
|
|
rows = append(rows, &model.OpenFlareAccessLogBucketIPRow{
|
|
RemoteAddr: remoteAddr,
|
|
RequestCount: partial.RequestCount,
|
|
SuccessCount: partial.SuccessCount,
|
|
ClientErrorCount: partial.ClientErrorCount,
|
|
ServerErrorCount: partial.ServerErrorCount,
|
|
LastSeenEpoch: partial.LastSeenEpoch,
|
|
})
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
func openFlareAccessLogQueryFromBucket(query model.OpenFlareAccessLogBucketQuery) model.OpenFlareAccessLogQuery {
|
|
return model.OpenFlareAccessLogQuery{
|
|
NodeID: query.NodeID,
|
|
RemoteAddr: query.RemoteAddr,
|
|
Host: query.Host,
|
|
Hosts: query.Hosts,
|
|
Path: query.Path,
|
|
Since: query.Since,
|
|
Until: query.Until,
|
|
Page: query.Page,
|
|
PageSize: query.PageSize,
|
|
SortBy: query.SortBy,
|
|
SortOrder: query.SortOrder,
|
|
}
|
|
}
|
|
|
|
func openFlareAccessLogQueryFromIPSummary(query model.OpenFlareAccessLogIPSummaryQuery) model.OpenFlareAccessLogQuery {
|
|
return model.OpenFlareAccessLogQuery{
|
|
NodeID: query.NodeID,
|
|
RemoteAddr: query.RemoteAddr,
|
|
Host: query.Host,
|
|
Since: query.Since,
|
|
Until: query.Until,
|
|
Page: query.Page,
|
|
PageSize: query.PageSize,
|
|
SortBy: query.SortBy,
|
|
SortOrder: query.SortOrder,
|
|
}
|
|
}
|
|
|
|
func sortOpenFlareAccessLogBucketIPRows(items []*model.OpenFlareAccessLogBucketIPRow, sortBy string, sortOrder string) {
|
|
desc := openFlareAccessLogNormalizeSortOrder(sortOrder) != sortOrderAsc
|
|
sort.Slice(items, func(i, j int) bool {
|
|
left := items[i]
|
|
right := items[j]
|
|
if left == nil || right == nil {
|
|
return left != nil
|
|
}
|
|
var compare int
|
|
switch strings.TrimSpace(sortBy) {
|
|
case "last_seen_at":
|
|
compare = openFlareAccessLogCompareInt64(left.LastSeenEpoch, right.LastSeenEpoch)
|
|
case "remote_addr":
|
|
compare = strings.Compare(left.RemoteAddr, right.RemoteAddr)
|
|
default:
|
|
compare = openFlareAccessLogCompareInt64(left.RequestCount, right.RequestCount)
|
|
}
|
|
if compare == 0 {
|
|
compare = openFlareAccessLogCompareInt64(left.LastSeenEpoch, right.LastSeenEpoch)
|
|
}
|
|
if compare == 0 {
|
|
compare = strings.Compare(left.RemoteAddr, right.RemoteAddr)
|
|
}
|
|
if desc {
|
|
return compare > 0
|
|
}
|
|
return compare < 0
|
|
})
|
|
}
|
|
|
|
func openFlareAccessLogPaginateBounds(total int, page int, pageSize int) (int, int) {
|
|
if page < 0 {
|
|
page = 0
|
|
}
|
|
if pageSize <= 0 {
|
|
return 0, total
|
|
}
|
|
start := min(page*pageSize, total)
|
|
end := min(start+pageSize, total)
|
|
return start, end
|
|
}
|
|
|
|
func openFlareAccessLogNormalizeSortOrder(sortOrder string) string {
|
|
if strings.EqualFold(strings.TrimSpace(sortOrder), sortOrderAsc) {
|
|
return sortOrderAsc
|
|
}
|
|
return "desc"
|
|
}
|
|
|
|
func openFlareAccessLogCompareInt64(left int64, right int64) int {
|
|
switch {
|
|
case left > right:
|
|
return 1
|
|
case left < right:
|
|
return -1
|
|
default:
|
|
return 0
|
|
}
|
|
}
|