Skip to content

Commit 7104d8d

Browse files
ironmike-aujoelanford
authored andcommitted
add support for iso8601 timestamps (#1529)
* add support for iso8601 timestamps * Update pkg/log/zap/flags.go Co-Authored-By: Lili Cosic <[email protected]> * timeformat flag accepts zapcore formats * add --zap-timeformat usage doc * Update pkg/log/zap/flags.go Co-Authored-By: Joe Lanford <[email protected]> * expand log encoder support functional options and passing multiple encoder options simultaneously * fix encoder references in tests * fix encoder references in tests * Update doc/user/logging.md Co-Authored-By: Joe Lanford <[email protected]> * set the encoder in both the json and console cases * use zap's time encoding UnmarshalText functionality to avoid manually defining supported time formats * rename timeformat to timeencoding/er so that it is consistent with zap's nomenclature * rename timeformat to timeencoding/er so that it is consistent with zap's nomenclature * add basic tests for zap time encoding * pkg/log/zap/*: test improvements for sampling and encoders * pkg/log/zap/flags*: timeEncoder Unmarshal comments/improvements * doc/user/logging.md: fix zap-time-encoding flag name * CHANGELOG.md,doc/user/logging.md: doc updates
1 parent bc5cbd4 commit 7104d8d

File tree

6 files changed

+260
-51
lines changed

6 files changed

+260
-51
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
### Added
44

55
- Document new compile-time dependency `mercurial` in user-facing documentation. ([#1683](https://github.com/operator-framework/operator-sdk/pull/1683))
6+
- Adds new flag `--zap-time-encoding` to the flagset provided by `pkg/log/zap`. This flag configures the timestamp format produced by the zap logger. See the [logging doc](https://github.com/operator-framework/operator-sdk/blob/master/doc/user/logging.md) for more information. ([#1529](https://github.com/operator-framework/operator-sdk/pull/1529))
67

78
### Changed
89

doc/user/logging.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ By default, `zap.Logger()` will return a logger that is ready for production use
1414
* `--zap-encoder` string - Sets the zap log encoding (`json` or `console`)
1515
* `--zap-level` string or integer - Sets the zap log level (`debug`, `info`, `error`, or an integer value greater than 0). If 4 or greater the verbosity of client-go will be set to this level.
1616
* `--zap-sample` - Enables zap's sampling mode. Sampling will be disabled for integer log levels greater than 1.
17+
* `--zap-time-encoding` string - Sets the zap time format (`epoch`, `millis`, `nano`, or `iso8601`)
1718

1819
### A simple example
1920

pkg/log/zap/flags.go

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ import (
2929
var (
3030
zapFlagSet *pflag.FlagSet
3131

32-
development bool
33-
encoderVal encoderValue
34-
levelVal levelValue
35-
sampleVal sampleValue
32+
development bool
33+
encoderVal encoderValue
34+
levelVal levelValue
35+
sampleVal sampleValue
36+
timeEncodingVal timeEncodingValue
3637
)
3738

3839
func init() {
@@ -41,26 +42,29 @@ func init() {
4142
zapFlagSet.Var(&encoderVal, "zap-encoder", "Zap log encoding ('json' or 'console')")
4243
zapFlagSet.Var(&levelVal, "zap-level", "Zap log level (one of 'debug', 'info', 'error' or any integer value > 0)")
4344
zapFlagSet.Var(&sampleVal, "zap-sample", "Enable zap log sampling. Sampling will be disabled for integer log levels > 1")
45+
zapFlagSet.Var(&timeEncodingVal, "zap-time-encoding", "Sets the zap time format ('epoch', 'millis', 'nano', or 'iso8601')")
4446
}
4547

4648
// FlagSet - The zap logging flagset.
4749
func FlagSet() *pflag.FlagSet {
4850
return zapFlagSet
4951
}
5052

53+
type encoderConfigFunc func(*zapcore.EncoderConfig)
54+
5155
type encoderValue struct {
52-
set bool
53-
encoder zapcore.Encoder
54-
str string
56+
set bool
57+
newEncoder func(...encoderConfigFunc) zapcore.Encoder
58+
str string
5559
}
5660

5761
func (v *encoderValue) Set(e string) error {
5862
v.set = true
5963
switch e {
6064
case "json":
61-
v.encoder = jsonEncoder()
65+
v.newEncoder = newJSONEncoder
6266
case "console":
63-
v.encoder = consoleEncoder()
67+
v.newEncoder = newConsoleEncoder
6468
default:
6569
return fmt.Errorf("unknown encoder \"%s\"", e)
6670
}
@@ -76,13 +80,19 @@ func (v encoderValue) Type() string {
7680
return "encoder"
7781
}
7882

79-
func jsonEncoder() zapcore.Encoder {
83+
func newJSONEncoder(ecfs ...encoderConfigFunc) zapcore.Encoder {
8084
encoderConfig := zap.NewProductionEncoderConfig()
85+
for _, f := range ecfs {
86+
f(&encoderConfig)
87+
}
8188
return zapcore.NewJSONEncoder(encoderConfig)
8289
}
8390

84-
func consoleEncoder() zapcore.Encoder {
91+
func newConsoleEncoder(ecfs ...encoderConfigFunc) zapcore.Encoder {
8592
encoderConfig := zap.NewDevelopmentEncoderConfig()
93+
for _, f := range ecfs {
94+
f(&encoderConfig)
95+
}
8696
return zapcore.NewConsoleEncoder(encoderConfig)
8797
}
8898

@@ -158,3 +168,48 @@ func (v sampleValue) IsBoolFlag() bool {
158168
func (v sampleValue) Type() string {
159169
return "sample"
160170
}
171+
172+
type timeEncodingValue struct {
173+
set bool
174+
timeEncoder zapcore.TimeEncoder
175+
str string
176+
}
177+
178+
func (v *timeEncodingValue) Set(s string) error {
179+
v.set = true
180+
181+
// As of zap v1.9.1, UnmarshalText does not return an error. Instead, it
182+
// uses the epoch time encoding when unknown strings are unmarshalled.
183+
//
184+
// Set s to "epoch" if it doesn't match one of the known formats, so that
185+
// it aligns with the default time encoder function.
186+
//
187+
// TODO: remove this entire switch statement if UnmarshalText is ever
188+
// refactored to return an error.
189+
switch s {
190+
case "iso8601", "ISO8601", "millis", "nanos":
191+
default:
192+
s = "epoch"
193+
}
194+
195+
v.str = s
196+
return v.timeEncoder.UnmarshalText([]byte(s))
197+
}
198+
199+
func (v timeEncodingValue) String() string {
200+
return v.str
201+
}
202+
203+
func (v timeEncodingValue) IsBoolFlag() bool {
204+
return false
205+
}
206+
207+
func (v timeEncodingValue) Type() string {
208+
return "timeEncoding"
209+
}
210+
211+
func withTimeEncoding(te zapcore.TimeEncoder) encoderConfigFunc {
212+
return func(ec *zapcore.EncoderConfig) {
213+
ec.EncodeTime = te
214+
}
215+
}

pkg/log/zap/flags_test.go

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,14 @@ func TestEncoder(t *testing.T) {
167167
input: "json",
168168
expStr: "json",
169169
expSet: true,
170-
expEncoder: jsonEncoder(),
170+
expEncoder: newJSONEncoder(),
171171
},
172172
{
173173
name: "console encoder",
174174
input: "console",
175175
expStr: "console",
176176
expSet: true,
177-
expEncoder: consoleEncoder(),
177+
expEncoder: newConsoleEncoder(),
178178
},
179179
{
180180
name: "unknown encoder",
@@ -196,7 +196,63 @@ func TestEncoder(t *testing.T) {
196196
assert.Equal(t, tc.expSet, encoder.set)
197197
assert.Equal(t, "encoder", encoder.Type())
198198
assert.Equal(t, tc.expStr, encoder.String())
199-
assert.ObjectsAreEqual(tc.expEncoder, encoder.encoder)
199+
assert.ObjectsAreEqual(tc.expEncoder, encoder.newEncoder)
200+
})
201+
}
202+
}
203+
func TestTimeEncoder(t *testing.T) {
204+
testCases := []struct {
205+
name string
206+
input string
207+
shouldErr bool
208+
expStr string
209+
expSet bool
210+
}{
211+
{
212+
name: "iso8601 time encoding",
213+
input: "iso8601",
214+
expStr: "iso8601",
215+
expSet: true,
216+
},
217+
{
218+
name: "millis time encoding",
219+
input: "millis",
220+
expStr: "millis",
221+
expSet: true,
222+
},
223+
{
224+
name: "nanos time encoding",
225+
input: "nanos",
226+
expStr: "nanos",
227+
expSet: true,
228+
},
229+
{
230+
name: "epoch time encoding",
231+
input: "epoch",
232+
expStr: "epoch",
233+
expSet: true,
234+
},
235+
{
236+
name: "invalid time encoding",
237+
input: "invalid",
238+
expStr: "epoch",
239+
expSet: true,
240+
shouldErr: false,
241+
},
242+
}
243+
for _, tc := range testCases {
244+
t.Run(tc.name, func(t *testing.T) {
245+
te := timeEncodingValue{}
246+
err := te.Set(tc.input)
247+
if err != nil && !tc.shouldErr {
248+
t.Fatalf("Unknown error - %v", err)
249+
}
250+
if tc.shouldErr {
251+
assert.Error(t, err)
252+
}
253+
assert.Equal(t, tc.expSet, te.set)
254+
assert.Equal(t, "timeEncoding", te.Type())
255+
assert.Equal(t, tc.expStr, te.String())
200256
})
201257
}
202258
}

pkg/log/zap/logger.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@ func Logger() logr.Logger {
3131
}
3232

3333
func LoggerTo(destWriter io.Writer) logr.Logger {
34-
syncer := zapcore.AddSync(destWriter)
3534
conf := getConfig()
35+
return createLogger(conf, destWriter)
36+
}
37+
38+
func createLogger(conf config, destWriter io.Writer) logr.Logger {
39+
syncer := zapcore.AddSync(destWriter)
3640

3741
conf.encoder = &logf.KubeAwareEncoder{Encoder: conf.encoder, Verbose: conf.level.Level() < 0}
3842
if conf.sample {
@@ -56,23 +60,32 @@ type config struct {
5660
func getConfig() config {
5761
var c config
5862

63+
var newEncoder func(...encoderConfigFunc) zapcore.Encoder
64+
5965
// Set the defaults depending on the log mode (development vs. production)
6066
if development {
61-
c.encoder = consoleEncoder()
67+
newEncoder = newConsoleEncoder
6268
c.level = zap.NewAtomicLevelAt(zap.DebugLevel)
6369
c.opts = append(c.opts, zap.Development(), zap.AddStacktrace(zap.ErrorLevel))
6470
c.sample = false
6571
} else {
66-
c.encoder = jsonEncoder()
72+
newEncoder = newJSONEncoder
6773
c.level = zap.NewAtomicLevelAt(zap.InfoLevel)
6874
c.opts = append(c.opts, zap.AddStacktrace(zap.WarnLevel))
6975
c.sample = true
7076
}
7177

7278
// Override the defaults if the flags were set explicitly on the command line
79+
var ecfs []encoderConfigFunc
7380
if encoderVal.set {
74-
c.encoder = encoderVal.encoder
81+
newEncoder = encoderVal.newEncoder
82+
}
83+
if timeEncodingVal.set {
84+
ecfs = append(ecfs, withTimeEncoding(timeEncodingVal.timeEncoder))
7585
}
86+
87+
c.encoder = newEncoder(ecfs...)
88+
7689
if levelVal.set {
7790
c.level = zap.NewAtomicLevelAt(levelVal.level)
7891
}

0 commit comments

Comments
 (0)