github.com/mattermost/mattermost-server/server/v8@v8.0.0-20230610055354-a6d1d38b273d/config/logconfigsrc.go (about)

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