From a5c776c84627cc6e7d0cfd633e98b4ad817c0970 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 7 Jan 2025 16:56:09 +0700 Subject: [PATCH] all: change send log to use x-www-form-urlencoded --- cmd/cli/control_server.go | 24 ++++++++++++------- cmd/cli/log_writer.go | 47 +++++++++++++++++++++++-------------- internal/controld/config.go | 20 ++++++++-------- 3 files changed, 54 insertions(+), 37 deletions(-) diff --git a/cmd/cli/control_server.go b/cmd/cli/control_server.go index 7a33407..302b902 100644 --- a/cmd/cli/control_server.go +++ b/cmd/cli/control_server.go @@ -2,8 +2,9 @@ package cli import ( "context" - "encoding/base64" "encoding/json" + "fmt" + "io" "net" "net/http" "os" @@ -215,17 +216,23 @@ func (p *prog) registerControlServerHandler() { w.WriteHeader(http.StatusBadRequest) })) p.cs.register(viewLogsPath, http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) { - data, err := p.logContent() + lr, err := p.logReader() if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - if len(data) == 0 { + if lr.size == 0 { w.WriteHeader(http.StatusMovedPermanently) return } + data, err := io.ReadAll(lr.r) + if err != nil { + http.Error(w, fmt.Sprintf("could not read log: %v", err), http.StatusInternalServerError) + return + } if err := json.NewEncoder(w).Encode(&logViewResponse{Data: string(data)}); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, fmt.Sprintf("could not marshal log data: %v", err), http.StatusInternalServerError) return } })) @@ -234,22 +241,21 @@ func (p *prog) registerControlServerHandler() { w.WriteHeader(http.StatusServiceUnavailable) return } - data, err := p.logContent() + r, err := p.logReader() if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - if len(data) == 0 { + if r.size == 0 { w.WriteHeader(http.StatusMovedPermanently) return } - logFile := base64.StdEncoding.EncodeToString(data) req := &controld.LogsRequest{ - UID: cdUID, - LogFile: logFile, + UID: cdUID, + Data: r.r, } mainLog.Load().Debug().Msg("sending log file to ControlD server") - resp := logSentResponse{Size: len(data)} + resp := logSentResponse{Size: r.size} if err := controld.SendLogs(req, cdDev); err != nil { mainLog.Load().Error().Msgf("could not send log file to ControlD server: %v", err) resp.Error = err.Error() diff --git a/cmd/cli/log_writer.go b/cmd/cli/log_writer.go index 7f12032..a6fb7eb 100644 --- a/cmd/cli/log_writer.go +++ b/cmd/cli/log_writer.go @@ -3,6 +3,7 @@ package cli import ( "bytes" "errors" + "fmt" "io" "os" "sync" @@ -25,10 +26,15 @@ type logViewResponse struct { } type logSentResponse struct { - Size int `json:"size"` + Size int64 `json:"size"` Error string `json:"error"` } +type logReader struct { + r io.ReadCloser + size int64 +} + // logWriter is an internal buffer to keep track of runtime log when no logging is enabled. type logWriter struct { mu sync.Mutex @@ -111,8 +117,7 @@ func (p *prog) needInternalLogging() bool { return true } -func (p *prog) logContent() ([]byte, error) { - var data []byte +func (p *prog) logReader() (*logReader, error) { if p.needInternalLogging() { p.mu.Lock() lw := p.internalLogWriter @@ -121,23 +126,29 @@ func (p *prog) logContent() ([]byte, error) { return nil, errors.New("nil internal log writer") } lw.mu.Lock() - data = lw.buf.Bytes() + lr := &logReader{r: io.NopCloser(bytes.NewReader(lw.buf.Bytes()))} + lr.size = int64(lw.buf.Len()) lw.mu.Unlock() - if len(data) == 0 { + if lr.size == 0 { return nil, errors.New("internal log is empty") } - } else { - if p.cfg.Service.LogPath == "" { - return nil, nil - } - buf, err := os.ReadFile(normalizeLogFilePath(p.cfg.Service.LogPath)) - if err != nil { - return nil, err - } - data = buf - if len(data) == 0 { - return nil, errors.New("log file is empty") - } + return lr, nil } - return data, nil + if p.cfg.Service.LogPath == "" { + return nil, nil + } + f, err := os.Open(normalizeLogFilePath(p.cfg.Service.LogPath)) + if err != nil { + return nil, err + } + lr := &logReader{r: f} + if st, err := f.Stat(); err == nil { + lr.size = st.Size() + } else { + return nil, fmt.Errorf("f.Stat: %w", err) + } + if lr.size == 0 { + return nil, errors.New("log file is empty") + } + return lr, nil } diff --git a/internal/controld/config.go b/internal/controld/config.go index 348dc54..b1814d1 100644 --- a/internal/controld/config.go +++ b/internal/controld/config.go @@ -77,8 +77,8 @@ type UtilityOrgRequest struct { // LogsRequest contains request data for sending runtime logs to API. type LogsRequest struct { - UID string `json:"uid"` - LogFile string `json:"log_file"` + UID string `json:"uid"` + Data io.ReadCloser `json:"-"` } // FetchResolverConfig fetch Control D config for given uid. @@ -160,20 +160,20 @@ func postUtilityAPI(version string, cdDev, lastUpdatedFailed bool, body io.Reade } // SendLogs sends runtime log to ControlD API. -func SendLogs(req *LogsRequest, cdDev bool) error { - body, _ := json.Marshal(req) - return postLogAPI(cdDev, bytes.NewReader(body)) -} - -func postLogAPI(cdDev bool, body io.Reader) error { +func SendLogs(lr *LogsRequest, cdDev bool) error { + defer lr.Data.Close() apiUrl := logURLCom if cdDev { apiUrl = logURLDev } - req, err := http.NewRequest("POST", apiUrl, body) + req, err := http.NewRequest("POST", apiUrl, lr.Data) if err != nil { return fmt.Errorf("http.NewRequest: %w", err) } + q := req.URL.Query() + q.Set("uid", lr.UID) + req.URL.RawQuery = q.Encode() + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") transport := apiTransport(cdDev) client := http.Client{ Timeout: 10 * time.Second, @@ -181,7 +181,7 @@ func postLogAPI(cdDev bool, body io.Reader) error { } resp, err := client.Do(req) if err != nil { - return fmt.Errorf("postLogAPI client.Do: %w", err) + return fmt.Errorf("SendLogs client.Do: %w", err) } defer resp.Body.Close() d := json.NewDecoder(resp.Body)