// Copyright 2016 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package loggo import ( "fmt" "strings" "sync" ) // Context produces loggers for a hierarchy of modules. The context holds // a collection of hierarchical loggers and their writers. type Context struct { root *module // Perhaps have one mutex? modulesMutex sync.Mutex modules map[string]*module writersMutex sync.Mutex writers map[string]Writer // writeMuxtex is used to serialise write operations. writeMutex sync.Mutex } // NewLoggers returns a new Context with no writers set. // If the root level is UNSPECIFIED, WARNING is used. func NewContext(rootLevel Level) *Context { if rootLevel < TRACE || rootLevel > CRITICAL { rootLevel = WARNING } context := &Context{ modules: make(map[string]*module), writers: make(map[string]Writer), } context.root = &module{ level: rootLevel, context: context, } context.modules[""] = context.root return context } // GetLogger returns a Logger for the given module name, creating it and // its parents if necessary. func (c *Context) GetLogger(name string) Logger { name = strings.TrimSpace(strings.ToLower(name)) c.modulesMutex.Lock() defer c.modulesMutex.Unlock() return Logger{c.getLoggerModule(name)} } func (c *Context) getLoggerModule(name string) *module { if name == rootString { name = "" } impl, found := c.modules[name] if found { return impl } parentName := "" if i := strings.LastIndex(name, "."); i >= 0 { parentName = name[0:i] } parent := c.getLoggerModule(parentName) impl = &module{name, UNSPECIFIED, parent, c} c.modules[name] = impl return impl } // Config returns the current configuration of the Loggers. Loggers // with UNSPECIFIED level will not be included. func (c *Context) Config() Config { result := make(Config) c.modulesMutex.Lock() defer c.modulesMutex.Unlock() for name, module := range c.modules { if module.level != UNSPECIFIED { result[name] = module.level } } return result } // CompleteConfig returns all the loggers and their defined levels, // even if that level is UNSPECIFIED. func (c *Context) CompleteConfig() Config { result := make(Config) c.modulesMutex.Lock() defer c.modulesMutex.Unlock() for name, module := range c.modules { result[name] = module.level } return result } // ApplyConfig configures the logging modules according to the provided config. func (c *Context) ApplyConfig(config Config) { c.modulesMutex.Lock() defer c.modulesMutex.Unlock() for name, level := range config { module := c.getLoggerModule(name) module.setLevel(level) } } // ResetLoggerLevels iterates through the known logging modules and sets the // levels of all to UNSPECIFIED, except for which is set to WARNING. func (c *Context) ResetLoggerLevels() { c.modulesMutex.Lock() defer c.modulesMutex.Unlock() // Setting the root module to UNSPECIFIED will set it to WARNING. for _, module := range c.modules { module.setLevel(UNSPECIFIED) } } func (c *Context) write(entry Entry) { c.writeMutex.Lock() defer c.writeMutex.Unlock() for _, writer := range c.getWriters() { writer.Write(entry) } } func (c *Context) getWriters() []Writer { c.writersMutex.Lock() defer c.writersMutex.Unlock() var result []Writer for _, writer := range c.writers { result = append(result, writer) } return result } // AddWriter adds a writer to the list to be called for each logging call. // The name cannot be empty, and the writer cannot be nil. If an existing // writer exists with the specified name, an error is returned. func (c *Context) AddWriter(name string, writer Writer) error { if name == "" { return fmt.Errorf("name cannot be empty") } if writer == nil { return fmt.Errorf("writer cannot be nil") } c.writersMutex.Lock() defer c.writersMutex.Unlock() if _, found := c.writers[name]; found { return fmt.Errorf("context already has a writer named %q", name) } c.writers[name] = writer return nil } // RemoveWriter remotes the specified writer. If a writer is not found with // the specified name an error is returned. The writer that was removed is also // returned. func (c *Context) RemoveWriter(name string) (Writer, error) { c.writersMutex.Lock() defer c.writersMutex.Unlock() reg, found := c.writers[name] if !found { return nil, fmt.Errorf("context has no writer named %q", name) } delete(c.writers, name) return reg, nil } // ReplaceWriter is a convenience method that does the equivalent of RemoveWriter // followed by AddWriter with the same name. The replaced writer is returned. func (c *Context) ReplaceWriter(name string, writer Writer) (Writer, error) { if name == "" { return nil, fmt.Errorf("name cannot be empty") } if writer == nil { return nil, fmt.Errorf("writer cannot be nil") } c.writersMutex.Lock() defer c.writersMutex.Unlock() reg, found := c.writers[name] if !found { return nil, fmt.Errorf("context has no writer named %q", name) } oldWriter := reg c.writers[name] = writer return oldWriter, nil } // ResetWriters is generally only used in testing and removes all the writers. func (c *Context) ResetWriters() { c.writersMutex.Lock() defer c.writersMutex.Unlock() c.writers = make(map[string]Writer) }