package logfmt
import (
"bytes"
"encoding"
"errors"
"fmt"
"io"
"reflect"
"strings"
"unicode/utf8"
)
func MarshalKeyvals (keyvals ...interface {}) ([]byte , error ) {
buf := &bytes .Buffer {}
if err := NewEncoder (buf ).EncodeKeyvals (keyvals ...); err != nil {
return nil , err
}
return buf .Bytes (), nil
}
type Encoder struct {
w io .Writer
scratch bytes .Buffer
needSep bool
}
func NewEncoder (w io .Writer ) *Encoder {
return &Encoder {
w : w ,
}
}
var (
space = []byte (" " )
equals = []byte ("=" )
newline = []byte ("\n" )
null = []byte ("null" )
)
func (enc *Encoder ) EncodeKeyval (key , value interface {}) error {
enc .scratch .Reset ()
if enc .needSep {
if _ , err := enc .scratch .Write (space ); err != nil {
return err
}
}
if err := writeKey (&enc .scratch , key ); err != nil {
return err
}
if _ , err := enc .scratch .Write (equals ); err != nil {
return err
}
if err := writeValue (&enc .scratch , value ); err != nil {
return err
}
_ , err := enc .w .Write (enc .scratch .Bytes ())
enc .needSep = true
return err
}
func (enc *Encoder ) EncodeKeyvals (keyvals ...interface {}) error {
if len (keyvals ) == 0 {
return nil
}
if len (keyvals )%2 == 1 {
keyvals = append (keyvals , nil )
}
for i := 0 ; i < len (keyvals ); i += 2 {
k , v := keyvals [i ], keyvals [i +1 ]
err := enc .EncodeKeyval (k , v )
if err == ErrUnsupportedKeyType {
continue
}
if _ , ok := err .(*MarshalerError ); ok || err == ErrUnsupportedValueType {
v = err
err = enc .EncodeKeyval (k , v )
}
if err != nil {
return err
}
}
return nil
}
type MarshalerError struct {
Type reflect .Type
Err error
}
func (e *MarshalerError ) Error () string {
return "error marshaling value of type " + e .Type .String () + ": " + e .Err .Error()
}
var ErrNilKey = errors .New ("nil key" )
var ErrInvalidKey = errors .New ("invalid key" )
var ErrUnsupportedKeyType = errors .New ("unsupported key type" )
var ErrUnsupportedValueType = errors .New ("unsupported value type" )
func writeKey(w io .Writer , key interface {}) error {
if key == nil {
return ErrNilKey
}
switch k := key .(type ) {
case string :
return writeStringKey (w , k )
case []byte :
if k == nil {
return ErrNilKey
}
return writeBytesKey (w , k )
case encoding .TextMarshaler :
kb , err := safeMarshal (k )
if err != nil {
return err
}
if kb == nil {
return ErrNilKey
}
return writeBytesKey (w , kb )
case fmt .Stringer :
ks , ok := safeString (k )
if !ok {
return ErrNilKey
}
return writeStringKey (w , ks )
default :
rkey := reflect .ValueOf (key )
switch rkey .Kind () {
case reflect .Array , reflect .Chan , reflect .Func , reflect .Map , reflect .Slice , reflect .Struct :
return ErrUnsupportedKeyType
case reflect .Ptr :
if rkey .IsNil () {
return ErrNilKey
}
return writeKey (w , rkey .Elem ().Interface ())
}
return writeStringKey (w , fmt .Sprint (k ))
}
}
func keyRuneFilter(r rune ) rune {
if r <= ' ' || r == '=' || r == '"' || r == utf8 .RuneError {
return -1
}
return r
}
func writeStringKey(w io .Writer , key string ) error {
k := strings .Map (keyRuneFilter , key )
if k == "" {
return ErrInvalidKey
}
_ , err := io .WriteString (w , k )
return err
}
func writeBytesKey(w io .Writer , key []byte ) error {
k := bytes .Map (keyRuneFilter , key )
if len (k ) == 0 {
return ErrInvalidKey
}
_ , err := w .Write (k )
return err
}
func writeValue(w io .Writer , value interface {}) error {
switch v := value .(type ) {
case nil :
return writeBytesValue (w , null )
case string :
return writeStringValue (w , v , true )
case []byte :
return writeBytesValue (w , v )
case encoding .TextMarshaler :
vb , err := safeMarshal (v )
if err != nil {
return err
}
if vb == nil {
vb = null
}
return writeBytesValue (w , vb )
case error :
se , ok := safeError (v )
return writeStringValue (w , se , ok )
case fmt .Stringer :
ss , ok := safeString (v )
return writeStringValue (w , ss , ok )
default :
rvalue := reflect .ValueOf (value )
switch rvalue .Kind () {
case reflect .Array , reflect .Chan , reflect .Func , reflect .Map , reflect .Slice , reflect .Struct :
return ErrUnsupportedValueType
case reflect .Ptr :
if rvalue .IsNil () {
return writeBytesValue (w , null )
}
return writeValue (w , rvalue .Elem ().Interface ())
}
return writeStringValue (w , fmt .Sprint (v ), true )
}
}
func needsQuotedValueRune(r rune ) bool {
return r <= ' ' || r == '=' || r == '"' || r == utf8 .RuneError
}
func writeStringValue(w io .Writer , value string , ok bool ) error {
var err error
if ok && value == "null" {
_, err = io .WriteString (w , `"null"` )
} else if strings .IndexFunc (value , needsQuotedValueRune ) != -1 {
_, err = writeQuotedString (w , value )
} else {
_, err = io .WriteString (w , value )
}
return err
}
func writeBytesValue(w io .Writer , value []byte ) error {
var err error
if bytes .IndexFunc (value , needsQuotedValueRune ) != -1 {
_, err = writeQuotedBytes (w , value )
} else {
_, err = w .Write (value )
}
return err
}
func (enc *Encoder ) EndRecord () error {
_ , err := enc .w .Write (newline )
if err == nil {
enc .needSep = false
}
return err
}
func (enc *Encoder ) Reset () {
enc .needSep = false
}
func safeError(err error ) (s string , ok bool ) {
defer func () {
if panicVal := recover (); panicVal != nil {
if v := reflect .ValueOf (err ); v .Kind () == reflect .Ptr && v .IsNil () {
s , ok = "null" , false
} else {
s , ok = fmt .Sprintf ("PANIC:%v" , panicVal ), false
}
}
}()
s , ok = err .Error(), true
return
}
func safeString(str fmt .Stringer ) (s string , ok bool ) {
defer func () {
if panicVal := recover (); panicVal != nil {
if v := reflect .ValueOf (str ); v .Kind () == reflect .Ptr && v .IsNil () {
s , ok = "null" , false
} else {
s , ok = fmt .Sprintf ("PANIC:%v" , panicVal ), true
}
}
}()
s , ok = str .String (), true
return
}
func safeMarshal(tm encoding .TextMarshaler ) (b []byte , err error ) {
defer func () {
if panicVal := recover (); panicVal != nil {
if v := reflect .ValueOf (tm ); v .Kind () == reflect .Ptr && v .IsNil () {
b , err = nil , nil
} else {
b , err = nil , fmt .Errorf ("panic when marshalling: %s" , panicVal )
}
}
}()
b , err = tm .MarshalText ()
if err != nil {
return nil , &MarshalerError {
Type : reflect .TypeOf (tm ),
Err : err ,
}
}
return
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .