1// Package chezmoilog contains support for chezmoi logging. 2package chezmoilog 3 4import ( 5 "errors" 6 "net/http" 7 "os" 8 "os/exec" 9 "time" 10 11 "github.com/rs/zerolog" 12 "github.com/rs/zerolog/log" 13) 14 15// An OSExecCmdLogObject wraps an *os/exec.Cmd and adds 16// github.com/rs/zerolog.LogObjectMarshaler functionality. 17type OSExecCmdLogObject struct { 18 *exec.Cmd 19} 20 21// An OSExecExitErrorLogObject wraps an error and adds 22// github.com/rs/zerolog.LogObjectMarshaler functionality if the wrapped error 23// is an os/exec.ExitError. 24type OSExecExitErrorLogObject struct { 25 Err error 26} 27 28// An OSProcessStateLogObject wraps an *os.ProcessState and adds 29// github.com/rs/zerolog.LogObjectMarshaler functionality. 30type OSProcessStateLogObject struct { 31 *os.ProcessState 32} 33 34// MarshalZerologObject implements 35// github.com/rs/zerolog.LogObjectMarshaler.MarshalZerologObject. 36func (cmd OSExecCmdLogObject) MarshalZerologObject(event *zerolog.Event) { 37 if cmd.Cmd == nil { 38 return 39 } 40 if cmd.Path != "" { 41 event.Str("path", cmd.Path) 42 } 43 if cmd.Args != nil { 44 event.Strs("args", cmd.Args) 45 } 46 if cmd.Dir != "" { 47 event.Str("dir", cmd.Dir) 48 } 49 if cmd.Env != nil { 50 event.Strs("env", cmd.Env) 51 } 52} 53 54// MarshalZerologObject implements 55// github.com/rs/zerolog.LogObjectMarshaler.MarshalZerologObject. 56func (err OSExecExitErrorLogObject) MarshalZerologObject(event *zerolog.Event) { 57 if err.Err == nil { 58 return 59 } 60 var osExecExitError *exec.ExitError 61 if !errors.As(err.Err, &osExecExitError) { 62 return 63 } 64 event.EmbedObject(OSProcessStateLogObject{osExecExitError.ProcessState}) 65 if osExecExitError.Stderr != nil { 66 event.Bytes("stderr", osExecExitError.Stderr) 67 } 68} 69 70// MarshalZerologObject implements 71// github.com/rs/zerolog.LogObjectMarshaler.MarshalZerologObject. 72func (p OSProcessStateLogObject) MarshalZerologObject(event *zerolog.Event) { 73 if p.ProcessState == nil { 74 return 75 } 76 if p.Exited() { 77 if !p.Success() { 78 event.Int("exitCode", p.ExitCode()) 79 } 80 } else { 81 event.Int("pid", p.Pid()) 82 } 83 if userTime := p.UserTime(); userTime != 0 { 84 event.Dur("userTime", userTime) 85 } 86 if systemTime := p.SystemTime(); systemTime != 0 { 87 event.Dur("systemTime", systemTime) 88 } 89} 90 91// FirstFewBytes returns the first few bytes of data in a human-readable form. 92func FirstFewBytes(data []byte) []byte { 93 const few = 64 94 if len(data) > few { 95 data = append([]byte{}, data[:few]...) 96 data = append(data, '.', '.', '.') 97 } 98 return data 99} 100 101// LogHTTPRequest calls httpClient.Do, logs the result to logger, and returns 102// the result. 103func LogHTTPRequest(logger *zerolog.Logger, client *http.Client, req *http.Request) (*http.Response, error) { 104 start := time.Now() 105 resp, err := client.Do(req) 106 if resp != nil { 107 logger.Err(err). 108 Stringer("duration", time.Since(start)). 109 Str("method", req.Method). 110 Int64("size", resp.ContentLength). 111 Int("statusCode", resp.StatusCode). 112 Str("status", resp.Status). 113 Stringer("url", req.URL). 114 Msg("HTTPRequest") 115 } else { 116 logger.Err(err). 117 Stringer("duration", time.Since(start)). 118 Str("method", req.Method). 119 Int64("size", resp.ContentLength). 120 Stringer("url", req.URL). 121 Msg("HTTPRequest") 122 } 123 return resp, err 124} 125 126// LogCmdCombinedOutput calls cmd.CombinedOutput, logs the result, and returns the result. 127func LogCmdCombinedOutput(cmd *exec.Cmd) ([]byte, error) { 128 start := time.Now() 129 combinedOutput, err := cmd.CombinedOutput() 130 log.Err(err). 131 EmbedObject(OSExecCmdLogObject{Cmd: cmd}). 132 EmbedObject(OSExecExitErrorLogObject{Err: err}). 133 Bytes("combinedOutput", Output(combinedOutput, err)). 134 Stringer("duration", time.Since(start)). 135 Int("size", len(combinedOutput)). 136 Msg("CombinedOutput") 137 return combinedOutput, err 138} 139 140// LogCmdOutput calls cmd.Output, logs the result, and returns the result. 141func LogCmdOutput(cmd *exec.Cmd) ([]byte, error) { 142 start := time.Now() 143 output, err := cmd.Output() 144 log.Err(err). 145 EmbedObject(OSExecCmdLogObject{Cmd: cmd}). 146 EmbedObject(OSExecExitErrorLogObject{Err: err}). 147 Stringer("duration", time.Since(start)). 148 Bytes("output", Output(output, err)). 149 Int("size", len(output)). 150 Msg("Output") 151 return output, err 152} 153 154// LogCmdRun calls cmd.Run, logs the result, and returns the result. 155func LogCmdRun(cmd *exec.Cmd) error { 156 start := time.Now() 157 err := cmd.Run() 158 log.Err(err). 159 EmbedObject(OSExecCmdLogObject{Cmd: cmd}). 160 EmbedObject(OSExecExitErrorLogObject{Err: err}). 161 Stringer("duration", time.Since(start)). 162 Msg("Run") 163 return err 164} 165 166// Output returns the first few bytes of output if err is nil, otherwise it 167// returns the full output. 168func Output(data []byte, err error) []byte { 169 if err != nil { 170 return data 171 } 172 return FirstFewBytes(data) 173} 174