diff --git a/text_formatter.go b/text_formatter.go index 51397cc..ef799f1 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -7,6 +7,7 @@ import ( "strings" "sync" "time" + "unicode/utf8" ) const ( @@ -52,9 +53,13 @@ type TextFormatter struct { // QuoteEmptyFields will wrap empty fields in quotes if true QuoteEmptyFields bool + // QuoteRune can be set to override the default quote style + QuoteRune rune + // Whether the logger's out is to a terminal - isTerminal bool - terminalOnce sync.Once + isTerminal bool + + sync.Once } func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { @@ -75,7 +80,10 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { prefixFieldClashes(entry.Data) - f.terminalOnce.Do(func() { + f.Do(func() { + if f.QuoteRune == 0 || !utf8.ValidRune(f.QuoteRune) { + f.QuoteRune = '"' + } if entry.Logger != nil { f.isTerminal = IsTerminal(entry.Logger.Out) } @@ -164,14 +172,14 @@ func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { if !f.needsQuoting(value) { b.WriteString(value) } else { - fmt.Fprintf(b, "%q", value) + fmt.Fprintf(b, "%c%v%c", f.QuoteRune, value, f.QuoteRune) } case error: errmsg := value.Error() if !f.needsQuoting(errmsg) { b.WriteString(errmsg) } else { - fmt.Fprintf(b, "%q", errmsg) + fmt.Fprintf(b, "%c%v%c", f.QuoteRune, errmsg, f.QuoteRune) } default: fmt.Fprint(b, value) diff --git a/text_formatter_test.go b/text_formatter_test.go index 54fc8fe..fce4cfe 100644 --- a/text_formatter_test.go +++ b/text_formatter_test.go @@ -14,7 +14,7 @@ func TestQuoting(t *testing.T) { checkQuoting := func(q bool, value interface{}) { b, _ := tf.Format(WithField("test", value)) idx := bytes.Index(b, ([]byte)("test=")) - cont := bytes.Contains(b[idx+5:], []byte{'"'}) + cont := bytes.ContainsRune(b[idx+5:], tf.QuoteRune) if cont != q { if q { t.Errorf("quoting expected for: %#v", value) @@ -34,6 +34,14 @@ func TestQuoting(t *testing.T) { checkQuoting(false, errors.New("invalid")) checkQuoting(true, errors.New("invalid argument")) + // Test for custom quote rune. + tf.QuoteRune = '`' + checkQuoting(false, "") + checkQuoting(false, "abcd") + checkQuoting(true, "/foobar") + checkQuoting(true, errors.New("invalid argument")) + + // Test for quoting empty fields. tf.QuoteEmptyFields = true checkQuoting(true, "") } @@ -45,7 +53,8 @@ func TestTimestampFormat(t *testing.T) { timeStart := bytes.Index(customStr, ([]byte)("time=")) timeEnd := bytes.Index(customStr, ([]byte)("level=")) timeStr := customStr[timeStart+5 : timeEnd-1] - if timeStr[0] == '"' && timeStr[len(timeStr)-1] == '"' { + if timeStr[0] == byte(customFormatter.QuoteRune) && + timeStr[len(timeStr)-1] == byte(customFormatter.QuoteRune) { timeStr = timeStr[1 : len(timeStr)-1] } if format == "" {