all: imp code
This commit is contained in:
parent
a3c96e3d21
commit
363a16f6bb
|
@ -2,13 +2,16 @@
|
||||||
package aghhttp
|
package aghhttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||||
"github.com/AdguardTeam/golibs/httphdr"
|
"github.com/AdguardTeam/golibs/httphdr"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTP scheme constants.
|
// HTTP scheme constants.
|
||||||
|
@ -31,12 +34,39 @@ func OK(w http.ResponseWriter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error writes formatted message to w and also logs it.
|
// Error writes formatted message to w and also logs it.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Remove it.
|
||||||
func Error(r *http.Request, w http.ResponseWriter, code int, format string, args ...any) {
|
func Error(r *http.Request, w http.ResponseWriter, code int, format string, args ...any) {
|
||||||
text := fmt.Sprintf(format, args...)
|
text := fmt.Sprintf(format, args...)
|
||||||
log.Error("%s %s %s: %s", r.Method, r.Host, r.URL, text)
|
log.Error("%s %s %s: %s", r.Method, r.Host, r.URL, text)
|
||||||
http.Error(w, text, code)
|
http.Error(w, text, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrorAndLog writes formatted message to w and also logs it with the specified
|
||||||
|
// logging level.
|
||||||
|
func ErrorAndLog(
|
||||||
|
ctx context.Context,
|
||||||
|
l *slog.Logger,
|
||||||
|
lvl slog.Level,
|
||||||
|
r *http.Request,
|
||||||
|
w http.ResponseWriter,
|
||||||
|
code int,
|
||||||
|
format string,
|
||||||
|
args ...any,
|
||||||
|
) {
|
||||||
|
text := fmt.Sprintf(format, args...)
|
||||||
|
l.Log(
|
||||||
|
ctx,
|
||||||
|
lvl,
|
||||||
|
"http error",
|
||||||
|
"method", r.Method,
|
||||||
|
"host", r.Host,
|
||||||
|
"url", r.URL,
|
||||||
|
slogutil.KeyError, text,
|
||||||
|
)
|
||||||
|
http.Error(w, text, code)
|
||||||
|
}
|
||||||
|
|
||||||
// UserAgent returns the ID of the service as a User-Agent string. It can also
|
// UserAgent returns the ID of the service as a User-Agent string. It can also
|
||||||
// be used as the value of the Server HTTP header.
|
// be used as the value of the Server HTTP header.
|
||||||
func UserAgent() (ua string) {
|
func UserAgent() (ua string) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ package stats
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -50,6 +51,8 @@ type StatsResp struct {
|
||||||
func (s *StatsCtx) handleStats(w http.ResponseWriter, r *http.Request) {
|
func (s *StatsCtx) handleStats(w http.ResponseWriter, r *http.Request) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
resp *StatsResp
|
resp *StatsResp
|
||||||
ok bool
|
ok bool
|
||||||
|
@ -61,12 +64,23 @@ func (s *StatsCtx) handleStats(w http.ResponseWriter, r *http.Request) {
|
||||||
resp, ok = s.getData(uint32(s.limit.Hours()))
|
resp, ok = s.getData(uint32(s.limit.Hours()))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
s.logger.Debug("prepared data", "elapsed", timeutil.Duration{Duration: time.Since(start)})
|
s.logger.DebugContext(
|
||||||
|
ctx,
|
||||||
|
"prepared data",
|
||||||
|
"elapsed", timeutil.Duration{Duration: time.Since(start)},
|
||||||
|
)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
// Don't bring the message to the lower case since it's a part of UI
|
// Don't bring the message to the lower case since it's a part of UI
|
||||||
// text for the moment.
|
// text for the moment.
|
||||||
aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't get statistics data")
|
aghhttp.ErrorAndLog(
|
||||||
|
ctx,
|
||||||
|
s.logger,
|
||||||
|
slog.LevelError,
|
||||||
|
r,
|
||||||
|
w,
|
||||||
|
http.StatusInternalServerError, "Couldn't get statistics data",
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -145,16 +159,35 @@ func (s *StatsCtx) handleGetStatsConfig(w http.ResponseWriter, r *http.Request)
|
||||||
//
|
//
|
||||||
// Deprecated: Remove it when migration to the new API is over.
|
// Deprecated: Remove it when migration to the new API is over.
|
||||||
func (s *StatsCtx) handleStatsConfig(w http.ResponseWriter, r *http.Request) {
|
func (s *StatsCtx) handleStatsConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
reqData := configResp{}
|
reqData := configResp{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&reqData)
|
err := json.NewDecoder(r.Body).Decode(&reqData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusBadRequest, "json decode: %s", err)
|
aghhttp.ErrorAndLog(
|
||||||
|
ctx,
|
||||||
|
s.logger,
|
||||||
|
slog.LevelError,
|
||||||
|
r,
|
||||||
|
w,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
"json decode: %s",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !checkInterval(reqData.IntervalDays) {
|
if !checkInterval(reqData.IntervalDays) {
|
||||||
aghhttp.Error(r, w, http.StatusBadRequest, "Unsupported interval")
|
aghhttp.ErrorAndLog(
|
||||||
|
ctx,
|
||||||
|
s.logger,
|
||||||
|
slog.LevelError,
|
||||||
|
r,
|
||||||
|
w,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
"Unsupported interval",
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -172,17 +205,37 @@ func (s *StatsCtx) handleStatsConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
// handlePutStatsConfig is the handler for the PUT /control/stats/config/update
|
// handlePutStatsConfig is the handler for the PUT /control/stats/config/update
|
||||||
// HTTP API.
|
// HTTP API.
|
||||||
func (s *StatsCtx) handlePutStatsConfig(w http.ResponseWriter, r *http.Request) {
|
func (s *StatsCtx) handlePutStatsConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
reqData := getConfigResp{}
|
reqData := getConfigResp{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&reqData)
|
err := json.NewDecoder(r.Body).Decode(&reqData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusBadRequest, "json decode: %s", err)
|
aghhttp.ErrorAndLog(
|
||||||
|
ctx,
|
||||||
|
s.logger,
|
||||||
|
slog.LevelError,
|
||||||
|
r,
|
||||||
|
w,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
"json decode: %s",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
engine, err := aghnet.NewIgnoreEngine(reqData.Ignored)
|
engine, err := aghnet.NewIgnoreEngine(reqData.Ignored)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusUnprocessableEntity, "ignored: %s", err)
|
aghhttp.ErrorAndLog(
|
||||||
|
ctx,
|
||||||
|
s.logger,
|
||||||
|
slog.LevelError,
|
||||||
|
r,
|
||||||
|
w,
|
||||||
|
http.StatusUnprocessableEntity,
|
||||||
|
"ignored: %s",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -190,13 +243,30 @@ func (s *StatsCtx) handlePutStatsConfig(w http.ResponseWriter, r *http.Request)
|
||||||
ivl := time.Duration(reqData.Interval) * time.Millisecond
|
ivl := time.Duration(reqData.Interval) * time.Millisecond
|
||||||
err = validateIvl(ivl)
|
err = validateIvl(ivl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusUnprocessableEntity, "unsupported interval: %s", err)
|
aghhttp.ErrorAndLog(
|
||||||
|
ctx,
|
||||||
|
s.logger,
|
||||||
|
slog.LevelError,
|
||||||
|
r,
|
||||||
|
w,
|
||||||
|
http.StatusUnprocessableEntity,
|
||||||
|
"unsupported interval: %s",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if reqData.Enabled == aghalg.NBNull {
|
if reqData.Enabled == aghalg.NBNull {
|
||||||
aghhttp.Error(r, w, http.StatusUnprocessableEntity, "enabled is null")
|
aghhttp.ErrorAndLog(
|
||||||
|
ctx,
|
||||||
|
s.logger,
|
||||||
|
slog.LevelError,
|
||||||
|
r,
|
||||||
|
w,
|
||||||
|
http.StatusUnprocessableEntity,
|
||||||
|
"enabled is null",
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -215,7 +285,16 @@ func (s *StatsCtx) handlePutStatsConfig(w http.ResponseWriter, r *http.Request)
|
||||||
func (s *StatsCtx) handleStatsReset(w http.ResponseWriter, r *http.Request) {
|
func (s *StatsCtx) handleStatsReset(w http.ResponseWriter, r *http.Request) {
|
||||||
err := s.clear()
|
err := s.clear()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusInternalServerError, "stats: %s", err)
|
aghhttp.ErrorAndLog(
|
||||||
|
r.Context(),
|
||||||
|
s.logger,
|
||||||
|
slog.LevelError,
|
||||||
|
r,
|
||||||
|
w,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"stats: %s",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -189,7 +189,7 @@ func New(conf Config) (s *StatsCtx, err error) {
|
||||||
|
|
||||||
tx, err := s.db.Load().Begin(true)
|
tx, err := s.db.Load().Begin(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("stats: opening a transaction: %w", err)
|
return nil, fmt.Errorf("opening a transaction: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
deleted := s.deleteOldUnits(tx, id-uint32(s.limit.Hours())-1)
|
deleted := s.deleteOldUnits(tx, id-uint32(s.limit.Hours())-1)
|
||||||
|
@ -239,8 +239,6 @@ func (s *StatsCtx) Start() {
|
||||||
|
|
||||||
// Close implements the [io.Closer] interface for *StatsCtx.
|
// Close implements the [io.Closer] interface for *StatsCtx.
|
||||||
func (s *StatsCtx) Close() (err error) {
|
func (s *StatsCtx) Close() (err error) {
|
||||||
defer func() { err = errors.Annotate(err, "stats: closing: %w") }()
|
|
||||||
|
|
||||||
db := s.db.Swap(nil)
|
db := s.db.Swap(nil)
|
||||||
if db == nil {
|
if db == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -392,7 +390,13 @@ func (s *StatsCtx) openDB() (err error) {
|
||||||
Please read the explanation here: https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#limitations`
|
Please read the explanation here: https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#limitations`
|
||||||
|
|
||||||
// TODO(s.chzhen): Use passed context.
|
// TODO(s.chzhen): Use passed context.
|
||||||
slogutil.PrintLines(context.TODO(), s.logger, slog.LevelError, "opening database", lines)
|
slogutil.PrintLines(
|
||||||
|
context.TODO(),
|
||||||
|
s.logger,
|
||||||
|
slog.LevelError,
|
||||||
|
"opening database",
|
||||||
|
lines,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -458,16 +462,17 @@ func (s *StatsCtx) flushDB(id, limit uint32, ptr *unit) (cont bool, sleepFor tim
|
||||||
}
|
}
|
||||||
|
|
||||||
delErr := tx.DeleteBucket(idToUnitName(id - limit))
|
delErr := tx.DeleteBucket(idToUnitName(id - limit))
|
||||||
|
|
||||||
if delErr != nil {
|
if delErr != nil {
|
||||||
// TODO(e.burkov): Improve the algorithm of deleting the oldest bucket
|
// TODO(e.burkov): Improve the algorithm of deleting the oldest bucket
|
||||||
// to avoid the error.
|
// to avoid the error.
|
||||||
msg := "deleting bucket"
|
logFunc := s.logger.Warn
|
||||||
if errors.Is(delErr, bbolt.ErrBucketNotFound) {
|
if !errors.Is(delErr, bbolt.ErrBucketNotFound) {
|
||||||
s.logger.Warn(msg, slogutil.KeyError, delErr)
|
|
||||||
} else {
|
|
||||||
isCommitable = false
|
isCommitable = false
|
||||||
s.logger.Error(msg, slogutil.KeyError, delErr)
|
logFunc = s.logger.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logFunc("deleting bucket", slogutil.KeyError, delErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, 0
|
return true, 0
|
||||||
|
|
|
@ -22,11 +22,6 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(s.chzhen): Remove once [aghhttp.Error] starts using slog.
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
testutil.DiscardLogOutput(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// constUnitID is the UnitIDGenFunc which always return 0.
|
// constUnitID is the UnitIDGenFunc which always return 0.
|
||||||
func constUnitID() (id uint32) { return 0 }
|
func constUnitID() (id uint32) { return 0 }
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue