blob: f5739d91494398747bc8dab4c3fed944c83de05c [file] [log] [blame]
David K. Bainbridge528b3182017-01-23 08:51:59 -08001// Copyright 2016 Canonical Ltd.
2// Licensed under the LGPLv3, see LICENCE file for details.
3
4package loggo
5
6import (
7 "fmt"
8 "strings"
9 "sync"
10)
11
12// Context produces loggers for a hierarchy of modules. The context holds
13// a collection of hierarchical loggers and their writers.
14type Context struct {
15 root *module
16
17 // Perhaps have one mutex?
18 modulesMutex sync.Mutex
19 modules map[string]*module
20
21 writersMutex sync.Mutex
22 writers map[string]Writer
23
24 // writeMuxtex is used to serialise write operations.
25 writeMutex sync.Mutex
26}
27
28// NewLoggers returns a new Context with no writers set.
29// If the root level is UNSPECIFIED, WARNING is used.
30func NewContext(rootLevel Level) *Context {
31 if rootLevel < TRACE || rootLevel > CRITICAL {
32 rootLevel = WARNING
33 }
34 context := &Context{
35 modules: make(map[string]*module),
36 writers: make(map[string]Writer),
37 }
38 context.root = &module{
39 level: rootLevel,
40 context: context,
41 }
42 context.modules[""] = context.root
43 return context
44}
45
46// GetLogger returns a Logger for the given module name, creating it and
47// its parents if necessary.
48func (c *Context) GetLogger(name string) Logger {
49 name = strings.TrimSpace(strings.ToLower(name))
50 c.modulesMutex.Lock()
51 defer c.modulesMutex.Unlock()
52 return Logger{c.getLoggerModule(name)}
53}
54
55func (c *Context) getLoggerModule(name string) *module {
56 if name == rootString {
57 name = ""
58 }
59 impl, found := c.modules[name]
60 if found {
61 return impl
62 }
63 parentName := ""
64 if i := strings.LastIndex(name, "."); i >= 0 {
65 parentName = name[0:i]
66 }
67 parent := c.getLoggerModule(parentName)
68 impl = &module{name, UNSPECIFIED, parent, c}
69 c.modules[name] = impl
70 return impl
71}
72
73// Config returns the current configuration of the Loggers. Loggers
74// with UNSPECIFIED level will not be included.
75func (c *Context) Config() Config {
76 result := make(Config)
77 c.modulesMutex.Lock()
78 defer c.modulesMutex.Unlock()
79
80 for name, module := range c.modules {
81 if module.level != UNSPECIFIED {
82 result[name] = module.level
83 }
84 }
85 return result
86}
87
88// CompleteConfig returns all the loggers and their defined levels,
89// even if that level is UNSPECIFIED.
90func (c *Context) CompleteConfig() Config {
91 result := make(Config)
92 c.modulesMutex.Lock()
93 defer c.modulesMutex.Unlock()
94
95 for name, module := range c.modules {
96 result[name] = module.level
97 }
98 return result
99}
100
101// ApplyConfig configures the logging modules according to the provided config.
102func (c *Context) ApplyConfig(config Config) {
103 c.modulesMutex.Lock()
104 defer c.modulesMutex.Unlock()
105 for name, level := range config {
106 module := c.getLoggerModule(name)
107 module.setLevel(level)
108 }
109}
110
111// ResetLoggerLevels iterates through the known logging modules and sets the
112// levels of all to UNSPECIFIED, except for <root> which is set to WARNING.
113func (c *Context) ResetLoggerLevels() {
114 c.modulesMutex.Lock()
115 defer c.modulesMutex.Unlock()
116 // Setting the root module to UNSPECIFIED will set it to WARNING.
117 for _, module := range c.modules {
118 module.setLevel(UNSPECIFIED)
119 }
120}
121
122func (c *Context) write(entry Entry) {
123 c.writeMutex.Lock()
124 defer c.writeMutex.Unlock()
125 for _, writer := range c.getWriters() {
126 writer.Write(entry)
127 }
128}
129
130func (c *Context) getWriters() []Writer {
131 c.writersMutex.Lock()
132 defer c.writersMutex.Unlock()
133 var result []Writer
134 for _, writer := range c.writers {
135 result = append(result, writer)
136 }
137 return result
138}
139
140// AddWriter adds a writer to the list to be called for each logging call.
141// The name cannot be empty, and the writer cannot be nil. If an existing
142// writer exists with the specified name, an error is returned.
143func (c *Context) AddWriter(name string, writer Writer) error {
144 if name == "" {
145 return fmt.Errorf("name cannot be empty")
146 }
147 if writer == nil {
148 return fmt.Errorf("writer cannot be nil")
149 }
150 c.writersMutex.Lock()
151 defer c.writersMutex.Unlock()
152 if _, found := c.writers[name]; found {
153 return fmt.Errorf("context already has a writer named %q", name)
154 }
155 c.writers[name] = writer
156 return nil
157}
158
159// RemoveWriter remotes the specified writer. If a writer is not found with
160// the specified name an error is returned. The writer that was removed is also
161// returned.
162func (c *Context) RemoveWriter(name string) (Writer, error) {
163 c.writersMutex.Lock()
164 defer c.writersMutex.Unlock()
165 reg, found := c.writers[name]
166 if !found {
167 return nil, fmt.Errorf("context has no writer named %q", name)
168 }
169 delete(c.writers, name)
170 return reg, nil
171}
172
173// ReplaceWriter is a convenience method that does the equivalent of RemoveWriter
174// followed by AddWriter with the same name. The replaced writer is returned.
175func (c *Context) ReplaceWriter(name string, writer Writer) (Writer, error) {
176 if name == "" {
177 return nil, fmt.Errorf("name cannot be empty")
178 }
179 if writer == nil {
180 return nil, fmt.Errorf("writer cannot be nil")
181 }
182 c.writersMutex.Lock()
183 defer c.writersMutex.Unlock()
184 reg, found := c.writers[name]
185 if !found {
186 return nil, fmt.Errorf("context has no writer named %q", name)
187 }
188 oldWriter := reg
189 c.writers[name] = writer
190 return oldWriter, nil
191}
192
193// ResetWriters is generally only used in testing and removes all the writers.
194func (c *Context) ResetWriters() {
195 c.writersMutex.Lock()
196 defer c.writersMutex.Unlock()
197 c.writers = make(map[string]Writer)
198}