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