Skip to content

Commit 2f73dc0

Browse files
authored
Adds log level guard functions (#299)
* Adds `loggertest.ProviderRootWithLevel` * Adds guard functions to provider root logger * Adds guard functions to provider subsystem logger * Adds `loggertest.SDKRootWithLevel` * Adds guard functions to SDK root logger * Adds guard functions to SDK subsystem logger * Adds example tests * Fixes linter errors in SDK logging tests
1 parent d6f71e6 commit 2f73dc0

13 files changed

Lines changed: 1006 additions & 0 deletions

‎internal/loggertest/provider.go‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"io"
99

10+
"github.com/hashicorp/go-hclog"
1011
"github.com/hashicorp/terraform-plugin-log/internal/logging"
1112
"github.com/hashicorp/terraform-plugin-log/tfsdklog"
1213
)
@@ -20,6 +21,30 @@ func ProviderRoot(ctx context.Context, output io.Writer) context.Context {
2021
)
2122
}
2223

24+
// SDKRootWithLevel creates an SDK root logger at the given level.
25+
// Use this in tests that need to verify behaviour at a specific log level.
26+
func SDKRootWithLevel(ctx context.Context, output io.Writer, level hclog.Level) context.Context {
27+
return tfsdklog.NewRootSDKLogger(
28+
ctx,
29+
logging.WithoutLocation(),
30+
logging.WithoutTimestamp(),
31+
logging.WithOutput(output),
32+
tfsdklog.WithLevel(level),
33+
)
34+
}
35+
36+
// ProviderRootWithLevel creates a provider root logger at the given level.
37+
// Use this in tests that need to verify behaviour at a specific log level.
38+
func ProviderRootWithLevel(ctx context.Context, output io.Writer, level hclog.Level) context.Context {
39+
return tfsdklog.NewRootProviderLogger(
40+
ctx,
41+
logging.WithoutLocation(),
42+
logging.WithoutTimestamp(),
43+
logging.WithOutput(output),
44+
tfsdklog.WithLevel(level),
45+
)
46+
}
47+
2348
// ProviderRootWithLocation is for testing code that affects go-hclog's caller
2449
// information (location offset). Most testing code should avoid this, since
2550
// correctly checking differences including the location is extra effort

‎tflog/provider.go‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,51 @@ func Error(ctx context.Context, msg string, additionalFields ...map[string]inter
140140
logger.Error(msg, additionalArgs...)
141141
}
142142

143+
// IsTrace returns true if the logger in ctx would emit a trace-level log.
144+
func IsTrace(ctx context.Context) bool {
145+
logger := logging.GetProviderRootLogger(ctx)
146+
if logger == nil {
147+
return false
148+
}
149+
return logger.IsTrace()
150+
}
151+
152+
// IsDebug returns true if the logger in ctx would emit a debug-level log.
153+
func IsDebug(ctx context.Context) bool {
154+
logger := logging.GetProviderRootLogger(ctx)
155+
if logger == nil {
156+
return false
157+
}
158+
return logger.IsDebug()
159+
}
160+
161+
// IsInfo returns true if the logger in ctx would emit an info-level log.
162+
func IsInfo(ctx context.Context) bool {
163+
logger := logging.GetProviderRootLogger(ctx)
164+
if logger == nil {
165+
return false
166+
}
167+
return logger.IsInfo()
168+
}
169+
170+
// IsWarn returns true if the logger in ctx would emit a warn-level log.
171+
func IsWarn(ctx context.Context) bool {
172+
logger := logging.GetProviderRootLogger(ctx)
173+
if logger == nil {
174+
return false
175+
}
176+
return logger.IsWarn()
177+
}
178+
179+
// IsError returns true if the logger in ctx would emit an error-level log.
180+
func IsError(ctx context.Context) bool {
181+
logger := logging.GetProviderRootLogger(ctx)
182+
if logger == nil {
183+
return false
184+
}
185+
return logger.IsError()
186+
}
187+
143188
// OmitLogWithFieldKeys returns a new context.Context that has a modified logger
144189
// that will omit to write any log when any of the given keys is found
145190
// within its fields.

‎tflog/provider_example_test.go‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,3 +365,27 @@ func ExampleMaskLogStrings() {
365365
// Output:
366366
// {"@level":"trace","@message":"example log ***","@module":"provider","k1":"*** plus some text","k2":"*** plus more text"}
367367
}
368+
369+
func ExampleIsTrace() {
370+
// virtually no plugin developers will need to worry about
371+
// instantiating loggers, as the libraries they're using will take care
372+
// of that, but we're not using those libraries in these examples. So
373+
// we need to do the injection ourselves. Plugin developers will
374+
// basically never need to do this, so the next line can safely be
375+
// considered setup for the example and ignored. Instead, use the
376+
// context passed in by the framework or library you're using.
377+
exampleCtx := getExampleContext()
378+
379+
// non-example-setup code begins here
380+
381+
// use IsTrace to skip building fields when trace logging is not enabled
382+
if IsTrace(exampleCtx) {
383+
fields := map[string]interface{}{
384+
"computed-key": "computed-value",
385+
}
386+
Trace(exampleCtx, "hello, world", fields)
387+
}
388+
389+
// Output:
390+
// {"@level":"trace","@message":"hello, world","@module":"provider","computed-key":"computed-value"}
391+
}

‎tflog/provider_test.go‎

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"testing"
1111

1212
"github.com/google/go-cmp/cmp"
13+
"github.com/hashicorp/go-hclog"
1314
"github.com/hashicorp/terraform-plugin-log/internal/loggertest"
1415
"github.com/hashicorp/terraform-plugin-log/tflog"
1516
)
@@ -1858,3 +1859,203 @@ func TestMaskLogStrings(t *testing.T) {
18581859
})
18591860
}
18601861
}
1862+
1863+
func TestIsTrace(t *testing.T) {
1864+
t.Parallel()
1865+
1866+
testCases := map[string]struct {
1867+
level hclog.Level
1868+
expected bool
1869+
}{
1870+
"root-level-trace": {level: hclog.Trace, expected: true},
1871+
"root-level-debug": {level: hclog.Debug, expected: false},
1872+
"root-level-info": {level: hclog.Info, expected: false},
1873+
"root-level-warn": {level: hclog.Warn, expected: false},
1874+
"root-level-error": {level: hclog.Error, expected: false},
1875+
}
1876+
1877+
for name, testCase := range testCases {
1878+
t.Run(name, func(t *testing.T) {
1879+
t.Parallel()
1880+
1881+
var outputBuffer bytes.Buffer
1882+
1883+
ctx := context.Background()
1884+
ctx = loggertest.ProviderRootWithLevel(ctx, &outputBuffer, testCase.level)
1885+
1886+
got := tflog.IsTrace(ctx)
1887+
1888+
if got != testCase.expected {
1889+
t.Errorf("expected %v, got %v", testCase.expected, got)
1890+
}
1891+
})
1892+
}
1893+
1894+
t.Run("no-logger", func(t *testing.T) {
1895+
t.Parallel()
1896+
1897+
if got := tflog.IsTrace(context.Background()); got != false {
1898+
t.Errorf("expected false with no logger in context, got %v", got)
1899+
}
1900+
})
1901+
}
1902+
1903+
func TestIsDebug(t *testing.T) {
1904+
t.Parallel()
1905+
1906+
testCases := map[string]struct {
1907+
level hclog.Level
1908+
expected bool
1909+
}{
1910+
"root-level-trace": {level: hclog.Trace, expected: true},
1911+
"root-level-debug": {level: hclog.Debug, expected: true},
1912+
"root-level-info": {level: hclog.Info, expected: false},
1913+
"root-level-warn": {level: hclog.Warn, expected: false},
1914+
"root-level-error": {level: hclog.Error, expected: false},
1915+
}
1916+
1917+
for name, testCase := range testCases {
1918+
t.Run(name, func(t *testing.T) {
1919+
t.Parallel()
1920+
1921+
var outputBuffer bytes.Buffer
1922+
1923+
ctx := context.Background()
1924+
ctx = loggertest.ProviderRootWithLevel(ctx, &outputBuffer, testCase.level)
1925+
1926+
got := tflog.IsDebug(ctx)
1927+
1928+
if got != testCase.expected {
1929+
t.Errorf("expected %v, got %v", testCase.expected, got)
1930+
}
1931+
})
1932+
}
1933+
1934+
t.Run("no-logger", func(t *testing.T) {
1935+
t.Parallel()
1936+
1937+
if got := tflog.IsDebug(context.Background()); got != false {
1938+
t.Errorf("expected false with no logger in context, got %v", got)
1939+
}
1940+
})
1941+
}
1942+
1943+
func TestIsInfo(t *testing.T) {
1944+
t.Parallel()
1945+
1946+
testCases := map[string]struct {
1947+
level hclog.Level
1948+
expected bool
1949+
}{
1950+
"root-level-trace": {level: hclog.Trace, expected: true},
1951+
"root-level-debug": {level: hclog.Debug, expected: true},
1952+
"root-level-info": {level: hclog.Info, expected: true},
1953+
"root-level-warn": {level: hclog.Warn, expected: false},
1954+
"root-level-error": {level: hclog.Error, expected: false},
1955+
}
1956+
1957+
for name, testCase := range testCases {
1958+
t.Run(name, func(t *testing.T) {
1959+
t.Parallel()
1960+
1961+
var outputBuffer bytes.Buffer
1962+
1963+
ctx := context.Background()
1964+
ctx = loggertest.ProviderRootWithLevel(ctx, &outputBuffer, testCase.level)
1965+
1966+
got := tflog.IsInfo(ctx)
1967+
1968+
if got != testCase.expected {
1969+
t.Errorf("expected %v, got %v", testCase.expected, got)
1970+
}
1971+
})
1972+
}
1973+
1974+
t.Run("no-logger", func(t *testing.T) {
1975+
t.Parallel()
1976+
1977+
if got := tflog.IsInfo(context.Background()); got != false {
1978+
t.Errorf("expected false with no logger in context, got %v", got)
1979+
}
1980+
})
1981+
}
1982+
1983+
func TestIsWarn(t *testing.T) {
1984+
t.Parallel()
1985+
1986+
testCases := map[string]struct {
1987+
level hclog.Level
1988+
expected bool
1989+
}{
1990+
"root-level-trace": {level: hclog.Trace, expected: true},
1991+
"root-level-debug": {level: hclog.Debug, expected: true},
1992+
"root-level-info": {level: hclog.Info, expected: true},
1993+
"root-level-warn": {level: hclog.Warn, expected: true},
1994+
"root-level-error": {level: hclog.Error, expected: false},
1995+
}
1996+
1997+
for name, testCase := range testCases {
1998+
t.Run(name, func(t *testing.T) {
1999+
t.Parallel()
2000+
2001+
var outputBuffer bytes.Buffer
2002+
2003+
ctx := context.Background()
2004+
ctx = loggertest.ProviderRootWithLevel(ctx, &outputBuffer, testCase.level)
2005+
2006+
got := tflog.IsWarn(ctx)
2007+
2008+
if got != testCase.expected {
2009+
t.Errorf("expected %v, got %v", testCase.expected, got)
2010+
}
2011+
})
2012+
}
2013+
2014+
t.Run("no-logger", func(t *testing.T) {
2015+
t.Parallel()
2016+
2017+
if got := tflog.IsWarn(context.Background()); got != false {
2018+
t.Errorf("expected false with no logger in context, got %v", got)
2019+
}
2020+
})
2021+
}
2022+
2023+
func TestIsError(t *testing.T) {
2024+
t.Parallel()
2025+
2026+
testCases := map[string]struct {
2027+
level hclog.Level
2028+
expected bool
2029+
}{
2030+
"root-level-trace": {level: hclog.Trace, expected: true},
2031+
"root-level-debug": {level: hclog.Debug, expected: true},
2032+
"root-level-info": {level: hclog.Info, expected: true},
2033+
"root-level-warn": {level: hclog.Warn, expected: true},
2034+
"root-level-error": {level: hclog.Error, expected: true},
2035+
}
2036+
2037+
for name, testCase := range testCases {
2038+
t.Run(name, func(t *testing.T) {
2039+
t.Parallel()
2040+
2041+
var outputBuffer bytes.Buffer
2042+
2043+
ctx := context.Background()
2044+
ctx = loggertest.ProviderRootWithLevel(ctx, &outputBuffer, testCase.level)
2045+
2046+
got := tflog.IsError(ctx)
2047+
2048+
if got != testCase.expected {
2049+
t.Errorf("expected %v, got %v", testCase.expected, got)
2050+
}
2051+
})
2052+
}
2053+
2054+
t.Run("no-logger", func(t *testing.T) {
2055+
t.Parallel()
2056+
2057+
if got := tflog.IsError(context.Background()); got != false {
2058+
t.Errorf("expected false with no logger in context, got %v", got)
2059+
}
2060+
})
2061+
}

‎tflog/subsystem.go‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,51 @@ func SubsystemError(ctx context.Context, subsystem, msg string, additionalFields
219219
logger.Error(msg, additionalArgs...)
220220
}
221221

222+
// SubsystemIsTrace returns true if the subsystem logger in ctx would emit a trace-level log.
223+
func SubsystemIsTrace(ctx context.Context, subsystem string) bool {
224+
logger := logging.GetProviderSubsystemLogger(ctx, subsystem)
225+
if logger == nil {
226+
return false
227+
}
228+
return logger.IsTrace()
229+
}
230+
231+
// SubsystemIsDebug returns true if the subsystem logger in ctx would emit a debug-level log.
232+
func SubsystemIsDebug(ctx context.Context, subsystem string) bool {
233+
logger := logging.GetProviderSubsystemLogger(ctx, subsystem)
234+
if logger == nil {
235+
return false
236+
}
237+
return logger.IsDebug()
238+
}
239+
240+
// SubsystemIsInfo returns true if the subsystem logger in ctx would emit an info-level log.
241+
func SubsystemIsInfo(ctx context.Context, subsystem string) bool {
242+
logger := logging.GetProviderSubsystemLogger(ctx, subsystem)
243+
if logger == nil {
244+
return false
245+
}
246+
return logger.IsInfo()
247+
}
248+
249+
// SubsystemIsWarn returns true if the subsystem logger in ctx would emit a warn-level log.
250+
func SubsystemIsWarn(ctx context.Context, subsystem string) bool {
251+
logger := logging.GetProviderSubsystemLogger(ctx, subsystem)
252+
if logger == nil {
253+
return false
254+
}
255+
return logger.IsWarn()
256+
}
257+
258+
// SubsystemIsError returns true if the subsystem logger in ctx would emit an error-level log.
259+
func SubsystemIsError(ctx context.Context, subsystem string) bool {
260+
logger := logging.GetProviderSubsystemLogger(ctx, subsystem)
261+
if logger == nil {
262+
return false
263+
}
264+
return logger.IsError()
265+
}
266+
222267
// SubsystemOmitLogWithFieldKeys returns a new context.Context that has a modified logger
223268
// that will omit to write any log when any of the given keys is found
224269
// within its fields.

0 commit comments

Comments
 (0)