174 lines
6.0 KiB
Go
174 lines
6.0 KiB
Go
package logging
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/config"
|
|
)
|
|
|
|
func TestCrossPlatformCoverageLoggerFailureAndRecoveryEdges(t *testing.T) {
|
|
if (*FileLogger)(nil).Writer() != io.Discard || (&FileLogger{}).Writer() != io.Discard {
|
|
t.Fatal("uninitialized Writer should discard")
|
|
}
|
|
|
|
dir := t.TempDir()
|
|
logPath := filepath.Join(dir, "direct.log")
|
|
file, err := NewRotatingFile(logPath)
|
|
if err != nil {
|
|
t.Fatalf("NewRotatingFile: %v", err)
|
|
}
|
|
if _, err := file.Write([]byte("entry")); err != nil {
|
|
t.Fatalf("write rotating file: %v", err)
|
|
}
|
|
if err := file.Close(); err != nil {
|
|
t.Fatalf("close rotating file: %v", err)
|
|
}
|
|
configured := Setup(t.TempDir())
|
|
if configured.Writer() == io.Discard {
|
|
t.Fatal("configured Writer should expose rotating writer")
|
|
}
|
|
_ = configured.Close()
|
|
if _, err := NewRotatingFile(filepath.Join(dir, "missing", "file")); err == nil {
|
|
t.Fatal("rotating file in missing directory should fail")
|
|
}
|
|
|
|
blocked := filepath.Join(t.TempDir(), logSubDir, logFileName)
|
|
if err := os.MkdirAll(blocked, config.DirPerm); err != nil {
|
|
t.Fatalf("create blocking directory: %v", err)
|
|
}
|
|
nop := Setup(filepath.Dir(filepath.Dir(blocked)))
|
|
if nop.writer != nil {
|
|
t.Fatal("Setup with directory at log path should return no-op logger")
|
|
}
|
|
|
|
oldOpen := openLogFile
|
|
t.Cleanup(func() { openLogFile = oldOpen })
|
|
wantErr := errors.New("open failed")
|
|
openLogFile = func(string, int, os.FileMode) (*os.File, error) { return nil, wantErr }
|
|
w := newRotatingWriter(logPath, 1, 1)
|
|
if _, err := w.Write([]byte("x")); !errors.Is(err, wantErr) {
|
|
t.Fatalf("reopen error = %v", err)
|
|
}
|
|
|
|
closedFile := func() *os.File {
|
|
f, err := os.CreateTemp(t.TempDir(), "closed")
|
|
if err != nil {
|
|
t.Fatalf("create temp file: %v", err)
|
|
}
|
|
_ = f.Close()
|
|
return f
|
|
}
|
|
openLogFile = func(string, int, os.FileMode) (*os.File, error) { return closedFile(), nil }
|
|
if err := newRotatingWriter(logPath, 1, 1).open(); err == nil {
|
|
t.Fatal("open should report stat failure")
|
|
}
|
|
w = newRotatingWriter(logPath, 1, 1)
|
|
if err := w.reopenLocked(); err == nil {
|
|
t.Fatal("reopen should report stat failure")
|
|
}
|
|
|
|
openLogFile = oldOpen
|
|
w = newRotatingWriter(logPath, 1, 1)
|
|
if err := w.open(); err != nil {
|
|
t.Fatalf("open before rotation failure: %v", err)
|
|
}
|
|
openLogFile = func(string, int, os.FileMode) (*os.File, error) { return nil, wantErr }
|
|
if _, err := w.Write([]byte("oversized")); !errors.Is(err, os.ErrClosed) {
|
|
t.Fatalf("rotation reopen error = %v", err)
|
|
}
|
|
|
|
openLogFile = oldOpen
|
|
w = newRotatingWriter(filepath.Join(t.TempDir(), "reopened.log"), 100, 1)
|
|
if _, err := w.Write([]byte("reopened")); err != nil {
|
|
t.Fatalf("successful reopen write: %v", err)
|
|
}
|
|
_ = w.close()
|
|
|
|
w = newRotatingWriter(filepath.Join(t.TempDir(), "closed-write.log"), 100, 1)
|
|
if err := w.open(); err != nil {
|
|
t.Fatalf("open for closed write: %v", err)
|
|
}
|
|
_ = w.file.Close()
|
|
if _, err := w.Write([]byte("x")); err == nil {
|
|
t.Fatal("write to externally closed file should fail")
|
|
}
|
|
if err := w.close(); err == nil {
|
|
t.Fatal("close of externally closed file should fail")
|
|
}
|
|
if err := w.close(); err != nil {
|
|
t.Fatalf("second close = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageLoggingTransportOptionalAndNilBranches(t *testing.T) {
|
|
LogRequest(nil, "", "", "", 0)
|
|
LogRequestBody(nil, "tools/call", "", "", nil)
|
|
LogResponse(nil, "", "", "", 0, 0, 0, nil)
|
|
LogResponseBody(nil, "", "", 0, nil, "")
|
|
LogRetryAttempt(nil, "", "", 0, 0, 0, 0, nil)
|
|
LogErrorClassified(nil, "", "", "", "", 0, 0, false, "")
|
|
LogCommandStart(nil, "", "", "", "", "", false, 0)
|
|
LogCommandEnd(nil, "", "", "", true, 0, "", "")
|
|
|
|
var buf bytes.Buffer
|
|
logger := slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
|
LogResponseBody(logger, "method", "exec", 200, []byte("ok"), "")
|
|
LogRetryAttempt(logger, "method", "exec", 1, 2, 503, time.Second, nil)
|
|
LogErrorClassified(logger, "method", "exec", "category", "reason", 0, 0, false, "")
|
|
LogErrorClassified(logger, "method", "exec", "category", "reason", 418, -32000, false, "trace")
|
|
LogCommandStart(logger, "exec", "product", "tool", "https://example.test?q=secret", "v1", true, 0)
|
|
LogCommandStart(logger, "exec", "product", "tool", "https://example.test", "v1", true, 30)
|
|
LogCommandEnd(logger, "exec", "product", "tool", true, time.Second, "", "")
|
|
LogCommandEnd(logger, "exec", "product", "tool", false, time.Second, "category", "reason")
|
|
if !strings.Contains(buf.String(), "command_start") || !strings.Contains(buf.String(), "command_end") {
|
|
t.Fatalf("optional log records = %q", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRedactionAndMultiHandlerEdges(t *testing.T) {
|
|
if got := TruncateBody([]byte{0xff, 0xfe, 'a'}, 2); !strings.Contains(got, "truncated") {
|
|
t.Fatalf("invalid UTF-8 truncation = %q", got)
|
|
}
|
|
if got := SanitizeArguments(map[string]any{"unsupported": make(chan int)}, 100); got != "{}" {
|
|
t.Fatalf("marshal failure sanitization = %q", got)
|
|
}
|
|
if RedactHeaders(http.Header{}) != nil {
|
|
t.Fatal("empty headers should return nil")
|
|
}
|
|
|
|
wantErr := errors.New("handler failure")
|
|
h := &failingLogHandler{err: wantErr, enabled: true}
|
|
multi := NewMultiHandler(&failingLogHandler{}, h)
|
|
if err := multi.Handle(context.Background(), slog.NewRecord(time.Now(), slog.LevelInfo, "message", 0)); !errors.Is(err, wantErr) {
|
|
t.Fatalf("multi handler error = %v", err)
|
|
}
|
|
if !multi.Enabled(context.Background(), slog.LevelInfo) {
|
|
t.Fatal("multi handler should be enabled")
|
|
}
|
|
if NewMultiHandler(&failingLogHandler{}).Enabled(context.Background(), slog.LevelInfo) {
|
|
t.Fatal("disabled multi handler should not be enabled")
|
|
}
|
|
}
|
|
|
|
type failingLogHandler struct {
|
|
err error
|
|
enabled bool
|
|
}
|
|
|
|
func (h *failingLogHandler) Enabled(context.Context, slog.Level) bool { return h.enabled }
|
|
func (h *failingLogHandler) Handle(context.Context, slog.Record) error {
|
|
return h.err
|
|
}
|
|
func (h *failingLogHandler) WithAttrs([]slog.Attr) slog.Handler { return h }
|
|
func (h *failingLogHandler) WithGroup(string) slog.Handler { return h }
|