1// Copyright (C) 2014 Space Monkey, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//   http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package spacelog
16
17import (
18	"regexp"
19	"runtime"
20	"strings"
21	"sync"
22	"text/template"
23)
24
25var (
26	// If set, these prefixes will be stripped out of automatic logger names.
27	IgnoredPrefixes []string
28
29	badChars = regexp.MustCompile("[^a-zA-Z0-9_.-]")
30	slashes  = regexp.MustCompile("[/]")
31)
32
33func callerName() string {
34	pc, _, _, ok := runtime.Caller(2)
35	if !ok {
36		return "unknown.unknown"
37	}
38	f := runtime.FuncForPC(pc)
39	if f == nil {
40		return "unknown.unknown"
41	}
42	name := f.Name()
43	for _, prefix := range IgnoredPrefixes {
44		name = strings.TrimPrefix(name, prefix)
45	}
46	return badChars.ReplaceAllLiteralString(
47		slashes.ReplaceAllLiteralString(name, "."), "_")
48}
49
50// LoggerCollections contain all of the loggers a program might use. Typically
51// a codebase will just use the default logger collection.
52type LoggerCollection struct {
53	mtx     sync.Mutex
54	loggers map[string]*Logger
55	level   LogLevel
56	handler Handler
57}
58
59// NewLoggerCollection creates a new logger collection. It's unlikely you will
60// ever practically need this method. Use the DefaultLoggerCollection instead.
61func NewLoggerCollection() *LoggerCollection {
62	return &LoggerCollection{
63		loggers: make(map[string]*Logger),
64		level:   DefaultLevel,
65		handler: defaultHandler}
66}
67
68// GetLogger returns a new Logger with a name automatically generated using
69// the callstack. If you want to avoid automatic name generation check out
70// GetLoggerNamed
71func (c *LoggerCollection) GetLogger() *Logger {
72	return c.GetLoggerNamed(callerName())
73}
74
75func (c *LoggerCollection) getLogger(name string, level LogLevel,
76	handler Handler) *Logger {
77	c.mtx.Lock()
78	defer c.mtx.Unlock()
79
80	logger, exists := c.loggers[name]
81	if !exists {
82		logger = &Logger{level: level,
83			collection: c,
84			name:       name,
85			handler:    handler}
86		c.loggers[name] = logger
87	}
88	return logger
89}
90
91// ConfigureLoggers configures loggers according to the given string
92// specification, which specifies a set of loggers and their associated
93// logging levels.  Loggers are semicolon-separated; each
94// configuration is specified as <logger>=<level>.  White space outside of
95// logger names and levels is ignored.  The default level is specified
96// with the name "DEFAULT".
97//
98// An example specification:
99//	`DEFAULT=ERROR; foo.bar=WARNING`
100func (c *LoggerCollection) ConfigureLoggers(specification string) error {
101	confs := strings.Split(strings.TrimSpace(specification), ";")
102	for i := range confs {
103		conf := strings.SplitN(confs[i], "=", 2)
104		levelstr := strings.TrimSpace(conf[1])
105		name := strings.TrimSpace(conf[0])
106		level, err := LevelFromString(levelstr)
107		if err != nil {
108			return err
109		}
110		if name == "DEFAULT" {
111			c.SetLevel(nil, level)
112			continue
113		}
114		logger := c.GetLoggerNamed(name)
115		logger.setLevel(level)
116	}
117	return nil
118}
119
120// GetLoggerNamed returns a new Logger with the provided name. GetLogger is
121// more frequently used.
122func (c *LoggerCollection) GetLoggerNamed(name string) *Logger {
123	c.mtx.Lock()
124	defer c.mtx.Unlock()
125
126	logger, exists := c.loggers[name]
127	if !exists {
128		logger = &Logger{level: c.level,
129			collection: c,
130			name:       name,
131			handler:    c.handler}
132		c.loggers[name] = logger
133	}
134	return logger
135}
136
137// SetLevel will set the current log level for all loggers with names that
138// match a provided regular expression. If the regular expression is nil, then
139// all loggers match.
140func (c *LoggerCollection) SetLevel(re *regexp.Regexp, level LogLevel) {
141	c.mtx.Lock()
142	defer c.mtx.Unlock()
143
144	if re == nil {
145		c.level = level
146	}
147	for name, logger := range c.loggers {
148		if re == nil || re.MatchString(name) {
149			logger.setLevel(level)
150		}
151	}
152}
153
154// SetHandler will set the current log handler for all loggers with names that
155// match a provided regular expression. If the regular expression is nil, then
156// all loggers match.
157func (c *LoggerCollection) SetHandler(re *regexp.Regexp, handler Handler) {
158	c.mtx.Lock()
159	defer c.mtx.Unlock()
160
161	if re == nil {
162		c.handler = handler
163	}
164	for name, logger := range c.loggers {
165		if re == nil || re.MatchString(name) {
166			logger.setHandler(handler)
167		}
168	}
169}
170
171// SetTextTemplate will set the current text template for all loggers with
172// names that match a provided regular expression. If the regular expression
173// is nil, then all loggers match. Note that not every handler is guaranteed
174// to support text templates and a text template will only apply to
175// text-oriented and unstructured handlers.
176func (c *LoggerCollection) SetTextTemplate(re *regexp.Regexp,
177	t *template.Template) {
178	c.mtx.Lock()
179	defer c.mtx.Unlock()
180
181	if re == nil {
182		c.handler.SetTextTemplate(t)
183	}
184	for name, logger := range c.loggers {
185		if re == nil || re.MatchString(name) {
186			logger.getHandler().SetTextTemplate(t)
187		}
188	}
189}
190
191// SetTextOutput will set the current output interface for all loggers with
192// names that match a provided regular expression. If the regular expression
193// is nil, then all loggers match. Note that not every handler is guaranteed
194// to support text output and a text output interface will only apply to
195// text-oriented and unstructured handlers.
196func (c *LoggerCollection) SetTextOutput(re *regexp.Regexp,
197	output TextOutput) {
198	c.mtx.Lock()
199	defer c.mtx.Unlock()
200
201	if re == nil {
202		c.handler.SetTextOutput(output)
203	}
204	for name, logger := range c.loggers {
205		if re == nil || re.MatchString(name) {
206			logger.getHandler().SetTextOutput(output)
207		}
208	}
209}
210
211var (
212	// It's unlikely you'll need to use this directly
213	DefaultLoggerCollection = NewLoggerCollection()
214)
215
216// GetLogger returns an automatically-named logger on the default logger
217// collection.
218func GetLogger() *Logger {
219	return DefaultLoggerCollection.GetLoggerNamed(callerName())
220}
221
222// GetLoggerNamed returns a new Logger with the provided name on the default
223// logger collection. GetLogger is more frequently used.
224func GetLoggerNamed(name string) *Logger {
225	return DefaultLoggerCollection.GetLoggerNamed(name)
226}
227
228// ConfigureLoggers configures loggers according to the given string
229// specification, which specifies a set of loggers and their associated
230// logging levels.  Loggers are colon- or semicolon-separated; each
231// configuration is specified as <logger>=<level>.  White space outside of
232// logger names and levels is ignored.  The DEFAULT module is specified
233// with the name "DEFAULT".
234//
235// An example specification:
236//	`DEFAULT=ERROR; foo.bar=WARNING`
237func ConfigureLoggers(specification string) error {
238	return DefaultLoggerCollection.ConfigureLoggers(specification)
239}
240
241// SetLevel will set the current log level for all loggers on the default
242// collection with names that match a provided regular expression. If the
243// regular expression is nil, then all loggers match.
244func SetLevel(re *regexp.Regexp, level LogLevel) {
245	DefaultLoggerCollection.SetLevel(re, level)
246}
247
248// SetHandler will set the current log handler for all loggers on the default
249// collection with names that match a provided regular expression. If the
250// regular expression is nil, then all loggers match.
251func SetHandler(re *regexp.Regexp, handler Handler) {
252	DefaultLoggerCollection.SetHandler(re, handler)
253}
254
255// SetTextTemplate will set the current text template for all loggers on the
256// default collection with names that match a provided regular expression. If
257// the regular expression is nil, then all loggers match. Note that not every
258// handler is guaranteed to support text templates and a text template will
259// only apply to text-oriented and unstructured handlers.
260func SetTextTemplate(re *regexp.Regexp, t *template.Template) {
261	DefaultLoggerCollection.SetTextTemplate(re, t)
262}
263
264// SetTextOutput will set the current output interface for all loggers on the
265// default collection with names that match a provided regular expression. If
266// the regular expression is nil, then all loggers match. Note that not every
267// handler is guaranteed to support text output and a text output interface
268// will only apply to text-oriented and unstructured handlers.
269func SetTextOutput(re *regexp.Regexp, output TextOutput) {
270	DefaultLoggerCollection.SetTextOutput(re, output)
271}
272