|
| 1 | +/* |
| 2 | +Copyright 2019 The Kubernetes Authors. |
| 3 | +
|
| 4 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +you may not use this file except in compliance with the License. |
| 6 | +You may obtain a copy of the License at |
| 7 | +
|
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +
|
| 10 | +Unless required by applicable law or agreed to in writing, software |
| 11 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +See the License for the specific language governing permissions and |
| 14 | +limitations under the License. |
| 15 | +*/ |
| 16 | + |
| 17 | +package cli |
| 18 | + |
| 19 | +import ( |
| 20 | + "bytes" |
| 21 | + "fmt" |
| 22 | + "io" |
| 23 | + "runtime" |
| 24 | + "strings" |
| 25 | + "sync" |
| 26 | + "sync/atomic" |
| 27 | + |
| 28 | + "github.com/skupperproject/skupper/internal/cmd/skupper/diagnose/env" |
| 29 | + "github.com/skupperproject/skupper/internal/cmd/skupper/diagnose/log" |
| 30 | +) |
| 31 | + |
| 32 | +// Logger is the kind cli's log.Logger implementation. |
| 33 | +type Logger struct { |
| 34 | + writer io.Writer |
| 35 | + bufferPool *bufferPool |
| 36 | + writerMu sync.Mutex |
| 37 | + verbosity log.Level |
| 38 | + // kind special additions |
| 39 | + isSmartWriter bool |
| 40 | +} |
| 41 | + |
| 42 | +var _ log.Logger = &Logger{} |
| 43 | + |
| 44 | +// NewLogger returns a new Logger with the given verbosity. |
| 45 | +func NewLogger(writer io.Writer, verbosity log.Level) *Logger { |
| 46 | + l := &Logger{ |
| 47 | + verbosity: verbosity, |
| 48 | + bufferPool: newBufferPool(), |
| 49 | + } |
| 50 | + l.SetWriter(writer) |
| 51 | + |
| 52 | + return l |
| 53 | +} |
| 54 | + |
| 55 | +// SetWriter sets the output writer. |
| 56 | +func (l *Logger) SetWriter(w io.Writer) { |
| 57 | + l.writerMu.Lock() |
| 58 | + defer l.writerMu.Unlock() |
| 59 | + l.writer = w |
| 60 | + _, isSpinner := w.(*Spinner) |
| 61 | + l.isSmartWriter = isSpinner || env.IsSmartTerminal(w) |
| 62 | +} |
| 63 | + |
| 64 | +// ColorEnabled returns true if the caller is OK to write colored output. |
| 65 | +func (l *Logger) ColorEnabled() bool { |
| 66 | + l.writerMu.Lock() |
| 67 | + defer l.writerMu.Unlock() |
| 68 | + |
| 69 | + return l.isSmartWriter |
| 70 | +} |
| 71 | + |
| 72 | +func (l *Logger) getVerbosity() log.Level { |
| 73 | + return log.Level(atomic.LoadInt32((*int32)(&l.verbosity))) |
| 74 | +} |
| 75 | + |
| 76 | +// SetVerbosity sets the loggers verbosity. |
| 77 | +func (l *Logger) SetVerbosity(verbosity log.Level) { |
| 78 | + atomic.StoreInt32((*int32)(&l.verbosity), int32(verbosity)) |
| 79 | +} |
| 80 | + |
| 81 | +// synchronized write to the inner writer. |
| 82 | +func (l *Logger) write(p []byte) (n int, err error) { |
| 83 | + l.writerMu.Lock() |
| 84 | + defer l.writerMu.Unlock() |
| 85 | + |
| 86 | + return l.writer.Write(p) //nolint:wrapcheck // No need to wrap here |
| 87 | +} |
| 88 | + |
| 89 | +// writeBuffer writes buf with write, ensuring there is a trailing newline. |
| 90 | +func (l *Logger) writeBuffer(buf *bytes.Buffer) { |
| 91 | + // ensure trailing newline |
| 92 | + if buf.Len() == 0 || buf.Bytes()[buf.Len()-1] != '\n' { |
| 93 | + buf.WriteByte('\n') |
| 94 | + } |
| 95 | + |
| 96 | + // TODO: should we handle this somehow?? |
| 97 | + // Who logs for the logger? 🤔 |
| 98 | + _, _ = l.write(buf.Bytes()) |
| 99 | +} |
| 100 | + |
| 101 | +// print writes a simple string to the log writer. |
| 102 | +func (l *Logger) print(message string) { |
| 103 | + buf := bytes.NewBufferString(message) |
| 104 | + l.writeBuffer(buf) |
| 105 | +} |
| 106 | + |
| 107 | +// printf is roughly fmt.Fprintf against the log writer. |
| 108 | +func (l *Logger) printf(format string, args ...interface{}) { |
| 109 | + buf := l.bufferPool.Get() |
| 110 | + fmt.Fprintf(buf, format, args...) |
| 111 | + l.writeBuffer(buf) |
| 112 | + l.bufferPool.Put(buf) |
| 113 | +} |
| 114 | + |
| 115 | +// addDebugHeader inserts the debug line header to buf. |
| 116 | +func addDebugHeader(buf *bytes.Buffer) { |
| 117 | + _, file, line, ok := runtime.Caller(3) |
| 118 | + // lifted from klog |
| 119 | + if !ok { |
| 120 | + file = "???" |
| 121 | + line = 1 |
| 122 | + } else if slash := strings.LastIndex(file, "/"); slash >= 0 { |
| 123 | + path := file |
| 124 | + file = path[slash+1:] |
| 125 | + |
| 126 | + if dirsep := strings.LastIndex(path[:slash], "/"); dirsep >= 0 { |
| 127 | + file = path[dirsep+1:] |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + buf.Grow(len(file) + 11) // we know at least this many bytes are needed. |
| 132 | + buf.WriteString("DEBUG: ") |
| 133 | + buf.WriteString(file) |
| 134 | + buf.WriteByte(':') |
| 135 | + fmt.Fprintf(buf, "%d", line) |
| 136 | + buf.WriteByte(']') |
| 137 | + buf.WriteByte(' ') |
| 138 | +} |
| 139 | + |
| 140 | +// debug is like print but with a debug log header. |
| 141 | +func (l *Logger) debug(message string) { |
| 142 | + buf := l.bufferPool.Get() |
| 143 | + addDebugHeader(buf) |
| 144 | + buf.WriteString(message) |
| 145 | + l.writeBuffer(buf) |
| 146 | + l.bufferPool.Put(buf) |
| 147 | +} |
| 148 | + |
| 149 | +// debugf is like printf but with a debug log header. |
| 150 | +func (l *Logger) debugf(format string, args ...interface{}) { |
| 151 | + buf := l.bufferPool.Get() |
| 152 | + addDebugHeader(buf) |
| 153 | + fmt.Fprintf(buf, format, args...) |
| 154 | + l.writeBuffer(buf) |
| 155 | + l.bufferPool.Put(buf) |
| 156 | +} |
| 157 | + |
| 158 | +// Warn is part of the log.Logger interface. |
| 159 | +func (l *Logger) Warn(message string) { |
| 160 | + l.print(message) |
| 161 | +} |
| 162 | + |
| 163 | +// Warnf is part of the log.Logger interface. |
| 164 | +func (l *Logger) Warnf(format string, args ...interface{}) { |
| 165 | + l.printf(format, args...) |
| 166 | +} |
| 167 | + |
| 168 | +// Error is part of the log.Logger interface. |
| 169 | +func (l *Logger) Error(message string) { |
| 170 | + l.print(message) |
| 171 | +} |
| 172 | + |
| 173 | +// Errorf is part of the log.Logger interface. |
| 174 | +func (l *Logger) Errorf(format string, args ...interface{}) { |
| 175 | + l.printf(format, args...) |
| 176 | +} |
| 177 | + |
| 178 | +// V is part of the log.Logger interface. |
| 179 | +func (l *Logger) V(level log.Level) log.InfoLogger { |
| 180 | + return infoLogger{ |
| 181 | + logger: l, |
| 182 | + level: level, |
| 183 | + enabled: level <= l.getVerbosity(), |
| 184 | + } |
| 185 | +} |
| 186 | + |
| 187 | +// infoLogger implements log.InfoLogger for Logger. |
| 188 | +type infoLogger struct { |
| 189 | + logger *Logger |
| 190 | + level log.Level |
| 191 | + enabled bool |
| 192 | +} |
| 193 | + |
| 194 | +// Enabled is part of the log.InfoLogger interface. |
| 195 | +func (i infoLogger) Enabled() bool { |
| 196 | + return i.enabled |
| 197 | +} |
| 198 | + |
| 199 | +// Info is part of the log.InfoLogger interface. |
| 200 | +func (i infoLogger) Info(message string) { |
| 201 | + if !i.enabled { |
| 202 | + return |
| 203 | + } |
| 204 | + // for > 0, we are writing debug messages, include extra info |
| 205 | + if i.level > 0 { |
| 206 | + i.logger.debug(message) |
| 207 | + } else { |
| 208 | + i.logger.print(message) |
| 209 | + } |
| 210 | +} |
| 211 | + |
| 212 | +// Infof is part of the log.InfoLogger interface. |
| 213 | +func (i infoLogger) Infof(format string, args ...interface{}) { |
| 214 | + if !i.enabled { |
| 215 | + return |
| 216 | + } |
| 217 | + // for > 0, we are writing debug messages, include extra info |
| 218 | + if i.level > 0 { |
| 219 | + i.logger.debugf(format, args...) |
| 220 | + } else { |
| 221 | + i.logger.printf(format, args...) |
| 222 | + } |
| 223 | +} |
| 224 | + |
| 225 | +// bufferPool is a type safe sync.Pool of *byte.Buffer, guaranteed to be Reset. |
| 226 | +type bufferPool struct { |
| 227 | + sync.Pool |
| 228 | +} |
| 229 | + |
| 230 | +// newBufferPool returns a new bufferPool. |
| 231 | +func newBufferPool() *bufferPool { |
| 232 | + return &bufferPool{ |
| 233 | + sync.Pool{ |
| 234 | + New: func() interface{} { |
| 235 | + // The Pool's New function should generally only return pointer |
| 236 | + // types, since a pointer can be put into the return interface |
| 237 | + // value without an allocation: |
| 238 | + return new(bytes.Buffer) |
| 239 | + }, |
| 240 | + }, |
| 241 | + } |
| 242 | +} |
| 243 | + |
| 244 | +// Get obtains a buffer from the pool. |
| 245 | +func (b *bufferPool) Get() *bytes.Buffer { |
| 246 | + return b.Pool.Get().(*bytes.Buffer) |
| 247 | +} |
| 248 | + |
| 249 | +// Put returns a buffer to the pool, resetting it first. |
| 250 | +func (b *bufferPool) Put(x *bytes.Buffer) { |
| 251 | + // only store small buffers to avoid pointless allocation |
| 252 | + // avoid keeping arbitrarily large buffers |
| 253 | + if x.Len() > 256 { |
| 254 | + return |
| 255 | + } |
| 256 | + |
| 257 | + x.Reset() |
| 258 | + b.Pool.Put(x) |
| 259 | +} |
0 commit comments