1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2// See LICENSE.txt for license information. 3 4package config 5 6import ( 7 "encoding/json" 8 "errors" 9 "path/filepath" 10 "strings" 11 "sync" 12 13 "github.com/mattermost/mattermost-server/v6/shared/mlog" 14) 15 16type LogSrcListener func(old, new mlog.LoggerConfiguration) 17 18// LogConfigSrc abstracts the Advanced Logging configuration so that implementations can 19// fetch from file, database, etc. 20type LogConfigSrc interface { 21 // Get fetches the current, cached configuration. 22 Get() mlog.LoggerConfiguration 23 24 // Set updates the dsn specifying the source and reloads 25 Set(dsn string, configStore *Store) (err error) 26 27 // Close cleans up resources. 28 Close() error 29} 30 31// NewLogConfigSrc creates an advanced logging configuration source, backed by a 32// file, JSON string, or database. 33func NewLogConfigSrc(dsn string, configStore *Store) (LogConfigSrc, error) { 34 if dsn == "" { 35 return nil, errors.New("dsn should not be empty") 36 } 37 38 if configStore == nil { 39 return nil, errors.New("configStore should not be nil") 40 } 41 42 dsn = strings.TrimSpace(dsn) 43 44 if isJSONMap(dsn) { 45 return newJSONSrc(dsn) 46 } 47 48 path := dsn 49 // If this is a file based config we need the full path so it can be watched. 50 if strings.HasPrefix(configStore.String(), "file://") && !filepath.IsAbs(dsn) { 51 configPath := strings.TrimPrefix(configStore.String(), "file://") 52 path = filepath.Join(filepath.Dir(configPath), dsn) 53 } 54 55 return newFileSrc(path, configStore) 56} 57 58// jsonSrc 59 60type jsonSrc struct { 61 logSrcEmitter 62 mutex sync.RWMutex 63 cfg mlog.LoggerConfiguration 64} 65 66func newJSONSrc(data string) (*jsonSrc, error) { 67 src := &jsonSrc{} 68 return src, src.Set(data, nil) 69} 70 71// Get fetches the current, cached configuration 72func (src *jsonSrc) Get() mlog.LoggerConfiguration { 73 src.mutex.RLock() 74 defer src.mutex.RUnlock() 75 return src.cfg 76} 77 78// Set updates the JSON specifying the source and reloads 79func (src *jsonSrc) Set(data string, _ *Store) error { 80 cfg, err := logTargetCfgFromJSON([]byte(data)) 81 if err != nil { 82 return err 83 } 84 85 src.set(cfg) 86 return nil 87} 88 89func (src *jsonSrc) set(cfg mlog.LoggerConfiguration) { 90 src.mutex.Lock() 91 defer src.mutex.Unlock() 92 93 old := src.cfg 94 src.cfg = cfg 95 src.invokeConfigListeners(old, cfg) 96} 97 98// Close cleans up resources. 99func (src *jsonSrc) Close() error { 100 return nil 101} 102 103// fileSrc 104 105type fileSrc struct { 106 mutex sync.RWMutex 107 cfg mlog.LoggerConfiguration 108 path string 109} 110 111func newFileSrc(path string, configStore *Store) (*fileSrc, error) { 112 src := &fileSrc{ 113 path: path, 114 } 115 if err := src.Set(path, configStore); err != nil { 116 return nil, err 117 } 118 return src, nil 119} 120 121// Get fetches the current, cached configuration 122func (src *fileSrc) Get() mlog.LoggerConfiguration { 123 src.mutex.RLock() 124 defer src.mutex.RUnlock() 125 return src.cfg 126} 127 128// Set updates the dsn specifying the file source and reloads. 129// The file will be watched for changes and reloaded as needed, 130// and all listeners notified. 131func (src *fileSrc) Set(path string, configStore *Store) error { 132 data, err := configStore.GetFile(path) 133 if err != nil { 134 return err 135 } 136 137 cfg, err := logTargetCfgFromJSON(data) 138 if err != nil { 139 return err 140 } 141 142 src.set(cfg) 143 return nil 144} 145 146func (src *fileSrc) set(cfg mlog.LoggerConfiguration) { 147 src.mutex.Lock() 148 defer src.mutex.Unlock() 149 150 src.cfg = cfg 151} 152 153// Close cleans up resources. 154func (src *fileSrc) Close() error { 155 return nil 156} 157 158func logTargetCfgFromJSON(data []byte) (mlog.LoggerConfiguration, error) { 159 cfg := make(mlog.LoggerConfiguration) 160 err := json.Unmarshal(data, &cfg) 161 if err != nil { 162 return nil, err 163 } 164 return cfg, nil 165} 166