mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-01 12:06:38 +08:00
Deduplicate missing cloud config warnings
This commit is contained in:
@@ -2,49 +2,58 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const cloudStorageMissingConfigWarnPrefix = "cloud.storage.missing_config_warned."
|
||||||
|
|
||||||
// BootCloudStorageHealthCheck验证所有已配置的云盘存储在启动时是否可用
|
// BootCloudStorageHealthCheck验证所有已配置的云盘存储在启动时是否可用
|
||||||
func (c *Container) BootCloudStorageHealthCheck(ctx context.Context) {
|
func (c *Container) BootCloudStorageHealthCheck(ctx context.Context) {
|
||||||
if c == nil || c.StorageCfg == nil {
|
if c == nil || c.StorageCfg == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
configs, err := c.StorageCfg.List(ctx)
|
configs, err := c.StorageCfg.List(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Log.Warn("boot: cloud storage health check failed to list configs", zap.Error(err))
|
c.Log.Warn("boot: cloud storage health check failed to list configs", zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cloudConfigs := make([]StorageView, 0)
|
cloudConfigs := make([]StorageView, 0)
|
||||||
for _, cfg := range configs {
|
for _, cfg := range configs {
|
||||||
if cfg.Enabled && (cfg.Type == "quark" || cfg.Type == "cloud115" || cfg.Type == "clouddrive2" || cfg.Type == "openlist") {
|
if cfg.Enabled && (cfg.Type == "quark" || cfg.Type == "cloud115" || cfg.Type == "clouddrive2" || cfg.Type == "openlist") {
|
||||||
cloudConfigs = append(cloudConfigs, cfg)
|
cloudConfigs = append(cloudConfigs, cfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cloudConfigs) == 0 {
|
if len(cloudConfigs) == 0 {
|
||||||
c.Log.Info("boot: no enabled cloud storage configured")
|
c.Log.Info("boot: no enabled cloud storage configured")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Log.Info("boot: checking cloud storage health", zap.Int("count", len(cloudConfigs)))
|
c.Log.Info("boot: checking cloud storage health", zap.Int("count", len(cloudConfigs)))
|
||||||
|
|
||||||
for _, cfg := range cloudConfigs {
|
for _, cfg := range cloudConfigs {
|
||||||
go func(typ string) {
|
go func(typ string) {
|
||||||
checkCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
checkCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
provider, err := c.StorageCfg.CloudProvider(checkCtx, typ)
|
provider, err := c.StorageCfg.CloudProvider(checkCtx, typ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if c.warnMissingCloudStorageConfigOnce(checkCtx, typ, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
c.Log.Warn("boot: cloud storage unavailable", zap.String("type", typ), zap.Error(err))
|
c.Log.Warn("boot: cloud storage unavailable", zap.String("type", typ), zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := provider.Ping(checkCtx); err != nil {
|
if err := provider.Ping(checkCtx); err != nil {
|
||||||
|
if c.warnMissingCloudStorageConfigOnce(checkCtx, typ, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
c.Log.Warn("boot: cloud storage ping failed", zap.String("type", typ), zap.Error(err))
|
c.Log.Warn("boot: cloud storage ping failed", zap.String("type", typ), zap.Error(err))
|
||||||
} else {
|
} else {
|
||||||
c.Log.Info("boot: cloud storage healthy", zap.String("type", typ))
|
c.Log.Info("boot: cloud storage healthy", zap.String("type", typ))
|
||||||
@@ -52,3 +61,42 @@ func (c *Container) BootCloudStorageHealthCheck(ctx context.Context) {
|
|||||||
}(cfg.Type)
|
}(cfg.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Container) warnMissingCloudStorageConfigOnce(ctx context.Context, typ string, err error) bool {
|
||||||
|
reason := cloudStorageMissingConfigReason(err)
|
||||||
|
if reason == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c == nil || c.Repo == nil || c.Repo.Setting == nil {
|
||||||
|
if c != nil && c.Log != nil {
|
||||||
|
c.Log.Warn("boot: cloud storage config incomplete; skipping health check", zap.String("type", typ), zap.String("reason", reason), zap.Error(err))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
key := cloudStorageMissingConfigWarnPrefix + strings.TrimSpace(typ) + "." + reason
|
||||||
|
if value, getErr := c.Repo.Setting.Get(ctx, key); getErr == nil && strings.EqualFold(strings.TrimSpace(value), "true") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if c.Log != nil {
|
||||||
|
c.Log.Warn("boot: cloud storage config incomplete; skipping health check", zap.String("type", typ), zap.String("reason", reason), zap.Error(err))
|
||||||
|
}
|
||||||
|
if setErr := c.Repo.Setting.Set(ctx, key, "true"); setErr != nil && c.Log != nil {
|
||||||
|
c.Log.Debug("remember cloud storage config warning failed", zap.String("type", typ), zap.Error(setErr))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloudStorageMissingConfigReason(err error) string {
|
||||||
|
if err == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
msg := strings.ToLower(strings.TrimSpace(err.Error()))
|
||||||
|
switch {
|
||||||
|
case strings.Contains(msg, "missing cookie"):
|
||||||
|
return "missing_cookie"
|
||||||
|
case strings.Contains(msg, "missing webdav url"):
|
||||||
|
return "missing_webdav_url"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/glebarez/sqlite"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zaptest/observer"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"github.com/ShukeBta/MediaStationGo/internal/model"
|
||||||
|
"github.com/ShukeBta/MediaStationGo/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCloudStorageMissingConfigReason(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
err error
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{errors.New("115: missing cookie"), "missing_cookie"},
|
||||||
|
{errors.New("clouddrive2: missing WebDAV URL"), "missing_webdav_url"},
|
||||||
|
{errors.New("quark: token expired"), ""},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
if got := cloudStorageMissingConfigReason(tc.err); got != tc.want {
|
||||||
|
t.Fatalf("reason(%q) = %q, want %q", tc.err, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWarnMissingCloudStorageConfigOncePersistsMarker(t *testing.T) {
|
||||||
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := db.AutoMigrate(&model.Setting{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
core, observed := observer.New(zap.WarnLevel)
|
||||||
|
c := &Container{
|
||||||
|
Log: zap.New(core),
|
||||||
|
Repo: repository.New(db),
|
||||||
|
}
|
||||||
|
err = errors.New("115: missing cookie")
|
||||||
|
|
||||||
|
if !c.warnMissingCloudStorageConfigOnce(context.Background(), "cloud115", err) {
|
||||||
|
t.Fatal("missing config should be handled")
|
||||||
|
}
|
||||||
|
if !c.warnMissingCloudStorageConfigOnce(context.Background(), "cloud115", err) {
|
||||||
|
t.Fatal("missing config should still be classified on second call")
|
||||||
|
}
|
||||||
|
if observed.FilterMessage("boot: cloud storage config incomplete; skipping health check").Len() != 1 {
|
||||||
|
t.Fatalf("warn count = %d, want 1", observed.Len())
|
||||||
|
}
|
||||||
|
if c.warnMissingCloudStorageConfigOnce(context.Background(), "cloud115", errors.New("network timeout")) {
|
||||||
|
t.Fatal("non-missing config error should not be swallowed")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user