diff --git a/raft/util.go b/raft/util.go index 91898ae0c..93ce2a2c2 100644 --- a/raft/util.go +++ b/raft/util.go @@ -54,9 +54,13 @@ func IsResponseMsg(m pb.Message) bool { return m.Type == pb.MsgAppResp || m.Type == pb.MsgVoteResp || m.Type == pb.MsgHeartbeatResp } +// EntryFormatter can be implemented by the application to provide human-readable formatting +// of entry data. Nil is a valid EntryFormatter and will use a default format. +type EntryFormatter func([]byte) string + // DescribeMessage returns a concise human-readable description of a // Message for debugging. -func DescribeMessage(m pb.Message) string { +func DescribeMessage(m pb.Message, f EntryFormatter) string { var buf bytes.Buffer fmt.Fprintf(&buf, "%d->%d %s Term:%d Log:%d/%d", m.From, m.To, m.Type, m.Term, m.LogTerm, m.Index) if m.Reject { @@ -68,7 +72,7 @@ func DescribeMessage(m pb.Message) string { if len(m.Entries) > 0 { fmt.Fprintf(&buf, " Entries:[") for _, e := range m.Entries { - buf.WriteString(DescribeEntry(e)) + buf.WriteString(DescribeEntry(e, f)) } fmt.Fprintf(&buf, "]") } @@ -80,6 +84,12 @@ func DescribeMessage(m pb.Message) string { // DescribeEntry returns a concise human-readable description of an // Entry for debugging. -func DescribeEntry(e pb.Entry) string { - return fmt.Sprintf("%d/%d %s %q", e.Term, e.Index, e.Type, string(e.Data)) +func DescribeEntry(e pb.Entry, f EntryFormatter) string { + var formatted string + if f == nil { + formatted = fmt.Sprintf("%q", e.Data) + } else { + formatted = f(e.Data) + } + return fmt.Sprintf("%d/%d %s %s", e.Term, e.Index, e.Type, formatted) } diff --git a/raft/util_test.go b/raft/util_test.go new file mode 100644 index 000000000..8527d8058 --- /dev/null +++ b/raft/util_test.go @@ -0,0 +1,47 @@ +/* + Copyright 2014 CoreOS, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package raft + +import ( + "strings" + "testing" + + pb "github.com/coreos/etcd/raft/raftpb" +) + +var testFormatter EntryFormatter = func(data []byte) string { + return strings.ToUpper(string(data)) +} + +func TestDescribeEntry(t *testing.T) { + entry := pb.Entry{ + Term: 1, + Index: 2, + Type: pb.EntryNormal, + Data: []byte("hello\x00world"), + } + + defaultFormatted := DescribeEntry(entry, nil) + if defaultFormatted != "1/2 EntryNormal \"hello\\x00world\"" { + t.Errorf("unexpected default output: %s", defaultFormatted) + } + + customFormatted := DescribeEntry(entry, testFormatter) + if customFormatted != "1/2 EntryNormal HELLO\x00WORLD" { + t.Errorf("unexpected custom output: %s", customFormatted) + } +}