...

Source file src/gitlab.com/tymonx/go-logger/logger/formatter.go

Documentation: gitlab.com/tymonx/go-logger/logger

     1  // Copyright 2020 Tymoteusz Blazejczyk
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package logger
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"os"
    21  	"path/filepath"
    22  	"reflect"
    23  	"strconv"
    24  	"strings"
    25  	"sync"
    26  	"text/template"
    27  	"time"
    28  )
    29  
    30  // These constants define default values for Formatter.
    31  const (
    32  	DefaultDateFormat  = "{year}-{month}-{day} {hour}:{minute}:{second},{millisecond}"
    33  	DefaultFormat      = "{date} - {Level | printf \"%-8s\"} - {file}:{line}:{function}(): {message}"
    34  	DefaultPlaceholder = "p"
    35  
    36  	kilo       = 1e3
    37  	mega       = 1e6
    38  	percentage = 100
    39  )
    40  
    41  // FormatterFuncs defines map of template functions.
    42  type FormatterFuncs map[string]interface{}
    43  
    44  // A Formatter represents a formatter object used by log handler to format log
    45  // message.
    46  type Formatter struct {
    47  	format        string
    48  	dateFormat    string
    49  	template      *template.Template
    50  	placeholder   string
    51  	timeBuffer    *bytes.Buffer
    52  	formatBuffer  *bytes.Buffer
    53  	messageBuffer *bytes.Buffer
    54  	mutex         sync.RWMutex
    55  	usedArguments map[int]bool
    56  }
    57  
    58  // NewFormatter creates a new Formatter object with default format settings.
    59  func NewFormatter() *Formatter {
    60  	f := &Formatter{
    61  		format:        DefaultFormat,
    62  		dateFormat:    DefaultDateFormat,
    63  		template:      template.New("").Delims("{", "}"),
    64  		placeholder:   DefaultPlaceholder,
    65  		timeBuffer:    new(bytes.Buffer),
    66  		formatBuffer:  new(bytes.Buffer),
    67  		messageBuffer: new(bytes.Buffer),
    68  	}
    69  
    70  	return f
    71  }
    72  
    73  // Reset resets Formatter.
    74  func (f *Formatter) Reset() *Formatter {
    75  	f.mutex.Lock()
    76  	defer f.mutex.Unlock()
    77  
    78  	f.format = DefaultFormat
    79  	f.dateFormat = DefaultDateFormat
    80  	f.placeholder = DefaultPlaceholder
    81  
    82  	return f
    83  }
    84  
    85  // SetPlaceholder sets placeholder string prefix used for automatic and
    86  // positional placeholders to format log message.
    87  func (f *Formatter) SetPlaceholder(placeholder string) *Formatter {
    88  	f.mutex.Lock()
    89  	defer f.mutex.Unlock()
    90  
    91  	f.placeholder = placeholder
    92  
    93  	return f
    94  }
    95  
    96  // GetPlaceholder returns placeholder string prefix used for automatic and
    97  // positional placeholders to format log message.
    98  func (f *Formatter) GetPlaceholder() string {
    99  	f.mutex.RLock()
   100  	defer f.mutex.RUnlock()
   101  
   102  	return f.placeholder
   103  }
   104  
   105  // AddFuncs adds template functions to format log message.
   106  func (f *Formatter) AddFuncs(funcs FormatterFuncs) *Formatter {
   107  	f.mutex.Lock()
   108  	defer f.mutex.Unlock()
   109  
   110  	f.template.Funcs(template.FuncMap(funcs))
   111  
   112  	return f
   113  }
   114  
   115  // SetFormat sets format string used for formatting log message.
   116  func (f *Formatter) SetFormat(format string) *Formatter {
   117  	f.mutex.Lock()
   118  	defer f.mutex.Unlock()
   119  
   120  	f.format = format
   121  
   122  	return f
   123  }
   124  
   125  // GetFormat returns format string used for formatting log message.
   126  func (f *Formatter) GetFormat() string {
   127  	f.mutex.RLock()
   128  	defer f.mutex.RUnlock()
   129  
   130  	return f.format
   131  }
   132  
   133  // SetDateFormat sets format string used for formatting date in log message.
   134  func (f *Formatter) SetDateFormat(dateFormat string) *Formatter {
   135  	f.mutex.Lock()
   136  	defer f.mutex.Unlock()
   137  
   138  	f.dateFormat = dateFormat
   139  
   140  	return f
   141  }
   142  
   143  // GetDateFormat returns format string used for formatting date in log message.
   144  func (f *Formatter) GetDateFormat() string {
   145  	f.mutex.RLock()
   146  	defer f.mutex.RUnlock()
   147  
   148  	return f.format
   149  }
   150  
   151  // Format returns formatted log message string based on provided log record
   152  // object.
   153  func (f *Formatter) Format(record *Record) (string, error) {
   154  	f.mutex.Lock()
   155  	defer f.mutex.Unlock()
   156  
   157  	f.template.Funcs(f.getRecordFuncs(record))
   158  
   159  	message, err := f.formatString(f.template, f.formatBuffer, f.format, nil)
   160  
   161  	if err != nil {
   162  		return "", NewRuntimeError("cannot format record", err)
   163  	}
   164  
   165  	return message, nil
   166  }
   167  
   168  // FormatTime returns formatted date string based on provided log record object.
   169  func (f *Formatter) FormatTime(record *Record) (string, error) {
   170  	f.mutex.Lock()
   171  	defer f.mutex.Unlock()
   172  
   173  	f.template.Funcs(f.getRecordFuncs(record))
   174  
   175  	message, err := f.formatString(f.template, f.timeBuffer, f.dateFormat, nil)
   176  
   177  	if err != nil {
   178  		return "", NewRuntimeError("cannot format time", err)
   179  	}
   180  
   181  	return message, nil
   182  }
   183  
   184  // FormatMessage returns formatted user message string based on provided log
   185  // record object.
   186  func (f *Formatter) FormatMessage(record *Record) (string, error) {
   187  	f.mutex.Lock()
   188  	defer f.mutex.Unlock()
   189  
   190  	message, err := f.formatMessageRecord(record)
   191  
   192  	if err != nil {
   193  		return "", NewRuntimeError("cannot format message", err)
   194  	}
   195  
   196  	return message, nil
   197  }
   198  
   199  // formatMessageUnsafe returns formatted user message string based on provided log
   200  // record object.
   201  func (f *Formatter) formatMessageRecord(record *Record) (string, error) {
   202  	if len(record.Arguments) == 0 {
   203  		return record.Message, nil
   204  	}
   205  
   206  	var err error
   207  
   208  	var object interface{}
   209  
   210  	message := record.Message
   211  
   212  	f.usedArguments = make(map[int]bool)
   213  
   214  	funcMap := make(template.FuncMap)
   215  
   216  	funcMap[f.placeholder] = f.argumentAutomatic(record)
   217  
   218  	for position, argument := range record.Arguments {
   219  		placeholder := f.placeholder + strconv.Itoa(position)
   220  
   221  		funcMap[placeholder] = f.argumentValue(position, argument)
   222  
   223  		valueOf := reflect.ValueOf(argument)
   224  
   225  		switch valueOf.Kind() {
   226  		case reflect.Map:
   227  			if reflect.TypeOf(argument).Key().Kind() == reflect.String {
   228  				for _, key := range valueOf.MapKeys() {
   229  					funcMap[key.String()] = f.argumentValue(position, valueOf.MapIndex(key).Interface())
   230  				}
   231  			}
   232  		case reflect.Struct:
   233  			object = argument
   234  		}
   235  	}
   236  
   237  	if message, err = f.formatString(
   238  		template.New("").Delims("{", "}").Funcs(f.getRecordFuncs(record)).Funcs(funcMap),
   239  		f.messageBuffer,
   240  		message,
   241  		object,
   242  	); err != nil {
   243  		return "", err
   244  	}
   245  
   246  	if len(f.usedArguments) >= len(record.Arguments) {
   247  		return message, nil
   248  	}
   249  
   250  	for position, argument := range record.Arguments {
   251  		if !f.isArgumentUsed(position, argument) {
   252  			if message != "" {
   253  				message += " "
   254  			}
   255  
   256  			message += fmt.Sprint(argument)
   257  		}
   258  	}
   259  
   260  	return message, nil
   261  }
   262  
   263  func (f *Formatter) isArgumentUsed(position int, argument interface{}) bool {
   264  	valueOf := reflect.ValueOf(argument)
   265  
   266  	switch valueOf.Kind() {
   267  	case reflect.Map:
   268  		if reflect.TypeOf(argument).Key().Kind() == reflect.String {
   269  			return true
   270  		}
   271  	case reflect.Struct:
   272  		return true
   273  	}
   274  
   275  	return f.usedArguments[position]
   276  }
   277  
   278  // argumentValue returns closure that returns log argument used in log message.
   279  func (f *Formatter) argumentValue(position int, argument interface{}) func() interface{} {
   280  	return func() interface{} {
   281  		f.usedArguments[position] = true
   282  		return argument
   283  	}
   284  }
   285  
   286  // argumentAutomatic returns closure that returns log argument from automatic
   287  // placeholder used in log message.
   288  func (f *Formatter) argumentAutomatic(record *Record) func() interface{} {
   289  	position := 0
   290  	arguments := len(record.Arguments)
   291  
   292  	return func() interface{} {
   293  		var argument interface{}
   294  
   295  		if position < arguments {
   296  			f.usedArguments[position] = true
   297  			argument = record.Arguments[position]
   298  			position++
   299  		}
   300  
   301  		return argument
   302  	}
   303  }
   304  
   305  // formatString returns formatted string.
   306  func (*Formatter) formatString(templ *template.Template, buffer *bytes.Buffer, format string, object interface{}) (string, error) {
   307  	var message string
   308  
   309  	if format != "" {
   310  		var err error
   311  
   312  		templ, err = templ.Parse(format)
   313  
   314  		if err != nil {
   315  			return "", NewRuntimeError("cannot parse text template", err)
   316  		}
   317  
   318  		buffer.Reset()
   319  
   320  		err = templ.Execute(buffer, object)
   321  
   322  		if err != nil {
   323  			return "", NewRuntimeError("cannot execute text template", err)
   324  		}
   325  
   326  		message = buffer.String()
   327  	}
   328  
   329  	return message, nil
   330  }
   331  
   332  // getRecordFuncs sets default template functions used to formatting log message.
   333  func (f *Formatter) getRecordFuncs(record *Record) template.FuncMap {
   334  	return template.FuncMap{
   335  		"uid":       os.Getuid,
   336  		"gid":       os.Getgid,
   337  		"pid":       os.Getpid,
   338  		"egid":      os.Getegid,
   339  		"euid":      os.Geteuid,
   340  		"ppid":      os.Getppid,
   341  		"getEnv":    os.Getenv,
   342  		"expandEnv": os.ExpandEnv,
   343  		"executable": func() string {
   344  			return filepath.Base(os.Args[0])
   345  		},
   346  		"date": func() string {
   347  			date, err := f.formatString(f.template, f.timeBuffer, f.dateFormat, nil)
   348  
   349  			if err != nil {
   350  				printError(NewRuntimeError("cannot format date", err))
   351  			}
   352  
   353  			return date
   354  		},
   355  		"message": func() string {
   356  			message, err := f.formatMessageRecord(record)
   357  
   358  			if err != nil {
   359  				printError(NewRuntimeError("cannot format message", err))
   360  			}
   361  
   362  			return message
   363  		},
   364  		"levelValue": func() int {
   365  			return record.Level.Value
   366  		},
   367  		"level": func() string {
   368  			return strings.ToLower(record.Level.Name)
   369  		},
   370  		"Level": func() string {
   371  			return strings.Title(strings.ToLower(record.Level.Name))
   372  		},
   373  		"LEVEL": func() string {
   374  			return strings.ToUpper(record.Level.Name)
   375  		},
   376  		"iso8601": func() string {
   377  			return record.Time.Format(time.RFC3339)
   378  		},
   379  		"id": func() interface{} {
   380  			return record.ID
   381  		},
   382  		"name": func() string {
   383  			return record.Name
   384  		},
   385  		"host": func() string {
   386  			return record.Address
   387  		},
   388  		"hostname": func() string {
   389  			return record.Hostname
   390  		},
   391  		"address": func() string {
   392  			return record.Address
   393  		},
   394  		"nanosecond": func() string {
   395  			return fmt.Sprintf("%09d", record.Time.Nanosecond())
   396  		},
   397  		"microsecond": func() string {
   398  			return fmt.Sprintf("%06d", record.Time.Nanosecond()/kilo)
   399  		},
   400  		"millisecond": func() string {
   401  			return fmt.Sprintf("%03d", record.Time.Nanosecond()/mega)
   402  		},
   403  		"second": func() string {
   404  			return fmt.Sprintf("%02d", record.Time.Second())
   405  		},
   406  		"minute": func() string {
   407  			return fmt.Sprintf("%02d", record.Time.Minute())
   408  		},
   409  		"hour": func() string {
   410  			return fmt.Sprintf("%02d", record.Time.Hour())
   411  		},
   412  		"day": func() string {
   413  			return fmt.Sprintf("%02d", record.Time.Day())
   414  		},
   415  		"month": func() string {
   416  			return fmt.Sprintf("%02d", record.Time.Month())
   417  		},
   418  		"YEAR": func() string {
   419  			return fmt.Sprintf("%02d", record.Time.Year()%percentage)
   420  		},
   421  		"year": func() int {
   422  			return record.Time.Year()
   423  		},
   424  		"file": func() string {
   425  			return record.File.Name
   426  		},
   427  		"line": func() int {
   428  			return record.File.Line
   429  		},
   430  		"function": func() string {
   431  			return record.File.Function
   432  		},
   433  	}
   434  }
   435  

View as plain text