diff --git a/pkg/cluster/tls.go b/pkg/cluster/tls.go index 66306e504ac..ada639699b6 100644 --- a/pkg/cluster/tls.go +++ b/pkg/cluster/tls.go @@ -4,12 +4,20 @@ package cluster // Licensed under the Apache License 2.0. import ( + "bytes" "context" + "fmt" + "io" + "io/ioutil" "net/http" + "net/url" + "strings" "sync" + "time" "github.com/Azure/go-autorest/logger" configv1 "github.com/openshift/api/config/v1" + "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -25,6 +33,101 @@ const ( OneCertPublicIssuerName = "OneCertV2-PublicCA" ) +type writer struct { + *manager + logLevel logger.LevelType +} + +func (w writer) Writeln(level logger.LevelType, message string) { + w.Writef(level, "%s\n", message) +} +func (w writer) Writef(level logger.LevelType, format string, a ...interface{}) { + if w.logLevel >= level { + w.log.Log(logrus.InfoLevel, entryHeader(level), fmt.Sprintf(format, a...)) + } +} +func (w writer) WriteRequest(req *http.Request, filter logger.Filter) { + if req == nil { + return + } + b := &bytes.Buffer{} + fmt.Fprintf(b, "%s REQUEST: %s %s\n", entryHeader(logger.LogInfo), req.Method, processURL(filter, req.URL)) + // dump headers + for k, v := range req.Header { + if ok, mv := processHeader(filter, k, v); ok { + fmt.Fprintf(b, "%s: %s\n", k, strings.Join(mv, ",")) + } + } + if req.Body != nil && !strings.Contains(req.Header.Get("Content-Type"), "application/octet-stream") { + // dump body + body, err := ioutil.ReadAll(req.Body) + if err == nil { + fmt.Fprintln(b, string(processBody(filter, body))) + if nc, ok := req.Body.(io.Seeker); ok { + // rewind to the beginning + nc.Seek(0, io.SeekStart) + } else { + // recreate the body + req.Body = ioutil.NopCloser(bytes.NewReader(body)) + } + } else { + fmt.Fprintf(b, "failed to read body: %v\n", err) + } + } + w.Writeln(logger.LogInfo, b.String()) +} +func (w writer) WriteResponse(resp *http.Response, filter logger.Filter) { + if resp == nil { + return + } + b := &bytes.Buffer{} + fmt.Fprintf(b, "%s RESPONSE: %d %s\n", entryHeader(logger.LogInfo), resp.StatusCode, processURL(filter, resp.Request.URL)) + // dump headers + for k, v := range resp.Header { + if ok, mv := processHeader(filter, k, v); ok { + fmt.Fprintf(b, "%s: %s\n", k, strings.Join(mv, ",")) + } + } + if resp.Body != nil && !strings.Contains(resp.Header.Get("Content-Type"), "application/octet-stream") { + // dump body + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err == nil { + fmt.Fprintln(b, string(processBody(filter, body))) + resp.Body = ioutil.NopCloser(bytes.NewReader(body)) + } else { + fmt.Fprintf(b, "failed to read body: %v\n", err) + } + } + w.Writeln(logger.LogInfo, b.String()) +} + +func processURL(f logger.Filter, u *url.URL) string { + if f.URL == nil { + return u.String() + } + return f.URL(u) +} + +func processHeader(f logger.Filter, k string, val []string) (bool, []string) { + if f.Header == nil { + return true, val + } + return f.Header(k, val) +} + +func processBody(f logger.Filter, b []byte) []byte { + if f.Body == nil { + return b + } + return f.Body(b) +} + +func entryHeader(level logger.LevelType) string { + // this format provides a fixed number of digits so the size of the timestamp is constant + return fmt.Sprintf("(%s) %s:", time.Now().Format("2006-01-02T15:04:05.0000000Z07:00"), level.String()) +} + func (m *manager) createCertificates(ctx context.Context) error { managedDomain, err := dns.ManagedDomain(m.env, m.doc.OpenShiftCluster.Properties.ClusterProfile.Domain) if err != nil { @@ -49,6 +152,8 @@ func (m *manager) createCertificates(ctx context.Context) error { }, } + logger.Instance = writer{manager: m, logLevel: logger.LogInfo} + for _, c := range certs { m.log.Printf("creating certificate %s", c.certificateName) err = m.env.ClusterKeyvault().CreateSignedCertificate(ctx, OneCertPublicIssuerName, c.certificateName, c.commonName, keyvault.EkuServerAuth) @@ -57,8 +162,6 @@ func (m *manager) createCertificates(ctx context.Context) error { } } - logger.Instance = logger.NewFileLogger() - for _, c := range certs { m.log.Printf("waiting for certificate %s", c.certificateName) wg := sync.WaitGroup{} diff --git a/vendor/github.com/Azure/go-autorest/logger/logger.go b/vendor/github.com/Azure/go-autorest/logger/logger.go index 6f05b248fc2..2f5d8cc1a19 100644 --- a/vendor/github.com/Azure/go-autorest/logger/logger.go +++ b/vendor/github.com/Azure/go-autorest/logger/logger.go @@ -223,7 +223,7 @@ func initDefaultLogger() { fmt.Fprintf(os.Stderr, "go-autorest: failed to create log file, using stderr: %s\n", err.Error()) } } - Instance = FileLogger{ + Instance = fileLogger{ logLevel: logLevel, mu: &sync.Mutex{}, logFile: dest, @@ -242,17 +242,17 @@ func (nilLogger) WriteRequest(*http.Request, Filter) {} func (nilLogger) WriteResponse(*http.Response, Filter) {} // A File is used instead of a Logger so the stream can be flushed after every write. -type FileLogger struct { +type fileLogger struct { logLevel LevelType mu *sync.Mutex // for synchronizing writes to logFile logFile *os.File } -func (fl FileLogger) Writeln(level LevelType, message string) { +func (fl fileLogger) Writeln(level LevelType, message string) { fl.Writef(level, "%s\n", message) } -func (fl FileLogger) Writef(level LevelType, format string, a ...interface{}) { +func (fl fileLogger) Writef(level LevelType, format string, a ...interface{}) { if fl.logLevel >= level { fl.mu.Lock() defer fl.mu.Unlock() @@ -261,7 +261,7 @@ func (fl FileLogger) Writef(level LevelType, format string, a ...interface{}) { } } -func (fl FileLogger) WriteRequest(req *http.Request, filter Filter) { +func (fl fileLogger) WriteRequest(req *http.Request, filter Filter) { if req == nil || fl.logLevel < LogInfo { return } @@ -295,7 +295,7 @@ func (fl FileLogger) WriteRequest(req *http.Request, filter Filter) { fl.logFile.Sync() } -func (fl FileLogger) WriteResponse(resp *http.Response, filter Filter) { +func (fl fileLogger) WriteResponse(resp *http.Response, filter Filter) { if resp == nil || fl.logLevel < LogInfo { return } @@ -325,19 +325,11 @@ func (fl FileLogger) WriteResponse(resp *http.Response, filter Filter) { } // returns true if the provided body should be included in the log -func (fl FileLogger) shouldLogBody(header http.Header, body io.ReadCloser) bool { +func (fl fileLogger) shouldLogBody(header http.Header, body io.ReadCloser) bool { ct := header.Get("Content-Type") return fl.logLevel >= LogDebug && body != nil && !strings.Contains(ct, "application/octet-stream") } -func NewFileLogger() FileLogger { - return FileLogger{ - logLevel: LogDebug, - mu: &sync.Mutex{}, - logFile: os.Stderr, - } -} - // creates standard header for log entries, it contains a timestamp and the log level func entryHeader(level LevelType) string { // this format provides a fixed number of digits so the size of the timestamp is constant