Skip to content

Commit ffa8c50

Browse files
authored
Merge pull request kubernetes-sigs#1688 from creydr/add-time-encoding-flag
🌱 Allow Specification of the Log Timestamp Format
2 parents 4d6d8ef + 5bb6cb7 commit ffa8c50

File tree

3 files changed

+120
-22
lines changed

3 files changed

+120
-22
lines changed

pkg/log/zap/flags.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,41 @@ func (ev *stackTraceFlag) String() string {
128128
func (ev *stackTraceFlag) Type() string {
129129
return "level"
130130
}
131+
132+
type timeEncodingFlag struct {
133+
setFunc func(zapcore.TimeEncoder)
134+
value string
135+
}
136+
137+
var _ flag.Value = &timeEncodingFlag{}
138+
139+
func (ev *timeEncodingFlag) String() string {
140+
return ev.value
141+
}
142+
143+
func (ev *timeEncodingFlag) Type() string {
144+
return "time-encoding"
145+
}
146+
147+
func (ev *timeEncodingFlag) Set(flagValue string) error {
148+
val := strings.ToLower(flagValue)
149+
switch val {
150+
case "rfc3339nano":
151+
ev.setFunc(zapcore.RFC3339NanoTimeEncoder)
152+
case "rfc3339":
153+
ev.setFunc(zapcore.RFC3339TimeEncoder)
154+
case "iso8601":
155+
ev.setFunc(zapcore.ISO8601TimeEncoder)
156+
case "millis":
157+
ev.setFunc(zapcore.EpochMillisTimeEncoder)
158+
case "nanos":
159+
ev.setFunc(zapcore.EpochNanosTimeEncoder)
160+
case "epoch":
161+
ev.setFunc(zapcore.EpochTimeEncoder)
162+
default:
163+
return fmt.Errorf("invalid time-encoding value \"%s\"", flagValue)
164+
}
165+
166+
ev.value = flagValue
167+
return nil
168+
}

pkg/log/zap/zap.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ type Options struct {
167167
// ZapOpts allows passing arbitrary zap.Options to configure on the
168168
// underlying Zap logger.
169169
ZapOpts []zap.Option
170+
// TimeEncoder specifies the encoder for the timestamps in log messages.
171+
// Defaults to EpochTimeEncoder as this is the default in Zap currently.
172+
TimeEncoder zapcore.TimeEncoder
170173
}
171174

172175
// addDefaults adds defaults to the Options.
@@ -212,6 +215,16 @@ func (o *Options) addDefaults() {
212215
}))
213216
}
214217
}
218+
219+
if o.TimeEncoder == nil {
220+
o.TimeEncoder = zapcore.EpochTimeEncoder
221+
}
222+
f := func(ecfg *zapcore.EncoderConfig) {
223+
ecfg.EncodeTime = o.TimeEncoder
224+
}
225+
// prepend instead of append it in case someone adds a time encoder option in it
226+
o.EncoderConfigOptions = append([]EncoderConfigOption{f}, o.EncoderConfigOptions...)
227+
215228
if o.Encoder == nil {
216229
o.Encoder = o.NewEncoder(o.EncoderConfigOptions...)
217230
}
@@ -273,6 +286,13 @@ func (o *Options) BindFlags(fs *flag.FlagSet) {
273286
}
274287
fs.Var(&stackVal, "zap-stacktrace-level",
275288
"Zap Level at and above which stacktraces are captured (one of 'info', 'error', 'panic').")
289+
290+
// Set the time encoding
291+
var timeEncoderVal timeEncodingFlag
292+
timeEncoderVal.setFunc = func(fromFlag zapcore.TimeEncoder) {
293+
o.TimeEncoder = fromFlag
294+
}
295+
fs.Var(&timeEncoderVal, "zap-time-encoding", "Zap time encoding (one of 'epoch', 'millis', 'nano', 'iso8601', 'rfc3339' or 'rfc3339nano'). Defaults to 'epoch'.")
276296
}
277297

278298
// UseFlagOptions configures the logger to use the Options set by parsing zap option flags from the CLI.

pkg/log/zap/zap_test.go

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"encoding/json"
2222
"flag"
2323
"os"
24+
"reflect"
2425

2526
"github.com/go-logr/logr"
2627
. "github.com/onsi/ginkgo"
@@ -472,35 +473,76 @@ var _ = Describe("Zap log level flag options setup", func() {
472473
})
473474
})
474475

475-
Context("with encoder options provided programmatically", func() {
476+
Context("with zap-time-encoding flag provided", func() {
477+
478+
It("Should set time encoder in options", func() {
479+
args := []string{"--zap-time-encoding=rfc3339"}
480+
fromFlags.BindFlags(&fs)
481+
err := fs.Parse(args)
482+
Expect(err).ToNot(HaveOccurred())
483+
484+
opt := Options{}
485+
UseFlagOptions(&fromFlags)(&opt)
486+
opt.addDefaults()
487+
488+
optVal := reflect.ValueOf(opt.TimeEncoder)
489+
expVal := reflect.ValueOf(zapcore.RFC3339TimeEncoder)
490+
491+
Expect(optVal.Pointer()).To(Equal(expVal.Pointer()))
492+
})
493+
494+
It("Should default to 'epoch' time encoding", func() {
495+
args := []string{""}
496+
fromFlags.BindFlags(&fs)
497+
err := fs.Parse(args)
498+
Expect(err).ToNot(HaveOccurred())
499+
500+
opt := Options{}
501+
UseFlagOptions(&fromFlags)(&opt)
502+
opt.addDefaults()
503+
504+
optVal := reflect.ValueOf(opt.TimeEncoder)
505+
expVal := reflect.ValueOf(zapcore.EpochTimeEncoder)
506+
507+
Expect(optVal.Pointer()).To(Equal(expVal.Pointer()))
508+
})
509+
510+
It("Should return an error message, with unknown time-encoding", func() {
511+
fs = *flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
512+
args := []string{"--zap-time-encoding=foobar"}
513+
fromFlags.BindFlags(&fs)
514+
err := fs.Parse(args)
515+
Expect(err).To(HaveOccurred())
516+
})
517+
518+
It("Should propagate time encoder to logger", func() {
519+
// zaps ISO8601TimeEncoder uses 2006-01-02T15:04:05.000Z0700 as pattern for iso8601 encoding
520+
iso8601Pattern := `^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}(\+[0-9]{4}|Z)`
476521

477-
It("Should set Console Encoder, with given Nanos TimeEncoder option.", func() {
522+
args := []string{"--zap-time-encoding=iso8601"}
523+
fromFlags.BindFlags(&fs)
524+
err := fs.Parse(args)
525+
Expect(err).ToNot(HaveOccurred())
478526
logOut := new(bytes.Buffer)
479-
f := func(ec *zapcore.EncoderConfig) {
480-
if err := ec.EncodeTime.UnmarshalText([]byte("nanos")); err != nil {
481-
Expect(err).ToNot(HaveOccurred())
482-
}
483-
}
484-
opts := func(o *Options) {
485-
o.EncoderConfigOptions = append(o.EncoderConfigOptions, f)
486-
}
487-
log := New(UseDevMode(true), WriteTo(logOut), opts)
488-
log.Info("This is a test message")
527+
528+
logger := New(UseFlagOptions(&fromFlags), WriteTo(logOut))
529+
logger.Info("This is a test message")
530+
489531
outRaw := logOut.Bytes()
490-
// Assert for Console Encoder
491-
res := map[string]interface{}{}
492-
Expect(json.Unmarshal(outRaw, &res)).ToNot(Succeed())
493-
// Assert for Epoch Nanos TimeEncoder
494-
Expect(string(outRaw)).ShouldNot(ContainSubstring("."))
495532

533+
res := map[string]interface{}{}
534+
Expect(json.Unmarshal(outRaw, &res)).To(Succeed())
535+
Expect(res["ts"]).Should(MatchRegexp(iso8601Pattern))
496536
})
537+
538+
})
539+
540+
Context("with encoder options provided programmatically", func() {
541+
497542
It("Should set JSON Encoder, with given Millis TimeEncoder option, and MessageKey", func() {
498543
logOut := new(bytes.Buffer)
499544
f := func(ec *zapcore.EncoderConfig) {
500545
ec.MessageKey = "MillisTimeFormat"
501-
if err := ec.EncodeTime.UnmarshalText([]byte("millis")); err != nil {
502-
Expect(err).ToNot(HaveOccurred())
503-
}
504546
}
505547
opts := func(o *Options) {
506548
o.EncoderConfigOptions = append(o.EncoderConfigOptions, f)
@@ -511,8 +553,6 @@ var _ = Describe("Zap log level flag options setup", func() {
511553
// Assert for JSON Encoder
512554
res := map[string]interface{}{}
513555
Expect(json.Unmarshal(outRaw, &res)).To(Succeed())
514-
// Assert for Epoch Nanos TimeEncoder
515-
Expect(string(outRaw)).Should(ContainSubstring("."))
516556
// Assert for MessageKey
517557
Expect(string(outRaw)).Should(ContainSubstring("MillisTimeFormat"))
518558
})

0 commit comments

Comments
 (0)