Skip to content

Commit a8b8db2

Browse files
committed
chore: enable runtime request logging
Signed-off-by: Danny Kopping <[email protected]>
1 parent 785748c commit a8b8db2

10 files changed

+107
-78
lines changed

config.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
package aibridge
22

3+
import "sync/atomic"
4+
35
type ProviderConfig struct {
46
BaseURL, Key string
57
// EnableUpstreamLogging enables logging of upstream API requests and responses to /tmp/$provider.log
6-
EnableUpstreamLogging bool
8+
enableUpstreamLogging atomic.Bool
9+
}
10+
11+
// SetEnableUpstreamLogging enables or disables upstream logging at runtime.
12+
func (c *ProviderConfig) SetEnableUpstreamLogging(enabled bool) {
13+
c.enableUpstreamLogging.Store(enabled)
714
}
815

9-
type Config struct {
10-
OpenAI ProviderConfig
11-
Anthropic ProviderConfig
16+
// EnableUpstreamLogging returns whether upstream logging is currently enabled.
17+
func (c *ProviderConfig) EnableUpstreamLogging() bool {
18+
return c.enableUpstreamLogging.Load()
1219
}

intercept_anthropic_messages_base.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type AnthropicMessagesInterceptionBase struct {
1414
id uuid.UUID
1515
req *MessageNewParamsWrapper
1616

17-
cfg ProviderConfig
17+
cfg *ProviderConfig
1818
logger slog.Logger
1919

2020
recorder Recorder

intercept_anthropic_messages_blocking.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type AnthropicMessagesBlockingInterception struct {
2222
AnthropicMessagesInterceptionBase
2323
}
2424

25-
func NewAnthropicMessagesBlockingInterception(id uuid.UUID, req *MessageNewParamsWrapper, cfg ProviderConfig) *AnthropicMessagesBlockingInterception {
25+
func NewAnthropicMessagesBlockingInterception(id uuid.UUID, req *MessageNewParamsWrapper, cfg *ProviderConfig) *AnthropicMessagesBlockingInterception {
2626
return &AnthropicMessagesBlockingInterception{AnthropicMessagesInterceptionBase: AnthropicMessagesInterceptionBase{
2727
id: id,
2828
req: req,
@@ -57,7 +57,7 @@ func (i *AnthropicMessagesBlockingInterception) ProcessRequest(w http.ResponseWr
5757

5858
opts := []option.RequestOption{option.WithRequestTimeout(time.Second * 60)} // TODO: configurable timeout
5959

60-
client := newAnthropicClient(i.cfg, i.id.String(), opts...)
60+
client := newAnthropicClient(i.cfg, i.id.String(), i.Model(), opts...)
6161
messages := i.req.MessageNewParams
6262
logger := i.logger.With(slog.F("model", i.req.Model))
6363

intercept_anthropic_messages_streaming.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type AnthropicMessagesStreamingInterception struct {
2525
AnthropicMessagesInterceptionBase
2626
}
2727

28-
func NewAnthropicMessagesStreamingInterception(id uuid.UUID, req *MessageNewParamsWrapper, cfg ProviderConfig) *AnthropicMessagesStreamingInterception {
28+
func NewAnthropicMessagesStreamingInterception(id uuid.UUID, req *MessageNewParamsWrapper, cfg *ProviderConfig) *AnthropicMessagesStreamingInterception {
2929
return &AnthropicMessagesStreamingInterception{AnthropicMessagesInterceptionBase: AnthropicMessagesInterceptionBase{
3030
id: id,
3131
req: req,
@@ -94,7 +94,7 @@ func (i *AnthropicMessagesStreamingInterception) ProcessRequest(w http.ResponseW
9494
_ = events.Shutdown(streamCtx) // Catch-all in case it doesn't get shutdown after stream completes.
9595
}()
9696

97-
client := newAnthropicClient(i.cfg, i.id.String())
97+
client := newAnthropicClient(i.cfg, i.id.String(), i.Model())
9898
messages := i.req.MessageNewParams
9999

100100
// Accumulate usage across the entire streaming interaction (including tool reinvocations).

intercept_openai_chat_base.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type OpenAIChatInterceptionBase struct {
1717
id uuid.UUID
1818
req *ChatCompletionNewParamsWrapper
1919

20-
cfg ProviderConfig
20+
cfg *ProviderConfig
2121
logger slog.Logger
2222

2323
recorder Recorder

intercept_openai_chat_blocking.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type OpenAIBlockingChatInterception struct {
2323
OpenAIChatInterceptionBase
2424
}
2525

26-
func NewOpenAIBlockingChatInterception(id uuid.UUID, req *ChatCompletionNewParamsWrapper, cfg ProviderConfig) *OpenAIBlockingChatInterception {
26+
func NewOpenAIBlockingChatInterception(id uuid.UUID, req *ChatCompletionNewParamsWrapper, cfg *ProviderConfig) *OpenAIBlockingChatInterception {
2727
return &OpenAIBlockingChatInterception{OpenAIChatInterceptionBase: OpenAIChatInterceptionBase{
2828
id: id,
2929
req: req,
@@ -41,7 +41,7 @@ func (i *OpenAIBlockingChatInterception) ProcessRequest(w http.ResponseWriter, r
4141
}
4242

4343
ctx := r.Context()
44-
client := newOpenAIClient(i.cfg, i.id.String())
44+
client := newOpenAIClient(i.cfg, i.id.String(), i.Model())
4545
logger := i.logger.With(slog.F("model", i.req.Model))
4646

4747
var (

intercept_openai_chat_streaming.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type OpenAIStreamingChatInterception struct {
2525
OpenAIChatInterceptionBase
2626
}
2727

28-
func NewOpenAIStreamingChatInterception(id uuid.UUID, req *ChatCompletionNewParamsWrapper, cfg ProviderConfig) *OpenAIStreamingChatInterception {
28+
func NewOpenAIStreamingChatInterception(id uuid.UUID, req *ChatCompletionNewParamsWrapper, cfg *ProviderConfig) *OpenAIStreamingChatInterception {
2929
return &OpenAIStreamingChatInterception{OpenAIChatInterceptionBase: OpenAIChatInterceptionBase{
3030
id: id,
3131
req: req,
@@ -64,7 +64,7 @@ func (i *OpenAIStreamingChatInterception) ProcessRequest(w http.ResponseWriter,
6464
defer cancel()
6565
r = r.WithContext(ctx) // Rewire context for SSE cancellation.
6666

67-
client := newOpenAIClient(i.cfg, i.id.String())
67+
client := newOpenAIClient(i.cfg, i.id.String(), i.Model())
6868
logger := i.logger.With(slog.F("model", i.req.Model))
6969

7070
streamCtx, streamCancel := context.WithCancelCause(ctx)

provider_anthropic.go

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"io"
8-
"log"
98
"net/http"
10-
"net/http/httputil"
119
"os"
1210

1311
"github.com/anthropics/anthropic-sdk-go"
@@ -21,7 +19,7 @@ var _ Provider = &AnthropicProvider{}
2119

2220
// AnthropicProvider allows for interactions with the Anthropic API.
2321
type AnthropicProvider struct {
24-
cfg ProviderConfig
22+
cfg *ProviderConfig
2523
}
2624

2725
const (
@@ -30,7 +28,7 @@ const (
3028
routeMessages = "/anthropic/v1/messages" // https://docs.anthropic.com/en/api/messages
3129
)
3230

33-
func NewAnthropicProvider(cfg ProviderConfig) *AnthropicProvider {
31+
func NewAnthropicProvider(cfg *ProviderConfig) *AnthropicProvider {
3432
if cfg.BaseURL == "" {
3533
cfg.BaseURL = "https://api.anthropic.com/"
3634
}
@@ -100,37 +98,13 @@ func (p *AnthropicProvider) InjectAuthHeader(headers *http.Header) {
10098
headers.Set(p.AuthHeader(), p.cfg.Key)
10199
}
102100

103-
func newAnthropicClient(cfg ProviderConfig, id string, opts ...option.RequestOption) anthropic.Client {
101+
func newAnthropicClient(cfg *ProviderConfig, id, model string, opts ...option.RequestOption) anthropic.Client {
104102
opts = append(opts, option.WithAPIKey(cfg.Key))
105103
opts = append(opts, option.WithBaseURL(cfg.BaseURL))
106104

107-
if cfg.EnableUpstreamLogging {
108-
reqLogFile, err := os.OpenFile("/tmp/anthropic-req.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
109-
if err == nil {
110-
reqLogger := log.New(reqLogFile, "", log.LstdFlags)
111-
112-
resLogFile, err := os.OpenFile("/tmp/anthropic-res.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
113-
if err == nil {
114-
resLogger := log.New(resLogFile, "", log.LstdFlags)
115-
116-
opts = append(opts, option.WithMiddleware(func(req *http.Request, next option.MiddlewareNext) (*http.Response, error) {
117-
if reqDump, err := httputil.DumpRequest(req, true); err == nil {
118-
reqLogger.Printf("[req] [%s] %s", id, reqDump)
119-
}
120-
121-
resp, err := next(req)
122-
if err != nil {
123-
resLogger.Printf("[res] [%s] Error: %v", id, err)
124-
return resp, err
125-
}
126-
127-
if respDump, err := httputil.DumpResponse(resp, true); err == nil {
128-
resLogger.Printf("[res] [%s] %s", id, respDump)
129-
}
130-
131-
return resp, err
132-
}))
133-
}
105+
if cfg.EnableUpstreamLogging() {
106+
if middleware := createLoggingMiddleware("anthropic", id, model); middleware != nil {
107+
opts = append(opts, option.WithMiddleware(middleware))
134108
}
135109
}
136110

provider_openai.go

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import (
44
"encoding/json"
55
"fmt"
66
"io"
7-
"log"
87
"net/http"
9-
"net/http/httputil"
108
"os"
119

1210
"github.com/google/uuid"
@@ -18,7 +16,7 @@ var _ Provider = &OpenAIProvider{}
1816

1917
// OpenAIProvider allows for interactions with the OpenAI API.
2018
type OpenAIProvider struct {
21-
cfg ProviderConfig
19+
cfg *ProviderConfig
2220
}
2321

2422
const (
@@ -27,7 +25,7 @@ const (
2725
routeChatCompletions = "/openai/v1/chat/completions" // https://platform.openai.com/docs/api-reference/chat
2826
)
2927

30-
func NewOpenAIProvider(cfg ProviderConfig) *OpenAIProvider {
28+
func NewOpenAIProvider(cfg *ProviderConfig) *OpenAIProvider {
3129
if cfg.BaseURL == "" {
3230
cfg.BaseURL = "https://api.openai.com/v1/"
3331
}
@@ -102,38 +100,14 @@ func (p *OpenAIProvider) InjectAuthHeader(headers *http.Header) {
102100
headers.Set(p.AuthHeader(), "Bearer "+p.cfg.Key)
103101
}
104102

105-
func newOpenAIClient(cfg ProviderConfig, id string) openai.Client {
103+
func newOpenAIClient(cfg *ProviderConfig, id, model string) openai.Client {
106104
var opts []option.RequestOption
107105
opts = append(opts, option.WithAPIKey(cfg.Key))
108106
opts = append(opts, option.WithBaseURL(cfg.BaseURL))
109107

110-
if cfg.EnableUpstreamLogging {
111-
reqLogFile, err := os.OpenFile("/tmp/openai-req.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
112-
if err == nil {
113-
reqLogger := log.New(reqLogFile, "", log.LstdFlags)
114-
115-
resLogFile, err := os.OpenFile("/tmp/openai-res.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
116-
if err == nil {
117-
resLogger := log.New(resLogFile, "", log.LstdFlags)
118-
119-
opts = append(opts, option.WithMiddleware(func(req *http.Request, next option.MiddlewareNext) (*http.Response, error) {
120-
if reqDump, err := httputil.DumpRequest(req, true); err == nil {
121-
reqLogger.Printf("[req] [%s] %s", id, reqDump)
122-
}
123-
124-
resp, err := next(req)
125-
if err != nil {
126-
resLogger.Printf("[res] [%s] Error: %v", id, err)
127-
return resp, err
128-
}
129-
130-
if respDump, err := httputil.DumpResponse(resp, true); err == nil {
131-
resLogger.Printf("[res] [%s] %s", id, respDump)
132-
}
133-
134-
return resp, err
135-
}))
136-
}
108+
if cfg.EnableUpstreamLogging() {
109+
if middleware := createLoggingMiddleware("openai", id, model); middleware != nil {
110+
opts = append(opts, option.WithMiddleware(middleware))
137111
}
138112
}
139113

request_logger.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package aibridge
2+
3+
import (
4+
"log"
5+
"net/http"
6+
"net/http/httputil"
7+
"os"
8+
)
9+
10+
// logUpstreamRequest logs an HTTP request with the given ID and model name.
11+
// The prefix format is: [req] [id] [model]
12+
func logUpstreamRequest(logger *log.Logger, id, model string, req *http.Request) {
13+
if logger == nil {
14+
return
15+
}
16+
17+
if reqDump, err := httputil.DumpRequest(req, true); err == nil {
18+
logger.Printf("[req] [%s] [%s] %s", id, model, reqDump)
19+
}
20+
}
21+
22+
// logUpstreamResponse logs an HTTP response with the given ID and model name.
23+
// The prefix format is: [res] [id] [model]
24+
func logUpstreamResponse(logger *log.Logger, id, model string, resp *http.Response) {
25+
if logger == nil {
26+
return
27+
}
28+
29+
if respDump, err := httputil.DumpResponse(resp, true); err == nil {
30+
logger.Printf("[res] [%s] [%s] %s", id, model, respDump)
31+
}
32+
}
33+
34+
// logUpstreamError logs an error that occurred during request/response processing.
35+
// The prefix format is: [res] [id] [model] Error:
36+
func logUpstreamError(logger *log.Logger, id, model string, err error) {
37+
if logger == nil {
38+
return
39+
}
40+
41+
logger.Printf("[res] [%s] [%s] Error: %v", id, model, err)
42+
}
43+
44+
// createLoggingMiddleware creates a middleware function that logs requests and responses.
45+
// Returns nil if logging setup fails.
46+
func createLoggingMiddleware(provider, id, model string) func(*http.Request, func(*http.Request) (*http.Response, error)) (*http.Response, error) {
47+
reqLogFile, err := os.OpenFile("/tmp/"+provider+"-req.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
48+
if err != nil {
49+
return nil
50+
}
51+
52+
resLogFile, err := os.OpenFile("/tmp/"+provider+"-res.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
53+
if err != nil {
54+
reqLogFile.Close()
55+
return nil
56+
}
57+
58+
reqLogger := log.New(reqLogFile, "", log.LstdFlags)
59+
resLogger := log.New(resLogFile, "", log.LstdFlags)
60+
61+
return func(req *http.Request, next func(*http.Request) (*http.Response, error)) (*http.Response, error) {
62+
logUpstreamRequest(reqLogger, id, model, req)
63+
64+
resp, err := next(req)
65+
if err != nil {
66+
logUpstreamError(resLogger, id, model, err)
67+
return resp, err
68+
}
69+
70+
logUpstreamResponse(resLogger, id, model, resp)
71+
72+
return resp, err
73+
}
74+
}

0 commit comments

Comments
 (0)