diff options
author | Wim <wim@42.be> | 2020-10-19 23:40:00 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-19 23:40:00 +0200 |
commit | 075a84427f6332aab707d283ad770d69f8816032 (patch) | |
tree | 0ff9f56a057919f3fe968e57f6f0b1c0d1f85078 /vendor/github.com/wiggin77 | |
parent | 950f2759bd2b20aa0bdedc3dc9a74d0dafb606d8 (diff) | |
download | matterbridge-msglm-075a84427f6332aab707d283ad770d69f8816032.tar.gz matterbridge-msglm-075a84427f6332aab707d283ad770d69f8816032.tar.bz2 matterbridge-msglm-075a84427f6332aab707d283ad770d69f8816032.zip |
Update vendor (#1265)
Diffstat (limited to 'vendor/github.com/wiggin77')
37 files changed, 2333 insertions, 0 deletions
diff --git a/vendor/github.com/wiggin77/cfg/.gitignore b/vendor/github.com/wiggin77/cfg/.gitignore new file mode 100644 index 00000000..f1c181ec --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/.gitignore @@ -0,0 +1,12 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out diff --git a/vendor/github.com/wiggin77/cfg/.travis.yml b/vendor/github.com/wiggin77/cfg/.travis.yml new file mode 100644 index 00000000..9899b387 --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/.travis.yml @@ -0,0 +1,5 @@ +language: go +sudo: false +before_script: + - go vet ./... +
\ No newline at end of file diff --git a/vendor/github.com/wiggin77/cfg/LICENSE b/vendor/github.com/wiggin77/cfg/LICENSE new file mode 100644 index 00000000..2b0bf7ef --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 wiggin77 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/wiggin77/cfg/README.md b/vendor/github.com/wiggin77/cfg/README.md new file mode 100644 index 00000000..583a82cb --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/README.md @@ -0,0 +1,43 @@ +# cfg + +[![GoDoc](https://godoc.org/github.com/wiggin77/cfg?status.svg)](https://godoc.org/github.com/wiggin77/cfg) +[![Build Status](https://travis-ci.org/wiggin77/cfg.svg?branch=master)](https://travis-ci.org/wiggin77/cfg) + +Go package for app configuration. Supports chained configuration sources for multiple levels of defaults. +Includes APIs for loading Linux style configuration files (name/value pairs) or INI files, map based properties, +or easily create new configuration sources (e.g. load from database). + +Supports monitoring configuration sources for changes, hot loading properties, and notifying listeners of changes. + +## Usage + +```Go +config := &cfg.Config{} +defer config.Shutdown() // stops monitoring + +// load file via filespec string, os.File +src, err := Config.NewSrcFileFromFilespec("./myfile.conf") +if err != nil { + return err +} +// add src to top of chain, meaning first searched +cfg.PrependSource(src) + +// fetch prop 'retries', default to 3 if not found +val := config.Int("retries", 3) +``` + +See [example](./example_test.go) for more complete example, including listening for configuration changes. + +Config API parses the following data types: + +| type | method | example property values | +| ------- | ------ | -------- | +| string | Config.String | test, "" | +| int | Config.Int | -1, 77, 0 | +| int64 | Config.Int64 | -9223372036854775, 372036854775808 | +| float64 | Config.Float64 | -77.3456, 95642331.1 | +| bool | Config.Bool | T,t,true,True,1,0,False,false,f,F | +| time.Duration | Config.Duration | "10ms", "2 hours", "5 min" * | + +\* Units of measure supported: ms, sec, min, hour, day, week, year. diff --git a/vendor/github.com/wiggin77/cfg/config.go b/vendor/github.com/wiggin77/cfg/config.go new file mode 100644 index 00000000..0e958102 --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/config.go @@ -0,0 +1,366 @@ +package cfg + +import ( + "errors" + "fmt" + "strconv" + "strings" + "sync" + "time" + + "github.com/wiggin77/cfg/timeconv" +) + +// ErrNotFound returned when an operation is attempted on a +// resource that doesn't exist, such as fetching a non-existing +// property name. +var ErrNotFound = errors.New("not found") + +type sourceEntry struct { + src Source + props map[string]string +} + +// Config provides methods for retrieving property values from one or more +// configuration sources. +type Config struct { + mutexSrc sync.RWMutex + mutexListeners sync.RWMutex + srcs []*sourceEntry + chgListeners []ChangedListener + shutdown chan interface{} + wantPanicOnError bool +} + +// PrependSource inserts one or more `Sources` at the beginning of +// the list of sources such that the first source will be the +// source checked first when resolving a property value. +func (config *Config) PrependSource(srcs ...Source) { + arr := config.wrapSources(srcs...) + + config.mutexSrc.Lock() + if config.shutdown == nil { + config.shutdown = make(chan interface{}) + } + config.srcs = append(arr, config.srcs...) + config.mutexSrc.Unlock() + + for _, se := range arr { + if _, ok := se.src.(SourceMonitored); ok { + config.monitor(se) + } + } +} + +// AppendSource appends one or more `Sources` at the end of +// the list of sources such that the last source will be the +// source checked last when resolving a property value. +func (config *Config) AppendSource(srcs ...Source) { + arr := config.wrapSources(srcs...) + + config.mutexSrc.Lock() + if config.shutdown == nil { + config.shutdown = make(chan interface{}) + } + config.srcs = append(config.srcs, arr...) + config.mutexSrc.Unlock() + + for _, se := range arr { + if _, ok := se.src.(SourceMonitored); ok { + config.monitor(se) + } + } +} + +// wrapSources wraps one or more Source's and returns +// them as an array of `sourceEntry`. +func (config *Config) wrapSources(srcs ...Source) []*sourceEntry { + arr := make([]*sourceEntry, 0, len(srcs)) + for _, src := range srcs { + se := &sourceEntry{src: src} + config.reloadProps(se) + arr = append(arr, se) + } + return arr +} + +// SetWantPanicOnError sets the flag determining if Config +// should panic when `GetProps` or `GetLastModified` errors +// for a `Source`. +func (config *Config) SetWantPanicOnError(b bool) { + config.mutexSrc.Lock() + config.wantPanicOnError = b + config.mutexSrc.Unlock() +} + +// ShouldPanicOnError gets the flag determining if Config +// should panic when `GetProps` or `GetLastModified` errors +// for a `Source`. +func (config *Config) ShouldPanicOnError() (b bool) { + config.mutexSrc.RLock() + b = config.wantPanicOnError + config.mutexSrc.RUnlock() + return b +} + +// getProp returns the value of a named property. +// Each `Source` is checked, in the order created by adding via +// `AppendSource` and `PrependSource`, until a value for the +// property is found. +func (config *Config) getProp(name string) (val string, ok bool) { + config.mutexSrc.RLock() + defer config.mutexSrc.RUnlock() + + var s string + for _, se := range config.srcs { + if se.props != nil { + if s, ok = se.props[name]; ok { + val = strings.TrimSpace(s) + return + } + } + } + return +} + +// String returns the value of the named prop as a string. +// If the property is not found then the supplied default `def` +// and `ErrNotFound` are returned. +func (config *Config) String(name string, def string) (val string, err error) { + if v, ok := config.getProp(name); ok { + val = v + err = nil + return + } + + err = ErrNotFound + val = def + return +} + +// Int returns the value of the named prop as an `int`. +// If the property is not found then the supplied default `def` +// and `ErrNotFound` are returned. +// +// See config.String +func (config *Config) Int(name string, def int) (val int, err error) { + var s string + if s, err = config.String(name, ""); err == nil { + var i int64 + if i, err = strconv.ParseInt(s, 10, 32); err == nil { + val = int(i) + } + } + if err != nil { + val = def + } + return +} + +// Int64 returns the value of the named prop as an `int64`. +// If the property is not found then the supplied default `def` +// and `ErrNotFound` are returned. +// +// See config.String +func (config *Config) Int64(name string, def int64) (val int64, err error) { + var s string + if s, err = config.String(name, ""); err == nil { + val, err = strconv.ParseInt(s, 10, 64) + } + if err != nil { + val = def + } + return +} + +// Float64 returns the value of the named prop as a `float64`. +// If the property is not found then the supplied default `def` +// and `ErrNotFound` are returned. +// +// See config.String +func (config *Config) Float64(name string, def float64) (val float64, err error) { + var s string + if s, err = config.String(name, ""); err == nil { + val, err = strconv.ParseFloat(s, 64) + } + if err != nil { + val = def + } + return +} + +// Bool returns the value of the named prop as a `bool`. +// If the property is not found then the supplied default `def` +// and `ErrNotFound` are returned. +// +// Supports (t, true, 1, y, yes) for true, and (f, false, 0, n, no) for false, +// all case-insensitive. +// +// See config.String +func (config *Config) Bool(name string, def bool) (val bool, err error) { + var s string + if s, err = config.String(name, ""); err == nil { + switch strings.ToLower(s) { + case "t", "true", "1", "y", "yes": + val = true + case "f", "false", "0", "n", "no": + val = false + default: + err = errors.New("invalid syntax") + } + } + if err != nil { + val = def + } + return +} + +// Duration returns the value of the named prop as a `time.Duration`, representing +// a span of time. +// +// Units of measure are supported: ms, sec, min, hour, day, week, year. +// See config.UnitsToMillis for a complete list of units supported. +// +// If the property is not found then the supplied default `def` +// and `ErrNotFound` are returned. +// +// See config.String +func (config *Config) Duration(name string, def time.Duration) (val time.Duration, err error) { + var s string + if s, err = config.String(name, ""); err == nil { + var ms int64 + ms, err = timeconv.ParseMilliseconds(s) + val = time.Duration(ms) * time.Millisecond + } + if err != nil { + val = def + } + return +} + +// AddChangedListener adds a listener that will receive notifications +// whenever one or more property values change within the config. +func (config *Config) AddChangedListener(l ChangedListener) { + config.mutexListeners.Lock() + defer config.mutexListeners.Unlock() + + config.chgListeners = append(config.chgListeners, l) +} + +// RemoveChangedListener removes all instances of a ChangedListener. +// Returns `ErrNotFound` if the listener was not present. +func (config *Config) RemoveChangedListener(l ChangedListener) error { + config.mutexListeners.Lock() + defer config.mutexListeners.Unlock() + + dest := make([]ChangedListener, 0, len(config.chgListeners)) + err := ErrNotFound + + // Remove all instances of the listener by + // copying list while filtering. + for _, s := range config.chgListeners { + if s != l { + dest = append(dest, s) + } else { + err = nil + } + } + config.chgListeners = dest + return err +} + +// Shutdown can be called to stop monitoring of all config sources. +func (config *Config) Shutdown() { + config.mutexSrc.RLock() + defer config.mutexSrc.RUnlock() + if config.shutdown != nil { + close(config.shutdown) + } +} + +// onSourceChanged is called whenever one or more properties of a +// config source has changed. +func (config *Config) onSourceChanged(src SourceMonitored) { + defer func() { + if p := recover(); p != nil { + fmt.Println(p) + } + }() + config.mutexListeners.RLock() + defer config.mutexListeners.RUnlock() + for _, l := range config.chgListeners { + l.ConfigChanged(config, src) + } +} + +// monitor periodically checks a config source for changes. +func (config *Config) monitor(se *sourceEntry) { + go func(se *sourceEntry, shutdown <-chan interface{}) { + var src SourceMonitored + var ok bool + if src, ok = se.src.(SourceMonitored); !ok { + return + } + paused := false + last := time.Time{} + freq := src.GetMonitorFreq() + if freq <= 0 { + paused = true + freq = 10 + last, _ = src.GetLastModified() + } + timer := time.NewTimer(freq) + for { + select { + case <-timer.C: + if !paused { + if latest, err := src.GetLastModified(); err != nil { + if config.ShouldPanicOnError() { + panic(fmt.Sprintf("error <%v> getting last modified for %v", err, src)) + } + } else { + if last.Before(latest) { + last = latest + config.reloadProps(se) + // TODO: calc diff and provide detailed changes + config.onSourceChanged(src) + } + } + } + freq = src.GetMonitorFreq() + if freq <= 0 { + paused = true + freq = 10 + } else { + paused = false + } + timer.Reset(freq) + case <-shutdown: + // stop the timer and exit + if !timer.Stop() { + <-timer.C + } + return + } + } + }(se, config.shutdown) +} + +// reloadProps causes a Source to reload its properties. +func (config *Config) reloadProps(se *sourceEntry) { + config.mutexSrc.Lock() + defer config.mutexSrc.Unlock() + + m, err := se.src.GetProps() + if err != nil { + if config.wantPanicOnError { + panic(fmt.Sprintf("GetProps error for %v", se.src)) + } + return + } + + se.props = make(map[string]string) + for k, v := range m { + se.props[k] = v + } +} diff --git a/vendor/github.com/wiggin77/cfg/go.mod b/vendor/github.com/wiggin77/cfg/go.mod new file mode 100644 index 00000000..2e5a038e --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/go.mod @@ -0,0 +1,5 @@ +module github.com/wiggin77/cfg + +go 1.12 + +require github.com/wiggin77/merror v1.0.2 diff --git a/vendor/github.com/wiggin77/cfg/go.sum b/vendor/github.com/wiggin77/cfg/go.sum new file mode 100644 index 00000000..30fd3b58 --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/go.sum @@ -0,0 +1,2 @@ +github.com/wiggin77/merror v1.0.2 h1:V0nH9eFp64ASyaXC+pB5WpvBoCg7NUwvaCSKdzlcHqw= +github.com/wiggin77/merror v1.0.2/go.mod h1:uQTcIU0Z6jRK4OwqganPYerzQxSFJ4GSHM3aurxxQpg= diff --git a/vendor/github.com/wiggin77/cfg/ini/ini.go b/vendor/github.com/wiggin77/cfg/ini/ini.go new file mode 100644 index 00000000..d28d7444 --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/ini/ini.go @@ -0,0 +1,167 @@ +package ini + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "sync" + "time" +) + +// Ini provides parsing and querying of INI format or simple name/value pairs +// such as a simple config file. +// A name/value pair format is just an INI with no sections, and properties can +// be queried using an empty section name. +type Ini struct { + mutex sync.RWMutex + m map[string]*Section + lm time.Time +} + +// LoadFromFilespec loads an INI file from string containing path and filename. +func (ini *Ini) LoadFromFilespec(filespec string) error { + f, err := os.Open(filespec) + if err != nil { + return err + } + return ini.LoadFromFile(f) +} + +// LoadFromFile loads an INI file from `os.File`. +func (ini *Ini) LoadFromFile(file *os.File) error { + + fi, err := file.Stat() + if err != nil { + return err + } + lm := fi.ModTime() + + if err := ini.LoadFromReader(file); err != nil { + return err + } + ini.lm = lm + return nil +} + +// LoadFromReader loads an INI file from an `io.Reader`. +func (ini *Ini) LoadFromReader(reader io.Reader) error { + data, err := ioutil.ReadAll(reader) + if err != nil { + return err + } + return ini.LoadFromString(string(data)) +} + +// LoadFromString parses an INI from a string . +func (ini *Ini) LoadFromString(s string) error { + m, err := getSections(s) + if err != nil { + return err + } + ini.mutex.Lock() + ini.m = m + ini.lm = time.Now() + ini.mutex.Unlock() + return nil +} + +// GetLastModified returns the last modified timestamp of the +// INI contents. +func (ini *Ini) GetLastModified() time.Time { + return ini.lm +} + +// GetSectionNames returns the names of all sections in this INI. +// Note, the returned section names are a snapshot in time, meaning +// other goroutines may change the contents of this INI as soon as +// the method returns. +func (ini *Ini) GetSectionNames() []string { + ini.mutex.RLock() + defer ini.mutex.RUnlock() + + arr := make([]string, 0, len(ini.m)) + for key := range ini.m { + arr = append(arr, key) + } + return arr +} + +// GetKeys returns the names of all keys in the specified section. +// Note, the returned key names are a snapshot in time, meaning other +// goroutines may change the contents of this INI as soon as the +// method returns. +func (ini *Ini) GetKeys(sectionName string) ([]string, error) { + sec, err := ini.getSection(sectionName) + if err != nil { + return nil, err + } + return sec.getKeys(), nil +} + +// getSection returns the named section. +func (ini *Ini) getSection(sectionName string) (*Section, error) { + ini.mutex.RLock() + defer ini.mutex.RUnlock() + + sec, ok := ini.m[sectionName] + if !ok { + return nil, fmt.Errorf("section '%s' not found", sectionName) + } + return sec, nil +} + +// GetFlattenedKeys returns all section names plus keys as one +// flattened array. +func (ini *Ini) GetFlattenedKeys() []string { + ini.mutex.RLock() + defer ini.mutex.RUnlock() + + arr := make([]string, 0, len(ini.m)*2) + for _, section := range ini.m { + keys := section.getKeys() + for _, key := range keys { + name := section.GetName() + if name != "" { + key = name + "." + key + } + arr = append(arr, key) + } + } + return arr +} + +// GetProp returns the value of the specified key in the named section. +func (ini *Ini) GetProp(section string, key string) (val string, ok bool) { + sec, err := ini.getSection(section) + if err != nil { + return val, false + } + return sec.GetProp(key) +} + +// ToMap returns a flattened map of the section name plus keys mapped +// to values. +func (ini *Ini) ToMap() map[string]string { + m := make(map[string]string) + + ini.mutex.RLock() + defer ini.mutex.RUnlock() + + for _, section := range ini.m { + for _, key := range section.getKeys() { + val, ok := section.GetProp(key) + if ok { + name := section.GetName() + var mapkey string + if name != "" { + mapkey = name + "." + key + } else { + mapkey = key + } + m[mapkey] = val + } + } + } + return m +} diff --git a/vendor/github.com/wiggin77/cfg/ini/parser.go b/vendor/github.com/wiggin77/cfg/ini/parser.go new file mode 100644 index 00000000..28916409 --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/ini/parser.go @@ -0,0 +1,142 @@ +package ini + +import ( + "fmt" + "strings" + + "github.com/wiggin77/merror" +) + +// LF is linefeed +const LF byte = 0x0A + +// CR is carriage return +const CR byte = 0x0D + +// getSections parses an INI formatted string, or string containing just name/value pairs, +// returns map of `Section`'s. +// +// Any name/value pairs appearing before a section name are added to the section named +// with an empty string (""). Also true for Linux-style config files where all props +// are outside a named section. +// +// Any errors encountered are aggregated and returned, along with the partially parsed +// sections. +func getSections(str string) (map[string]*Section, error) { + merr := merror.New() + mapSections := make(map[string]*Section) + lines := buildLineArray(str) + section := newSection("") + + for _, line := range lines { + name, ok := parseSection(line) + if ok { + // A section name encountered. Stop processing the current one. + // Don't add the current section to the map if the section name is blank + // and the prop map is empty. + nameCurr := section.GetName() + if nameCurr != "" || section.hasKeys() { + mapSections[nameCurr] = section + } + // Start processing a new section. + section = newSection(name) + } else { + // Parse the property and add to the current section, or ignore if comment. + if k, v, comment, err := parseProp(line); !comment && err == nil { + section.setProp(k, v) + } else if err != nil { + merr.Append(err) // aggregate errors + } + } + + } + // If the current section is not empty, add it. + if section.hasKeys() { + mapSections[section.GetName()] = section + } + return mapSections, merr.ErrorOrNil() +} + +// buildLineArray parses the given string buffer and creates a list of strings, +// one for each line in the string buffer. +// +// A line is considered to be terminated by any one of a line feed ('\n'), +// a carriage return ('\r'), or a carriage return followed immediately by a +// linefeed. +// +// Lines prefixed with ';' or '#' are considered comments and skipped. +func buildLineArray(str string) []string { + arr := make([]string, 0, 10) + str = str + "\n" + + iLen := len(str) + iPos, iBegin := 0, 0 + var ch byte + + for iPos < iLen { + ch = str[iPos] + if ch == LF || ch == CR { + sub := str[iBegin:iPos] + sub = strings.TrimSpace(sub) + if sub != "" && !strings.HasPrefix(sub, ";") && !strings.HasPrefix(sub, "#") { + arr = append(arr, sub) + } + iPos++ + if ch == CR && iPos < iLen && str[iPos] == LF { + iPos++ + } + iBegin = iPos + } else { + iPos++ + } + } + return arr +} + +// parseSection parses the specified string for a section name enclosed in square brackets. +// Returns the section name found, or `ok=false` if `str` is not a section header. +func parseSection(str string) (name string, ok bool) { + str = strings.TrimSpace(str) + if !strings.HasPrefix(str, "[") { + return "", false + } + iCloser := strings.Index(str, "]") + if iCloser == -1 { + return "", false + } + return strings.TrimSpace(str[1:iCloser]), true +} + +// parseProp parses the specified string and extracts a key/value pair. +// +// If the string is a comment (prefixed with ';' or '#') then `comment=true` +// and key will be empty. +func parseProp(str string) (key string, val string, comment bool, err error) { + iLen := len(str) + iEqPos := strings.Index(str, "=") + if iEqPos == -1 { + return "", "", false, fmt.Errorf("not a key/value pair:'%s'", str) + } + + key = str[0:iEqPos] + key = strings.TrimSpace(key) + if iEqPos+1 < iLen { + val = str[iEqPos+1:] + val = strings.TrimSpace(val) + } + + // Check that the key has at least 1 char. + if key == "" { + return "", "", false, fmt.Errorf("key is empty for '%s'", str) + } + + // Check if this line is a comment that just happens + // to have an equals sign in it. Not an error, but not a + // useable line either. + if strings.HasPrefix(key, ";") || strings.HasPrefix(key, "#") { + key = "" + val = "" + comment = true + } + return key, val, comment, err +} diff --git a/vendor/github.com/wiggin77/cfg/ini/section.go b/vendor/github.com/wiggin77/cfg/ini/section.go new file mode 100644 index 00000000..18c4c254 --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/ini/section.go @@ -0,0 +1,109 @@ +package ini + +import ( + "fmt" + "strings" + "sync" +) + +// Section represents a section in an INI file. The section has a name, which is +// enclosed in square brackets in the file. The section also has an array of +// key/value pairs. +type Section struct { + name string + props map[string]string + mtx sync.RWMutex +} + +func newSection(name string) *Section { + sec := &Section{} + sec.name = name + sec.props = make(map[string]string) + return sec +} + +// addLines addes an array of strings containing name/value pairs +// of the format `key=value`. +//func addLines(lines []string) { +// TODO +//} + +// GetName returns the name of the section. +func (sec *Section) GetName() (name string) { + sec.mtx.RLock() + name = sec.name + sec.mtx.RUnlock() + return +} + +// GetProp returns the value associated with the given key, or +// `ok=false` if key does not exist. +func (sec *Section) GetProp(key string) (val string, ok bool) { + sec.mtx.RLock() + val, ok = sec.props[key] + sec.mtx.RUnlock() + return +} + +// SetProp sets the value associated with the given key. +func (sec *Section) setProp(key string, val string) { + sec.mtx.Lock() + sec.props[key] = val + sec.mtx.Unlock() +} + +// hasKeys returns true if there are one or more properties in +// this section. +func (sec *Section) hasKeys() (b bool) { + sec.mtx.RLock() + b = len(sec.props) > 0 + sec.mtx.RUnlock() + return +} + +// getKeys returns an array containing all keys in this section. +func (sec *Section) getKeys() []string { + sec.mtx.RLock() + defer sec.mtx.RUnlock() + + arr := make([]string, len(sec.props)) + idx := 0 + for k := range sec.props { + arr[idx] = k + idx++ + } + return arr +} + +// combine the given section with this one. +func (sec *Section) combine(sec2 *Section) { + sec.mtx.Lock() + sec2.mtx.RLock() + defer sec.mtx.Unlock() + defer sec2.mtx.RUnlock() + + for k, v := range sec2.props { + sec.props[k] = v + } +} + +// String returns a string representation of this section. +func (sec *Section) String() string { + return fmt.Sprintf("[%s]\n%s", sec.GetName(), sec.StringPropsOnly()) +} + +// StringPropsOnly returns a string representation of this section +// without the section header. +func (sec *Section) StringPropsOnly() string { + sec.mtx.RLock() + defer sec.mtx.RUnlock() + sb := &strings.Builder{} + + for k, v := range sec.props { + sb.WriteString(k) + sb.WriteString("=") + sb.WriteString(v) + sb.WriteString("\n") + } + return sb.String() +} diff --git a/vendor/github.com/wiggin77/cfg/listener.go b/vendor/github.com/wiggin77/cfg/listener.go new file mode 100644 index 00000000..12ea4e45 --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/listener.go @@ -0,0 +1,11 @@ +package cfg + +// ChangedListener interface is for receiving notifications +// when one or more properties within monitored config sources +// (SourceMonitored) have changed values. +type ChangedListener interface { + + // Changed is called when one or more properties in a `SourceMonitored` has a + // changed value. + ConfigChanged(cfg *Config, src SourceMonitored) +} diff --git a/vendor/github.com/wiggin77/cfg/nocopy.go b/vendor/github.com/wiggin77/cfg/nocopy.go new file mode 100644 index 00000000..f2450c0b --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/nocopy.go @@ -0,0 +1,11 @@ +package cfg + +// noCopy may be embedded into structs which must not be copied +// after the first use. +// +// See https://golang.org/issues/8005#issuecomment-190753527 +// for details. +type noCopy struct{} + +// Lock is a no-op used by -copylocks checker from `go vet`. +func (*noCopy) Lock() {} diff --git a/vendor/github.com/wiggin77/cfg/source.go b/vendor/github.com/wiggin77/cfg/source.go new file mode 100644 index 00000000..09083e97 --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/source.go @@ -0,0 +1,58 @@ +package cfg + +import ( + "sync" + "time" +) + +// Source is the interface required for any source of name/value pairs. +type Source interface { + + // GetProps fetches all the properties from a source and returns + // them as a map. + GetProps() (map[string]string, error) +} + +// SourceMonitored is the interface required for any config source that is +// monitored for changes. +type SourceMonitored interface { + Source + + // GetLastModified returns the time of the latest modification to any + // property value within the source. If a source does not support + // modifying properties at runtime then the zero value for `Time` + // should be returned to ensure reload events are not generated. + GetLastModified() (time.Time, error) + + // GetMonitorFreq returns the frequency as a `time.Duration` between + // checks for changes to this config source. + // + // Returning zero (or less) will temporarily suspend calls to `GetLastModified` + // and `GetMonitorFreq` will be called every 10 seconds until resumed, after which + // `GetMontitorFreq` will be called at a frequency roughly equal to the `time.Duration` + // returned. + GetMonitorFreq() time.Duration +} + +// AbstractSourceMonitor can be embedded in a custom `Source` to provide the +// basic plumbing for monitor frequency. +type AbstractSourceMonitor struct { + mutex sync.RWMutex + freq time.Duration +} + +// GetMonitorFreq returns the frequency as a `time.Duration` between +// checks for changes to this config source. +func (asm *AbstractSourceMonitor) GetMonitorFreq() (freq time.Duration) { + asm.mutex.RLock() + freq = asm.freq + asm.mutex.RUnlock() + return +} + +// SetMonitorFreq sets the frequency between checks for changes to this config source. +func (asm *AbstractSourceMonitor) SetMonitorFreq(freq time.Duration) { + asm.mutex.Lock() + asm.freq = freq + asm.mutex.Unlock() +} diff --git a/vendor/github.com/wiggin77/cfg/srcfile.go b/vendor/github.com/wiggin77/cfg/srcfile.go new file mode 100644 index 00000000..f42c69fa --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/srcfile.go @@ -0,0 +1,63 @@ +package cfg + +import ( + "os" + "time" + + "github.com/wiggin77/cfg/ini" +) + +// SrcFile is a configuration `Source` backed by a file containing +// name/value pairs or INI format. +type SrcFile struct { + AbstractSourceMonitor + ini ini.Ini + file *os.File +} + +// NewSrcFileFromFilespec creates a new SrcFile with the specified filespec. +func NewSrcFileFromFilespec(filespec string) (*SrcFile, error) { + file, err := os.Open(filespec) + if err != nil { + return nil, err + } + return NewSrcFile(file) +} + +// NewSrcFile creates a new SrcFile with the specified os.File. +func NewSrcFile(file *os.File) (*SrcFile, error) { + sf := &SrcFile{} + sf.freq = time.Minute + sf.file = file + if err := sf.ini.LoadFromFile(file); err != nil { + return nil, err + } + return sf, nil +} + +// GetProps fetches all the properties from a source and returns +// them as a map. +func (sf *SrcFile) GetProps() (map[string]string, error) { + lm, err := sf.GetLastModified() + if err != nil { + return nil, err + } + + // Check if we need to reload. + if sf.ini.GetLastModified() != lm { + if err := sf.ini.LoadFromFile(sf.file); err != nil { + return nil, err + } + } + return sf.ini.ToMap(), nil +} + +// GetLastModified returns the time of the latest modification to any +// property value within the source. +func (sf *SrcFile) GetLastModified() (time.Time, error) { + fi, err := sf.file.Stat() + if err != nil { + return time.Now(), err + } + return fi.ModTime(), nil +} diff --git a/vendor/github.com/wiggin77/cfg/srcmap.go b/vendor/github.com/wiggin77/cfg/srcmap.go new file mode 100644 index 00000000..321db27a --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/srcmap.go @@ -0,0 +1,78 @@ +package cfg + +import ( + "time" +) + +// SrcMap is a configuration `Source` backed by a simple map. +type SrcMap struct { + AbstractSourceMonitor + m map[string]string + lm time.Time +} + +// NewSrcMap creates an empty `SrcMap`. +func NewSrcMap() *SrcMap { + sm := &SrcMap{} + sm.m = make(map[string]string) + sm.lm = time.Now() + sm.freq = time.Minute + return sm +} + +// NewSrcMapFromMap creates a `SrcMap` containing a copy of the +// specified map. +func NewSrcMapFromMap(mapIn map[string]string) *SrcMap { + sm := NewSrcMap() + sm.PutAll(mapIn) + return sm +} + +// Put inserts or updates a value in the `SrcMap`. +func (sm *SrcMap) Put(key string, val string) { + sm.mutex.Lock() + sm.m[key] = val + sm.lm = time.Now() + sm.mutex.Unlock() +} + +// PutAll inserts a copy of `mapIn` into the `SrcMap` +func (sm *SrcMap) PutAll(mapIn map[string]string) { + sm.mutex.Lock() + defer sm.mutex.Unlock() + + for k, v := range mapIn { + sm.m[k] = v + } + sm.lm = time.Now() +} + +// GetProps fetches all the properties from a source and returns +// them as a map. +func (sm *SrcMap) GetProps() (m map[string]string, err error) { + sm.mutex.RLock() + m = sm.m + sm.mutex.RUnlock() + return +} + +// GetLastModified returns the time of the latest modification to any +// property value within the source. If a source does not support +// modifying properties at runtime then the zero value for `Time` +// should be returned to ensure reload events are not generated. +func (sm *SrcMap) GetLastModified() (last time.Time, err error) { + sm.mutex.RLock() + last = sm.lm + sm.mutex.RUnlock() + return +} + +// GetMonitorFreq returns the frequency as a `time.Duration` between +// checks for changes to this config source. Defaults to 1 minute +// unless changed with `SetMonitorFreq`. +func (sm *SrcMap) GetMonitorFreq() (freq time.Duration) { + sm.mutex.RLock() + freq = sm.freq + sm.mutex.RUnlock() + return +} diff --git a/vendor/github.com/wiggin77/cfg/timeconv/parse.go b/vendor/github.com/wiggin77/cfg/timeconv/parse.go new file mode 100644 index 00000000..218ef43a --- /dev/null +++ b/vendor/github.com/wiggin77/cfg/timeconv/parse.go @@ -0,0 +1,108 @@ +package timeconv + +import ( + "fmt" + "math" + "regexp" + "strconv" + "strings" +) + +// MillisPerSecond is the number of millseconds per second. +const MillisPerSecond int64 = 1000 + +// MillisPerMinute is the number of millseconds per minute. +const MillisPerMinute int64 = MillisPerSecond * 60 + +// MillisPerHour is the number of millseconds per hour. +const MillisPerHour int64 = MillisPerMinute * 60 + +// MillisPerDay is the number of millseconds per day. +const MillisPerDay int64 = MillisPerHour * 24 + +// MillisPerWeek is the number of millseconds per week. +const MillisPerWeek int64 = MillisPerDay * 7 + +// MillisPerYear is the approximate number of millseconds per year. +const MillisPerYear int64 = MillisPerDay*365 + int64((float64(MillisPerDay) * 0.25)) + +// ParseMilliseconds parses a string containing a number plus +// a unit of measure for time and returns the number of milliseconds +// it represents. +// +// Example: +// * "1 second" returns 1000 +// * "1 minute" returns 60000 +// * "1 hour" returns 3600000 +// +// See config.UnitsToMillis for a list of supported units of measure. +func ParseMilliseconds(str string) (int64, error) { + s := strings.TrimSpace(str) + reg := regexp.MustCompile("([0-9\\.\\-+]*)(.*)") + matches := reg.FindStringSubmatch(s) + if matches == nil || len(matches) < 1 || matches[1] == "" { + return 0, fmt.Errorf("invalid syntax - '%s'", s) + } + digits := matches[1] + units := "ms" + if len(matches) > 1 && matches[2] != "" { + units = matches[2] + } + + fDigits, err := strconv.ParseFloat(digits, 64) + if err != nil { + return 0, err + } + + msPerUnit, err := UnitsToMillis(units) + if err != nil { + return 0, err + } + + // Check for overflow. + fms := float64(msPerUnit) * fDigits + if fms > math.MaxInt64 || fms < math.MinInt64 { + return 0, fmt.Errorf("out of range - '%s' overflows", s) + } + ms := int64(fms) + return ms, nil +} + +// UnitsToMillis returns the number of milliseconds represented by the specified unit of measure. +// +// Example: +// * "second" returns 1000 <br/> +// * "minute" returns 60000 <br/> +// * "hour" returns 3600000 <br/> +// +// Supported units of measure: +// * "milliseconds", "millis", "ms", "millisecond" +// * "seconds", "sec", "s", "second" +// * "minutes", "mins", "min", "m", "minute" +// * "hours", "h", "hour" +// * "days", "d", "day" +// * "weeks", "w", "week" +// * "years", "y", "year" +func UnitsToMillis(units string) (ms int64, err error) { + u := strings.TrimSpace(units) + u = strings.ToLower(u) + switch u { + case "milliseconds", "millisecond", "millis", "ms": + ms = 1 + case "seconds", "second", "sec", "s": + ms = MillisPerSecond + case "minutes", "minute", "mins", "min", "m": + ms = MillisPerMinute + case "hours", "hour", "h": + ms = MillisPerHour + case "days", "day", "d": + ms = MillisPerDay + case "weeks", "week", "w": + ms = MillisPerWeek + case "years", "year", "y": + ms = MillisPerYear + default: + err = fmt.Errorf("invalid syntax - '%s' not a supported unit of measure", u) + } + return +} diff --git a/vendor/github.com/wiggin77/merror/.gitignore b/vendor/github.com/wiggin77/merror/.gitignore new file mode 100644 index 00000000..f1c181ec --- /dev/null +++ b/vendor/github.com/wiggin77/merror/.gitignore @@ -0,0 +1,12 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out diff --git a/vendor/github.com/wiggin77/merror/LICENSE b/vendor/github.com/wiggin77/merror/LICENSE new file mode 100644 index 00000000..2b0bf7ef --- /dev/null +++ b/vendor/github.com/wiggin77/merror/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 wiggin77 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/wiggin77/merror/README.md b/vendor/github.com/wiggin77/merror/README.md new file mode 100644 index 00000000..8a31687f --- /dev/null +++ b/vendor/github.com/wiggin77/merror/README.md @@ -0,0 +1,2 @@ +# merror +Multiple Error aggregator for Golang. diff --git a/vendor/github.com/wiggin77/merror/format.go b/vendor/github.com/wiggin77/merror/format.go new file mode 100644 index 00000000..8ba9aa82 --- /dev/null +++ b/vendor/github.com/wiggin77/merror/format.go @@ -0,0 +1,43 @@ +package merror + +import ( + "fmt" + "strings" +) + +// FormatterFunc is a function that converts a merror +// to a string. +type FormatterFunc func(merr *MError) string + +// GlobalFormatter is the global merror formatter. +// Set this to a custom formatter if desired. +var GlobalFormatter = defaultFormatter + +// defaultFormatter +func defaultFormatter(merr *MError) string { + count := 0 + overflow := 0 + + var format func(sb *strings.Builder, merr *MError, indent string) + format = func(sb *strings.Builder, merr *MError, indent string) { + count += merr.Len() + overflow += merr.Overflow() + + fmt.Fprintf(sb, "%sMError:\n", indent) + for _, err := range merr.Errors() { + if e, ok := err.(*MError); ok { + format(sb, e, indent+" ") + } else { + fmt.Fprintf(sb, "%s%s\n", indent, err.Error()) + } + } + } + + sb := &strings.Builder{} + format(sb, merr, "") + fmt.Fprintf(sb, "%d errors total.\n", count) + if merr.overflow > 0 { + fmt.Fprintf(sb, "%d errors truncated.\n", overflow) + } + return sb.String() +} diff --git a/vendor/github.com/wiggin77/merror/go.mod b/vendor/github.com/wiggin77/merror/go.mod new file mode 100644 index 00000000..44982f78 --- /dev/null +++ b/vendor/github.com/wiggin77/merror/go.mod @@ -0,0 +1 @@ +module github.com/wiggin77/merror diff --git a/vendor/github.com/wiggin77/merror/merror.go b/vendor/github.com/wiggin77/merror/merror.go new file mode 100644 index 00000000..01f19913 --- /dev/null +++ b/vendor/github.com/wiggin77/merror/merror.go @@ -0,0 +1,87 @@ +package merror + +// MError represents zero or more errors that can be +// accumulated via the `Append` method. +type MError struct { + cap int + errors []error + overflow int + formatter FormatterFunc +} + +// New returns a new instance of `MError` with no limit on the +// number of errors that can be appended. +func New() *MError { + me := &MError{} + me.errors = make([]error, 0, 10) + return me +} + +// NewWithCap returns a new instance of `MError` with a maximum +// capacity of `cap` errors. If exceeded only the overflow counter +// will be incremented. +// +// A `cap` of zero of less means no cap and max size of a slice +// on the current platform is the upper bound. +func NewWithCap(cap int) *MError { + me := New() + me.cap = cap + return me +} + +// Append adds an error to the aggregated error list. +func (me *MError) Append(err error) { + if err == nil { + return + } + if me.cap > 0 && len(me.errors) >= me.cap { + me.overflow++ + } else { + me.errors = append(me.errors, err) + } +} + +// Errors returns an array of the `error` instances that have been +// appended to this `MError`. +func (me *MError) Errors() []error { + return me.errors +} + +// Len returns the number of errors that have been appended. +func (me *MError) Len() int { + return len(me.errors) +} + +// Overflow returns the number of errors that have been truncated +// because maximum capacity was exceeded. +func (me *MError) Overflow() int { + return me.overflow +} + +// SetFormatter sets the `FormatterFunc` to be used when `Error` is +// called. The previous `FormatterFunc` is returned. +func (me *MError) SetFormatter(f FormatterFunc) (old FormatterFunc) { + old = me.formatter + me.formatter = f + return +} + +// ErrorOrNil returns nil if this `MError` contains no errors, +// otherwise this `MError` is returned. +func (me *MError) ErrorOrNil() error { + if me == nil || len(me.errors) == 0 { + return nil + } + return me +} + +// Error returns a string representation of this MError. +// The output format depends on the `Formatter` set for this +// merror instance, or the global formatter if none set. +func (me *MError) Error() string { + f := me.formatter + if f == nil { + f = GlobalFormatter + } + return f(me) +} diff --git a/vendor/github.com/wiggin77/srslog/.gitignore b/vendor/github.com/wiggin77/srslog/.gitignore new file mode 100644 index 00000000..ebf0f2e4 --- /dev/null +++ b/vendor/github.com/wiggin77/srslog/.gitignore @@ -0,0 +1 @@ +.cover diff --git a/vendor/github.com/wiggin77/srslog/.travis.yml b/vendor/github.com/wiggin77/srslog/.travis.yml new file mode 100644 index 00000000..921150e9 --- /dev/null +++ b/vendor/github.com/wiggin77/srslog/.travis.yml @@ -0,0 +1,15 @@ +sudo: required +dist: trusty +group: edge +language: go +go: +- 1.5 +before_install: + - pip install --user codecov +script: +- | + go get ./... + go test -v -coverprofile=coverage.txt -covermode=atomic + go vet +after_success: + - codecov diff --git a/vendor/github.com/wiggin77/srslog/CODE_OF_CONDUCT.md b/vendor/github.com/wiggin77/srslog/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..18ac49fc --- /dev/null +++ b/vendor/github.com/wiggin77/srslog/CODE_OF_CONDUCT.md @@ -0,0 +1,50 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of +fostering an open and welcoming community, we pledge to respect all people who +contribute through reporting issues, posting feature requests, updating +documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free +experience for everyone, regardless of level of experience, gender, gender +identity and expression, sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic + addresses, without explicit permission +* Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +By adopting this Code of Conduct, project maintainers commit themselves to +fairly and consistently applying these principles to every aspect of managing +this project. Project maintainers who do not follow or enforce the Code of +Conduct may be permanently removed from the project team. + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting a project maintainer at [sirsean@gmail.com]. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. Maintainers are +obligated to maintain confidentiality with regard to the reporter of an +incident. + + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.3.0, available at +[http://contributor-covenant.org/version/1/3/0/][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/3/0/ diff --git a/vendor/github.com/wiggin77/srslog/LICENSE b/vendor/github.com/wiggin77/srslog/LICENSE new file mode 100644 index 00000000..9269338f --- /dev/null +++ b/vendor/github.com/wiggin77/srslog/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2015 Rackspace. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/wiggin77/srslog/README.md b/vendor/github.com/wiggin77/srslog/README.md new file mode 100644 index 00000000..dcacc348 --- /dev/null +++ b/vendor/github.com/wiggin77/srslog/README.md @@ -0,0 +1,147 @@ +[![Build Status](https://travis-ci.org/RackSec/srslog.svg?branch=master)](https://travis-ci.org/RackSec/srslog) + +# srslog + +Go has a `syslog` package in the standard library, but it has the following +shortcomings: + +1. It doesn't have TLS support +2. [According to bradfitz on the Go team, it is no longer being maintained.](https://github.com/golang/go/issues/13449#issuecomment-161204716) + +I agree that it doesn't need to be in the standard library. So, I've +followed Brad's suggestion and have made a separate project to handle syslog. + +This code was taken directly from the Go project as a base to start from. + +However, this _does_ have TLS support. + +# Usage + +Basic usage retains the same interface as the original `syslog` package. We +only added to the interface where required to support new functionality. + +Switch from the standard library: + +``` +import( + //"log/syslog" + syslog "github.com/RackSec/srslog" +) +``` + +You can still use it for local syslog: + +``` +w, err := syslog.Dial("", "", syslog.LOG_ERR, "testtag") +``` + +Or to unencrypted UDP: + +``` +w, err := syslog.Dial("udp", "192.168.0.50:514", syslog.LOG_ERR, "testtag") +``` + +Or to unencrypted TCP: + +``` +w, err := syslog.Dial("tcp", "192.168.0.51:514", syslog.LOG_ERR, "testtag") +``` + +But now you can also send messages via TLS-encrypted TCP: + +``` +w, err := syslog.DialWithTLSCertPath("tcp+tls", "192.168.0.52:514", syslog.LOG_ERR, "testtag", "/path/to/servercert.pem") +``` + +And if you need more control over your TLS configuration : + +``` +pool := x509.NewCertPool() +serverCert, err := ioutil.ReadFile("/path/to/servercert.pem") +if err != nil { + return nil, err +} +pool.AppendCertsFromPEM(serverCert) +config := tls.Config{ + RootCAs: pool, +} + +w, err := DialWithTLSConfig(network, raddr, priority, tag, &config) +``` + +(Note that in both TLS cases, this uses a self-signed certificate, where the +remote syslog server has the keypair and the client has only the public key.) + +And then to write log messages, continue like so: + +``` +if err != nil { + log.Fatal("failed to connect to syslog:", err) +} +defer w.Close() + +w.Alert("this is an alert") +w.Crit("this is critical") +w.Err("this is an error") +w.Warning("this is a warning") +w.Notice("this is a notice") +w.Info("this is info") +w.Debug("this is debug") +w.Write([]byte("these are some bytes")) +``` + +If you need further control over connection attempts, you can use the DialWithCustomDialer +function. To continue with the DialWithTLSConfig example: + +``` +netDialer := &net.Dialer{Timeout: time.Second*5} // easy timeouts +realNetwork := "tcp" // real network, other vars your dail func can close over +dial := func(network, addr string) (net.Conn, error) { + // cannot use "network" here as it'll simply be "custom" which will fail + return tls.DialWithDialer(netDialer, realNetwork, addr, &config) +} + +w, err := DialWithCustomDialer("custom", "192.168.0.52:514", syslog.LOG_ERR, "testtag", dial) +``` + +Your custom dial func can set timeouts, proxy connections, and do whatever else it needs before returning a net.Conn. + +# Generating TLS Certificates + +We've provided a script that you can use to generate a self-signed keypair: + +``` +pip install cryptography +python script/gen-certs.py +``` + +That outputs the public key and private key to standard out. Put those into +`.pem` files. (And don't put them into any source control. The certificate in +the `test` directory is used by the unit tests, and please do not actually use +it anywhere else.) + +# Running Tests + +Run the tests as usual: + +``` +go test +``` + +But we've also provided a test coverage script that will show you which +lines of code are not covered: + +``` +script/coverage --html +``` + +That will open a new browser tab showing coverage information. + +# License + +This project uses the New BSD License, the same as the Go project itself. + +# Code of Conduct + +Please note that this project is released with a Contributor Code of Conduct. +By participating in this project you agree to abide by its terms. diff --git a/vendor/github.com/wiggin77/srslog/constants.go b/vendor/github.com/wiggin77/srslog/constants.go new file mode 100644 index 00000000..600801ee --- /dev/null +++ b/vendor/github.com/wiggin77/srslog/constants.go @@ -0,0 +1,68 @@ +package srslog + +import ( + "errors" +) + +// Priority is a combination of the syslog facility and +// severity. For example, LOG_ALERT | LOG_FTP sends an alert severity +// message from the FTP facility. The default severity is LOG_EMERG; +// the default facility is LOG_KERN. +type Priority int + +const severityMask = 0x07 +const facilityMask = 0xf8 + +const ( + // Severity. + + // From /usr/include/sys/syslog.h. + // These are the same on Linux, BSD, and OS X. + LOG_EMERG Priority = iota + LOG_ALERT + LOG_CRIT + LOG_ERR + LOG_WARNING + LOG_NOTICE + LOG_INFO + LOG_DEBUG +) + +const ( + // Facility. + + // From /usr/include/sys/syslog.h. + // These are the same up to LOG_FTP on Linux, BSD, and OS X. + LOG_KERN Priority = iota << 3 + LOG_USER + LOG_MAIL + LOG_DAEMON + LOG_AUTH + LOG_SYSLOG + LOG_LPR + LOG_NEWS + LOG_UUCP + LOG_CRON + LOG_AUTHPRIV + LOG_FTP + _ // unused + _ // unused + _ // unused + _ // unused + LOG_LOCAL0 + LOG_LOCAL1 + LOG_LOCAL2 + LOG_LOCAL3 + LOG_LOCAL4 + LOG_LOCAL5 + LOG_LOCAL6 + LOG_LOCAL7 +) + +func validatePriority(p Priority) error { + if p < 0 || p > LOG_LOCAL7|LOG_DEBUG { + return errors.New("log/syslog: invalid priority") + } else { + return nil + } +} diff --git a/vendor/github.com/wiggin77/srslog/dialer.go b/vendor/github.com/wiggin77/srslog/dialer.go new file mode 100644 index 00000000..1ecf29b2 --- /dev/null +++ b/vendor/github.com/wiggin77/srslog/dialer.go @@ -0,0 +1,104 @@ +package srslog + +import ( + "crypto/tls" + "net" +) + +// dialerFunctionWrapper is a simple object that consists of a dialer function +// and its name. This is primarily for testing, so we can make sure that the +// getDialer method returns the correct dialer function. However, if you ever +// find that you need to check which dialer function you have, this would also +// be useful for you without having to use reflection. +type dialerFunctionWrapper struct { + Name string + Dialer func() (serverConn, string, error) +} + +// Call the wrapped dialer function and return its return values. +func (df dialerFunctionWrapper) Call() (serverConn, string, error) { + return df.Dialer() +} + +// getDialer returns a "dialer" function that can be called to connect to a +// syslog server. +// +// Each dialer function is responsible for dialing the remote host and returns +// a serverConn, the hostname (or a default if the Writer has not specified a +// hostname), and an error in case dialing fails. +// +// The reason for separate dialers is that different network types may need +// to dial their connection differently, yet still provide a net.Conn interface +// that you can use once they have dialed. Rather than an increasingly long +// conditional, we have a map of network -> dialer function (with a sane default +// value), and adding a new network type is as easy as writing the dialer +// function and adding it to the map. +func (w *Writer) getDialer() dialerFunctionWrapper { + dialers := map[string]dialerFunctionWrapper{ + "": dialerFunctionWrapper{"unixDialer", w.unixDialer}, + "tcp+tls": dialerFunctionWrapper{"tlsDialer", w.tlsDialer}, + "custom": dialerFunctionWrapper{"customDialer", w.customDialer}, + } + dialer, ok := dialers[w.network] + if !ok { + dialer = dialerFunctionWrapper{"basicDialer", w.basicDialer} + } + return dialer +} + +// unixDialer uses the unixSyslog method to open a connection to the syslog +// daemon running on the local machine. +func (w *Writer) unixDialer() (serverConn, string, error) { + sc, err := unixSyslog() + hostname := w.hostname + if hostname == "" { + hostname = "localhost" + } + return sc, hostname, err +} + +// tlsDialer connects to TLS over TCP, and is used for the "tcp+tls" network +// type. +func (w *Writer) tlsDialer() (serverConn, string, error) { + c, err := tls.Dial("tcp", w.raddr, w.tlsConfig) + var sc serverConn + hostname := w.hostname + if err == nil { + sc = newNetConn(c) + if hostname == "" { + hostname = c.LocalAddr().String() + } + } + return sc, hostname, err +} + +// basicDialer is the most common dialer for syslog, and supports both TCP and +// UDP connections. +func (w *Writer) basicDialer() (serverConn, string, error) { + c, err := net.Dial(w.network, w.raddr) + var sc serverConn + hostname := w.hostname + if err == nil { + sc = newNetConn(c) + if hostname == "" { + hostname = c.LocalAddr().String() + } + } + return sc, hostname, err +} + +// customDialer uses the custom dialer when the Writer was created +// giving developers total control over how connections are made and returned. +// Note it does not check if cdialer is nil, as it should only be referenced from getDialer. +func (w *Writer) customDialer() (serverConn, string, error) { + c, err := w.customDial(w.network, w.raddr) + var sc serverConn + hostname := w.hostname + if err == nil { + sc = newNetConn(c) + if hostname == "" { + hostname = c.LocalAddr().String() + } + } + return sc, hostname, err +} diff --git a/vendor/github.com/wiggin77/srslog/formatter.go b/vendor/github.com/wiggin77/srslog/formatter.go new file mode 100644 index 00000000..e306fd67 --- /dev/null +++ b/vendor/github.com/wiggin77/srslog/formatter.go @@ -0,0 +1,58 @@ +package srslog + +import ( + "fmt" + "os" + "time" +) + +const appNameMaxLength = 48 // limit to 48 chars as per RFC5424 + +// Formatter is a type of function that takes the consituent parts of a +// syslog message and returns a formatted string. A different Formatter is +// defined for each different syslog protocol we support. +type Formatter func(p Priority, hostname, tag, content string) string + +// DefaultFormatter is the original format supported by the Go syslog package, +// and is a non-compliant amalgamation of 3164 and 5424 that is intended to +// maximize compatibility. +func DefaultFormatter(p Priority, hostname, tag, content string) string { + timestamp := time.Now().Format(time.RFC3339) + msg := fmt.Sprintf("<%d> %s %s %s[%d]: %s", + p, timestamp, hostname, tag, os.Getpid(), content) + return msg +} + +// UnixFormatter omits the hostname, because it is only used locally. +func UnixFormatter(p Priority, hostname, tag, content string) string { + timestamp := time.Now().Format(time.Stamp) + msg := fmt.Sprintf("<%d>%s %s[%d]: %s", + p, timestamp, tag, os.Getpid(), content) + return msg +} + +// RFC3164Formatter provides an RFC 3164 compliant message. +func RFC3164Formatter(p Priority, hostname, tag, content string) string { + timestamp := time.Now().Format(time.Stamp) + msg := fmt.Sprintf("<%d>%s %s %s[%d]: %s", + p, timestamp, hostname, tag, os.Getpid(), content) + return msg +} + +// if string's length is greater than max, then use the last part +func truncateStartStr(s string, max int) string { + if (len(s) > max) { + return s[len(s) - max:] + } + return s +} + +// RFC5424Formatter provides an RFC 5424 compliant message. +func RFC5424Formatter(p Priority, hostname, tag, content string) string { + timestamp := time.Now().Format(time.RFC3339) + pid := os.Getpid() + appName := truncateStartStr(os.Args[0], appNameMaxLength) + msg := fmt.Sprintf("<%d>%d %s %s %s %d %s - %s", + p, 1, timestamp, hostname, appName, pid, tag, content) + return msg +} diff --git a/vendor/github.com/wiggin77/srslog/framer.go b/vendor/github.com/wiggin77/srslog/framer.go new file mode 100644 index 00000000..ab46f0de --- /dev/null +++ b/vendor/github.com/wiggin77/srslog/framer.go @@ -0,0 +1,24 @@ +package srslog + +import ( + "fmt" +) + +// Framer is a type of function that takes an input string (typically an +// already-formatted syslog message) and applies "message framing" to it. We +// have different framers because different versions of the syslog protocol +// and its transport requirements define different framing behavior. +type Framer func(in string) string + +// DefaultFramer does nothing, since there is no framing to apply. This is +// the original behavior of the Go syslog package, and is also typically used +// for UDP syslog. +func DefaultFramer(in string) string { + return in +} + +// RFC5425MessageLengthFramer prepends the message length to the front of the +// provided message, as defined in RFC 5425. +func RFC5425MessageLengthFramer(in string) string { + return fmt.Sprintf("%d %s", len(in), in) +} diff --git a/vendor/github.com/wiggin77/srslog/go.mod b/vendor/github.com/wiggin77/srslog/go.mod new file mode 100644 index 00000000..393b0761 --- /dev/null +++ b/vendor/github.com/wiggin77/srslog/go.mod @@ -0,0 +1,3 @@ +module github.com/wiggin77/srslog + +go 1.14 diff --git a/vendor/github.com/wiggin77/srslog/logger.go b/vendor/github.com/wiggin77/srslog/logger.go new file mode 100644 index 00000000..3a738565 --- /dev/null +++ b/vendor/github.com/wiggin77/srslog/logger.go @@ -0,0 +1,13 @@ +package srslog + +import ( + "io/ioutil" + "log" +) + +var Logger log.Logger + +func init() { + Logger = log.Logger{} + Logger.SetOutput(ioutil.Discard) +} diff --git a/vendor/github.com/wiggin77/srslog/net_conn.go b/vendor/github.com/wiggin77/srslog/net_conn.go new file mode 100644 index 00000000..f3cfeb60 --- /dev/null +++ b/vendor/github.com/wiggin77/srslog/net_conn.go @@ -0,0 +1,76 @@ +package srslog + +import ( + "io" + "net" + "time" +) + +// netConn has an internal net.Conn and adheres to the serverConn interface, +// allowing us to send syslog messages over the network. +type netConn struct { + conn net.Conn + done chan interface{} +} + +// newNetConn creates a netConn instance that is monitored for unexpected socket closure. +func newNetConn(conn net.Conn) *netConn { + nc := &netConn{conn: conn, done: make(chan interface{})} + go monitor(nc.conn, nc.done) + return nc +} + +// writeString formats syslog messages using time.RFC3339 and includes the +// hostname, and sends the message to the connection. +func (n *netConn) writeString(framer Framer, formatter Formatter, p Priority, hostname, tag, msg string) error { + if framer == nil { + framer = DefaultFramer + } + if formatter == nil { + formatter = DefaultFormatter + } + formattedMessage := framer(formatter(p, hostname, tag, msg)) + _, err := n.conn.Write([]byte(formattedMessage)) + return err +} + +// close the network connection +func (n *netConn) close() error { + // signal monitor goroutine to exit + close(n.done) + // wake up monitor blocked on read (close usually is enough) + _ = n.conn.SetReadDeadline(time.Now()) + // close the connection + return n.conn.Close() +} + +// monitor continuously tries to read from the connection to detect socket close. +// This is needed because syslog server uses a write only socket and Linux systems +// take a long time to detect a loss of connectivity on a socket when only writing; +// the writes simply fail without an error returned. +func monitor(conn net.Conn, done chan interface{}) { + defer Logger.Println("monitor exit") + + buf := make([]byte, 1) + for { + Logger.Println("monitor loop") + + select { + case <-done: + return + case <-time.After(1 * time.Second): + } + + err := conn.SetReadDeadline(time.Now().Add(time.Second * 30)) + if err != nil { + continue + } + + _, err = conn.Read(buf) + Logger.Println("monitor -- ", err) + if err == io.EOF { + Logger.Println("monitor close conn") + conn.Close() + } + } +} diff --git a/vendor/github.com/wiggin77/srslog/srslog.go b/vendor/github.com/wiggin77/srslog/srslog.go new file mode 100644 index 00000000..b47ad72d --- /dev/null +++ b/vendor/github.com/wiggin77/srslog/srslog.go @@ -0,0 +1,125 @@ +package srslog + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "io/ioutil" + "log" + "net" + "os" +) + +// This interface allows us to work with both local and network connections, +// and enables Solaris support (see syslog_unix.go). +type serverConn interface { + writeString(framer Framer, formatter Formatter, p Priority, hostname, tag, s string) error + close() error +} + +// DialFunc is the function signature to be used for a custom dialer callback +// with DialWithCustomDialer +type DialFunc func(string, string) (net.Conn, error) + +// New establishes a new connection to the system log daemon. Each +// write to the returned Writer sends a log message with the given +// priority and prefix. +func New(priority Priority, tag string) (w *Writer, err error) { + return Dial("", "", priority, tag) +} + +// Dial establishes a connection to a log daemon by connecting to +// address raddr on the specified network. Each write to the returned +// Writer sends a log message with the given facility, severity and +// tag. +// If network is empty, Dial will connect to the local syslog server. +func Dial(network, raddr string, priority Priority, tag string) (*Writer, error) { + return DialWithTLSConfig(network, raddr, priority, tag, nil) +} + +// ErrNilDialFunc is returned from DialWithCustomDialer when a nil DialFunc is passed, +// avoiding a nil pointer deference panic. +var ErrNilDialFunc = errors.New("srslog: nil DialFunc passed to DialWithCustomDialer") + +// DialWithCustomDialer establishes a connection by calling customDial. +// Each write to the returned Writer sends a log message with the given facility, severity and tag. +// Network must be "custom" in order for this package to use customDial. +// While network and raddr will be passed to customDial, it is allowed for customDial to ignore them. +// If customDial is nil, this function returns ErrNilDialFunc. +func DialWithCustomDialer(network, raddr string, priority Priority, tag string, customDial DialFunc) (*Writer, error) { + if customDial == nil { + return nil, ErrNilDialFunc + } + return dialAllParameters(network, raddr, priority, tag, nil, customDial) +} + +// DialWithTLSCertPath establishes a secure connection to a log daemon by connecting to +// address raddr on the specified network. It uses certPath to load TLS certificates and configure +// the secure connection. +func DialWithTLSCertPath(network, raddr string, priority Priority, tag, certPath string) (*Writer, error) { + serverCert, err := ioutil.ReadFile(certPath) + if err != nil { + return nil, err + } + + return DialWithTLSCert(network, raddr, priority, tag, serverCert) +} + +// DialWIthTLSCert establishes a secure connection to a log daemon by connecting to +// address raddr on the specified network. It uses serverCert to load a TLS certificate +// and configure the secure connection. +func DialWithTLSCert(network, raddr string, priority Priority, tag string, serverCert []byte) (*Writer, error) { + pool := x509.NewCertPool() + pool.AppendCertsFromPEM(serverCert) + config := tls.Config{ + RootCAs: pool, + } + + return DialWithTLSConfig(network, raddr, priority, tag, &config) +} + +// DialWithTLSConfig establishes a secure connection to a log daemon by connecting to +// address raddr on the specified network. It uses tlsConfig to configure the secure connection. +func DialWithTLSConfig(network, raddr string, priority Priority, tag string, tlsConfig *tls.Config) (*Writer, error) { + return dialAllParameters(network, raddr, priority, tag, tlsConfig, nil) +} + +// implementation of the various functions above +func dialAllParameters(network, raddr string, priority Priority, tag string, tlsConfig *tls.Config, customDial DialFunc) (*Writer, error) { + if err := validatePriority(priority); err != nil { + return nil, err + } + + if tag == "" { + tag = os.Args[0] + } + hostname, _ := os.Hostname() + + w := &Writer{ + priority: priority, + tag: tag, + hostname: hostname, + network: network, + raddr: raddr, + tlsConfig: tlsConfig, + customDial: customDial, + } + + _, err := w.connect() + if err != nil { + return nil, err + } + return w, err +} + +// NewLogger creates a log.Logger whose output is written to +// the system log service with the specified priority. The logFlag +// argument is the flag set passed through to log.New to create +// the Logger. +func NewLogger(p Priority, logFlag int) (*log.Logger, error) { + s, err := New(p, "") + if err != nil { + return nil, err + } + return log.New(s, "", logFlag), nil +} diff --git a/vendor/github.com/wiggin77/srslog/srslog_unix.go b/vendor/github.com/wiggin77/srslog/srslog_unix.go new file mode 100644 index 00000000..a04d9396 --- /dev/null +++ b/vendor/github.com/wiggin77/srslog/srslog_unix.go @@ -0,0 +1,54 @@ +package srslog + +import ( + "errors" + "io" + "net" +) + +// unixSyslog opens a connection to the syslog daemon running on the +// local machine using a Unix domain socket. This function exists because of +// Solaris support as implemented by gccgo. On Solaris you can not +// simply open a TCP connection to the syslog daemon. The gccgo +// sources have a syslog_solaris.go file that implements unixSyslog to +// return a type that satisfies the serverConn interface and simply calls the C +// library syslog function. +func unixSyslog() (conn serverConn, err error) { + logTypes := []string{"unixgram", "unix"} + logPaths := []string{"/dev/log", "/var/run/syslog", "/var/run/log"} + for _, network := range logTypes { + for _, path := range logPaths { + conn, err := net.Dial(network, path) + if err != nil { + continue + } else { + return &localConn{conn: conn}, nil + } + } + } + return nil, errors.New("Unix syslog delivery error") +} + +// localConn adheres to the serverConn interface, allowing us to send syslog +// messages to the local syslog daemon over a Unix domain socket. +type localConn struct { + conn io.WriteCloser +} + +// writeString formats syslog messages using time.Stamp instead of time.RFC3339, +// and omits the hostname (because it is expected to be used locally). +func (n *localConn) writeString(framer Framer, formatter Formatter, p Priority, hostname, tag, msg string) error { + if framer == nil { + framer = DefaultFramer + } + if formatter == nil { + formatter = UnixFormatter + } + _, err := n.conn.Write([]byte(framer(formatter(p, hostname, tag, msg)))) + return err +} + +// close the (local) network connection +func (n *localConn) close() error { + return n.conn.Close() +} diff --git a/vendor/github.com/wiggin77/srslog/writer.go b/vendor/github.com/wiggin77/srslog/writer.go new file mode 100644 index 00000000..86bccba1 --- /dev/null +++ b/vendor/github.com/wiggin77/srslog/writer.go @@ -0,0 +1,201 @@ +package srslog + +import ( + "crypto/tls" + "strings" + "sync" +) + +// A Writer is a connection to a syslog server. +type Writer struct { + priority Priority + tag string + hostname string + network string + raddr string + tlsConfig *tls.Config + framer Framer + formatter Formatter + + //non-nil if custom dialer set, used in getDialer + customDial DialFunc + + mu sync.RWMutex // guards conn + conn serverConn +} + +// getConn provides access to the internal conn, protected by a mutex. The +// conn is threadsafe, so it can be used while unlocked, but we want to avoid +// race conditions on grabbing a reference to it. +func (w *Writer) getConn() serverConn { + w.mu.RLock() + conn := w.conn + w.mu.RUnlock() + return conn +} + +// setConn updates the internal conn, protected by a mutex. +func (w *Writer) setConn(c serverConn) { + w.mu.Lock() + w.conn = c + w.mu.Unlock() +} + +// connect makes a connection to the syslog server. +func (w *Writer) connect() (serverConn, error) { + conn := w.getConn() + if conn != nil { + // ignore err from close, it makes sense to continue anyway + conn.close() + w.setConn(nil) + } + + var hostname string + var err error + dialer := w.getDialer() + conn, hostname, err = dialer.Call() + if err == nil { + w.setConn(conn) + w.hostname = hostname + + return conn, nil + } else { + return nil, err + } +} + +// SetFormatter changes the formatter function for subsequent messages. +func (w *Writer) SetFormatter(f Formatter) { + w.formatter = f +} + +// SetFramer changes the framer function for subsequent messages. +func (w *Writer) SetFramer(f Framer) { + w.framer = f +} + +// SetHostname changes the hostname for syslog messages if needed. +func (w *Writer) SetHostname(hostname string) { + w.hostname = hostname +} + +// Write sends a log message to the syslog daemon using the default priority +// passed into `srslog.New` or the `srslog.Dial*` functions. +func (w *Writer) Write(b []byte) (int, error) { + return w.writeAndRetry(w.priority, string(b)) +} + +// WriteWithPriority sends a log message with a custom priority. +func (w *Writer) WriteWithPriority(p Priority, b []byte) (int, error) { + return w.writeAndRetryWithPriority(p, string(b)) +} + +// Close closes a connection to the syslog daemon. +func (w *Writer) Close() error { + conn := w.getConn() + if conn != nil { + err := conn.close() + w.setConn(nil) + return err + } + return nil +} + +// Emerg logs a message with severity LOG_EMERG; this overrides the default +// priority passed to `srslog.New` and the `srslog.Dial*` functions. +func (w *Writer) Emerg(m string) (err error) { + _, err = w.writeAndRetry(LOG_EMERG, m) + return err +} + +// Alert logs a message with severity LOG_ALERT; this overrides the default +// priority passed to `srslog.New` and the `srslog.Dial*` functions. +func (w *Writer) Alert(m string) (err error) { + _, err = w.writeAndRetry(LOG_ALERT, m) + return err +} + +// Crit logs a message with severity LOG_CRIT; this overrides the default +// priority passed to `srslog.New` and the `srslog.Dial*` functions. +func (w *Writer) Crit(m string) (err error) { + _, err = w.writeAndRetry(LOG_CRIT, m) + return err +} + +// Err logs a message with severity LOG_ERR; this overrides the default +// priority passed to `srslog.New` and the `srslog.Dial*` functions. +func (w *Writer) Err(m string) (err error) { + _, err = w.writeAndRetry(LOG_ERR, m) + return err +} + +// Warning logs a message with severity LOG_WARNING; this overrides the default +// priority passed to `srslog.New` and the `srslog.Dial*` functions. +func (w *Writer) Warning(m string) (err error) { + _, err = w.writeAndRetry(LOG_WARNING, m) + return err +} + +// Notice logs a message with severity LOG_NOTICE; this overrides the default +// priority passed to `srslog.New` and the `srslog.Dial*` functions. +func (w *Writer) Notice(m string) (err error) { + _, err = w.writeAndRetry(LOG_NOTICE, m) + return err +} + +// Info logs a message with severity LOG_INFO; this overrides the default +// priority passed to `srslog.New` and the `srslog.Dial*` functions. +func (w *Writer) Info(m string) (err error) { + _, err = w.writeAndRetry(LOG_INFO, m) + return err +} + +// Debug logs a message with severity LOG_DEBUG; this overrides the default +// priority passed to `srslog.New` and the `srslog.Dial*` functions. +func (w *Writer) Debug(m string) (err error) { + _, err = w.writeAndRetry(LOG_DEBUG, m) + return err +} + +// writeAndRetry takes a severity and the string to write. Any facility passed to +// it as part of the severity Priority will be ignored. +func (w *Writer) writeAndRetry(severity Priority, s string) (int, error) { + pr := (w.priority & facilityMask) | (severity & severityMask) + + return w.writeAndRetryWithPriority(pr, s) +} + +// writeAndRetryWithPriority differs from writeAndRetry in that it allows setting +// of both the facility and the severity. +func (w *Writer) writeAndRetryWithPriority(p Priority, s string) (int, error) { + conn := w.getConn() + if conn != nil { + if n, err := w.write(conn, p, s); err == nil { + return n, err + } + } + + var err error + if conn, err = w.connect(); err != nil { + return 0, err + } + return w.write(conn, p, s) +} + +// write generates and writes a syslog formatted string. It formats the +// message based on the current Formatter and Framer. +func (w *Writer) write(conn serverConn, p Priority, msg string) (int, error) { + // ensure it ends in a \n + if !strings.HasSuffix(msg, "\n") { + msg += "\n" + } + + err := conn.writeString(w.framer, w.formatter, p, w.hostname, w.tag, msg) + if err != nil { + return 0, err + } + // Note: return the length of the input, not the number of + // bytes printed by Fprintf, because this must behave like + // an io.Writer. + return len(msg), nil +} |