feat(logging): add home request-log forwarding support
- Introduced `SetHomeEnabled` to enable/disable request-log forwarding to the home control plane. - Implemented `forwardRequestLogToHome` for non-streaming logs and `homeStreamingLogWriter` for real-time streaming logs. - Enhanced `FileRequestLogger` to bypass local logging when home forwarding is enabled. - Updated server configuration to dynamically toggle home request-log forwarding based on changes. - Added corresponding unit tests to ensure correct forwarding behavior and fallback mechanisms.
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stubHomeRequestLogClient struct {
|
||||
heartbeatOK bool
|
||||
pushed [][]byte
|
||||
}
|
||||
|
||||
func (c *stubHomeRequestLogClient) HeartbeatOK() bool { return c.heartbeatOK }
|
||||
|
||||
func (c *stubHomeRequestLogClient) RPushRequestLog(_ context.Context, payload []byte) error {
|
||||
c.pushed = append(c.pushed, bytes.Clone(payload))
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestFileRequestLogger_HomeEnabled_ForwardsWhenRequestLogEnabled(t *testing.T) {
|
||||
original := currentHomeRequestLogClient
|
||||
defer func() {
|
||||
currentHomeRequestLogClient = original
|
||||
}()
|
||||
|
||||
stub := &stubHomeRequestLogClient{heartbeatOK: true}
|
||||
currentHomeRequestLogClient = func() homeRequestLogClient {
|
||||
return stub
|
||||
}
|
||||
|
||||
logsDir := t.TempDir()
|
||||
logger := NewFileRequestLogger(true, logsDir, "", 0)
|
||||
logger.SetHomeEnabled(true)
|
||||
|
||||
requestHeaders := map[string][]string{
|
||||
"Content-Type": {"application/json"},
|
||||
"Authorization": {"Bearer secret"},
|
||||
}
|
||||
|
||||
errLog := logger.LogRequest(
|
||||
"/v1/chat/completions",
|
||||
http.MethodPost,
|
||||
requestHeaders,
|
||||
[]byte(`{"input":"hello"}`),
|
||||
http.StatusOK,
|
||||
map[string][]string{"Content-Type": {"application/json"}},
|
||||
[]byte(`{"ok":true}`),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
"req-1",
|
||||
time.Now(),
|
||||
time.Now(),
|
||||
)
|
||||
if errLog != nil {
|
||||
t.Fatalf("LogRequest error: %v", errLog)
|
||||
}
|
||||
|
||||
entries, errRead := os.ReadDir(logsDir)
|
||||
if errRead != nil {
|
||||
t.Fatalf("failed to read logs dir: %v", errRead)
|
||||
}
|
||||
if len(entries) != 0 {
|
||||
t.Fatalf("expected no local request log files, got entries: %+v", entries)
|
||||
}
|
||||
|
||||
if len(stub.pushed) != 1 {
|
||||
t.Fatalf("home pushed records = %d, want 1", len(stub.pushed))
|
||||
}
|
||||
|
||||
var got struct {
|
||||
Headers map[string][]string `json:"headers"`
|
||||
RequestLog string `json:"request_log"`
|
||||
}
|
||||
if errUnmarshal := json.Unmarshal(stub.pushed[0], &got); errUnmarshal != nil {
|
||||
t.Fatalf("unmarshal payload: %v payload=%s", errUnmarshal, string(stub.pushed[0]))
|
||||
}
|
||||
if got.Headers == nil || got.Headers["Content-Type"][0] != "application/json" {
|
||||
t.Fatalf("headers.content-type = %+v, want application/json", got.Headers["Content-Type"])
|
||||
}
|
||||
if got.Headers == nil || got.Headers["Authorization"][0] != "Bearer secret" {
|
||||
t.Fatalf("headers.authorization = %+v, want Bearer secret", got.Headers["Authorization"])
|
||||
}
|
||||
if got.RequestLog == "" {
|
||||
t.Fatalf("request_log empty, want non-empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileRequestLogger_HomeEnabled_DoesNotForwardForcedErrorLogsWhenRequestLogDisabled(t *testing.T) {
|
||||
original := currentHomeRequestLogClient
|
||||
defer func() {
|
||||
currentHomeRequestLogClient = original
|
||||
}()
|
||||
|
||||
stub := &stubHomeRequestLogClient{heartbeatOK: true}
|
||||
currentHomeRequestLogClient = func() homeRequestLogClient {
|
||||
return stub
|
||||
}
|
||||
|
||||
logsDir := t.TempDir()
|
||||
logger := NewFileRequestLogger(false, logsDir, "", 0)
|
||||
logger.SetHomeEnabled(true)
|
||||
|
||||
errLog := logger.LogRequestWithOptions(
|
||||
"/v1/chat/completions",
|
||||
http.MethodPost,
|
||||
map[string][]string{"Content-Type": {"application/json"}},
|
||||
[]byte(`{"input":"hello"}`),
|
||||
http.StatusBadGateway,
|
||||
map[string][]string{"Content-Type": {"application/json"}},
|
||||
[]byte(`{"error":"upstream failure"}`),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
"req-2",
|
||||
time.Now(),
|
||||
time.Now(),
|
||||
)
|
||||
if errLog != nil {
|
||||
t.Fatalf("LogRequestWithOptions error: %v", errLog)
|
||||
}
|
||||
|
||||
if len(stub.pushed) != 0 {
|
||||
t.Fatalf("home pushed records = %d, want 0", len(stub.pushed))
|
||||
}
|
||||
|
||||
entries, errRead := os.ReadDir(logsDir)
|
||||
if errRead != nil {
|
||||
t.Fatalf("failed to read logs dir: %v", errRead)
|
||||
}
|
||||
found := false
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
if entry.Name() != "" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("expected local forced error log file when request-log disabled")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user