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 }