github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/cmd/fossa/display/log.go (about)

     1  package display
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"runtime"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/apex/log"
    12  	"github.com/fatih/color"
    13  )
    14  
    15  // SetInteractive turns colors and ANSI control characters on or off.
    16  func SetInteractive(interactive bool) {
    17  	// Disable Unicode and ANSI control characters on Windows.
    18  	if runtime.GOOS == "windows" {
    19  		useANSI = false
    20  	} else {
    21  		useANSI = interactive
    22  	}
    23  	// Use color when interactive.
    24  	color.NoColor = !useANSI
    25  }
    26  
    27  // SetDebug turns debug logging to STDERR on or off.
    28  //
    29  // The log file always writes debug-level entries.
    30  func SetDebug(debug bool) {
    31  	// This sets the `level` variable rather than calling `log.SetLevel`, because
    32  	// calling `log.SetLevel` filters entries by level _before_ they reach the
    33  	// handler. This is not desirable, because we always want our handler to see
    34  	// debug entries so they can be written to the log file.
    35  	if debug {
    36  		level = log.DebugLevel
    37  	} else {
    38  		level = log.InfoLevel
    39  	}
    40  }
    41  
    42  // SetFile sets the log file. By default, this is set to a temporary file.
    43  func SetFile(filename string) error {
    44  	f, err := os.Open(filename)
    45  	if err != nil {
    46  		return err
    47  	}
    48  	file = f
    49  	return nil
    50  }
    51  
    52  // File returns the log file name.
    53  func File() string {
    54  	return file.Name()
    55  }
    56  
    57  // Handler handles log entries. It multiplexes them into two outputs, writing
    58  // human-readable messages to STDERR and machine-readable entries to a log file.
    59  //
    60  // TODO: does this need to be synchronised?
    61  func Handler(entry *log.Entry) error {
    62  	// If in debug mode, add caller.
    63  	if level == log.DebugLevel {
    64  		entry.Fields["callers"] = []string{}
    65  		// See https://golang.org/pkg/runtime/#Frames
    66  		pcs := make([]uintptr, 20)
    67  		n := runtime.Callers(0, pcs)
    68  		pcs = pcs[:n]
    69  		frames := runtime.CallersFrames(pcs)
    70  		for {
    71  			frame, more := frames.Next()
    72  			if !strings.Contains(frame.File, "runtime/") &&
    73  				!strings.Contains(frame.File, "cmd/fossa/display/") &&
    74  				!strings.Contains(frame.File, "apex/log/") {
    75  				entry.Fields["callers"] = append(entry.Fields["callers"].([]string), frame.File+":"+frame.Function+":"+strconv.Itoa(frame.Line))
    76  			}
    77  			if !more {
    78  				break
    79  			}
    80  		}
    81  	}
    82  
    83  	// Write entry to STDERR.
    84  	if entry.Level >= level {
    85  		msg := ""
    86  		switch entry.Level {
    87  		case log.DebugLevel:
    88  			msg += color.WhiteString("DEBUG")
    89  		case log.InfoLevel:
    90  			msg += color.WhiteString("INFO")
    91  		case log.WarnLevel:
    92  			msg += color.YellowString("WARNING")
    93  		case log.ErrorLevel:
    94  			msg += color.RedString("ERROR")
    95  		case log.FatalLevel:
    96  			msg += color.RedString("FATAL")
    97  		}
    98  
    99  		msg += " " + entry.Message
   100  		for _, field := range entry.Fields.Names() {
   101  			msg += fmt.Sprintf(" %s=%+v", field, entry.Fields.Get(field))
   102  		}
   103  
   104  		_, err := fmt.Fprintln(os.Stderr, msg)
   105  		if err != nil {
   106  			return err
   107  		}
   108  	}
   109  
   110  	// Write entry to log file.
   111  	data, err := json.Marshal(entry)
   112  	if err != nil {
   113  		// There are some entries that we can't serialize to JSON (for example, a
   114  		// map that uses a struct as a key). In these cases, serialize to Go format
   115  		// and print a string.
   116  		switch err.(type) {
   117  		case *json.UnsupportedTypeError:
   118  			data = []byte(fmt.Sprintf("%#v", entry))
   119  		case *json.UnsupportedValueError:
   120  			data = []byte(fmt.Sprintf("%#v", entry))
   121  		default:
   122  			return err
   123  		}
   124  	}
   125  	data = append(data, byte('\n'))
   126  	_, err = file.Write(data)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	err = file.Sync()
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	return nil
   136  }