1package logging
2
3import (
4	"fmt"
5	"io"
6	"log"
7	"os"
8	"strings"
9	"syscall"
10
11	// go.etcd.io/etcd imports capnslog, which calls log.SetOutput in its
12	// init() function, so importing it here means that our log.SetOutput
13	// wins. this is fixed in coreos v3.5, which is not released yet. See
14	// https://github.com/etcd-io/etcd/issues/12498 for more information.
15	_ "github.com/coreos/pkg/capnslog"
16	"github.com/hashicorp/go-hclog"
17)
18
19// These are the environmental variables that determine if we log, and if
20// we log whether or not the log should go to a file.
21const (
22	envLog     = "TF_LOG"
23	envLogFile = "TF_LOG_PATH"
24
25	// Allow logging of specific subsystems.
26	// We only separate core and providers for now, but this could be extended
27	// to other loggers, like provisioners and remote-state backends.
28	envLogCore     = "TF_LOG_CORE"
29	envLogProvider = "TF_LOG_PROVIDER"
30)
31
32var (
33	// ValidLevels are the log level names that Terraform recognizes.
34	ValidLevels = []string{"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"}
35
36	// logger is the global hclog logger
37	logger hclog.Logger
38
39	// logWriter is a global writer for logs, to be used with the std log package
40	logWriter io.Writer
41
42	// initialize our cache of panic output from providers
43	panics = &panicRecorder{
44		panics:   make(map[string][]string),
45		maxLines: 100,
46	}
47)
48
49func init() {
50	logger = newHCLogger("")
51	logWriter = logger.StandardWriter(&hclog.StandardLoggerOptions{InferLevels: true})
52
53	// set up the default std library logger to use our output
54	log.SetFlags(0)
55	log.SetPrefix("")
56	log.SetOutput(logWriter)
57}
58
59// SetupTempLog adds a new log sink which writes all logs to the given file.
60func RegisterSink(f *os.File) {
61	l, ok := logger.(hclog.InterceptLogger)
62	if !ok {
63		panic("global logger is not an InterceptLogger")
64	}
65
66	if f == nil {
67		return
68	}
69
70	l.RegisterSink(hclog.NewSinkAdapter(&hclog.LoggerOptions{
71		Level:  hclog.Trace,
72		Output: f,
73	}))
74}
75
76// LogOutput return the default global log io.Writer
77func LogOutput() io.Writer {
78	return logWriter
79}
80
81// HCLogger returns the default global hclog logger
82func HCLogger() hclog.Logger {
83	return logger
84}
85
86// newHCLogger returns a new hclog.Logger instance with the given name
87func newHCLogger(name string) hclog.Logger {
88	logOutput := io.Writer(os.Stderr)
89	logLevel, json := globalLogLevel()
90
91	if logPath := os.Getenv(envLogFile); logPath != "" {
92		f, err := os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666)
93		if err != nil {
94			fmt.Fprintf(os.Stderr, "Error opening log file: %v\n", err)
95		} else {
96			logOutput = f
97		}
98	}
99
100	return hclog.NewInterceptLogger(&hclog.LoggerOptions{
101		Name:              name,
102		Level:             logLevel,
103		Output:            logOutput,
104		IndependentLevels: true,
105		JSONFormat:        json,
106	})
107}
108
109// NewLogger returns a new logger based in the current global logger, with the
110// given name appended.
111func NewLogger(name string) hclog.Logger {
112	if name == "" {
113		panic("logger name required")
114	}
115	return &logPanicWrapper{
116		Logger: logger.Named(name),
117	}
118}
119
120// NewProviderLogger returns a logger for the provider plugin, possibly with a
121// different log level from the global logger.
122func NewProviderLogger(prefix string) hclog.Logger {
123	l := &logPanicWrapper{
124		Logger: logger.Named(prefix + "provider"),
125	}
126
127	level := providerLogLevel()
128	logger.Debug("created provider logger", "level", level)
129
130	l.SetLevel(level)
131	return l
132}
133
134// CurrentLogLevel returns the current log level string based the environment vars
135func CurrentLogLevel() string {
136	ll, _ := globalLogLevel()
137	return strings.ToUpper(ll.String())
138}
139
140func providerLogLevel() hclog.Level {
141	providerEnvLevel := strings.ToUpper(os.Getenv(envLogProvider))
142	if providerEnvLevel == "" {
143		providerEnvLevel = strings.ToUpper(os.Getenv(envLog))
144	}
145
146	return parseLogLevel(providerEnvLevel)
147}
148
149func globalLogLevel() (hclog.Level, bool) {
150	var json bool
151	envLevel := strings.ToUpper(os.Getenv(envLog))
152	if envLevel == "" {
153		envLevel = strings.ToUpper(os.Getenv(envLogCore))
154	}
155	if envLevel == "JSON" {
156		json = true
157	}
158	return parseLogLevel(envLevel), json
159}
160
161func parseLogLevel(envLevel string) hclog.Level {
162	if envLevel == "" {
163		return hclog.Off
164	}
165	if envLevel == "JSON" {
166		envLevel = "TRACE"
167	}
168
169	logLevel := hclog.Trace
170	if isValidLogLevel(envLevel) {
171		logLevel = hclog.LevelFromString(envLevel)
172	} else {
173		fmt.Fprintf(os.Stderr, "[WARN] Invalid log level: %q. Defaulting to level: TRACE. Valid levels are: %+v",
174			envLevel, ValidLevels)
175	}
176
177	return logLevel
178}
179
180// IsDebugOrHigher returns whether or not the current log level is debug or trace
181func IsDebugOrHigher() bool {
182	level, _ := globalLogLevel()
183	return level == hclog.Debug || level == hclog.Trace
184}
185
186func isValidLogLevel(level string) bool {
187	for _, l := range ValidLevels {
188		if level == string(l) {
189			return true
190		}
191	}
192
193	return false
194}
195