github.com/paketo-buildpacks/libpak@v1.70.0/bard/logger.go (about) 1 /* 2 * Copyright 2018-2020 the original author or 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 * https://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 bard 18 19 import ( 20 "fmt" 21 "io" 22 "os" 23 "strings" 24 25 "github.com/buildpacks/libcnb" 26 "github.com/buildpacks/libcnb/poet" 27 "github.com/heroku/color" 28 ) 29 30 // TODO: Remove once TTY support is in place 31 func init() { 32 color.Enabled() 33 } 34 35 // Logger logs message to a writer. 36 type Logger struct { 37 poet.Logger 38 39 body io.Writer 40 header io.Writer 41 terminalBody io.Writer 42 terminalHeader io.Writer 43 title io.Writer 44 } 45 46 // Option is a function for configuring a Logger instance. 47 type Option func(logger Logger) Logger 48 49 // WithDebug configures the debug Writer. 50 func WithDebug(writer io.Writer) Option { 51 return func(logger Logger) Logger { 52 logger.Logger = poet.WithDebug(writer)(logger.Logger) 53 return logger 54 } 55 } 56 57 // NewLoggerWithOptions create a new instance of Logger. It configures the Logger with options. 58 func NewLoggerWithOptions(writer io.Writer, options ...Option) Logger { 59 l := Logger{ 60 Logger: poet.NewLogger(writer), 61 body: NewWriter(writer, WithAttributes(color.Faint), WithIndent(2)), 62 header: NewWriter(writer, WithIndent(1)), 63 terminalBody: NewWriter(writer, WithAttributes(color.FgRed, color.Bold), WithIndent(1)), 64 terminalHeader: NewWriter(writer, WithAttributes(color.FgRed)), 65 title: NewWriter(writer, WithAttributes(color.FgBlue)), 66 } 67 68 for _, option := range options { 69 l = option(l) 70 } 71 72 return l 73 } 74 75 // NewLogger creates a new instance of Logger. It configures debug logging if $BP_DEBUG is set. 76 func NewLogger(writer io.Writer) Logger { 77 var options []Option 78 79 // check for presence and value of log level environment variable 80 options = LogLevel(options, writer) 81 82 return NewLoggerWithOptions(writer, options...) 83 } 84 85 func LogLevel(options []Option, writer io.Writer) []Option { 86 87 // Check for older log level env variable 88 _, dbSet := os.LookupEnv("BP_DEBUG") 89 90 // Then check for common buildpack log level env variable - if either are set to DEBUG/true, enable Debug Writer 91 if level, ok := os.LookupEnv("BP_LOG_LEVEL"); (ok && strings.ToLower(level) == "debug") || dbSet { 92 93 options = append(options, WithDebug(writer)) 94 } 95 return options 96 } 97 98 // Body formats using the default formats for its operands and logs a message to the configured body writer. Spaces 99 // are added between operands when neither is a string. 100 func (l Logger) Body(a ...interface{}) { 101 if !l.IsBodyEnabled() { 102 return 103 } 104 105 l.print(l.body, a...) 106 } 107 108 // Bodyf formats according to a format specifier and logs a message to the configured body writer. 109 func (l Logger) Bodyf(format string, a ...interface{}) { 110 if !l.IsBodyEnabled() { 111 return 112 } 113 114 l.printf(l.body, format, a...) 115 } 116 117 // BodyWriter returns the configured body writer. 118 func (l Logger) BodyWriter() io.Writer { 119 return l.body 120 } 121 122 // IsBodyEnabled indicates whether body logging is enabled. 123 func (l Logger) IsBodyEnabled() bool { 124 return l.body != nil 125 } 126 127 // Header formats using the default formats for its operands and logs a message to the configured header writer. Spaces 128 // are added between operands when neither is a string. 129 func (l Logger) Header(a ...interface{}) { 130 if !l.IsHeaderEnabled() { 131 return 132 } 133 134 l.print(l.header, a...) 135 } 136 137 // Headerf formats according to a format specifier and logs a message to the configured header writer. 138 func (l Logger) Headerf(format string, a ...interface{}) { 139 if !l.IsHeaderEnabled() { 140 return 141 } 142 143 l.printf(l.header, format, a...) 144 } 145 146 // HeaderWriter returns the configured header writer. 147 func (l Logger) HeaderWriter() io.Writer { 148 return l.header 149 } 150 151 // IsHeaderEnabled indicates whether header logging is enabled. 152 func (l Logger) IsHeaderEnabled() bool { 153 return l.header != nil 154 } 155 156 // IdentifiableError is an error associated with an Identifiable for logging purposes. 157 type IdentifiableError struct { 158 159 // Name is the name of the identified object. 160 Name string 161 162 // Description is the description of the identified object. 163 Description string 164 165 // Err is the nested error. 166 Err error 167 } 168 169 func (i IdentifiableError) Error() string { 170 return i.Err.Error() 171 } 172 173 // TerminalError logs a message to the configured terminal error writer. 174 func (l Logger) TerminalError(err IdentifiableError) { 175 if !l.IsTerminalErrorEnabled() { 176 return 177 } 178 179 l.printf(l.terminalHeader, "\n%s", FormatIdentity(err.Name, err.Description)) 180 l.print(l.terminalBody, err.Err) 181 } 182 183 // TerminalErrorWriter returns the configured terminal error writer. 184 func (l Logger) TerminalErrorWriter() io.Writer { 185 return l.terminalBody 186 } 187 188 // IsTerminalErrorEnabled indicates whether terminal error logging is enabled. 189 func (l Logger) IsTerminalErrorEnabled() bool { 190 return l.terminalHeader != nil && l.terminalBody != nil 191 } 192 193 // Title logs a message to the configured title writer. 194 func (l Logger) Title(buildpack libcnb.Buildpack) { 195 if !l.IsTitleEnabled() { 196 return 197 } 198 199 l.printf(l.title, "\n%s", FormatIdentity(buildpack.Info.Name, buildpack.Info.Version)) 200 l.Header(color.New(color.FgBlue, color.Faint, color.Italic).Sprint(buildpack.Info.Homepage)) 201 } 202 203 // TitleWriter returns the configured title writer. 204 func (l Logger) TitleWriter() io.Writer { 205 return l.title 206 } 207 208 // IsTitleEnabled indicates whether title logging is enabled. 209 func (l Logger) IsTitleEnabled() bool { 210 return l.title != nil 211 } 212 213 func (Logger) print(writer io.Writer, a ...interface{}) { 214 s := fmt.Sprint(a...) 215 216 if !strings.HasSuffix(s, "\n") { 217 s = s + "\n" 218 } 219 220 _, _ = fmt.Fprint(writer, s) 221 } 222 223 func (Logger) printf(writer io.Writer, format string, a ...interface{}) { 224 if !strings.HasSuffix(format, "\n") { 225 format = format + "\n" 226 } 227 228 _, _ = fmt.Fprintf(writer, format, a...) 229 }