This repository has been archived by the owner on Mar 31, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathexec.go
137 lines (118 loc) · 3.24 KB
/
exec.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package well
import (
"bytes"
"context"
"os/exec"
"time"
"unicode/utf8"
"github.com/cybozu-go/log"
)
// UTF8StringFromBytes returns a valid UTF-8 string from
// maybe invalid slice of bytes.
func UTF8StringFromBytes(b []byte) string {
if utf8.Valid(b) {
return string(b)
}
// This effectively replaces invalid bytes to \uFFFD (replacement char).
return string(bytes.Runes(b))
}
// LogCmd is a wrapper for *exec.Cmd to record command execution results.
// If command fails, log level will be log.LvError.
// If command succeeds, log level will be log.LvInfo.
//
// In most cases, use CommandContext function to prepare LogCmd.
type LogCmd struct {
*exec.Cmd
// Severity is used to log successful requests.
//
// Zero suppresses logging. Valid values are one of
// log.LvDebug, log.LvInfo, and so on.
//
// Errors are always logged with log.LvError.
Severity int
// Fields is passed to Logger as log fields.
Fields map[string]interface{}
// Logger for execution results. If nil, the default logger is used.
Logger *log.Logger
}
func (c *LogCmd) log(st time.Time, err error, output []byte) {
logger := c.Logger
if logger == nil {
logger = log.DefaultLogger()
}
if err == nil && (c.Severity == 0 || !logger.Enabled(c.Severity)) {
// successful logs are suppressed if c.Severity is 0 or
// logger threshold is under c.Severity.
return
}
fields := c.Fields
fields[log.FnType] = "exec"
fields[log.FnResponseTime] = time.Since(st).Seconds()
fields["command"] = c.Cmd.Path
fields["args"] = c.Cmd.Args
if err == nil {
logger.Log(c.Severity, "well: exec", fields)
return
}
fields["error"] = err.Error()
if len(output) > 0 {
fields["stderr"] = UTF8StringFromBytes(output)
}
logger.Error("well: exec", fields)
}
// CombinedOutput overrides exec.Cmd.CombinedOutput to record the result.
func (c *LogCmd) CombinedOutput() ([]byte, error) {
st := time.Now()
data, err := c.Cmd.CombinedOutput()
c.log(st, err, nil)
return data, err
}
// Output overrides exec.Cmd.Output to record the result.
// If Cmd.Stderr is nil, Output logs outputs to stderr as well.
func (c *LogCmd) Output() ([]byte, error) {
st := time.Now()
data, err := c.Cmd.Output()
if err != nil {
ee, ok := err.(*exec.ExitError)
if ok {
c.log(st, err, ee.Stderr)
return data, err
}
}
c.log(st, err, nil)
return data, err
}
// Run overrides exec.Cmd.Run to record the result.
// If both Cmd.Stdout and Cmd.Stderr are nil, this calls Output
// instead to log stderr.
func (c *LogCmd) Run() error {
if c.Cmd.Stdout == nil && c.Cmd.Stderr == nil {
_, err := c.Output()
return err
}
st := time.Now()
err := c.Cmd.Run()
c.log(st, err, nil)
return err
}
// Wait overrides exec.Cmd.Wait to record the result.
func (c *LogCmd) Wait() error {
st := time.Now()
err := c.Cmd.Wait()
c.log(st, err, nil)
return err
}
// CommandContext is similar to exec.CommandContext,
// but returns *LogCmd with its Context set to ctx.
//
// LogCmd.Severity is set to log.LvInfo.
//
// LogCmd.Logger is left nil. If you want to use another logger,
// set it manually.
func CommandContext(ctx context.Context, name string, args ...string) *LogCmd {
return &LogCmd{
Cmd: exec.CommandContext(ctx, name, args...),
Severity: log.LvInfo,
Fields: FieldsFromContext(ctx),
}
}