mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-10 07:11:08 +00:00
916ec63af6
This is a new revision of the discovery server. Relevant changes and non-changes: - Protocol towards clients is unchanged. - Recommended large scale design is still to be deployed nehind nginx (I tested, and it's still a lot faster at terminating TLS). - Database backend is leveldb again, only. It scales enough, is easy to setup, and we don't need any backend to take care of. - Server supports replication. This is a simple TCP channel - protect it with a firewall when deploying over the internet. (We deploy this within the same datacenter, and with firewall.) Any incoming client announces are sent over the replication channel(s) to other peer discosrvs. Incoming replication changes are applied to the database as if they came from clients, but without the TLS/certificate overhead. - Metrics are exposed using the prometheus library, when enabled. - The database values and replication protocol is protobuf, because JSON was quite CPU intensive when I tried that and benchmarked it. - The "Retry-After" value for failed lookups gets slowly increased from a default of 120 seconds, by 5 seconds for each failed lookup, independently by each discosrv. This lowers the query load over time for clients that are never seen. The Retry-After maxes out at 3600 after a couple of weeks of this increase. The number of failed lookups is stored in the database, now and then (avoiding making each lookup a database put). All in all this means clients can be pointed towards a cluster using just multiple A / AAAA records to gain both load sharing and redundancy (if one is down, clients will talk to the remaining ones). GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4648
280 lines
6.8 KiB
Go
280 lines
6.8 KiB
Go
package logrus
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var bufferPool *sync.Pool
|
|
|
|
func init() {
|
|
bufferPool = &sync.Pool{
|
|
New: func() interface{} {
|
|
return new(bytes.Buffer)
|
|
},
|
|
}
|
|
}
|
|
|
|
// Defines the key when adding errors using WithError.
|
|
var ErrorKey = "error"
|
|
|
|
// An entry is the final or intermediate Logrus logging entry. It contains all
|
|
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
|
|
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
|
|
// passed around as much as you wish to avoid field duplication.
|
|
type Entry struct {
|
|
Logger *Logger
|
|
|
|
// Contains all the fields set by the user.
|
|
Data Fields
|
|
|
|
// Time at which the log entry was created
|
|
Time time.Time
|
|
|
|
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
|
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
|
Level Level
|
|
|
|
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
|
Message string
|
|
|
|
// When formatter is called in entry.log(), an Buffer may be set to entry
|
|
Buffer *bytes.Buffer
|
|
}
|
|
|
|
func NewEntry(logger *Logger) *Entry {
|
|
return &Entry{
|
|
Logger: logger,
|
|
// Default is three fields, give a little extra room
|
|
Data: make(Fields, 5),
|
|
}
|
|
}
|
|
|
|
// Returns the string representation from the reader and ultimately the
|
|
// formatter.
|
|
func (entry *Entry) String() (string, error) {
|
|
serialized, err := entry.Logger.Formatter.Format(entry)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
str := string(serialized)
|
|
return str, nil
|
|
}
|
|
|
|
// Add an error as single field (using the key defined in ErrorKey) to the Entry.
|
|
func (entry *Entry) WithError(err error) *Entry {
|
|
return entry.WithField(ErrorKey, err)
|
|
}
|
|
|
|
// Add a single field to the Entry.
|
|
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
|
return entry.WithFields(Fields{key: value})
|
|
}
|
|
|
|
// Add a map of fields to the Entry.
|
|
func (entry *Entry) WithFields(fields Fields) *Entry {
|
|
data := make(Fields, len(entry.Data)+len(fields))
|
|
for k, v := range entry.Data {
|
|
data[k] = v
|
|
}
|
|
for k, v := range fields {
|
|
data[k] = v
|
|
}
|
|
return &Entry{Logger: entry.Logger, Data: data}
|
|
}
|
|
|
|
// This function is not declared with a pointer value because otherwise
|
|
// race conditions will occur when using multiple goroutines
|
|
func (entry Entry) log(level Level, msg string) {
|
|
var buffer *bytes.Buffer
|
|
entry.Time = time.Now()
|
|
entry.Level = level
|
|
entry.Message = msg
|
|
|
|
entry.Logger.mu.Lock()
|
|
err := entry.Logger.Hooks.Fire(level, &entry)
|
|
entry.Logger.mu.Unlock()
|
|
if err != nil {
|
|
entry.Logger.mu.Lock()
|
|
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
|
entry.Logger.mu.Unlock()
|
|
}
|
|
buffer = bufferPool.Get().(*bytes.Buffer)
|
|
buffer.Reset()
|
|
defer bufferPool.Put(buffer)
|
|
entry.Buffer = buffer
|
|
serialized, err := entry.Logger.Formatter.Format(&entry)
|
|
entry.Buffer = nil
|
|
if err != nil {
|
|
entry.Logger.mu.Lock()
|
|
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
|
entry.Logger.mu.Unlock()
|
|
} else {
|
|
entry.Logger.mu.Lock()
|
|
_, err = entry.Logger.Out.Write(serialized)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
|
}
|
|
entry.Logger.mu.Unlock()
|
|
}
|
|
|
|
// To avoid Entry#log() returning a value that only would make sense for
|
|
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
|
// directly here.
|
|
if level <= PanicLevel {
|
|
panic(&entry)
|
|
}
|
|
}
|
|
|
|
func (entry *Entry) Debug(args ...interface{}) {
|
|
if entry.Logger.level() >= DebugLevel {
|
|
entry.log(DebugLevel, fmt.Sprint(args...))
|
|
}
|
|
}
|
|
|
|
func (entry *Entry) Print(args ...interface{}) {
|
|
entry.Info(args...)
|
|
}
|
|
|
|
func (entry *Entry) Info(args ...interface{}) {
|
|
if entry.Logger.level() >= InfoLevel {
|
|
entry.log(InfoLevel, fmt.Sprint(args...))
|
|
}
|
|
}
|
|
|
|
func (entry *Entry) Warn(args ...interface{}) {
|
|
if entry.Logger.level() >= WarnLevel {
|
|
entry.log(WarnLevel, fmt.Sprint(args...))
|
|
}
|
|
}
|
|
|
|
func (entry *Entry) Warning(args ...interface{}) {
|
|
entry.Warn(args...)
|
|
}
|
|
|
|
func (entry *Entry) Error(args ...interface{}) {
|
|
if entry.Logger.level() >= ErrorLevel {
|
|
entry.log(ErrorLevel, fmt.Sprint(args...))
|
|
}
|
|
}
|
|
|
|
func (entry *Entry) Fatal(args ...interface{}) {
|
|
if entry.Logger.level() >= FatalLevel {
|
|
entry.log(FatalLevel, fmt.Sprint(args...))
|
|
}
|
|
Exit(1)
|
|
}
|
|
|
|
func (entry *Entry) Panic(args ...interface{}) {
|
|
if entry.Logger.level() >= PanicLevel {
|
|
entry.log(PanicLevel, fmt.Sprint(args...))
|
|
}
|
|
panic(fmt.Sprint(args...))
|
|
}
|
|
|
|
// Entry Printf family functions
|
|
|
|
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
|
if entry.Logger.level() >= DebugLevel {
|
|
entry.Debug(fmt.Sprintf(format, args...))
|
|
}
|
|
}
|
|
|
|
func (entry *Entry) Infof(format string, args ...interface{}) {
|
|
if entry.Logger.level() >= InfoLevel {
|
|
entry.Info(fmt.Sprintf(format, args...))
|
|
}
|
|
}
|
|
|
|
func (entry *Entry) Printf(format string, args ...interface{}) {
|
|
entry.Infof(format, args...)
|
|
}
|
|
|
|
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
|
if entry.Logger.level() >= WarnLevel {
|
|
entry.Warn(fmt.Sprintf(format, args...))
|
|
}
|
|
}
|
|
|
|
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
|
entry.Warnf(format, args...)
|
|
}
|
|
|
|
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
|
if entry.Logger.level() >= ErrorLevel {
|
|
entry.Error(fmt.Sprintf(format, args...))
|
|
}
|
|
}
|
|
|
|
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
|
if entry.Logger.level() >= FatalLevel {
|
|
entry.Fatal(fmt.Sprintf(format, args...))
|
|
}
|
|
Exit(1)
|
|
}
|
|
|
|
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
|
if entry.Logger.level() >= PanicLevel {
|
|
entry.Panic(fmt.Sprintf(format, args...))
|
|
}
|
|
}
|
|
|
|
// Entry Println family functions
|
|
|
|
func (entry *Entry) Debugln(args ...interface{}) {
|
|
if entry.Logger.level() >= DebugLevel {
|
|
entry.Debug(entry.sprintlnn(args...))
|
|
}
|
|
}
|
|
|
|
func (entry *Entry) Infoln(args ...interface{}) {
|
|
if entry.Logger.level() >= InfoLevel {
|
|
entry.Info(entry.sprintlnn(args...))
|
|
}
|
|
}
|
|
|
|
func (entry *Entry) Println(args ...interface{}) {
|
|
entry.Infoln(args...)
|
|
}
|
|
|
|
func (entry *Entry) Warnln(args ...interface{}) {
|
|
if entry.Logger.level() >= WarnLevel {
|
|
entry.Warn(entry.sprintlnn(args...))
|
|
}
|
|
}
|
|
|
|
func (entry *Entry) Warningln(args ...interface{}) {
|
|
entry.Warnln(args...)
|
|
}
|
|
|
|
func (entry *Entry) Errorln(args ...interface{}) {
|
|
if entry.Logger.level() >= ErrorLevel {
|
|
entry.Error(entry.sprintlnn(args...))
|
|
}
|
|
}
|
|
|
|
func (entry *Entry) Fatalln(args ...interface{}) {
|
|
if entry.Logger.level() >= FatalLevel {
|
|
entry.Fatal(entry.sprintlnn(args...))
|
|
}
|
|
Exit(1)
|
|
}
|
|
|
|
func (entry *Entry) Panicln(args ...interface{}) {
|
|
if entry.Logger.level() >= PanicLevel {
|
|
entry.Panic(entry.sprintlnn(args...))
|
|
}
|
|
}
|
|
|
|
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
|
// fmt.Sprintln where spaces are always added between operands, regardless of
|
|
// their type. Instead of vendoring the Sprintln implementation to spare a
|
|
// string allocation, we do the simplest thing.
|
|
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
|
msg := fmt.Sprintln(args...)
|
|
return msg[:len(msg)-1]
|
|
}
|