// 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) } }