package layout import ( "fmt" "path/filepath" "regexp" "runtime" "strconv" "strings" "time" "github.com/ian-kent/go-log/levels" ) // http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html // DefaultTimeLayout is the default layout used by %d var DefaultTimeLayout = "2006-01-02 15:04:05.000000000 -0700 MST" // LegacyDefaultTimeLayout is the legacy (non-zero padded) time layout. // Set layout.DefaultTimeLayout = layout.LegacyDefaultTimeLayout to revert behaviour. var LegacyDefaultTimeLayout = "2006-01-02 15:04:05.999999999 -0700 MST" type patternLayout struct { Layout Pattern string created int64 re *regexp.Regexp } type caller struct { pc uintptr file string line int ok bool pkg string fullpkg string filename string } func Pattern(pattern string) *patternLayout { return &patternLayout{ Pattern: pattern, re: regexp.MustCompile("%(\\w|%)(?:{([^}]+)})?"), created: time.Now().UnixNano(), } } func getCaller() *caller { pc, file, line, ok := runtime.Caller(2) // TODO feels nasty? dir, fn := filepath.Split(file) bits := strings.Split(dir, "/") pkg := bits[len(bits)-2] if ok { return &caller{pc, file, line, ok, pkg, pkg, fn} } return nil } func (a *patternLayout) Format(level levels.LogLevel, message string, args ...interface{}) string { // TODO // padding, e.g. %20c, %-20c, %.30c, %20.30c, %-20.30c // %t - thread name // %M - function name caller := getCaller() r := time.Now().UnixNano() msg := a.re.ReplaceAllStringFunc(a.Pattern, func(m string) string { parts := a.re.FindStringSubmatch(m) switch parts[1] { // FIXME // %c and %C should probably return the logger name, not the package // name, since that's how the logger is created in the first place! case "c": return caller.pkg case "C": return caller.pkg case "d": // FIXME specifier, e.g. %d{HH:mm:ss,SSS} return time.Now().Format(DefaultTimeLayout) case "F": return caller.file case "l": return fmt.Sprintf("%s/%s:%d", caller.pkg, caller.filename, caller.line) case "L": return strconv.Itoa(caller.line) case "m": return fmt.Sprintf(message, args...) case "n": // FIXME platform-specific? return "\n" case "p": return levels.LogLevelsToString[level] case "r": return strconv.FormatInt((r-a.created)/100000, 10) case "x": return "" // NDC case "X": return "" // MDC (must specify key) case "%": return "%" } return m }) return msg }