github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/config/logging.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  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/masterhung0112/hk_server/v5/shared/mlog"
    15  )
    16  
    17  type LogSrcListener func(old, new mlog.LogTargetCfg)
    18  
    19  // LogConfigSrc abstracts the Advanced Logging configuration so that implementations can
    20  // fetch from file, database, etc.
    21  type LogConfigSrc interface {
    22  	// Get fetches the current, cached configuration.
    23  	Get() mlog.LogTargetCfg
    24  
    25  	// Set updates the dsn specifying the source and reloads
    26  	Set(dsn string, configStore *Store) (err error)
    27  
    28  	// AddListener adds a callback function to invoke when the configuration is modified.
    29  	AddListener(listener LogSrcListener) string
    30  
    31  	// RemoveListener removes a callback function using an id returned from AddListener.
    32  	RemoveListener(id string)
    33  
    34  	// Close cleans up resources.
    35  	Close() error
    36  }
    37  
    38  // NewLogConfigSrc creates an advanced logging configuration source, backed by a
    39  // file, JSON string, or database.
    40  func NewLogConfigSrc(dsn string, configStore *Store) (LogConfigSrc, error) {
    41  	if configStore == nil {
    42  		return nil, errors.New("configStore should not be nil")
    43  	}
    44  
    45  	dsn = strings.TrimSpace(dsn)
    46  
    47  	if isJSONMap(dsn) {
    48  		return newJSONSrc(dsn)
    49  	}
    50  
    51  	path := dsn
    52  	// If this is a file based config we need the full path so it can be watched.
    53  	if strings.HasPrefix(configStore.String(), "file://") && !filepath.IsAbs(dsn) {
    54  		configPath := strings.TrimPrefix(configStore.String(), "file://")
    55  		path = filepath.Join(filepath.Dir(configPath), dsn)
    56  	}
    57  
    58  	return newFileSrc(path, configStore)
    59  }
    60  
    61  // jsonSrc
    62  
    63  type jsonSrc struct {
    64  	logSrcEmitter
    65  	mutex sync.RWMutex
    66  	cfg   mlog.LogTargetCfg
    67  }
    68  
    69  func newJSONSrc(data string) (*jsonSrc, error) {
    70  	src := &jsonSrc{}
    71  	return src, src.Set(data, nil)
    72  }
    73  
    74  // Get fetches the current, cached configuration
    75  func (src *jsonSrc) Get() mlog.LogTargetCfg {
    76  	src.mutex.RLock()
    77  	defer src.mutex.RUnlock()
    78  	return src.cfg
    79  }
    80  
    81  // Set updates the JSON specifying the source and reloads
    82  func (src *jsonSrc) Set(data string, _ *Store) error {
    83  	cfg, err := logTargetCfgFromJSON([]byte(data))
    84  	if err != nil {
    85  		return err
    86  	}
    87  
    88  	src.set(cfg)
    89  	return nil
    90  }
    91  
    92  func (src *jsonSrc) set(cfg mlog.LogTargetCfg) {
    93  	src.mutex.Lock()
    94  	defer src.mutex.Unlock()
    95  
    96  	old := src.cfg
    97  	src.cfg = cfg
    98  	src.invokeConfigListeners(old, cfg)
    99  }
   100  
   101  // Close cleans up resources.
   102  func (src *jsonSrc) Close() error {
   103  	return nil
   104  }
   105  
   106  // fileSrc
   107  
   108  type fileSrc struct {
   109  	logSrcEmitter
   110  	mutex sync.RWMutex
   111  	cfg   mlog.LogTargetCfg
   112  
   113  	path    string
   114  	watcher *watcher
   115  }
   116  
   117  func newFileSrc(path string, configStore *Store) (*fileSrc, error) {
   118  	src := &fileSrc{
   119  		path: path,
   120  	}
   121  	if err := src.Set(path, configStore); err != nil {
   122  		return nil, err
   123  	}
   124  	return src, nil
   125  }
   126  
   127  // Get fetches the current, cached configuration
   128  func (src *fileSrc) Get() mlog.LogTargetCfg {
   129  	src.mutex.RLock()
   130  	defer src.mutex.RUnlock()
   131  	return src.cfg
   132  }
   133  
   134  // Set updates the dsn specifying the file source and reloads.
   135  // The file will be watched for changes and reloaded as needed,
   136  // and all listeners notified.
   137  func (src *fileSrc) Set(path string, configStore *Store) error {
   138  	data, err := configStore.GetFile(path)
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	cfg, err := logTargetCfgFromJSON(data)
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	src.set(cfg)
   149  
   150  	// If path is a real file and not just the name of a database resource then watch it for changes.
   151  	// Absolute paths are explicit and require no resolution.
   152  	if _, err = os.Stat(path); os.IsNotExist(err) {
   153  		return nil
   154  	}
   155  
   156  	src.mutex.Lock()
   157  	defer src.mutex.Unlock()
   158  
   159  	if src.watcher != nil {
   160  		if err = src.watcher.Close(); err != nil {
   161  			mlog.Error("Failed to close watcher", mlog.Err(err))
   162  		}
   163  		src.watcher = nil
   164  	}
   165  
   166  	watcher, err := newWatcher(path, func() {
   167  		if serr := src.Set(path, configStore); serr != nil {
   168  			mlog.Error("Failed to reload file on change", mlog.String("path", path), mlog.Err(serr))
   169  		}
   170  	})
   171  	if err != nil {
   172  		return err
   173  	}
   174  
   175  	src.watcher = watcher
   176  
   177  	return nil
   178  }
   179  
   180  func (src *fileSrc) set(cfg mlog.LogTargetCfg) {
   181  	src.mutex.Lock()
   182  	defer src.mutex.Unlock()
   183  
   184  	old := src.cfg
   185  	src.cfg = cfg
   186  	src.invokeConfigListeners(old, cfg)
   187  }
   188  
   189  // Close cleans up resources.
   190  func (src *fileSrc) Close() error {
   191  	var err error
   192  	src.mutex.Lock()
   193  	defer src.mutex.Unlock()
   194  	if src.watcher != nil {
   195  		err = src.watcher.Close()
   196  		src.watcher = nil
   197  	}
   198  	return err
   199  }
   200  
   201  func logTargetCfgFromJSON(data []byte) (mlog.LogTargetCfg, error) {
   202  	cfg := make(mlog.LogTargetCfg)
   203  	err := json.Unmarshal(data, &cfg)
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  	return cfg, nil
   208  }