Files
kopia/internal/repodiag/log_manager.go
Julio López 2a22281271 fix(server): ensure server uploads "repodiag" blobs (#4358)
The kopia server was not uploading any logs to the repository,
because "repodiag" blob uploads would always fail.

The cause was the following: when the (log) repodiag blob
PUT operation was initiated, the `Context` used for this
operation was already canceled.

The context used for blob uploads is passed to
`repodiag.NewLogManager` when opening the repository.
In the case of the kopia server, the repository is asynchronously
opened in `server.Server.InitReposotoryAsync`. The context
passed to `repo.Open` is canceled after the "open repository"
server task completes.

This issue was introduced in #1691

Change:
Use `context.WithoutCancel()` instead of the context passed
when the repo diagnoser is created.

New tests are included to reproduce this failure and verify
the fix.
- test: ensure server logs are uploaded to the repo
- test: honor cancellation in map storage
- test: repodiag context cancellation

Ref:
- #1691
2025-01-22 20:13:39 -08:00

87 lines
2.2 KiB
Go

// Package repodiag manages logs and metrics in the repository.
package repodiag
import (
"context"
"crypto/rand"
"fmt"
"sync/atomic"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/kopia/kopia/internal/clock"
"github.com/kopia/kopia/internal/gather"
"github.com/kopia/kopia/internal/zaplogutil"
"github.com/kopia/kopia/repo/blob"
)
const blobLoggerFlushThreshold = 4 << 20
// LogBlobPrefix is a prefix given to text logs stored in repository.
const LogBlobPrefix = "_log_"
// LogManager manages writing encrypted log blobs to the repository.
type LogManager struct {
// Set by Enable(). Log blobs are not written to the repository until
// Enable() is called.
enabled atomic.Bool
// InternalLogManager implements io.Writer and we must be able to write to the
// repository asynchronously when the context is not provided.
ctx context.Context //nolint:containedctx
writer *BlobWriter
timeFunc func() time.Time
flushThreshold int
}
func (m *LogManager) encryptAndWriteLogBlob(prefix blob.ID, data gather.Bytes, closeFunc func()) {
m.writer.EncryptAndWriteBlobAsync(m.ctx, prefix, data, closeFunc)
}
// NewLogger creates new logger.
func (m *LogManager) NewLogger() *zap.SugaredLogger {
if m == nil {
return zap.NewNop().Sugar()
}
var rnd [2]byte
rand.Read(rnd[:]) //nolint:errcheck
w := &logWriteSyncer{
m: m,
prefix: blob.ID(fmt.Sprintf("%v%v_%x", LogBlobPrefix, clock.Now().Local().Format("20060102150405"), rnd)),
}
return zap.New(zapcore.NewCore(
zaplogutil.NewStdConsoleEncoder(zaplogutil.StdConsoleEncoderConfig{
TimeLayout: zaplogutil.PreciseLayout,
LocalTime: false,
}),
w, zap.DebugLevel), zap.WithClock(zaplogutil.Clock())).Sugar()
}
// Enable enables writing log blobs to repository.
// Logs are not written to the repository until Enable is called.
func (m *LogManager) Enable() {
if m == nil {
return
}
m.enabled.Store(true)
}
// NewLogManager creates a new LogManager that will emit logs as repository blobs.
func NewLogManager(ctx context.Context, w *BlobWriter) *LogManager {
return &LogManager{
ctx: context.WithoutCancel(ctx),
writer: w,
flushThreshold: blobLoggerFlushThreshold,
timeFunc: clock.Now,
}
}