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