diff --git a/README.md b/README.md index e800e7e5..4ea55fd5 100644 --- a/README.md +++ b/README.md @@ -564,6 +564,7 @@ The log file of Ayd is stored in [JSON Lines](https://jsonlines.org/) format, en Each record has at least 4 fields. - `time` when status check started, in [RFC3339 format](https://tools.ietf.org/html/rfc3339) like `2001-02-30T16:05:06+00:00`. + Ayd can parse some variant formats like `2001-02-03 16:05:06+0000` or `20010203_160506Z`, and the UNIX time seconds. - `status` of the record that `HEALTHY`, `DEGRADE`, `FAILURE`, `UNKNOWN`, or `ABORTED`. diff --git a/lib-ayd/record.go b/lib-ayd/record.go index 58a8addf..e8916429 100644 --- a/lib-ayd/record.go +++ b/lib-ayd/record.go @@ -120,10 +120,15 @@ func (r *Record) UnmarshalJSON(data []byte) error { if value, ok := raw["time"]; !ok { return ayderr.New(ErrInvalidRecord, nil, "invalid record: time: missing required field") } else { - if s, ok := value.(string); !ok { - return ayderr.New(ErrInvalidRecord, nil, "invalid record: time: should be a string") - } else if r.Time, err = ParseTime(s); err != nil { - return ayderr.New(ErrInvalidRecord, err, "invalid record: time") + switch v := value.(type) { + case float64: + r.Time = time.UnixMilli(int64(v * 1000)) + case string: + if r.Time, err = ParseTime(v); err != nil { + return ayderr.New(ErrInvalidRecord, err, "invalid record: time") + } + default: + return ayderr.New(ErrInvalidRecord, nil, "invalid record: time: should be a string or a number") } delete(raw, "time") } diff --git a/lib-ayd/record_test.go b/lib-ayd/record_test.go index ce4d08d4..d72d9e7b 100644 --- a/lib-ayd/record_test.go +++ b/lib-ayd/record_test.go @@ -54,6 +54,7 @@ func TestRecord(t *testing.T) { tests := []struct { String string + Encode string Record ayd.Record Error string }{ @@ -88,7 +89,8 @@ func TestRecord(t *testing.T) { }, }, { - String: `{"time":"2021-01-02T15:04:05+09:00", "status":"DEGRADE", "latency":1027.890, "target":"dummy:"}`, + String: `{"time":"2021-01-02 15:04:05+09:00", "status":"DEGRADE", "latency":1027.890, "target":"dummy:"}`, + Encode: `{"time":"2021-01-02T15:04:05+09:00", "status":"DEGRADE", "latency":1027.890, "target":"dummy:"}`, Record: ayd.Record{ Time: time.Date(2021, 1, 2, 15, 4, 5, 0, tokyo), Target: &ayd.URL{Scheme: "dummy"}, @@ -97,6 +99,17 @@ func TestRecord(t *testing.T) { Latency: 1027890 * time.Microsecond, }, }, + { + String: `{"time":1641135845, "status":"HEALTHY", "latency":12.345, "target":"dummy:"}`, + Encode: `{"time":"2022-01-02T15:04:05Z", "status":"HEALTHY", "latency":12.345, "target":"dummy:"}`, + Record: ayd.Record{ + Time: time.Date(2022, 1, 2, 15, 4, 5, 0, time.UTC), + Target: &ayd.URL{Scheme: "dummy"}, + Status: ayd.StatusHealthy, + Message: "", + Latency: 12345 * time.Microsecond, + }, + }, { String: `{"time":"2021-01-02T15:04:05+09:00", "status":"HEALTHY", "latency":123abc, "target":"ping:example.com", "message":"hello world"}`, Error: "invalid record: invalid character 'a' after object key:value pair", @@ -122,8 +135,8 @@ func TestRecord(t *testing.T) { Error: `invalid record: time: missing required field`, }, { - String: `{"time":123, "status":"HEALTHY", "latency":123.456, "target":"ping:example.com", "message":"hello world"}`, - Error: `invalid record: time: should be a string`, + String: `{"time":{}, "status":"HEALTHY", "latency":123.456, "target":"ping:example.com", "message":"hello world"}`, + Error: `invalid record: time: should be a string or a number`, }, { String: `{"time":"2021-01-02T15:04:05+09:00", "status":null, "latency":123.456, "target":"ping:example.com", "message":"hello world"}`, @@ -209,8 +222,12 @@ func TestRecord(t *testing.T) { t.Errorf("unexpected extra\n%s", diff) } - if tt.Record.String() != tt.String { - t.Errorf("expected: %#v\n but got: %#v", tt.String, tt.Record.String()) + expect := tt.Encode + if expect == "" { + expect = tt.String + } + if tt.Record.String() != expect { + t.Errorf("expected: %#v\n but got: %#v", expect, tt.Record.String()) } } }