467 lines
14 KiB
Go
467 lines
14 KiB
Go
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||
|
//
|
||
|
// All rights reserved.
|
||
|
//
|
||
|
// Redistribution and use in source and binary forms, with or without
|
||
|
// modification, are permitted provided that the following conditions are met:
|
||
|
//
|
||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||
|
// list of conditions and the following disclaimer.
|
||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||
|
// this list of conditions and the following disclaimer in the documentation
|
||
|
// and/or other materials provided with the distribution.
|
||
|
//
|
||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
|
||
|
package seelog
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"unicode"
|
||
|
"unicode/utf8"
|
||
|
)
|
||
|
|
||
|
// FormatterSymbol is a special symbol used in config files to mark special format aliases.
|
||
|
const (
|
||
|
FormatterSymbol = '%'
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
formatterParameterStart = '('
|
||
|
formatterParameterEnd = ')'
|
||
|
)
|
||
|
|
||
|
// Time and date formats used for %Date and %Time aliases.
|
||
|
const (
|
||
|
DateDefaultFormat = "2006-01-02"
|
||
|
TimeFormat = "15:04:05"
|
||
|
)
|
||
|
|
||
|
var DefaultMsgFormat = "%Ns [%Level] %Msg%n"
|
||
|
|
||
|
var (
|
||
|
DefaultFormatter *formatter
|
||
|
msgonlyformatter *formatter
|
||
|
)
|
||
|
|
||
|
func init() {
|
||
|
var err error
|
||
|
if DefaultFormatter, err = NewFormatter(DefaultMsgFormat); err != nil {
|
||
|
reportInternalError(fmt.Errorf("error during creating DefaultFormatter: %s", err))
|
||
|
}
|
||
|
if msgonlyformatter, err = NewFormatter("%Msg"); err != nil {
|
||
|
reportInternalError(fmt.Errorf("error during creating msgonlyformatter: %s", err))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// FormatterFunc represents one formatter object that starts with '%' sign in the 'format' attribute
|
||
|
// of the 'format' config item. These special symbols are replaced with context values or special
|
||
|
// strings when message is written to byte receiver.
|
||
|
//
|
||
|
// Check https://github.com/cihub/seelog/wiki/Formatting for details.
|
||
|
// Full list (with descriptions) of formatters: https://github.com/cihub/seelog/wiki/Format-reference
|
||
|
//
|
||
|
// FormatterFunc takes raw log message, level, log context and returns a string, number (of any type) or any object
|
||
|
// that can be evaluated as string.
|
||
|
type FormatterFunc func(message string, level LogLevel, context LogContextInterface) interface{}
|
||
|
|
||
|
// FormatterFuncCreator is a factory of FormatterFunc objects. It is used to generate parameterized
|
||
|
// formatters (such as %Date or %EscM) and custom user formatters.
|
||
|
type FormatterFuncCreator func(param string) FormatterFunc
|
||
|
|
||
|
var formatterFuncs = map[string]FormatterFunc{
|
||
|
"Level": formatterLevel,
|
||
|
"Lev": formatterLev,
|
||
|
"LEVEL": formatterLEVEL,
|
||
|
"LEV": formatterLEV,
|
||
|
"l": formatterl,
|
||
|
"Msg": formatterMsg,
|
||
|
"FullPath": formatterFullPath,
|
||
|
"File": formatterFile,
|
||
|
"RelFile": formatterRelFile,
|
||
|
"Func": FormatterFunction,
|
||
|
"FuncShort": FormatterFunctionShort,
|
||
|
"Line": formatterLine,
|
||
|
"Time": formatterTime,
|
||
|
"UTCTime": formatterUTCTime,
|
||
|
"Ns": formatterNs,
|
||
|
"UTCNs": formatterUTCNs,
|
||
|
"r": formatterr,
|
||
|
"n": formattern,
|
||
|
"t": formattert,
|
||
|
}
|
||
|
|
||
|
var formatterFuncsParameterized = map[string]FormatterFuncCreator{
|
||
|
"Date": createDateTimeFormatterFunc,
|
||
|
"UTCDate": createUTCDateTimeFormatterFunc,
|
||
|
"EscM": createANSIEscapeFunc,
|
||
|
}
|
||
|
|
||
|
func errorAliasReserved(name string) error {
|
||
|
return fmt.Errorf("cannot use '%s' as custom formatter name. Name is reserved", name)
|
||
|
}
|
||
|
|
||
|
// RegisterCustomFormatter registers a new custom formatter factory with a given name. If returned error is nil,
|
||
|
// then this name (prepended by '%' symbol) can be used in 'format' attributes in configuration and
|
||
|
// it will be treated like the standard parameterized formatter identifiers.
|
||
|
//
|
||
|
// RegisterCustomFormatter needs to be called before creating a logger for it to take effect. The general recommendation
|
||
|
// is to call it once in 'init' func of your application or any initializer func.
|
||
|
//
|
||
|
// For usage examples, check https://github.com/cihub/seelog/wiki/Custom-formatters.
|
||
|
//
|
||
|
// Name must only consist of letters (unicode.IsLetter).
|
||
|
//
|
||
|
// Name must not be one of the already registered standard formatter names
|
||
|
// (https://github.com/cihub/seelog/wiki/Format-reference) and previously registered
|
||
|
// custom format names. To avoid any potential name conflicts (in future releases), it is recommended
|
||
|
// to start your custom formatter name with a namespace (e.g. 'MyCompanySomething') or a 'Custom' keyword.
|
||
|
func RegisterCustomFormatter(name string, creator FormatterFuncCreator) error {
|
||
|
if _, ok := formatterFuncs[name]; ok {
|
||
|
return errorAliasReserved(name)
|
||
|
}
|
||
|
if _, ok := formatterFuncsParameterized[name]; ok {
|
||
|
return errorAliasReserved(name)
|
||
|
}
|
||
|
formatterFuncsParameterized[name] = creator
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// formatter is used to write messages in a specific format, inserting such additional data
|
||
|
// as log level, date/time, etc.
|
||
|
type formatter struct {
|
||
|
fmtStringOriginal string
|
||
|
fmtString string
|
||
|
formatterFuncs []FormatterFunc
|
||
|
}
|
||
|
|
||
|
// NewFormatter creates a new formatter using a format string
|
||
|
func NewFormatter(formatString string) (*formatter, error) {
|
||
|
fmtr := new(formatter)
|
||
|
fmtr.fmtStringOriginal = formatString
|
||
|
if err := buildFormatterFuncs(fmtr); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return fmtr, nil
|
||
|
}
|
||
|
|
||
|
func buildFormatterFuncs(formatter *formatter) error {
|
||
|
var (
|
||
|
fsbuf = new(bytes.Buffer)
|
||
|
fsolm1 = len(formatter.fmtStringOriginal) - 1
|
||
|
)
|
||
|
for i := 0; i <= fsolm1; i++ {
|
||
|
if char := formatter.fmtStringOriginal[i]; char != FormatterSymbol {
|
||
|
fsbuf.WriteByte(char)
|
||
|
continue
|
||
|
}
|
||
|
// Check if the index is at the end of the string.
|
||
|
if i == fsolm1 {
|
||
|
return fmt.Errorf("format error: %c cannot be last symbol", FormatterSymbol)
|
||
|
}
|
||
|
// Check if the formatter symbol is doubled and skip it as nonmatching.
|
||
|
if formatter.fmtStringOriginal[i+1] == FormatterSymbol {
|
||
|
fsbuf.WriteRune(FormatterSymbol)
|
||
|
i++
|
||
|
continue
|
||
|
}
|
||
|
function, ni, err := formatter.extractFormatterFunc(i + 1)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// Append formatting string "%v".
|
||
|
fsbuf.Write([]byte{37, 118})
|
||
|
i = ni
|
||
|
formatter.formatterFuncs = append(formatter.formatterFuncs, function)
|
||
|
}
|
||
|
formatter.fmtString = fsbuf.String()
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (formatter *formatter) extractFormatterFunc(index int) (FormatterFunc, int, error) {
|
||
|
letterSequence := formatter.extractLetterSequence(index)
|
||
|
if len(letterSequence) == 0 {
|
||
|
return nil, 0, fmt.Errorf("format error: lack of formatter after %c at %d", FormatterSymbol, index)
|
||
|
}
|
||
|
|
||
|
function, formatterLength, ok := formatter.findFormatterFunc(letterSequence)
|
||
|
if ok {
|
||
|
return function, index + formatterLength - 1, nil
|
||
|
}
|
||
|
|
||
|
function, formatterLength, ok, err := formatter.findFormatterFuncParametrized(letterSequence, index)
|
||
|
if err != nil {
|
||
|
return nil, 0, err
|
||
|
}
|
||
|
if ok {
|
||
|
return function, index + formatterLength - 1, nil
|
||
|
}
|
||
|
|
||
|
return nil, 0, errors.New("format error: unrecognized formatter at " + strconv.Itoa(index) + ": " + letterSequence)
|
||
|
}
|
||
|
|
||
|
func (formatter *formatter) extractLetterSequence(index int) string {
|
||
|
letters := ""
|
||
|
|
||
|
bytesToParse := []byte(formatter.fmtStringOriginal[index:])
|
||
|
runeCount := utf8.RuneCount(bytesToParse)
|
||
|
for i := 0; i < runeCount; i++ {
|
||
|
rune, runeSize := utf8.DecodeRune(bytesToParse)
|
||
|
bytesToParse = bytesToParse[runeSize:]
|
||
|
|
||
|
if unicode.IsLetter(rune) {
|
||
|
letters += string(rune)
|
||
|
} else {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
return letters
|
||
|
}
|
||
|
|
||
|
func (formatter *formatter) findFormatterFunc(letters string) (FormatterFunc, int, bool) {
|
||
|
currentVerb := letters
|
||
|
for i := 0; i < len(letters); i++ {
|
||
|
function, ok := formatterFuncs[currentVerb]
|
||
|
if ok {
|
||
|
return function, len(currentVerb), ok
|
||
|
}
|
||
|
currentVerb = currentVerb[:len(currentVerb)-1]
|
||
|
}
|
||
|
|
||
|
return nil, 0, false
|
||
|
}
|
||
|
|
||
|
func (formatter *formatter) findFormatterFuncParametrized(letters string, lettersStartIndex int) (FormatterFunc, int, bool, error) {
|
||
|
currentVerb := letters
|
||
|
for i := 0; i < len(letters); i++ {
|
||
|
functionCreator, ok := formatterFuncsParameterized[currentVerb]
|
||
|
if ok {
|
||
|
parameter := ""
|
||
|
parameterLen := 0
|
||
|
isVerbEqualsLetters := i == 0 // if not, then letter goes after formatter, and formatter is parameterless
|
||
|
if isVerbEqualsLetters {
|
||
|
userParameter := ""
|
||
|
var err error
|
||
|
userParameter, parameterLen, ok, err = formatter.findparameter(lettersStartIndex + len(currentVerb))
|
||
|
if ok {
|
||
|
parameter = userParameter
|
||
|
} else if err != nil {
|
||
|
return nil, 0, false, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return functionCreator(parameter), len(currentVerb) + parameterLen, true, nil
|
||
|
}
|
||
|
|
||
|
currentVerb = currentVerb[:len(currentVerb)-1]
|
||
|
}
|
||
|
|
||
|
return nil, 0, false, nil
|
||
|
}
|
||
|
|
||
|
func (formatter *formatter) findparameter(startIndex int) (string, int, bool, error) {
|
||
|
if len(formatter.fmtStringOriginal) == startIndex || formatter.fmtStringOriginal[startIndex] != formatterParameterStart {
|
||
|
return "", 0, false, nil
|
||
|
}
|
||
|
|
||
|
endIndex := strings.Index(formatter.fmtStringOriginal[startIndex:], string(formatterParameterEnd))
|
||
|
if endIndex == -1 {
|
||
|
return "", 0, false, fmt.Errorf("Unmatched parenthesis or invalid parameter at %d: %s",
|
||
|
startIndex, formatter.fmtStringOriginal[startIndex:])
|
||
|
}
|
||
|
endIndex += startIndex
|
||
|
|
||
|
length := endIndex - startIndex + 1
|
||
|
|
||
|
return formatter.fmtStringOriginal[startIndex+1 : endIndex], length, true, nil
|
||
|
}
|
||
|
|
||
|
// Format processes a message with special formatters, log level, and context. Returns formatted string
|
||
|
// with all formatter identifiers changed to appropriate values.
|
||
|
func (formatter *formatter) Format(message string, level LogLevel, context LogContextInterface) string {
|
||
|
if len(formatter.formatterFuncs) == 0 {
|
||
|
return formatter.fmtString
|
||
|
}
|
||
|
|
||
|
params := make([]interface{}, len(formatter.formatterFuncs))
|
||
|
for i, function := range formatter.formatterFuncs {
|
||
|
params[i] = function(message, level, context)
|
||
|
}
|
||
|
|
||
|
return fmt.Sprintf(formatter.fmtString, params...)
|
||
|
}
|
||
|
|
||
|
func (formatter *formatter) String() string {
|
||
|
return formatter.fmtStringOriginal
|
||
|
}
|
||
|
|
||
|
//=====================================================
|
||
|
|
||
|
const (
|
||
|
wrongLogLevel = "WRONG_LOGLEVEL"
|
||
|
wrongEscapeCode = "WRONG_ESCAPE"
|
||
|
)
|
||
|
|
||
|
var levelToString = map[LogLevel]string{
|
||
|
TraceLvl: "Trace",
|
||
|
DebugLvl: "Debug",
|
||
|
InfoLvl: "Info",
|
||
|
WarnLvl: "Warn",
|
||
|
ErrorLvl: "Error",
|
||
|
CriticalLvl: "Critical",
|
||
|
Off: "Off",
|
||
|
}
|
||
|
|
||
|
var levelToShortString = map[LogLevel]string{
|
||
|
TraceLvl: "Trc",
|
||
|
DebugLvl: "Dbg",
|
||
|
InfoLvl: "Inf",
|
||
|
WarnLvl: "Wrn",
|
||
|
ErrorLvl: "Err",
|
||
|
CriticalLvl: "Crt",
|
||
|
Off: "Off",
|
||
|
}
|
||
|
|
||
|
var levelToShortestString = map[LogLevel]string{
|
||
|
TraceLvl: "t",
|
||
|
DebugLvl: "d",
|
||
|
InfoLvl: "i",
|
||
|
WarnLvl: "w",
|
||
|
ErrorLvl: "e",
|
||
|
CriticalLvl: "c",
|
||
|
Off: "o",
|
||
|
}
|
||
|
|
||
|
func formatterLevel(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
levelStr, ok := levelToString[level]
|
||
|
if !ok {
|
||
|
return wrongLogLevel
|
||
|
}
|
||
|
return levelStr
|
||
|
}
|
||
|
|
||
|
func formatterLev(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
levelStr, ok := levelToShortString[level]
|
||
|
if !ok {
|
||
|
return wrongLogLevel
|
||
|
}
|
||
|
return levelStr
|
||
|
}
|
||
|
|
||
|
func formatterLEVEL(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return strings.ToTitle(formatterLevel(message, level, context).(string))
|
||
|
}
|
||
|
|
||
|
func formatterLEV(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return strings.ToTitle(formatterLev(message, level, context).(string))
|
||
|
}
|
||
|
|
||
|
func formatterl(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
levelStr, ok := levelToShortestString[level]
|
||
|
if !ok {
|
||
|
return wrongLogLevel
|
||
|
}
|
||
|
return levelStr
|
||
|
}
|
||
|
|
||
|
func formatterMsg(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return message
|
||
|
}
|
||
|
|
||
|
func formatterFullPath(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return context.FullPath()
|
||
|
}
|
||
|
|
||
|
func formatterFile(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return context.FileName()
|
||
|
}
|
||
|
|
||
|
func formatterRelFile(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return context.ShortPath()
|
||
|
}
|
||
|
|
||
|
func FormatterFunction(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return context.Func()
|
||
|
}
|
||
|
|
||
|
func FormatterFunctionShort(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
f := context.Func()
|
||
|
spl := strings.Split(f, ".")
|
||
|
return spl[len(spl)-1]
|
||
|
}
|
||
|
|
||
|
func formatterLine(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return context.Line()
|
||
|
}
|
||
|
|
||
|
func formatterTime(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return context.CallTime().Format(TimeFormat)
|
||
|
}
|
||
|
|
||
|
func formatterUTCTime(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return context.CallTime().UTC().Format(TimeFormat)
|
||
|
}
|
||
|
|
||
|
func formatterNs(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return context.CallTime().UnixNano()
|
||
|
}
|
||
|
|
||
|
func formatterUTCNs(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return context.CallTime().UTC().UnixNano()
|
||
|
}
|
||
|
|
||
|
func formatterr(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return "\r"
|
||
|
}
|
||
|
|
||
|
func formattern(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return "\n"
|
||
|
}
|
||
|
|
||
|
func formattert(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return "\t"
|
||
|
}
|
||
|
|
||
|
func createDateTimeFormatterFunc(dateTimeFormat string) FormatterFunc {
|
||
|
format := dateTimeFormat
|
||
|
if format == "" {
|
||
|
format = DateDefaultFormat
|
||
|
}
|
||
|
return func(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return context.CallTime().Format(format)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func createUTCDateTimeFormatterFunc(dateTimeFormat string) FormatterFunc {
|
||
|
format := dateTimeFormat
|
||
|
if format == "" {
|
||
|
format = DateDefaultFormat
|
||
|
}
|
||
|
return func(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
return context.CallTime().UTC().Format(format)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func createANSIEscapeFunc(escapeCodeString string) FormatterFunc {
|
||
|
return func(message string, level LogLevel, context LogContextInterface) interface{} {
|
||
|
if len(escapeCodeString) == 0 {
|
||
|
return wrongEscapeCode
|
||
|
}
|
||
|
|
||
|
return fmt.Sprintf("%c[%sm", 0x1B, escapeCodeString)
|
||
|
}
|
||
|
}
|