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  }