github.com/atlassian/git-lob@v0.0.0-20150806085256-2386a5ed291a/util/log.go (about) 1 package util 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "log" 9 "os" 10 "path/filepath" 11 "runtime" 12 "runtime/debug" 13 "strings" 14 15 "github.com/atlassian/git-lob/Godeps/_workspace/src/github.com/mitchellh/go-homedir" 16 ) 17 18 var ( 19 // Console output (can be overridden by changing) 20 consoleErr io.Writer = os.Stderr 21 consoleOut io.Writer = os.Stdout 22 // Loggers for file output 23 debugLog *log.Logger 24 errorLog *log.Logger 25 outputLog *log.Logger 26 logFile *os.File 27 ) 28 29 // Always send all console output to stderr, including info/debug messages 30 // This is mostly useful when stdout is reserved for piping content 31 func LogAllConsoleOutputToStdErr() { 32 consoleOut = os.Stderr 33 consoleErr = os.Stderr 34 } 35 36 // Suppress all console output 37 func LogSuppressAllConsoleOutput() { 38 consoleOut = ioutil.Discard 39 consoleErr = ioutil.Discard 40 errorLog = log.New(ioutil.Discard, "", 0) 41 debugLog = log.New(ioutil.Discard, "", 0) 42 outputLog = log.New(ioutil.Discard, "", 0) 43 } 44 45 func writeToLog(log *log.Logger, addNewline bool, includeStack bool, msgs ...interface{}) { 46 if log != nil { 47 // Prefix message with repo root (this is cached for efficiency) 48 // We don't add this to the Logger prefix in New() because this prefixes before the timestamp & other 49 // flag-based fields, which means things don't line up nicely in the log 50 root, _, _ := GetRepoRoot() // ignore failure, just use blank string 51 buf := bytes.NewBufferString(fmt.Sprintf("[%v]: ", root)) 52 fmt.Fprint(buf, msgs...) 53 if addNewline { 54 buf.WriteString("\n") 55 } 56 log.Print(buf.String()) 57 if includeStack { 58 log.Println(string(debug.Stack())) 59 } 60 } 61 62 } 63 64 // Log error to console and log with format (no implicit newline) 65 func LogErrorf(format string, v ...interface{}) { 66 msg := fmt.Sprintf(format, v...) 67 fmt.Fprint(consoleErr, msg) 68 writeToLog(errorLog, false, true, msg) 69 } 70 71 // Log debug message to console and log with format (if verbose) 72 func LogDebugf(format string, v ...interface{}) { 73 if GlobalOptions.Verbose { 74 fmt.Fprintf(consoleOut, format, v...) 75 } 76 77 if GlobalOptions.VerboseLog { 78 writeToLog(debugLog, false, false, fmt.Sprintf(format, v...)) 79 } 80 81 } 82 83 // Log output message to console and log with format (if not quiet) 84 // You probably don't want to use this since most info messages are for 85 // an interactive user only; see LogConsolef instead for that 86 func Logf(format string, v ...interface{}) { 87 if !GlobalOptions.Quiet { 88 msg := fmt.Sprintf(format, v...) 89 fmt.Fprint(consoleOut, msg) 90 writeToLog(outputLog, false, false, msg) 91 } 92 } 93 94 // Log error message to console and log with newline & spaces in between 95 func LogError(msgs ...interface{}) { 96 fmt.Fprintln(consoleErr, msgs...) 97 writeToLog(errorLog, true, true, msgs...) 98 } 99 100 // Log debug message to console and log with newline (if verbose) 101 func LogDebug(msgs ...interface{}) { 102 if GlobalOptions.Verbose { 103 fmt.Fprintln(consoleOut, msgs...) 104 } 105 106 if GlobalOptions.VerboseLog { 107 writeToLog(debugLog, true, false, msgs...) 108 } 109 } 110 111 // Log output message to console and log with newline (if not quiet) 112 // You probably don't want to use this since most info messages are for 113 // an interactive user only; see util.LogConsole instead for that 114 func Log(msgs ...interface{}) { 115 if !GlobalOptions.Quiet { 116 fmt.Fprintln(consoleOut, msgs...) 117 writeToLog(outputLog, true, false, msgs...) 118 } 119 } 120 121 // Write an informational message to the console with newline (if not quiet), and not the log 122 func LogConsole(msgs ...interface{}) { 123 if !GlobalOptions.Quiet { 124 fmt.Fprintln(consoleOut, msgs...) 125 } 126 } 127 128 // Overwrite the current line in the console (e.g. for progressive update), if not quiet 129 // Requires the previous line length so that it can clear it with spaces 130 // Does not add a newline after writing 131 func LogConsoleOverwrite(newString string, lastLineLength int) { 132 if len(newString) < lastLineLength { 133 LogConsolef("\r%v%v", newString, strings.Repeat(" ", lastLineLength-len(newString))) 134 } else { 135 LogConsolef("\r%v", newString) 136 } 137 138 } 139 140 // Write an informational message to the console (if not quiet), and not the log 141 func LogConsolef(format string, v ...interface{}) { 142 if !GlobalOptions.Quiet { 143 fmt.Fprintf(consoleOut, format, v...) 144 } 145 } 146 147 // Write an error message to the console with newline and not the log 148 func LogConsoleError(msgs ...interface{}) { 149 fmt.Fprintln(consoleErr, msgs...) 150 } 151 152 // Write an error message to the console and not the log 153 func LogConsoleErrorf(format string, v ...interface{}) { 154 fmt.Fprintf(consoleErr, format, v...) 155 } 156 157 // Write a debug message to the console with newline (if verbose), and not the log 158 func LogConsoleDebug(msgs ...interface{}) { 159 if GlobalOptions.Verbose { 160 fmt.Fprintln(consoleOut, msgs...) 161 } 162 } 163 164 // Write a debug message to the console (if verbose), and not the log 165 func LogConsoleDebugf(format string, v ...interface{}) { 166 if GlobalOptions.Verbose { 167 fmt.Fprintf(consoleOut, format, v...) 168 } 169 } 170 171 func getLogFileHandle() *os.File { 172 var logFileName string 173 if GlobalOptions.LogFile != "" { 174 logFileName = GlobalOptions.LogFile 175 } else { 176 home, err := homedir.Dir() 177 if err != nil { 178 log.Fatal(err) 179 } 180 logFileName = filepath.Join(home, "git-lob.log") 181 } 182 var err error 183 logFile, err = os.OpenFile(logFileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) 184 if err != nil { 185 log.Fatal(err) 186 } 187 return logFile 188 } 189 190 // Initialise logging, make sure GlobalOptions is initialised 191 func InitLogging() { 192 193 if GlobalOptions.LogEnabled { 194 const logFlags = log.Ldate | log.Ltime 195 f := getLogFileHandle() 196 outputLog = log.New(f, "", logFlags) 197 errorLog = log.New(f, "ERROR: ", logFlags) 198 debugLog = log.New(f, "", logFlags) 199 } 200 } 201 func ShutDownLogging() { 202 if logFile != nil { 203 logFile.Close() 204 } 205 206 } 207 208 var spinnerCycle = 0 209 var spinnerChars = []byte{'|', '/', '-', '\\'} 210 211 // Render a 'spinner' in the console, with optional prefix (which must stay constant between calls unless you clear the line) 212 func LogConsoleSpinner(prefix string) { 213 LogConsoleOverwrite(fmt.Sprintf("%v%c", prefix, spinnerChars[spinnerCycle]), len(prefix)+1) 214 spinnerCycle = (spinnerCycle + 1) % len(spinnerChars) 215 } 216 217 // Finish a spinner progress with a check mark and a newline 218 func LogConsoleSpinnerFinish(prefix string) { 219 if runtime.GOOS == "windows" { 220 // Windows console sucks, can't do nice check mark except in ConEmu (not cmd or git bash) 221 // So play it safe & boring 222 LogConsoleOverwrite(fmt.Sprintf("%v%v\n", prefix, "Done"), len(prefix)+1) 223 } else { 224 LogConsoleOverwrite(fmt.Sprintf("%v%c\n", prefix, '\u2714'), len(prefix)+1) 225 } 226 spinnerCycle = 0 227 }