/
sink.go
152 lines (130 loc) · 4.67 KB
/
sink.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
package tfsdklog
import (
"context"
"fmt"
"io"
"os"
"strings"
"sync"
"syscall"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/terraform-plugin-log/internal/logging"
testing "github.com/mitchellh/go-testing-interface"
)
const (
// envLog is the environment variable that users can set to control the
// least-verbose level of logs that will be output during testing. If
// this environment variable is set, it will default to off. This is
// just the default; specific loggers and sub-loggers can set a lower
// or higher verbosity level without a problem right now. In theory,
// they should not be able to.
//
// Valid values are TRACE, DEBUG, INFO, WARN, ERROR, and OFF. A special
// pseudo-value, JSON, will set the value to TRACE and output the
// results in their JSON format.
envLog = "TF_LOG"
// envLogFile is the environment variable that controls where log
// output is written during tests. By default, logs will be written to
// standard error. Setting this environment variable to another file
// path will write logs there instead during tests.
envLogFile = "TF_LOG_PATH"
// envAccLogFile is the environment variable that controls where log
// output from the provider under test and the Terraform binary (and
// other providers) will be written during tests. Setting this
// environment variable to a file will combine all log output in that
// file. If both this environment variable and TF_LOG_PATH are set,
// this environment variable will take precedence.
envAccLogFile = "TF_ACC_LOG_PATH"
// envLogPathMask is the environment variable that controls per-test
// logging output. It should be set to a fmt-compatible string, where a
// single %s will be replaced with the test name, and the log output
// for that test (and only that test) will be written to that file.
// Setting this environment variable will override TF_LOG_PATH.
// Only the logs for the provider under test are included.
envLogPathMask = "TF_LOG_PATH_MASK"
)
// ValidLevels are the string representations of levels that can be set for
// loggers.
var ValidLevels = []string{"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"}
// Only show invalid log level message once across any number of level lookups.
var invalidLogLevelMessage sync.Once
// RegisterTestSink sets up a logging sink, for use with test frameworks and
// other cases where plugin logs don't get routed through Terraform. This
// applies the same filtering and file output behaviors that Terraform does.
//
// RegisterTestSink should only ever be called by test frameworks, providers
// should never call it.
//
// RegisterTestSink must be called prior to any loggers being setup or
// instantiated.
func RegisterTestSink(ctx context.Context, t testing.T) context.Context {
logger, loggerOptions := newSink(t)
ctx = logging.SetSink(ctx, logger)
ctx = logging.SetSinkOptions(ctx, loggerOptions)
return ctx
}
func newSink(t testing.T) (hclog.Logger, *hclog.LoggerOptions) {
logOutput := io.Writer(os.Stderr)
var json bool
var logLevel hclog.Level
var logFile string
envLevel := strings.ToUpper(os.Getenv(envLog))
// if TF_LOG_PATH is set, output logs there
if logPath := os.Getenv(envLogFile); logPath != "" {
logFile = logPath
}
// if TF_ACC_LOG_PATH is set, output logs there instead
if logPath := os.Getenv(envAccLogFile); logPath != "" {
logFile = logPath
// helper/resource makes this default to TRACE, so we should,
// too
envLevel = "TRACE"
}
// if TF_LOG_PATH_MASK is set, use a test-name specific logging file,
// instead
if logPathMask := os.Getenv(envLogPathMask); logPathMask != "" {
testName := strings.Replace(t.Name(), "/", "__", -1)
logFile = fmt.Sprintf(logPathMask, testName)
}
if logFile != "" {
f, err := os.OpenFile(logFile, 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
}
}
// if TF_LOG is set, set the level
if envLevel == "" {
logLevel = hclog.Off
} else if envLevel == "JSON" {
logLevel = hclog.Trace
json = true
} else if isValidLogLevel(envLevel) {
logLevel = hclog.LevelFromString(envLevel)
} else {
invalidLogLevelMessage.Do(func() {
fmt.Fprintf(
os.Stderr,
"[WARN] Invalid log level: %q. Defaulting to level: OFF. Valid levels are: %+v\n",
envLevel,
ValidLevels,
)
})
}
loggerOptions := &hclog.LoggerOptions{
Level: logLevel,
Output: logOutput,
IndependentLevels: true,
JSONFormat: json,
}
return hclog.New(loggerOptions), loggerOptions
}
func isValidLogLevel(level string) bool {
for _, validLevel := range ValidLevels {
if level == validLevel {
return true
}
}
return false
}