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  }