1package layout 2 3import ( 4 "fmt" 5 "path/filepath" 6 "regexp" 7 "runtime" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/ian-kent/go-log/levels" 13) 14 15// http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html 16 17// DefaultTimeLayout is the default layout used by %d 18var DefaultTimeLayout = "2006-01-02 15:04:05.000000000 -0700 MST" 19 20// LegacyDefaultTimeLayout is the legacy (non-zero padded) time layout. 21// Set layout.DefaultTimeLayout = layout.LegacyDefaultTimeLayout to revert behaviour. 22var LegacyDefaultTimeLayout = "2006-01-02 15:04:05.999999999 -0700 MST" 23 24type patternLayout struct { 25 Layout 26 Pattern string 27 created int64 28 re *regexp.Regexp 29} 30 31type caller struct { 32 pc uintptr 33 file string 34 line int 35 ok bool 36 pkg string 37 fullpkg string 38 filename string 39} 40 41func Pattern(pattern string) *patternLayout { 42 return &patternLayout{ 43 Pattern: pattern, 44 re: regexp.MustCompile("%(\\w|%)(?:{([^}]+)})?"), 45 created: time.Now().UnixNano(), 46 } 47} 48 49func getCaller() *caller { 50 pc, file, line, ok := runtime.Caller(2) 51 52 // TODO feels nasty? 53 dir, fn := filepath.Split(file) 54 bits := strings.Split(dir, "/") 55 pkg := bits[len(bits)-2] 56 57 if ok { 58 return &caller{pc, file, line, ok, pkg, pkg, fn} 59 } 60 return nil 61} 62 63func (a *patternLayout) Format(level levels.LogLevel, message string, args ...interface{}) string { 64 65 // TODO 66 // padding, e.g. %20c, %-20c, %.30c, %20.30c, %-20.30c 67 // %t - thread name 68 // %M - function name 69 70 caller := getCaller() 71 r := time.Now().UnixNano() 72 73 msg := a.re.ReplaceAllStringFunc(a.Pattern, func(m string) string { 74 parts := a.re.FindStringSubmatch(m) 75 switch parts[1] { 76 // FIXME 77 // %c and %C should probably return the logger name, not the package 78 // name, since that's how the logger is created in the first place! 79 case "c": 80 return caller.pkg 81 case "C": 82 return caller.pkg 83 case "d": 84 // FIXME specifier, e.g. %d{HH:mm:ss,SSS} 85 return time.Now().Format(DefaultTimeLayout) 86 case "F": 87 return caller.file 88 case "l": 89 return fmt.Sprintf("%s/%s:%d", caller.pkg, caller.filename, caller.line) 90 case "L": 91 return strconv.Itoa(caller.line) 92 case "m": 93 return fmt.Sprintf(message, args...) 94 case "n": 95 // FIXME platform-specific? 96 return "\n" 97 case "p": 98 return levels.LogLevelsToString[level] 99 case "r": 100 return strconv.FormatInt((r-a.created)/100000, 10) 101 case "x": 102 return "" // NDC 103 case "X": 104 return "" // MDC (must specify key) 105 case "%": 106 return "%" 107 } 108 return m 109 }) 110 111 return msg 112} 113