diff --git a/pkg/util/log/clog.go b/pkg/util/log/clog.go index 34a426c68c82..508a04749deb 100644 --- a/pkg/util/log/clog.go +++ b/pkg/util/log/clog.go @@ -401,7 +401,6 @@ func (l *loggerT) outputLogEntry(entry logEntry) { // The sink was not accepting entries at this level. Nothing to do. continue } - if err := s.sink.output(extraFlush, bufs.b[i].Bytes()); err != nil { if !s.criticality { // An error on this sink is not critical. Just report diff --git a/pkg/util/log/flags.go b/pkg/util/log/flags.go index 720e6efbdbbf..a26bcbbf813c 100644 --- a/pkg/util/log/flags.go +++ b/pkg/util/log/flags.go @@ -381,6 +381,7 @@ func newHTTPSinkInfo(c logconfig.HTTPSinkConfig) (*sinkInfo, error) { unsafeTLS: *c.UnsafeTLS, timeout: *c.Timeout, disableKeepAlives: *c.DisableKeepAlives, + contentType: info.formatter.contentType(), }) if err != nil { return nil, err diff --git a/pkg/util/log/format_crdb_v1.go b/pkg/util/log/format_crdb_v1.go index a3b627b6e00e..bdcc0fc68fa1 100644 --- a/pkg/util/log/format_crdb_v1.go +++ b/pkg/util/log/format_crdb_v1.go @@ -37,6 +37,8 @@ func (formatCrdbV1) formatEntry(entry logEntry) *buffer { func (formatCrdbV1) doc() string { return formatCrdbV1CommonDoc(false /* withCounter */) } +func (formatCrdbV1) contentType() string { return "text/plain" } + func formatCrdbV1CommonDoc(withCounter bool) string { var buf strings.Builder @@ -167,6 +169,8 @@ func (formatCrdbV1WithCounter) formatEntry(entry logEntry) *buffer { func (formatCrdbV1WithCounter) doc() string { return formatCrdbV1CommonDoc(true /* withCounter */) } +func (formatCrdbV1WithCounter) contentType() string { return "text/plain" } + // formatCrdbV1TTY is like formatCrdbV1 and includes VT color codes if // the stderr output is a TTY and -nocolor is not passed on the // command line. @@ -192,6 +196,8 @@ func (formatCrdbV1TTY) doc() string { return "Same textual format as `" + formatCrdbV1{}.formatterName() + "`." + ttyFormatDoc } +func (formatCrdbV1TTY) contentType() string { return "text/plain" } + // formatCrdbV1ColorWithCounter is like formatCrdbV1WithCounter and // includes VT color codes if the stderr output is a TTY and -nocolor // is not passed on the command line. @@ -211,6 +217,8 @@ func (formatCrdbV1TTYWithCounter) doc() string { return "Same textual format as `" + formatCrdbV1WithCounter{}.formatterName() + "`." + ttyFormatDoc } +func (formatCrdbV1TTYWithCounter) contentType() string { return "text/plain" } + // formatEntryInternalV1 renders a log entry. // Log lines are colorized depending on severity. // It uses a newly allocated *buffer. The caller is responsible diff --git a/pkg/util/log/format_crdb_v2.go b/pkg/util/log/format_crdb_v2.go index cae71a8d87ad..4680f0c473bb 100644 --- a/pkg/util/log/format_crdb_v2.go +++ b/pkg/util/log/format_crdb_v2.go @@ -40,6 +40,8 @@ func (formatCrdbV2) formatEntry(entry logEntry) *buffer { func (formatCrdbV2) doc() string { return formatCrdbV2CommonDoc() } +func (formatCrdbV2) contentType() string { return "text/plain" } + func formatCrdbV2CommonDoc() string { var buf strings.Builder @@ -188,6 +190,8 @@ func (formatCrdbV2TTY) doc() string { return "Same textual format as `" + formatCrdbV2{}.formatterName() + "`." + ttyFormatDoc } +func (formatCrdbV2TTY) contentType() string { return "text/plain" } + // formatEntryInternalV2 renders a log entry. // Log lines are colorized depending on severity. // It uses a newly allocated *buffer. The caller is responsible diff --git a/pkg/util/log/format_json.go b/pkg/util/log/format_json.go index c38f9a3887ef..ebe8a1bfd184 100644 --- a/pkg/util/log/format_json.go +++ b/pkg/util/log/format_json.go @@ -31,6 +31,8 @@ func (f formatFluentJSONCompact) formatEntry(entry logEntry) *buffer { return formatJSON(entry, true /* fluent */, tagCompact) } +func (formatFluentJSONCompact) contentType() string { return "application/json" } + type formatFluentJSONFull struct{} func (formatFluentJSONFull) formatterName() string { return "json-fluent" } @@ -41,6 +43,8 @@ func (f formatFluentJSONFull) formatEntry(entry logEntry) *buffer { func (formatFluentJSONFull) doc() string { return formatJSONDoc(true /* fluent */, tagVerbose) } +func (formatFluentJSONFull) contentType() string { return "application/json" } + type formatJSONCompact struct{} func (formatJSONCompact) formatterName() string { return "json-compact" } @@ -51,6 +55,8 @@ func (f formatJSONCompact) formatEntry(entry logEntry) *buffer { func (formatJSONCompact) doc() string { return formatJSONDoc(false /* fluent */, tagCompact) } +func (formatJSONCompact) contentType() string { return "application/json" } + type formatJSONFull struct{} func (formatJSONFull) formatterName() string { return "json" } @@ -61,6 +67,8 @@ func (f formatJSONFull) formatEntry(entry logEntry) *buffer { func (formatJSONFull) doc() string { return formatJSONDoc(false /* fluent */, tagVerbose) } +func (formatJSONFull) contentType() string { return "application/json" } + func formatJSONDoc(forFluent bool, tags tagChoice) string { var buf strings.Builder buf.WriteString(`This format emits log entries as a JSON payload. diff --git a/pkg/util/log/formats.go b/pkg/util/log/formats.go index 89ec7331c629..f3678b27c623 100644 --- a/pkg/util/log/formats.go +++ b/pkg/util/log/formats.go @@ -17,6 +17,10 @@ type logFormatter interface { // formatEntry formats a logEntry into a newly allocated *buffer. // The caller is responsible for calling putBuffer() afterwards. formatEntry(entry logEntry) *buffer + + // contentType is the MIME content-type field to use on + // transports which use this metadata. + contentType() string } var formatParsers = map[string]string{ diff --git a/pkg/util/log/http_sink.go b/pkg/util/log/http_sink.go index 57141e26c7ad..fd940c782cda 100644 --- a/pkg/util/log/http_sink.go +++ b/pkg/util/log/http_sink.go @@ -30,15 +30,7 @@ type httpSinkOptions struct { timeout time.Duration method string disableKeepAlives bool -} - -// formatToContentType map contains a mapping from the log format -// to the content type header that should be set for that format -// for the HTTP POST method. The content type header defaults -// to `text/plain` when the http sink is configured with a format -// not included in this map. -var formatToContentType = map[string]string{ - "json": "application/json", + contentType string } func newHTTPSink(url string, opt httpSinkOptions) (*httpSink, error) { @@ -53,8 +45,9 @@ func newHTTPSink(url string, opt httpSinkOptions) (*httpSink, error) { Transport: transport, Timeout: opt.timeout, }, - address: url, - doRequest: doPost, + address: url, + doRequest: doPost, + contentType: "application/octet-stream", } if opt.unsafeTLS { @@ -65,13 +58,18 @@ func newHTTPSink(url string, opt httpSinkOptions) (*httpSink, error) { hs.doRequest = doGet } + if opt.contentType != "" { + hs.contentType = opt.contentType + } + return hs, nil } type httpSink struct { - client http.Client - address string - doRequest func(sink *httpSink, logEntry []byte) (*http.Response, error) + client http.Client + address string + contentType string + doRequest func(sink *httpSink, logEntry []byte) (*http.Response, error) } // output emits some formatted bytes to this sink. @@ -85,7 +83,6 @@ type httpSink struct { func (hs *httpSink) output(extraSync bool, b []byte) (err error) { resp, err := hs.doRequest(hs, b) - if err != nil { return err } @@ -93,7 +90,8 @@ func (hs *httpSink) output(extraSync bool, b []byte) (err error) { if resp.StatusCode >= 400 { return HTTPLogError{ StatusCode: resp.StatusCode, - Address: hs.address} + Address: hs.address, + } } return nil } @@ -109,7 +107,7 @@ func (hs *httpSink) emergencyOutput(b []byte) { } func doPost(hs *httpSink, b []byte) (*http.Response, error) { - resp, err := hs.client.Post(hs.address, "text/plain", bytes.NewReader(b)) + resp, err := hs.client.Post(hs.address, hs.contentType, bytes.NewReader(b)) if err != nil { return nil, err } diff --git a/pkg/util/log/http_sink_test.go b/pkg/util/log/http_sink_test.go index b5f5f5f8d71a..2654eafc0fa8 100644 --- a/pkg/util/log/http_sink_test.go +++ b/pkg/util/log/http_sink_test.go @@ -214,7 +214,7 @@ func TestHTTPSinkContentTypeJSON(t *testing.T) { address := "http://localhost" // testBase appends the port timeout := 5 * time.Second tb := true - format := "json" + format := "json-fluent" expectedContentType := "application/json" defaults := logconfig.HTTPDefaults{ Address: &address, @@ -232,7 +232,7 @@ func TestHTTPSinkContentTypeJSON(t *testing.T) { t.Log(body) contentType := header.Get("Content-Type") if contentType != expectedContentType { - return errors.Newf("mismatched content type: expected %s, got %s") + return errors.Newf("mismatched content type: expected %s, got %s", expectedContentType, contentType) } return nil } @@ -266,7 +266,7 @@ func TestHTTPSinkContentTypePlainText(t *testing.T) { t.Log(body) contentType := header.Get("Content-Type") if contentType != expectedContentType { - return errors.Newf("mismatched content type: expected %s, got %s") + return errors.Newf("mismatched content type: expected %s, got %s", expectedContentType, contentType) } return nil } diff --git a/pkg/util/log/intercept.go b/pkg/util/log/intercept.go index 8c4846849b71..f30a71701b8b 100644 --- a/pkg/util/log/intercept.go +++ b/pkg/util/log/intercept.go @@ -73,6 +73,7 @@ type formatInterceptor struct{} func (formatInterceptor) formatterName() string { return "json-intercept" } func (formatInterceptor) doc() string { return "internal only" } +func (formatInterceptor) contentType() string { return "application/json" } func (formatInterceptor) formatEntry(entry logEntry) *buffer { pEntry := entry.convertToLegacy() buf := getBuffer()