-
Notifications
You must be signed in to change notification settings - Fork 10
/
logging.go
226 lines (190 loc) · 5.76 KB
/
logging.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package logging
import (
"fmt"
"io"
"log"
"os"
"strings"
"syscall"
// go.etcd.io/etcd imports capnslog, which calls log.SetOutput in its
// init() function, so importing it here means that our log.SetOutput
// wins. this is fixed in coreos v3.5, which is not released yet. See
// https://github.com/etcd-io/etcd/issues/12498 for more information.
_ "github.com/coreos/pkg/capnslog"
"github.com/hashicorp/go-hclog"
)
// These are the environmental variables that determine if we log, and if
// we log whether or not the log should go to a file.
const (
envLog = "TF_LOG"
envLogFile = "TF_LOG_PATH"
// Allow logging of specific subsystems.
// We only separate core and providers for now, but this could be extended
// to other loggers, like provisioners and remote-state backends.
envLogCore = "TF_LOG_CORE"
envLogProvider = "TF_LOG_PROVIDER"
)
var (
// ValidLevels are the log level names that Terraform recognizes.
ValidLevels = []string{"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"}
// logger is the global hclog logger
logger hclog.Logger
// logWriter is a global writer for logs, to be used with the std log package
logWriter io.Writer
// initialize our cache of panic output from providers
panics = &panicRecorder{
panics: make(map[string][]string),
maxLines: 100,
}
)
func init() {
logger = newHCLogger("")
logWriter = logger.StandardWriter(&hclog.StandardLoggerOptions{InferLevels: true})
// set up the default std library logger to use our output
log.SetFlags(0)
log.SetPrefix("")
log.SetOutput(logWriter)
}
// SetupTempLog adds a new log sink which writes all logs to the given file.
func RegisterSink(f *os.File) {
l, ok := logger.(hclog.InterceptLogger)
if !ok {
panic("global logger is not an InterceptLogger")
}
if f == nil {
return
}
l.RegisterSink(hclog.NewSinkAdapter(&hclog.LoggerOptions{
Level: hclog.Trace,
Output: f,
}))
}
// LogOutput return the default global log io.Writer
func LogOutput() io.Writer {
return logWriter
}
// HCLogger returns the default global hclog logger
func HCLogger() hclog.Logger {
return logger
}
// newHCLogger returns a new hclog.Logger instance with the given name
func newHCLogger(name string) hclog.Logger {
logOutput := io.Writer(os.Stderr)
logLevel, json := globalLogLevel()
if logPath := os.Getenv(envLogFile); logPath != "" {
f, err := os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666)
if err != nil {
fmt.Fprintf(os.Stderr, "Error opening log file: %v\n", err)
} else {
logOutput = f
}
}
return hclog.NewInterceptLogger(&hclog.LoggerOptions{
Name: name,
Level: logLevel,
Output: logOutput,
IndependentLevels: true,
JSONFormat: json,
})
}
// NewLogger returns a new logger based in the current global logger, with the
// given name appended.
func NewLogger(name string) hclog.Logger {
if name == "" {
panic("logger name required")
}
return &logPanicWrapper{
Logger: logger.Named(name),
}
}
// NewProviderLogger returns a logger for the provider plugin, possibly with a
// different log level from the global logger.
func NewProviderLogger(prefix string) hclog.Logger {
l := &logPanicWrapper{
Logger: logger.Named(prefix + "provider"),
}
level := providerLogLevel()
logger.Debug("created provider logger", "level", level)
l.SetLevel(level)
return l
}
// CurrentLogLevel returns the current log level string based the environment vars
func CurrentLogLevel() string {
ll, _ := globalLogLevel()
return strings.ToUpper(ll.String())
}
func providerLogLevel() hclog.Level {
providerEnvLevel := strings.ToUpper(os.Getenv(envLogProvider))
if providerEnvLevel == "" {
providerEnvLevel = strings.ToUpper(os.Getenv(envLog))
}
return parseLogLevel(providerEnvLevel)
}
func globalLogLevel() (hclog.Level, bool) {
var json bool
envLevel := strings.ToUpper(os.Getenv(envLog))
if envLevel == "" {
envLevel = strings.ToUpper(os.Getenv(envLogCore))
}
if envLevel == "JSON" {
json = true
}
return parseLogLevel(envLevel), json
}
func parseLogLevel(envLevel string) hclog.Level {
if envLevel == "" {
return hclog.Off
}
if envLevel == "JSON" {
envLevel = "TRACE"
}
logLevel := hclog.Trace
if isValidLogLevel(envLevel) {
logLevel = hclog.LevelFromString(envLevel)
} else {
fmt.Fprintf(os.Stderr, "[WARN] Invalid log level: %q. Defaulting to level: TRACE. Valid levels are: %+v",
envLevel, ValidLevels)
}
return logLevel
}
// IsDebugOrHigher returns whether or not the current log level is debug or trace
func IsDebugOrHigher() bool {
level, _ := globalLogLevel()
return level == hclog.Debug || level == hclog.Trace
}
func isValidLogLevel(level string) bool {
for _, l := range ValidLevels {
if level == string(l) {
return true
}
}
return false
}
// PluginOutputMonitor creates an io.Writer that will warn about any writes in
// the default logger. This is used to catch unexpected output from plugins,
// notifying them about the problem as well as surfacing the lost data for
// context.
func PluginOutputMonitor(source string) io.Writer {
return pluginOutputMonitor{
source: source,
log: logger,
}
}
// pluginOutputMonitor is an io.Writer that logs all writes as
// "unexpected data" with the source name.
type pluginOutputMonitor struct {
source string
log hclog.Logger
}
func (w pluginOutputMonitor) Write(d []byte) (int, error) {
// Limit the write size to 1024 bytes We're not expecting any data to come
// through this channel, so accidental writes will usually be stray fmt
// debugging statements and the like, but we want to provide context to the
// provider to indicate what the unexpected data might be.
n := len(d)
if n > 1024 {
d = append(d[:1024], '.', '.', '.')
}
w.log.Warn("unexpected data", w.source, strings.TrimSpace(string(d)))
return n, nil
}