Merge pull request #514 from go-kit/defer-time-format
log: Add TimestampFormat and Timeformat.
Chris Hines authored 7 years ago
GitHub committed 7 years ago
31 | 31 | return false |
32 | 32 | } |
33 | 33 | |
34 | // Timestamp returns a Valuer that invokes the underlying function when bound, | |
35 | // returning a time.Time. Users will probably want to use DefaultTimestamp or | |
36 | // DefaultTimestampUTC. | |
34 | // Timestamp returns a timestamp Valuer. It invokes the t function to get the | |
35 | // time; unless you are doing something tricky, pass time.Now. | |
36 | // | |
37 | // Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which | |
38 | // are TimestampFormats that use the RFC3339Nano format. | |
37 | 39 | func Timestamp(t func() time.Time) Valuer { |
38 | 40 | return func() interface{} { return t() } |
41 | } | |
42 | ||
43 | // TimestampFormat returns a timestamp Valuer with a custom time format. It | |
44 | // invokes the t function to get the time to format; unless you are doing | |
45 | // something tricky, pass time.Now. The layout string is passed to | |
46 | // Time.Format. | |
47 | // | |
48 | // Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which | |
49 | // are TimestampFormats that use the RFC3339Nano format. | |
50 | func TimestampFormat(t func() time.Time, layout string) Valuer { | |
51 | return func() interface{} { | |
52 | return timeFormat{ | |
53 | time: t(), | |
54 | layout: layout, | |
55 | } | |
56 | } | |
57 | } | |
58 | ||
59 | // A timeFormat represents an instant in time and a layout used when | |
60 | // marshaling to a text format. | |
61 | type timeFormat struct { | |
62 | time time.Time | |
63 | layout string | |
64 | } | |
65 | ||
66 | func (tf timeFormat) String() string { | |
67 | return tf.time.Format(tf.layout) | |
68 | } | |
69 | ||
70 | // MarshalText implements encoding.TextMarshaller. | |
71 | func (tf timeFormat) MarshalText() (text []byte, err error) { | |
72 | // The following code adapted from the standard library time.Time.Format | |
73 | // method. Using the same undocumented magic constant to extend the size | |
74 | // of the buffer as seen there. | |
75 | b := make([]byte, 0, len(tf.layout)+10) | |
76 | b = tf.time.AppendFormat(b, tf.layout) | |
77 | return b, nil | |
39 | 78 | } |
40 | 79 | |
41 | 80 | // Caller returns a Valuer that returns a file and line from a specified depth |
47 | 86 | var ( |
48 | 87 | // DefaultTimestamp is a Valuer that returns the current wallclock time, |
49 | 88 | // respecting time zones, when bound. |
50 | DefaultTimestamp = Valuer(func() interface{} { | |
51 | return time.Now().Format(time.RFC3339Nano) | |
52 | }) | |
89 | DefaultTimestamp = TimestampFormat(time.Now, time.RFC3339Nano) | |
53 | 90 | |
54 | 91 | // DefaultTimestampUTC is a Valuer that returns the current time in UTC |
55 | 92 | // when bound. |
56 | DefaultTimestampUTC = Valuer(func() interface{} { | |
57 | return time.Now().UTC().Format(time.RFC3339Nano) | |
58 | }) | |
93 | DefaultTimestampUTC = TimestampFormat( | |
94 | func() time.Time { return time.Now().UTC() }, | |
95 | time.RFC3339Nano, | |
96 | ) | |
59 | 97 | |
60 | 98 | // DefaultCaller is a Valuer that returns the file and line where the Log |
61 | 99 | // method was invoked. It can only be used with log.With. |
0 | 0 | package log_test |
1 | 1 | |
2 | 2 | import ( |
3 | "encoding" | |
3 | 4 | "fmt" |
5 | "reflect" | |
4 | 6 | "testing" |
5 | 7 | "time" |
6 | 8 | |
33 | 35 | if want, have := start.Add(time.Second), timestamp; want != have { |
34 | 36 | t.Errorf("output[1]: want %v, have %v", want, have) |
35 | 37 | } |
36 | if want, have := "value_test.go:29", fmt.Sprint(output[3]); want != have { | |
38 | if want, have := "value_test.go:31", fmt.Sprint(output[3]); want != have { | |
37 | 39 | t.Errorf("output[3]: want %s, have %s", want, have) |
38 | 40 | } |
39 | 41 | |
46 | 48 | if want, have := start.Add(2*time.Second), timestamp; want != have { |
47 | 49 | t.Errorf("output[1]: want %v, have %v", want, have) |
48 | 50 | } |
49 | if want, have := "value_test.go:42", fmt.Sprint(output[3]); want != have { | |
51 | if want, have := "value_test.go:44", fmt.Sprint(output[3]); want != have { | |
50 | 52 | t.Errorf("output[3]: want %s, have %s", want, have) |
51 | 53 | } |
52 | 54 | } |
89 | 91 | } |
90 | 92 | } |
91 | 93 | |
94 | func TestTimestampFormat(t *testing.T) { | |
95 | t.Parallel() | |
96 | ||
97 | start := time.Date(2015, time.April, 25, 0, 0, 0, 0, time.UTC) | |
98 | now := start | |
99 | mocktime := func() time.Time { | |
100 | now = now.Add(time.Second) | |
101 | return now | |
102 | } | |
103 | ||
104 | tv := log.TimestampFormat(mocktime, time.RFC822) | |
105 | ||
106 | if want, have := now.Add(time.Second).Format(time.RFC822), fmt.Sprint(tv()); want != have { | |
107 | t.Errorf("wrong time format: want %v, have %v", want, have) | |
108 | } | |
109 | ||
110 | if want, have := now.Add(2*time.Second).Format(time.RFC822), fmt.Sprint(tv()); want != have { | |
111 | t.Errorf("wrong time format: want %v, have %v", want, have) | |
112 | } | |
113 | ||
114 | mustMarshal := func(v interface{}) []byte { | |
115 | b, err := v.(encoding.TextMarshaler).MarshalText() | |
116 | if err != nil { | |
117 | t.Fatal("error marshaling text:", err) | |
118 | } | |
119 | return b | |
120 | } | |
121 | ||
122 | if want, have := now.Add(3*time.Second).AppendFormat(nil, time.RFC822), mustMarshal(tv()); !reflect.DeepEqual(want, have) { | |
123 | t.Errorf("wrong time format: want %s, have %s", want, have) | |
124 | } | |
125 | ||
126 | if want, have := now.Add(4*time.Second).AppendFormat(nil, time.RFC822), mustMarshal(tv()); !reflect.DeepEqual(want, have) { | |
127 | t.Errorf("wrong time format: want %s, have %s", want, have) | |
128 | } | |
129 | } | |
130 | ||
92 | 131 | func BenchmarkValueBindingTimestamp(b *testing.B) { |
93 | 132 | logger := log.NewNopLogger() |
94 | 133 | lc := log.With(logger, "ts", log.DefaultTimestamp) |