Skip to content

Commit 5753d1a

Browse files
committed
feat(logging): enable file-backed request/response sources for enhanced API logging
- Introduced support for file-backed logging of API requests and responses to handle large payloads efficiently. - Refactored `attachWebsocketLogSources` to `attachRequestLogSources` for broader request and response handling. - Added new methods for appending request/response data to file-backed sources and updated existing logging workflows for compatibility. - Improved cleanup and merge logic for file-backed sources during request processing. - Updated tests to cover newly introduced file-backed logging functionality.
1 parent 387c783 commit 5753d1a

9 files changed

Lines changed: 386 additions & 79 deletions

File tree

config.example.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pprof:
4949
enable: false
5050
addr: "127.0.0.1:8316"
5151

52-
# When true, disable high-overhead HTTP middleware features to reduce per-request memory usage under high concurrency.
52+
# When true, disable high-overhead request logging and HTTP middleware features to reduce per-request memory usage under high concurrency.
5353
commercial-mode: false
5454

5555
# When true, write application logs to rotating files instead of stdout

internal/api/middleware/request_logging.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func RequestLoggingMiddleware(logger logging.RequestLogger) gin.HandlerFunc {
5858
wrapper.logOnErrorOnly = true
5959
}
6060
c.Writer = wrapper
61-
attachWebsocketLogSources(c, logger, loggerEnabled)
61+
attachRequestLogSources(c, logger, loggerEnabled)
6262

6363
// Process the request
6464
c.Next()
@@ -75,14 +75,23 @@ type fileBodySourceFactory interface {
7575
NewFileBodySource(prefix string) (*logging.FileBodySource, error)
7676
}
7777

78-
func attachWebsocketLogSources(c *gin.Context, logger logging.RequestLogger, loggerEnabled bool) {
79-
if c == nil || !loggerEnabled || !isResponsesWebsocketUpgrade(c.Request) {
78+
func attachRequestLogSources(c *gin.Context, logger logging.RequestLogger, loggerEnabled bool) {
79+
if c == nil || !loggerEnabled {
8080
return
8181
}
8282
factory, ok := logger.(fileBodySourceFactory)
8383
if !ok || factory == nil {
8484
return
8585
}
86+
if source, errSource := factory.NewFileBodySource("api-request"); errSource == nil {
87+
c.Set(logging.APIRequestSourceContextKey, source)
88+
}
89+
if source, errSource := factory.NewFileBodySource("api-response"); errSource == nil {
90+
c.Set(logging.APIResponseSourceContextKey, source)
91+
}
92+
if !isResponsesWebsocketUpgrade(c.Request) {
93+
return
94+
}
8695
if source, errSource := factory.NewFileBodySource("websocket-timeline"); errSource == nil {
8796
c.Set(logging.WebsocketTimelineSourceContextKey, source)
8897
}

internal/api/middleware/request_logging_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ func TestShouldCaptureRequestBody(t *testing.T) {
144144
}
145145
}
146146

147-
func TestAttachWebsocketLogSourcesUsesLoggerLogsDir(t *testing.T) {
147+
func TestAttachRequestLogSourcesUsesLoggerLogsDir(t *testing.T) {
148148
gin.SetMode(gin.TestMode)
149149

150150
logsDir := t.TempDir()
@@ -154,7 +154,7 @@ func TestAttachWebsocketLogSourcesUsesLoggerLogsDir(t *testing.T) {
154154
c.Request = httptest.NewRequest(http.MethodGet, "/v1/responses", nil)
155155
c.Request.Header.Set("Upgrade", "websocket")
156156

157-
attachWebsocketLogSources(c, logger, true)
157+
attachRequestLogSources(c, logger, true)
158158
defer cleanupFileBodySourcesFromContext(c)
159159

160160
for _, key := range []string{

internal/api/middleware/response_writer.go

Lines changed: 99 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,11 @@ func (w *ResponseWriterWrapper) Finalize(c *gin.Context) error {
282282
hasAPIError := len(slicesAPIResponseError) > 0 || finalStatusCode >= http.StatusBadRequest
283283
forceLog := w.logOnErrorOnly && hasAPIError && !w.logger.IsEnabled()
284284
websocketTimelineSource := w.extractWebsocketTimelineSource(c)
285+
apiRequestSource := w.extractAPIRequestSource(c)
286+
apiResponseSource := w.extractAPIResponseSource(c)
285287
apiWebsocketTimelineSource := w.extractAPIWebsocketTimelineSource(c)
286288
if !w.logger.IsEnabled() && !forceLog {
287-
cleanupFileBodySources(websocketTimelineSource, apiWebsocketTimelineSource)
289+
cleanupFileBodySources(websocketTimelineSource, apiRequestSource, apiResponseSource, apiWebsocketTimelineSource)
288290
return nil
289291
}
290292

@@ -303,33 +305,63 @@ func (w *ResponseWriterWrapper) Finalize(c *gin.Context) error {
303305

304306
// Write API Request and Response to the streaming log before closing
305307
apiRequest := w.extractAPIRequest(c)
306-
if len(apiRequest) > 0 {
307-
_ = w.streamWriter.WriteAPIRequest(apiRequest)
308-
}
309308
apiResponse := w.extractAPIResponse(c)
310-
if len(apiResponse) > 0 {
311-
_ = w.streamWriter.WriteAPIResponse(apiResponse)
309+
if sourceWriter, ok := w.streamWriter.(interface {
310+
WriteAPIRequestSource(*logging.FileBodySource) error
311+
WriteAPIResponseSource(*logging.FileBodySource) error
312+
}); ok {
313+
if len(apiRequest) > 0 {
314+
_ = w.streamWriter.WriteAPIRequest(apiRequest)
315+
}
316+
if apiRequestSource != nil && apiRequestSource.HasPayload() {
317+
_ = sourceWriter.WriteAPIRequestSource(apiRequestSource)
318+
}
319+
if len(apiResponse) > 0 {
320+
_ = w.streamWriter.WriteAPIResponse(apiResponse)
321+
}
322+
if apiResponseSource != nil && apiResponseSource.HasPayload() {
323+
_ = sourceWriter.WriteAPIResponseSource(apiResponseSource)
324+
}
325+
} else {
326+
var errMerge error
327+
apiRequest, errMerge = mergeFileBodySource(apiRequest, apiRequestSource)
328+
if errMerge != nil {
329+
cleanupFileBodySources(websocketTimelineSource, apiResponseSource, apiWebsocketTimelineSource)
330+
return errMerge
331+
}
332+
apiResponse, errMerge = mergeFileBodySource(apiResponse, apiResponseSource)
333+
if errMerge != nil {
334+
cleanupFileBodySources(websocketTimelineSource, apiWebsocketTimelineSource)
335+
return errMerge
336+
}
337+
if len(apiRequest) > 0 {
338+
_ = w.streamWriter.WriteAPIRequest(apiRequest)
339+
}
340+
if len(apiResponse) > 0 {
341+
_ = w.streamWriter.WriteAPIResponse(apiResponse)
342+
}
312343
}
313344
apiWebsocketTimeline := w.extractAPIWebsocketTimeline(c)
314345
var errMerge error
315346
apiWebsocketTimeline, errMerge = mergeFileBodySource(apiWebsocketTimeline, apiWebsocketTimelineSource)
316347
if errMerge != nil {
317-
cleanupFileBodySources(websocketTimelineSource)
348+
cleanupFileBodySources(websocketTimelineSource, apiRequestSource, apiResponseSource)
318349
return errMerge
319350
}
320-
cleanupFileBodySources(websocketTimelineSource)
321351
if len(apiWebsocketTimeline) > 0 {
322352
_ = w.streamWriter.WriteAPIWebsocketTimeline(apiWebsocketTimeline)
323353
}
324354
if err := w.streamWriter.Close(); err != nil {
325355
w.streamWriter = nil
356+
cleanupFileBodySources(websocketTimelineSource, apiRequestSource, apiResponseSource)
326357
return err
327358
}
328359
w.streamWriter = nil
360+
cleanupFileBodySources(websocketTimelineSource, apiRequestSource, apiResponseSource)
329361
return nil
330362
}
331363

332-
return w.logRequest(w.extractRequestBody(c), finalStatusCode, w.cloneHeaders(), w.extractResponseBody(c), w.extractWebsocketTimeline(c), websocketTimelineSource, w.extractAPIRequest(c), w.extractAPIResponse(c), w.extractAPIWebsocketTimeline(c), apiWebsocketTimelineSource, w.extractAPIResponseTimestamp(c), slicesAPIResponseError, forceLog)
364+
return w.logRequest(w.extractRequestBody(c), finalStatusCode, w.cloneHeaders(), w.extractResponseBody(c), w.extractWebsocketTimeline(c), websocketTimelineSource, w.extractAPIRequest(c), apiRequestSource, w.extractAPIResponse(c), apiResponseSource, w.extractAPIWebsocketTimeline(c), apiWebsocketTimelineSource, w.extractAPIResponseTimestamp(c), slicesAPIResponseError, forceLog)
333365
}
334366

335367
func (w *ResponseWriterWrapper) cloneHeaders() map[string][]string {
@@ -369,6 +401,14 @@ func (w *ResponseWriterWrapper) extractAPIResponse(c *gin.Context) []byte {
369401
return data
370402
}
371403

404+
func (w *ResponseWriterWrapper) extractAPIRequestSource(c *gin.Context) *logging.FileBodySource {
405+
return extractFileBodySource(c, logging.APIRequestSourceContextKey)
406+
}
407+
408+
func (w *ResponseWriterWrapper) extractAPIResponseSource(c *gin.Context) *logging.FileBodySource {
409+
return extractFileBodySource(c, logging.APIResponseSourceContextKey)
410+
}
411+
372412
func (w *ResponseWriterWrapper) extractAPIWebsocketTimeline(c *gin.Context) []byte {
373413
apiTimeline, isExist := c.Get("API_WEBSOCKET_TIMELINE")
374414
if !isExist {
@@ -460,15 +500,53 @@ func extractBodyOverride(c *gin.Context, key string) []byte {
460500
return nil
461501
}
462502

463-
func (w *ResponseWriterWrapper) logRequest(requestBody []byte, statusCode int, headers map[string][]string, body, websocketTimeline []byte, websocketTimelineSource *logging.FileBodySource, apiRequestBody, apiResponseBody, apiWebsocketTimeline []byte, apiWebsocketTimelineSource *logging.FileBodySource, apiResponseTimestamp time.Time, apiResponseErrors []*interfaces.ErrorMessage, forceLog bool) error {
503+
func (w *ResponseWriterWrapper) logRequest(requestBody []byte, statusCode int, headers map[string][]string, body, websocketTimeline []byte, websocketTimelineSource *logging.FileBodySource, apiRequestBody []byte, apiRequestSource *logging.FileBodySource, apiResponseBody []byte, apiResponseSource *logging.FileBodySource, apiWebsocketTimeline []byte, apiWebsocketTimelineSource *logging.FileBodySource, apiResponseTimestamp time.Time, apiResponseErrors []*interfaces.ErrorMessage, forceLog bool) error {
464504
if w.requestInfo == nil {
465-
cleanupFileBodySources(websocketTimelineSource, apiWebsocketTimelineSource)
505+
cleanupFileBodySources(websocketTimelineSource, apiRequestSource, apiResponseSource, apiWebsocketTimelineSource)
466506
return nil
467507
}
468508

509+
if loggerWithAllSources, ok := w.logger.(interface {
510+
LogRequestWithOptionsAndAllSources(string, string, map[string][]string, []byte, int, map[string][]string, []byte, []byte, *logging.FileBodySource, []byte, *logging.FileBodySource, []byte, *logging.FileBodySource, []byte, *logging.FileBodySource, []*interfaces.ErrorMessage, bool, string, time.Time, time.Time) error
511+
}); ok {
512+
return loggerWithAllSources.LogRequestWithOptionsAndAllSources(
513+
w.requestInfo.URL,
514+
w.requestInfo.Method,
515+
w.requestInfo.Headers,
516+
requestBody,
517+
statusCode,
518+
headers,
519+
body,
520+
websocketTimeline,
521+
websocketTimelineSource,
522+
apiRequestBody,
523+
apiRequestSource,
524+
apiResponseBody,
525+
apiResponseSource,
526+
apiWebsocketTimeline,
527+
apiWebsocketTimelineSource,
528+
apiResponseErrors,
529+
forceLog,
530+
w.requestInfo.RequestID,
531+
w.requestInfo.Timestamp,
532+
apiResponseTimestamp,
533+
)
534+
}
535+
469536
if loggerWithSources, ok := w.logger.(interface {
470537
LogRequestWithOptionsAndSources(string, string, map[string][]string, []byte, int, map[string][]string, []byte, []byte, *logging.FileBodySource, []byte, []byte, []byte, *logging.FileBodySource, []*interfaces.ErrorMessage, bool, string, time.Time, time.Time) error
471538
}); ok {
539+
var errMerge error
540+
apiRequestBody, errMerge = mergeFileBodySource(apiRequestBody, apiRequestSource)
541+
if errMerge != nil {
542+
cleanupFileBodySources(websocketTimelineSource, apiResponseSource, apiWebsocketTimelineSource)
543+
return errMerge
544+
}
545+
apiResponseBody, errMerge = mergeFileBodySource(apiResponseBody, apiResponseSource)
546+
if errMerge != nil {
547+
cleanupFileBodySources(websocketTimelineSource, apiWebsocketTimelineSource)
548+
return errMerge
549+
}
472550
return loggerWithSources.LogRequestWithOptionsAndSources(
473551
w.requestInfo.URL,
474552
w.requestInfo.Method,
@@ -493,6 +571,16 @@ func (w *ResponseWriterWrapper) logRequest(requestBody []byte, statusCode int, h
493571

494572
var errMerge error
495573
websocketTimeline, errMerge = mergeFileBodySource(websocketTimeline, websocketTimelineSource)
574+
if errMerge != nil {
575+
cleanupFileBodySources(apiRequestSource, apiResponseSource, apiWebsocketTimelineSource)
576+
return errMerge
577+
}
578+
apiRequestBody, errMerge = mergeFileBodySource(apiRequestBody, apiRequestSource)
579+
if errMerge != nil {
580+
cleanupFileBodySources(apiResponseSource, apiWebsocketTimelineSource)
581+
return errMerge
582+
}
583+
apiResponseBody, errMerge = mergeFileBodySource(apiResponseBody, apiResponseSource)
496584
if errMerge != nil {
497585
cleanupFileBodySources(apiWebsocketTimelineSource)
498586
return errMerge

internal/api/server.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ func defaultRequestLoggerFactory(cfg *config.Config, configPath string) logging.
7272
return logger
7373
}
7474

75+
func effectiveSDKConfig(cfg *config.Config) *config.SDKConfig {
76+
if cfg == nil {
77+
return nil
78+
}
79+
sdkCfg := cfg.SDKConfig
80+
if cfg.CommercialMode {
81+
sdkCfg.RequestLog = false
82+
}
83+
return &sdkCfg
84+
}
85+
7586
// WithMiddleware appends additional Gin middleware during server construction.
7687
func WithMiddleware(mw ...gin.HandlerFunc) ServerOption {
7788
return func(cfg *serverOptionConfig) {
@@ -257,7 +268,7 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk
257268
// Create server instance
258269
s := &Server{
259270
engine: engine,
260-
handlers: handlers.NewBaseAPIHandlers(&cfg.SDKConfig, authManager),
271+
handlers: handlers.NewBaseAPIHandlers(effectiveSDKConfig(cfg), authManager),
261272
cfg: cfg,
262273
accessManager: accessManager,
263274
requestLogger: requestLogger,
@@ -1453,7 +1464,7 @@ func (s *Server) UpdateClients(cfg *config.Config) {
14531464
// Save YAML snapshot for next comparison
14541465
s.oldConfigYaml, _ = yaml.Marshal(cfg)
14551466

1456-
s.handlers.UpdateClients(&cfg.SDKConfig)
1467+
s.handlers.UpdateClients(effectiveSDKConfig(cfg))
14571468

14581469
if s.mgmt != nil {
14591470
s.mgmt.SetConfig(cfg)

internal/config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ type Config struct {
5252
// Pprof config controls the optional pprof HTTP debug server.
5353
Pprof PprofConfig `yaml:"pprof" json:"pprof"`
5454

55-
// CommercialMode disables high-overhead HTTP middleware features to minimize per-request memory usage.
55+
// CommercialMode disables high-overhead request logging and HTTP middleware features to minimize per-request memory usage.
5656
CommercialMode bool `yaml:"commercial-mode" json:"commercial-mode"`
5757

5858
// LoggingToFile controls whether application logs are written to rotating files or stdout.

0 commit comments

Comments
 (0)