diff --git a/text_formatter.go b/text_formatter.go index b4dffa1..ba88854 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -49,9 +49,26 @@ type TextFormatter struct { // be desired. DisableSorting bool + // QuoteEmptyFields will wrap empty fields in quotes if true + QuoteEmptyFields bool + + // QuoteCharacter can be set to the override the default quoting character " + // with something else. For example: ', or `. + QuoteCharacter string + // Whether the logger's out is to a terminal - isTerminal bool - terminalOnce sync.Once + isTerminal bool + + sync.Once +} + +func (f *TextFormatter) init(entry *Entry) { + if len(f.QuoteCharacter) == 0 { + f.QuoteCharacter = "\"" + } + if entry.Logger != nil { + f.isTerminal = IsTerminal(entry.Logger.Out) + } } func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { @@ -72,11 +89,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { prefixFieldClashes(entry.Data) - f.terminalOnce.Do(func() { - if entry.Logger != nil { - f.isTerminal = IsTerminal(entry.Logger.Out) - } - }) + f.Do(func() { f.init(entry) }) isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors @@ -132,7 +145,10 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin } } -func needsQuoting(text string) bool { +func (f *TextFormatter) needsQuoting(text string) bool { + if f.QuoteEmptyFields && len(text) == 0 { + return true + } for _, ch := range text { if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || @@ -155,17 +171,17 @@ func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interf func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { switch value := value.(type) { case string: - if !needsQuoting(value) { + if !f.needsQuoting(value) { b.WriteString(value) } else { - fmt.Fprintf(b, "%q", value) + fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, value, f.QuoteCharacter) } case error: errmsg := value.Error() - if !needsQuoting(errmsg) { + if !f.needsQuoting(errmsg) { b.WriteString(errmsg) } else { - fmt.Fprintf(b, "%q", errmsg) + fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, errmsg, f.QuoteCharacter) } default: fmt.Fprint(b, value) diff --git a/text_formatter_test.go b/text_formatter_test.go index 107703f..9793b5f 100644 --- a/text_formatter_test.go +++ b/text_formatter_test.go @@ -3,9 +3,9 @@ package logrus import ( "bytes" "errors" + "strings" "testing" "time" - "strings" ) func TestQuoting(t *testing.T) { @@ -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.Contains(b[idx+5:], []byte(tf.QuoteCharacter)) if cont != q { if q { t.Errorf("quoting expected for: %#v", value) @@ -24,6 +24,7 @@ func TestQuoting(t *testing.T) { } } + checkQuoting(false, "") checkQuoting(false, "abcd") checkQuoting(false, "v1.0") checkQuoting(false, "1234567890") @@ -32,6 +33,24 @@ func TestQuoting(t *testing.T) { checkQuoting(true, "x,y") checkQuoting(false, errors.New("invalid")) checkQuoting(true, errors.New("invalid argument")) + + // Test for custom quote character. + tf.QuoteCharacter = "`" + checkQuoting(false, "") + checkQuoting(false, "abcd") + checkQuoting(true, "/foobar") + checkQuoting(true, errors.New("invalid argument")) + + // Test for multi-character quotes. + tf.QuoteCharacter = "§~±" + checkQuoting(false, "abcd") + checkQuoting(true, errors.New("invalid argument")) + + // Test for quoting empty fields. + tf.QuoteEmptyFields = true + checkQuoting(true, "") + checkQuoting(false, "abcd") + checkQuoting(true, errors.New("invalid argument")) } func TestTimestampFormat(t *testing.T) { @@ -40,10 +59,7 @@ func TestTimestampFormat(t *testing.T) { customStr, _ := customFormatter.Format(WithField("test", "test")) 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] == '"' { - timeStr = timeStr[1 : len(timeStr)-1] - } + timeStr := customStr[timeStart+5+len(customFormatter.QuoteCharacter) : timeEnd-1-len(customFormatter.QuoteCharacter)] if format == "" { format = time.RFC3339 }