浏览代码

all: Remove global events.Default (ref #4085) (#5886)

Simon Frei 6 年之前
父节点
当前提交
b1c74860e8
共有 46 个文件被更改,包括 469 次插入376 次删除
  1. 2 1
      cmd/stcli/main.go
  2. 2 1
      cmd/stfinddevice/main.go
  3. 2 1
      cmd/strelaysrv/main.go
  4. 18 14
      cmd/syncthing/main.go
  5. 2 1
      cmd/syncthing/monitor.go
  6. 5 3
      lib/api/api.go
  7. 5 5
      lib/api/api_auth.go
  8. 5 5
      lib/api/api_test.go
  9. 1 1
      lib/config/commit_test.go
  10. 32 23
      lib/config/config_test.go
  11. 11 9
      lib/config/wrapper.go
  12. 2 1
      lib/connections/lan_test.go
  13. 2 1
      lib/connections/limiter_test.go
  14. 5 3
      lib/connections/service.go
  15. 5 3
      lib/discover/global.go
  16. 6 5
      lib/discover/global_test.go
  17. 4 2
      lib/discover/local.go
  18. 3 2
      lib/discover/local_test.go
  19. 2 2
      lib/events/debug.go
  20. 95 54
      lib/events/events.go
  21. 14 14
      lib/events/events_test.go
  22. 6 6
      lib/model/folder.go
  23. 3 2
      lib/model/folder_recvonly.go
  24. 1 0
      lib/model/folder_recvonly_test.go
  25. 3 2
      lib/model/folder_sendonly.go
  26. 19 19
      lib/model/folder_sendrecv.go
  27. 21 48
      lib/model/folder_sendrecv_test.go
  28. 7 5
      lib/model/folder_summary.go
  29. 5 3
      lib/model/folderstate.go
  30. 19 15
      lib/model/model.go
  31. 6 6
      lib/model/model_test.go
  32. 4 2
      lib/model/progressemitter.go
  33. 13 5
      lib/model/progressemitter_test.go
  34. 6 6
      lib/model/requests_test.go
  35. 6 1
      lib/model/testutils_test.go
  36. 4 3
      lib/rc/rc.go
  37. 3 1
      lib/scanner/walk.go
  38. 45 45
      lib/scanner/walk_test.go
  39. 4 4
      lib/syncthing/auditservice.go
  40. 14 7
      lib/syncthing/auditservice_test.go
  41. 22 20
      lib/syncthing/syncthing.go
  42. 3 2
      lib/syncthing/syncthing_test.go
  43. 7 6
      lib/syncthing/utils.go
  44. 4 4
      lib/syncthing/verboseservice.go
  45. 5 5
      lib/watchaggregator/aggregator.go
  46. 16 8
      lib/watchaggregator/aggregator_test.go

+ 2 - 1
cmd/stcli/main.go

@@ -22,6 +22,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/syncthing/syncthing/lib/build"
 	"github.com/syncthing/syncthing/lib/config"
+	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/locations"
 	"github.com/syncthing/syncthing/lib/protocol"
 	"github.com/urfave/cli"
@@ -85,7 +86,7 @@ func main() {
 		myID := protocol.NewDeviceID(cert.Certificate[0])
 
 		// Load the config
-		cfg, err := config.Load(locations.Get(locations.ConfigFile), myID)
+		cfg, err := config.Load(locations.Get(locations.ConfigFile), myID, events.NoopLogger)
 		if err != nil {
 			log.Fatalln(errors.Wrap(err, "loading config"))
 		}

+ 2 - 1
cmd/stfinddevice/main.go

@@ -17,6 +17,7 @@ import (
 
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/discover"
+	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/protocol"
 )
 
@@ -82,7 +83,7 @@ func checkServers(deviceID protocol.DeviceID, servers ...string) {
 }
 
 func checkServer(deviceID protocol.DeviceID, server string) checkResult {
-	disco, err := discover.NewGlobal(server, tls.Certificate{}, nil)
+	disco, err := discover.NewGlobal(server, tls.Certificate{}, nil, events.NoopLogger)
 	if err != nil {
 		return checkResult{error: err}
 	}

+ 2 - 1
cmd/strelaysrv/main.go

@@ -20,6 +20,7 @@ import (
 	"syscall"
 	"time"
 
+	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/osutil"
 	"github.com/syncthing/syncthing/lib/relay/protocol"
 	"github.com/syncthing/syncthing/lib/tlsutil"
@@ -194,7 +195,7 @@ func main() {
 		log.Println("ID:", id)
 	}
 
-	wrapper := config.Wrap("config", config.New(id))
+	wrapper := config.Wrap("config", config.New(id), events.NoopLogger)
 	wrapper.SetOptions(config.OptionsConfiguration{
 		NATLeaseM:   natLease,
 		NATRenewalM: natRenewal,

+ 18 - 14
cmd/syncthing/main.go

@@ -394,7 +394,7 @@ func main() {
 }
 
 func openGUI(myID protocol.DeviceID) error {
-	cfg, err := loadOrDefaultConfig(myID)
+	cfg, err := loadOrDefaultConfig(myID, events.NoopLogger)
 	if err != nil {
 		return err
 	}
@@ -437,7 +437,7 @@ func generate(generateDir string) error {
 		l.Warnln("Config exists; will not overwrite.")
 		return nil
 	}
-	cfg, err := syncthing.DefaultConfig(cfgFile, myID, noDefaultFolder)
+	cfg, err := syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, noDefaultFolder)
 	if err != nil {
 		return err
 	}
@@ -471,7 +471,7 @@ func debugFacilities() string {
 }
 
 func checkUpgrade() upgrade.Release {
-	cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID)
+	cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
 	opts := cfg.Options()
 	release, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
 	if err != nil {
@@ -512,7 +512,7 @@ func performUpgrade(release upgrade.Release) {
 }
 
 func upgradeViaRest() error {
-	cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID)
+	cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
 	u, err := url.Parse(cfg.GUI().URL())
 	if err != nil {
 		return err
@@ -566,7 +566,11 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
 		os.Exit(1)
 	}
 
-	cfg, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, runtimeOptions.allowNewerConfig, noDefaultFolder)
+	evLogger := events.NewLogger()
+	go evLogger.Serve()
+	defer evLogger.Stop()
+
+	cfg, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, runtimeOptions.allowNewerConfig, noDefaultFolder)
 	if err != nil {
 		l.Warnln("Failed to initialize config:", err)
 		os.Exit(exitError)
@@ -594,7 +598,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
 		appOpts.DeadlockTimeoutS = secs
 	}
 
-	app := syncthing.New(cfg, ldb, cert, appOpts)
+	app := syncthing.New(cfg, ldb, evLogger, cert, appOpts)
 
 	setupSignalHandling(app)
 
@@ -639,7 +643,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
 		if runtimeOptions.NoUpgrade {
 			l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.")
 		} else {
-			go autoUpgrade(cfg, app)
+			go autoUpgrade(cfg, app, evLogger)
 		}
 	}
 
@@ -684,12 +688,12 @@ func setupSignalHandling(app *syncthing.App) {
 	}()
 }
 
-func loadOrDefaultConfig(myID protocol.DeviceID) (config.Wrapper, error) {
+func loadOrDefaultConfig(myID protocol.DeviceID, evLogger events.Logger) (config.Wrapper, error) {
 	cfgFile := locations.Get(locations.ConfigFile)
-	cfg, err := config.Load(cfgFile, myID)
+	cfg, err := config.Load(cfgFile, myID, evLogger)
 
 	if err != nil {
-		cfg, err = syncthing.DefaultConfig(cfgFile, myID, noDefaultFolder)
+		cfg, err = syncthing.DefaultConfig(cfgFile, myID, evLogger, noDefaultFolder)
 	}
 
 	return cfg, err
@@ -774,9 +778,9 @@ func standbyMonitor(app *syncthing.App) {
 	}
 }
 
-func autoUpgrade(cfg config.Wrapper, app *syncthing.App) {
+func autoUpgrade(cfg config.Wrapper, app *syncthing.App, evLogger events.Logger) {
 	timer := time.NewTimer(0)
-	sub := events.Default.Subscribe(events.DeviceConnected)
+	sub := evLogger.Subscribe(events.DeviceConnected)
 	for {
 		select {
 		case event := <-sub.C():
@@ -798,7 +802,7 @@ func autoUpgrade(cfg config.Wrapper, app *syncthing.App) {
 
 		rel, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
 		if err == upgrade.ErrUpgradeUnsupported {
-			events.Default.Unsubscribe(sub)
+			sub.Unsubscribe()
 			return
 		}
 		if err != nil {
@@ -822,7 +826,7 @@ func autoUpgrade(cfg config.Wrapper, app *syncthing.App) {
 			timer.Reset(checkInterval)
 			continue
 		}
-		events.Default.Unsubscribe(sub)
+		sub.Unsubscribe()
 		l.Warnf("Automatically upgraded to version %q. Restarting in 1 minute.", rel.Tag)
 		time.Sleep(time.Minute)
 		app.Stop(syncthing.ExitUpgrade)

+ 2 - 1
cmd/syncthing/monitor.go

@@ -18,6 +18,7 @@ import (
 	"syscall"
 	"time"
 
+	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/locations"
 	"github.com/syncthing/syncthing/lib/osutil"
 	"github.com/syncthing/syncthing/lib/protocol"
@@ -450,7 +451,7 @@ func childEnv() []string {
 // panicUploadMaxWait uploading panics...
 func maybeReportPanics() {
 	// Try to get a config to see if/where panics should be reported.
-	cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID)
+	cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
 	if err != nil {
 		l.Warnln("Couldn't load config; not reporting crash")
 		return

+ 5 - 3
lib/api/api.go

@@ -71,6 +71,7 @@ type service struct {
 	model                model.Model
 	eventSubs            map[events.EventType]events.BufferedSubscription
 	eventSubsMut         sync.Mutex
+	evLogger             events.Logger
 	discoverer           discover.CachingMux
 	connectionsService   connections.Service
 	fss                  model.FolderSummaryService
@@ -105,7 +106,7 @@ type Service interface {
 	WaitForStart() error
 }
 
-func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonName string, m model.Model, defaultSub, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService connections.Service, urService *ur.Service, fss model.FolderSummaryService, errors, systemLog logger.Recorder, cpu Rater, contr Controller, noUpgrade bool) Service {
+func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonName string, m model.Model, defaultSub, diskSub events.BufferedSubscription, evLogger events.Logger, discoverer discover.CachingMux, connectionsService connections.Service, urService *ur.Service, fss model.FolderSummaryService, errors, systemLog logger.Recorder, cpu Rater, contr Controller, noUpgrade bool) Service {
 	s := &service{
 		id:      id,
 		cfg:     cfg,
@@ -116,6 +117,7 @@ func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonNam
 			DiskEventMask:    diskSub,
 		},
 		eventSubsMut:         sync.NewMutex(),
+		evLogger:             evLogger,
 		discoverer:           discoverer,
 		connectionsService:   connectionsService,
 		fss:                  fss,
@@ -315,7 +317,7 @@ func (s *service) serve(stop chan struct{}) {
 
 	// Wrap everything in basic auth, if user/password is set.
 	if guiCfg.IsAuthEnabled() {
-		handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], guiCfg, s.cfg.LDAP(), handler)
+		handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], guiCfg, s.cfg.LDAP(), handler, s.evLogger)
 	}
 
 	// Redirect to HTTPS if we are supposed to
@@ -1215,7 +1217,7 @@ func (s *service) getEventSub(mask events.EventType) events.BufferedSubscription
 	s.eventSubsMut.Lock()
 	bufsub, ok := s.eventSubs[mask]
 	if !ok {
-		evsub := events.Default.Subscribe(mask)
+		evsub := s.evLogger.Subscribe(mask)
 		bufsub = events.NewBufferedSubscription(evsub, EventSubBufferSize)
 		s.eventSubs[mask] = bufsub
 	}

+ 5 - 5
lib/api/api_auth.go

@@ -28,14 +28,14 @@ var (
 	sessionsMut = sync.NewMutex()
 )
 
-func emitLoginAttempt(success bool, username string) {
-	events.Default.Log(events.LoginAttempt, map[string]interface{}{
+func emitLoginAttempt(success bool, username string, evLogger events.Logger) {
+	evLogger.Log(events.LoginAttempt, map[string]interface{}{
 		"success":  success,
 		"username": username,
 	})
 }
 
-func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfiguration, ldapCfg config.LDAPConfiguration, next http.Handler) http.Handler {
+func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfiguration, ldapCfg config.LDAPConfiguration, next http.Handler, evLogger events.Logger) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		if guiCfg.IsValidAPIKey(r.Header.Get("X-API-Key")) {
 			next.ServeHTTP(w, r)
@@ -94,7 +94,7 @@ func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfigura
 		}
 
 		if !authOk {
-			emitLoginAttempt(false, username)
+			emitLoginAttempt(false, username, evLogger)
 			error()
 			return
 		}
@@ -109,7 +109,7 @@ func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfigura
 			MaxAge: 0,
 		})
 
-		emitLoginAttempt(true, username)
+		emitLoginAttempt(true, username, evLogger)
 		next.ServeHTTP(w, r)
 	})
 }

+ 5 - 5
lib/api/api_test.go

@@ -100,9 +100,9 @@ func TestStopAfterBrokenConfig(t *testing.T) {
 			RawUseTLS:  false,
 		},
 	}
-	w := config.Wrap("/dev/null", cfg)
+	w := config.Wrap("/dev/null", cfg, events.NoopLogger)
 
-	srv := New(protocol.LocalDeviceID, w, "", "syncthing", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, false).(*service)
+	srv := New(protocol.LocalDeviceID, w, "", "syncthing", nil, nil, nil, events.NoopLogger, nil, nil, nil, nil, nil, nil, nil, nil, false).(*service)
 	defer os.Remove(token)
 	srv.started = make(chan string)
 
@@ -512,8 +512,8 @@ func startHTTP(cfg *mockedConfig) (string, error) {
 
 	// Instantiate the API service
 	urService := ur.New(cfg, m, connections, false)
-	summaryService := model.NewFolderSummaryService(cfg, m, protocol.LocalDeviceID)
-	svc := New(protocol.LocalDeviceID, cfg, assetDir, "syncthing", m, eventSub, diskEventSub, discoverer, connections, urService, summaryService, errorLog, systemLog, cpu, nil, false).(*service)
+	summaryService := model.NewFolderSummaryService(cfg, m, protocol.LocalDeviceID, events.NoopLogger)
+	svc := New(protocol.LocalDeviceID, cfg, assetDir, "syncthing", m, eventSub, diskEventSub, events.NoopLogger, discoverer, connections, urService, summaryService, errorLog, systemLog, cpu, nil, false).(*service)
 	defer os.Remove(token)
 	svc.started = addrChan
 
@@ -979,7 +979,7 @@ func TestEventMasks(t *testing.T) {
 	cfg := new(mockedConfig)
 	defSub := new(mockedEventSub)
 	diskSub := new(mockedEventSub)
-	svc := New(protocol.LocalDeviceID, cfg, "", "syncthing", nil, defSub, diskSub, nil, nil, nil, nil, nil, nil, nil, nil, false).(*service)
+	svc := New(protocol.LocalDeviceID, cfg, "", "syncthing", nil, defSub, diskSub, events.NoopLogger, nil, nil, nil, nil, nil, nil, nil, nil, false).(*service)
 	defer os.Remove(token)
 
 	if mask := svc.getEventMask(""); mask != DefaultEventMask {

+ 1 - 1
lib/config/commit_test.go

@@ -44,7 +44,7 @@ func (validationError) String() string {
 func TestReplaceCommit(t *testing.T) {
 	t.Skip("broken, fails randomly, #3834")
 
-	w := Wrap("/dev/null", Configuration{Version: 0})
+	w := wrap("/dev/null", Configuration{Version: 0})
 	if w.RawCopy().Version != 0 {
 		t.Fatal("Config incorrect")
 	}

+ 32 - 23
lib/config/config_test.go

@@ -20,6 +20,7 @@ import (
 	"testing"
 
 	"github.com/d4l3k/messagediff"
+	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/fs"
 	"github.com/syncthing/syncthing/lib/osutil"
 	"github.com/syncthing/syncthing/lib/protocol"
@@ -86,7 +87,7 @@ func TestDefaultValues(t *testing.T) {
 func TestDeviceConfig(t *testing.T) {
 	for i := OldestHandledVersion; i <= CurrentVersion; i++ {
 		os.RemoveAll(filepath.Join("testdata", DefaultMarkerName))
-		wr, err := Load(fmt.Sprintf("testdata/v%d.xml", i), device1)
+		wr, err := load(fmt.Sprintf("testdata/v%d.xml", i), device1)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -168,7 +169,7 @@ func TestDeviceConfig(t *testing.T) {
 }
 
 func TestNoListenAddresses(t *testing.T) {
-	cfg, err := Load("testdata/nolistenaddress.xml", device1)
+	cfg, err := load("testdata/nolistenaddress.xml", device1)
 	if err != nil {
 		t.Error(err)
 	}
@@ -225,7 +226,7 @@ func TestOverriddenValues(t *testing.T) {
 	}
 
 	os.Unsetenv("STNOUPGRADE")
-	cfg, err := Load("testdata/overridenvalues.xml", device1)
+	cfg, err := load("testdata/overridenvalues.xml", device1)
 	if err != nil {
 		t.Error(err)
 	}
@@ -270,7 +271,7 @@ func TestDeviceAddressesDynamic(t *testing.T) {
 		},
 	}
 
-	cfg, err := Load("testdata/deviceaddressesdynamic.xml", device4)
+	cfg, err := load("testdata/deviceaddressesdynamic.xml", device4)
 	if err != nil {
 		t.Error(err)
 	}
@@ -319,7 +320,7 @@ func TestDeviceCompression(t *testing.T) {
 		},
 	}
 
-	cfg, err := Load("testdata/devicecompression.xml", device4)
+	cfg, err := load("testdata/devicecompression.xml", device4)
 	if err != nil {
 		t.Error(err)
 	}
@@ -365,7 +366,7 @@ func TestDeviceAddressesStatic(t *testing.T) {
 		},
 	}
 
-	cfg, err := Load("testdata/deviceaddressesstatic.xml", device4)
+	cfg, err := load("testdata/deviceaddressesstatic.xml", device4)
 	if err != nil {
 		t.Error(err)
 	}
@@ -377,7 +378,7 @@ func TestDeviceAddressesStatic(t *testing.T) {
 }
 
 func TestVersioningConfig(t *testing.T) {
-	cfg, err := Load("testdata/versioningconfig.xml", device4)
+	cfg, err := load("testdata/versioningconfig.xml", device4)
 	if err != nil {
 		t.Error(err)
 	}
@@ -404,7 +405,7 @@ func TestIssue1262(t *testing.T) {
 		t.Skipf("path gets converted to absolute as part of the filesystem initialization on linux")
 	}
 
-	cfg, err := Load("testdata/issue-1262.xml", device4)
+	cfg, err := load("testdata/issue-1262.xml", device4)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -418,7 +419,7 @@ func TestIssue1262(t *testing.T) {
 }
 
 func TestIssue1750(t *testing.T) {
-	cfg, err := Load("testdata/issue-1750.xml", device4)
+	cfg, err := load("testdata/issue-1750.xml", device4)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -520,7 +521,7 @@ func TestNewSaveLoad(t *testing.T) {
 	}
 
 	intCfg := New(device1)
-	cfg := Wrap(path, intCfg)
+	cfg := wrap(path, intCfg)
 
 	// To make the equality pass later
 	cfg.(*wrapper).cfg.XMLName.Local = "configuration"
@@ -537,7 +538,7 @@ func TestNewSaveLoad(t *testing.T) {
 		t.Error(path, "does not exist")
 	}
 
-	cfg2, err := Load(path, device1)
+	cfg2, err := load(path, device1)
 	if err != nil {
 		t.Error(err)
 	}
@@ -564,7 +565,7 @@ func TestPrepare(t *testing.T) {
 }
 
 func TestCopy(t *testing.T) {
-	wrapper, err := Load("testdata/example.xml", device1)
+	wrapper, err := load("testdata/example.xml", device1)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -603,7 +604,7 @@ func TestCopy(t *testing.T) {
 }
 
 func TestPullOrder(t *testing.T) {
-	wrapper, err := Load("testdata/pullorder.xml", device1)
+	wrapper, err := load("testdata/pullorder.xml", device1)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -643,7 +644,7 @@ func TestPullOrder(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	wrapper = Wrap("testdata/pullorder.xml", cfg)
+	wrapper = wrap("testdata/pullorder.xml", cfg)
 	folders = wrapper.Folders()
 
 	for _, tc := range expected {
@@ -654,7 +655,7 @@ func TestPullOrder(t *testing.T) {
 }
 
 func TestLargeRescanInterval(t *testing.T) {
-	wrapper, err := Load("testdata/largeinterval.xml", device1)
+	wrapper, err := load("testdata/largeinterval.xml", device1)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -692,7 +693,7 @@ func TestGUIConfigURL(t *testing.T) {
 func TestDuplicateDevices(t *testing.T) {
 	// Duplicate devices should be removed
 
-	wrapper, err := Load("testdata/dupdevices.xml", device1)
+	wrapper, err := load("testdata/dupdevices.xml", device1)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -710,7 +711,7 @@ func TestDuplicateDevices(t *testing.T) {
 func TestDuplicateFolders(t *testing.T) {
 	// Duplicate folders are a loading error
 
-	_, err := Load("testdata/dupfolders.xml", device1)
+	_, err := load("testdata/dupfolders.xml", device1)
 	if err == nil || !strings.Contains(err.Error(), errFolderIDDuplicate.Error()) {
 		t.Fatal(`Expected error to mention "duplicate folder ID":`, err)
 	}
@@ -721,7 +722,7 @@ func TestEmptyFolderPaths(t *testing.T) {
 	// get messed up by the prepare steps (e.g., become the current dir or
 	// get a slash added so that it becomes the root directory or similar).
 
-	_, err := Load("testdata/nopath.xml", device1)
+	_, err := load("testdata/nopath.xml", device1)
 	if err == nil || !strings.Contains(err.Error(), errFolderPathEmpty.Error()) {
 		t.Fatal("Expected error due to empty folder path, got", err)
 	}
@@ -790,7 +791,7 @@ func TestIgnoredDevices(t *testing.T) {
 	// Verify that ignored devices that are also present in the
 	// configuration are not in fact ignored.
 
-	wrapper, err := Load("testdata/ignoreddevices.xml", device1)
+	wrapper, err := load("testdata/ignoreddevices.xml", device1)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -808,7 +809,7 @@ func TestIgnoredFolders(t *testing.T) {
 	// configuration are not in fact ignored.
 	// Also, verify that folders that are shared with a device are not ignored.
 
-	wrapper, err := Load("testdata/ignoredfolders.xml", device1)
+	wrapper, err := load("testdata/ignoredfolders.xml", device1)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -844,7 +845,7 @@ func TestIgnoredFolders(t *testing.T) {
 func TestGetDevice(t *testing.T) {
 	// Verify that the Device() call does the right thing
 
-	wrapper, err := Load("testdata/ignoreddevices.xml", device1)
+	wrapper, err := load("testdata/ignoreddevices.xml", device1)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -871,7 +872,7 @@ func TestGetDevice(t *testing.T) {
 }
 
 func TestSharesRemovedOnDeviceRemoval(t *testing.T) {
-	wrapper, err := Load("testdata/example.xml", device1)
+	wrapper, err := load("testdata/example.xml", device1)
 	if err != nil {
 		t.Errorf("Failed: %s", err)
 	}
@@ -956,7 +957,7 @@ func TestIssue4219(t *testing.T) {
 		t.Errorf("There should be three ignored folders, not %d", ignoredFolders)
 	}
 
-	w := Wrap("/tmp/cfg", cfg)
+	w := wrap("/tmp/cfg", cfg)
 	if !w.IgnoredFolder(device2, "t1") {
 		t.Error("Folder device2 t1 should be ignored")
 	}
@@ -1145,3 +1146,11 @@ func defaultConfigAsMap() map[string]interface{} {
 	}
 	return tmp
 }
+
+func load(path string, myID protocol.DeviceID) (Wrapper, error) {
+	return Load(path, myID, events.NoopLogger)
+}
+
+func wrap(path string, cfg Configuration) Wrapper {
+	return Wrap(path, cfg, events.NoopLogger)
+}

+ 11 - 9
lib/config/wrapper.go

@@ -96,8 +96,9 @@ type Wrapper interface {
 }
 
 type wrapper struct {
-	cfg  Configuration
-	path string
+	cfg      Configuration
+	path     string
+	evLogger events.Logger
 
 	deviceMap map[protocol.DeviceID]DeviceConfiguration
 	folderMap map[string]FolderConfiguration
@@ -133,18 +134,19 @@ func (w *wrapper) StunServers() []string {
 
 // Wrap wraps an existing Configuration structure and ties it to a file on
 // disk.
-func Wrap(path string, cfg Configuration) Wrapper {
+func Wrap(path string, cfg Configuration, evLogger events.Logger) Wrapper {
 	w := &wrapper{
-		cfg:  cfg,
-		path: path,
-		mut:  sync.NewMutex(),
+		cfg:      cfg,
+		path:     path,
+		evLogger: evLogger,
+		mut:      sync.NewMutex(),
 	}
 	return w
 }
 
 // Load loads an existing file on disk and returns a new configuration
 // wrapper.
-func Load(path string, myID protocol.DeviceID) (Wrapper, error) {
+func Load(path string, myID protocol.DeviceID, evLogger events.Logger) (Wrapper, error) {
 	fd, err := os.Open(path)
 	if err != nil {
 		return nil, err
@@ -156,7 +158,7 @@ func Load(path string, myID protocol.DeviceID) (Wrapper, error) {
 		return nil, err
 	}
 
-	return Wrap(path, cfg), nil
+	return Wrap(path, cfg, evLogger), nil
 }
 
 func (w *wrapper) ConfigPath() string {
@@ -450,7 +452,7 @@ func (w *wrapper) Save() error {
 		return err
 	}
 
-	events.Default.Log(events.ConfigSaved, w.cfg)
+	w.evLogger.Log(events.ConfigSaved, w.cfg)
 	return nil
 }
 

+ 2 - 1
lib/connections/lan_test.go

@@ -10,6 +10,7 @@ import (
 	"testing"
 
 	"github.com/syncthing/syncthing/lib/config"
+	"github.com/syncthing/syncthing/lib/events"
 )
 
 func TestIsLANHost(t *testing.T) {
@@ -35,7 +36,7 @@ func TestIsLANHost(t *testing.T) {
 		Options: config.OptionsConfiguration{
 			AlwaysLocalNets: []string{"10.20.30.0/24"},
 		},
-	})
+	}, events.NoopLogger)
 	s := &service{cfg: cfg}
 
 	for _, tc := range cases {

+ 2 - 1
lib/connections/limiter_test.go

@@ -8,6 +8,7 @@ package connections
 
 import (
 	"github.com/syncthing/syncthing/lib/config"
+	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/protocol"
 	"golang.org/x/time/rate"
 	"math/rand"
@@ -25,7 +26,7 @@ func init() {
 }
 
 func initConfig() config.Wrapper {
-	cfg := config.Wrap("/dev/null", config.New(device1))
+	cfg := config.Wrap("/dev/null", config.New(device1), events.NoopLogger)
 	dev1Conf = config.NewDeviceConfiguration(device1, "device1")
 	dev2Conf = config.NewDeviceConfiguration(device2, "device2")
 	dev3Conf = config.NewDeviceConfiguration(device3, "device3")

+ 5 - 3
lib/connections/service.go

@@ -120,6 +120,7 @@ type service struct {
 	limiter              *limiter
 	natService           *nat.Service
 	natServiceToken      *suture.ServiceToken
+	evLogger             events.Logger
 
 	listenersMut       sync.RWMutex
 	listeners          map[string]genericListener
@@ -130,7 +131,7 @@ type service struct {
 	connectionStatus    map[string]ConnectionStatusEntry // address -> latest error/status
 }
 
-func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder, bepProtocolName string, tlsDefaultCommonName string) Service {
+func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder, bepProtocolName string, tlsDefaultCommonName string, evLogger events.Logger) Service {
 	service := &service{
 		Supervisor: suture.New("connections.Service", suture.Spec{
 			Log: func(line string) {
@@ -148,6 +149,7 @@ func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *t
 		tlsDefaultCommonName: tlsDefaultCommonName,
 		limiter:              newLimiter(cfg),
 		natService:           nat.NewService(myID, cfg),
+		evLogger:             evLogger,
 
 		listenersMut:   sync.NewRWMutex(),
 		listeners:      make(map[string]genericListener),
@@ -552,7 +554,7 @@ func (s *service) createListener(factory listenerFactory, uri *url.URL) bool {
 }
 
 func (s *service) logListenAddressesChangedEvent(l genericListener) {
-	events.Default.Log(events.ListenAddressesChanged, map[string]interface{}{
+	s.evLogger.Log(events.ListenAddressesChanged, map[string]interface{}{
 		"address": l.URI(),
 		"lan":     l.LANAddresses(),
 		"wan":     l.WANAddresses(),
@@ -579,7 +581,7 @@ func (s *service) CommitConfiguration(from, to config.Configuration) bool {
 
 	s.listenersMut.Lock()
 	seen := make(map[string]struct{})
-	for _, addr := range config.Wrap("", to).ListenAddresses() {
+	for _, addr := range config.Wrap("", to, s.evLogger).ListenAddresses() {
 		if addr == "" {
 			// We can get an empty address if there is an empty listener
 			// element in the config, indicating no listeners should be

+ 5 - 3
lib/discover/global.go

@@ -35,6 +35,7 @@ type globalClient struct {
 	queryClient    httpClient
 	noAnnounce     bool
 	noLookup       bool
+	evLogger       events.Logger
 	errorHolder
 }
 
@@ -70,7 +71,7 @@ func (e lookupError) CacheFor() time.Duration {
 	return e.cacheFor
 }
 
-func NewGlobal(server string, cert tls.Certificate, addrList AddressLister) (FinderService, error) {
+func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, evLogger events.Logger) (FinderService, error) {
 	server, opts, err := parseOptions(server)
 	if err != nil {
 		return nil, err
@@ -125,6 +126,7 @@ func NewGlobal(server string, cert tls.Certificate, addrList AddressLister) (Fin
 		queryClient:    queryClient,
 		noAnnounce:     opts.noAnnounce,
 		noLookup:       opts.noLookup,
+		evLogger:       evLogger,
 	}
 	cl.Service = util.AsService(cl.serve)
 	if !opts.noAnnounce {
@@ -197,8 +199,8 @@ func (c *globalClient) serve(stop chan struct{}) {
 	timer := time.NewTimer(0)
 	defer timer.Stop()
 
-	eventSub := events.Default.Subscribe(events.ListenAddressesChanged)
-	defer events.Default.Unsubscribe(eventSub)
+	eventSub := c.evLogger.Subscribe(events.ListenAddressesChanged)
+	defer eventSub.Unsubscribe()
 
 	for {
 		select {

+ 6 - 5
lib/discover/global_test.go

@@ -15,6 +15,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/protocol"
 	"github.com/syncthing/syncthing/lib/tlsutil"
 )
@@ -54,15 +55,15 @@ func TestGlobalOverHTTP(t *testing.T) {
 	// is only allowed in combination with the "insecure" and "noannounce"
 	// parameters.
 
-	if _, err := NewGlobal("http://192.0.2.42/", tls.Certificate{}, nil); err == nil {
+	if _, err := NewGlobal("http://192.0.2.42/", tls.Certificate{}, nil, events.NoopLogger); err == nil {
 		t.Fatal("http is not allowed without insecure and noannounce")
 	}
 
-	if _, err := NewGlobal("http://192.0.2.42/?insecure", tls.Certificate{}, nil); err == nil {
+	if _, err := NewGlobal("http://192.0.2.42/?insecure", tls.Certificate{}, nil, events.NoopLogger); err == nil {
 		t.Fatal("http is not allowed without noannounce")
 	}
 
-	if _, err := NewGlobal("http://192.0.2.42/?noannounce", tls.Certificate{}, nil); err == nil {
+	if _, err := NewGlobal("http://192.0.2.42/?noannounce", tls.Certificate{}, nil, events.NoopLogger); err == nil {
 		t.Fatal("http is not allowed without insecure")
 	}
 
@@ -193,7 +194,7 @@ func TestGlobalAnnounce(t *testing.T) {
 	go func() { _ = http.Serve(list, mux) }()
 
 	url := "https://" + list.Addr().String() + "?insecure"
-	disco, err := NewGlobal(url, cert, new(fakeAddressLister))
+	disco, err := NewGlobal(url, cert, new(fakeAddressLister), events.NoopLogger)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -217,7 +218,7 @@ func TestGlobalAnnounce(t *testing.T) {
 }
 
 func testLookup(url string) ([]string, error) {
-	disco, err := NewGlobal(url, tls.Certificate{}, nil)
+	disco, err := NewGlobal(url, tls.Certificate{}, nil, events.NoopLogger)
 	if err != nil {
 		return nil, err
 	}

+ 4 - 2
lib/discover/local.go

@@ -30,6 +30,7 @@ type localClient struct {
 	myID     protocol.DeviceID
 	addrList AddressLister
 	name     string
+	evLogger events.Logger
 
 	beacon          beacon.Interface
 	localBcastStart time.Time
@@ -46,13 +47,14 @@ const (
 	v13Magic          = uint32(0x7D79BC40) // previous version
 )
 
-func NewLocal(id protocol.DeviceID, addr string, addrList AddressLister) (FinderService, error) {
+func NewLocal(id protocol.DeviceID, addr string, addrList AddressLister, evLogger events.Logger) (FinderService, error) {
 	c := &localClient{
 		Supervisor: suture.New("local", suture.Spec{
 			PassThroughPanics: true,
 		}),
 		myID:            id,
 		addrList:        addrList,
+		evLogger:        evLogger,
 		localBcastTick:  time.NewTicker(BroadcastInterval).C,
 		forcedBcastTick: make(chan time.Time),
 		localBcastStart: time.Now(),
@@ -272,7 +274,7 @@ func (c *localClient) registerDevice(src net.Addr, device Announce) bool {
 	})
 
 	if isNewDevice {
-		events.Default.Log(events.DeviceDiscovered, map[string]interface{}{
+		c.evLogger.Log(events.DeviceDiscovered, map[string]interface{}{
 			"device": device.ID.String(),
 			"addrs":  validAddresses,
 		})

+ 3 - 2
lib/discover/local_test.go

@@ -11,11 +11,12 @@ import (
 	"net"
 	"testing"
 
+	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/protocol"
 )
 
 func TestLocalInstanceID(t *testing.T) {
-	c, err := NewLocal(protocol.LocalDeviceID, ":0", &fakeAddressLister{})
+	c, err := NewLocal(protocol.LocalDeviceID, ":0", &fakeAddressLister{}, events.NoopLogger)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -38,7 +39,7 @@ func TestLocalInstanceID(t *testing.T) {
 }
 
 func TestLocalInstanceIDShouldTriggerNew(t *testing.T) {
-	c, err := NewLocal(protocol.LocalDeviceID, ":0", &fakeAddressLister{})
+	c, err := NewLocal(protocol.LocalDeviceID, ":0", &fakeAddressLister{}, events.NoopLogger)
 	if err != nil {
 		t.Fatal(err)
 	}

+ 2 - 2
lib/events/debug.go

@@ -10,11 +10,11 @@ import (
 	"os"
 	"strings"
 
-	"github.com/syncthing/syncthing/lib/logger"
+	liblogger "github.com/syncthing/syncthing/lib/logger"
 )
 
 var (
-	dl = logger.DefaultLogger.NewFacility("events", "Event generation and logging")
+	dl = liblogger.DefaultLogger.NewFacility("events", "Event generation and logging")
 )
 
 func init() {

+ 95 - 54
lib/events/events.go

@@ -13,7 +13,10 @@ import (
 	"runtime"
 	"time"
 
+	"github.com/thejerf/suture"
+
 	"github.com/syncthing/syncthing/lib/sync"
+	"github.com/syncthing/syncthing/lib/util"
 )
 
 type EventType int
@@ -51,7 +54,10 @@ const (
 	AllEvents = (1 << iota) - 1
 )
 
-var runningTests = false
+var (
+	runningTests = false
+	errNoop      = errors.New("method of a noop object called")
+)
 
 const eventLogTimeout = 15 * time.Millisecond
 
@@ -199,13 +205,21 @@ func UnmarshalEventType(s string) EventType {
 
 const BufferSize = 64
 
-type Logger struct {
-	subs                []*Subscription
+type Logger interface {
+	suture.Service
+	Log(t EventType, data interface{})
+	Subscribe(mask EventType) Subscription
+}
+
+type logger struct {
+	suture.Service
+	subs                []*subscription
 	nextSubscriptionIDs []int
 	nextGlobalID        int
 	timeout             *time.Timer
 	events              chan Event
 	funcs               chan func()
+	toUnsubscribe       chan *subscription
 	stop                chan struct{}
 }
 
@@ -219,19 +233,17 @@ type Event struct {
 	Data     interface{} `json:"data"`
 }
 
-type Subscription struct {
-	mask    EventType
-	events  chan Event
-	timeout *time.Timer
+type Subscription interface {
+	C() <-chan Event
+	Poll(timeout time.Duration) (Event, error)
+	Unsubscribe()
 }
 
-var Default = NewLogger()
-
-func init() {
-	// The default logger never stops. To ensure this we nil out the stop
-	// channel so any attempt to stop it will panic.
-	Default.stop = nil
-	go Default.Serve()
+type subscription struct {
+	mask          EventType
+	events        chan Event
+	toUnsubscribe chan *subscription
+	timeout       *time.Timer
 }
 
 var (
@@ -239,13 +251,14 @@ var (
 	ErrClosed  = errors.New("closed")
 )
 
-func NewLogger() *Logger {
-	l := &Logger{
-		timeout: time.NewTimer(time.Second),
-		events:  make(chan Event, BufferSize),
-		funcs:   make(chan func()),
-		stop:    make(chan struct{}),
+func NewLogger() Logger {
+	l := &logger{
+		timeout:       time.NewTimer(time.Second),
+		events:        make(chan Event, BufferSize),
+		funcs:         make(chan func()),
+		toUnsubscribe: make(chan *subscription),
 	}
+	l.Service = util.AsService(l.serve)
 	// Make sure the timer is in the stopped state and hasn't fired anything
 	// into the channel.
 	if !l.timeout.Stop() {
@@ -254,7 +267,7 @@ func NewLogger() *Logger {
 	return l
 }
 
-func (l *Logger) Serve() {
+func (l *logger) serve(stop chan struct{}) {
 loop:
 	for {
 		select {
@@ -263,10 +276,13 @@ loop:
 			l.sendEvent(e)
 
 		case fn := <-l.funcs:
-			// Subscriptions etc are handled here.
+			// Subscriptions are handled here.
 			fn()
 
-		case <-l.stop:
+		case s := <-l.toUnsubscribe:
+			l.unsubscribe(s)
+
+		case <-stop:
 			break loop
 		}
 	}
@@ -279,11 +295,7 @@ loop:
 	}
 }
 
-func (l *Logger) Stop() {
-	close(l.stop)
-}
-
-func (l *Logger) Log(t EventType, data interface{}) {
+func (l *logger) Log(t EventType, data interface{}) {
 	l.events <- Event{
 		Time: time.Now(),
 		Type: t,
@@ -292,7 +304,7 @@ func (l *Logger) Log(t EventType, data interface{}) {
 	}
 }
 
-func (l *Logger) sendEvent(e Event) {
+func (l *logger) sendEvent(e Event) {
 	l.nextGlobalID++
 	dl.Debugln("log", l.nextGlobalID, e.Type, e.Data)
 
@@ -323,15 +335,16 @@ func (l *Logger) sendEvent(e Event) {
 	}
 }
 
-func (l *Logger) Subscribe(mask EventType) *Subscription {
-	res := make(chan *Subscription)
+func (l *logger) Subscribe(mask EventType) Subscription {
+	res := make(chan Subscription)
 	l.funcs <- func() {
 		dl.Debugln("subscribe", mask)
 
-		s := &Subscription{
-			mask:    mask,
-			events:  make(chan Event, BufferSize),
-			timeout: time.NewTimer(0),
+		s := &subscription{
+			mask:          mask,
+			events:        make(chan Event, BufferSize),
+			toUnsubscribe: l.toUnsubscribe,
+			timeout:       time.NewTimer(0),
 		}
 
 		// We need to create the timeout timer in the stopped, non-fired state so
@@ -355,32 +368,30 @@ func (l *Logger) Subscribe(mask EventType) *Subscription {
 	return <-res
 }
 
-func (l *Logger) Unsubscribe(s *Subscription) {
-	l.funcs <- func() {
-		dl.Debugln("unsubscribe")
-		for i, ss := range l.subs {
-			if s == ss {
-				last := len(l.subs) - 1
+func (l *logger) unsubscribe(s *subscription) {
+	dl.Debugln("unsubscribe", s.mask)
+	for i, ss := range l.subs {
+		if s == ss {
+			last := len(l.subs) - 1
 
-				l.subs[i] = l.subs[last]
-				l.subs[last] = nil
-				l.subs = l.subs[:last]
+			l.subs[i] = l.subs[last]
+			l.subs[last] = nil
+			l.subs = l.subs[:last]
 
-				l.nextSubscriptionIDs[i] = l.nextSubscriptionIDs[last]
-				l.nextSubscriptionIDs[last] = 0
-				l.nextSubscriptionIDs = l.nextSubscriptionIDs[:last]
+			l.nextSubscriptionIDs[i] = l.nextSubscriptionIDs[last]
+			l.nextSubscriptionIDs[last] = 0
+			l.nextSubscriptionIDs = l.nextSubscriptionIDs[:last]
 
-				break
-			}
+			break
 		}
-		close(s.events)
 	}
+	close(s.events)
 }
 
 // Poll returns an event from the subscription or an error if the poll times
 // out of the event channel is closed. Poll should not be called concurrently
 // from multiple goroutines for a single subscription.
-func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
+func (s *subscription) Poll(timeout time.Duration) (Event, error) {
 	dl.Debugln("poll", timeout)
 
 	s.timeout.Reset(timeout)
@@ -409,12 +420,16 @@ func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
 	}
 }
 
-func (s *Subscription) C() <-chan Event {
+func (s *subscription) C() <-chan Event {
 	return s.events
 }
 
+func (s *subscription) Unsubscribe() {
+	s.toUnsubscribe <- s
+}
+
 type bufferedSubscription struct {
-	sub  *Subscription
+	sub  Subscription
 	buf  []Event
 	next int
 	cur  int // Current SubscriptionID
@@ -426,7 +441,7 @@ type BufferedSubscription interface {
 	Since(id int, into []Event, timeout time.Duration) []Event
 }
 
-func NewBufferedSubscription(s *Subscription, size int) BufferedSubscription {
+func NewBufferedSubscription(s Subscription, size int) BufferedSubscription {
 	bs := &bufferedSubscription{
 		sub: s,
 		buf: make([]Event, size),
@@ -489,3 +504,29 @@ func Error(err error) *string {
 	str := err.Error()
 	return &str
 }
+
+type noopLogger struct{}
+
+var NoopLogger Logger = &noopLogger{}
+
+func (*noopLogger) Serve() {}
+
+func (*noopLogger) Stop() {}
+
+func (*noopLogger) Log(t EventType, data interface{}) {}
+
+func (*noopLogger) Subscribe(mask EventType) Subscription {
+	return &noopSubscription{}
+}
+
+type noopSubscription struct{}
+
+func (*noopSubscription) C() <-chan Event {
+	return nil
+}
+
+func (*noopSubscription) Poll(timeout time.Duration) (Event, error) {
+	return Event{}, errNoop
+}
+
+func (*noopSubscription) Unsubscribe() {}

+ 14 - 14
lib/events/events_test.go

@@ -33,7 +33,7 @@ func TestSubscriber(t *testing.T) {
 	go l.Serve()
 
 	s := l.Subscribe(0)
-	defer l.Unsubscribe(s)
+	defer s.Unsubscribe()
 	if s == nil {
 		t.Fatal("Unexpected nil Subscription")
 	}
@@ -45,7 +45,7 @@ func TestTimeout(t *testing.T) {
 	go l.Serve()
 
 	s := l.Subscribe(0)
-	defer l.Unsubscribe(s)
+	defer s.Unsubscribe()
 	_, err := s.Poll(timeout)
 	if err != ErrTimeout {
 		t.Fatal("Unexpected non-Timeout error:", err)
@@ -59,7 +59,7 @@ func TestEventBeforeSubscribe(t *testing.T) {
 
 	l.Log(DeviceConnected, "foo")
 	s := l.Subscribe(0)
-	defer l.Unsubscribe(s)
+	defer s.Unsubscribe()
 
 	_, err := s.Poll(timeout)
 	if err != ErrTimeout {
@@ -73,7 +73,7 @@ func TestEventAfterSubscribe(t *testing.T) {
 	go l.Serve()
 
 	s := l.Subscribe(AllEvents)
-	defer l.Unsubscribe(s)
+	defer s.Unsubscribe()
 	l.Log(DeviceConnected, "foo")
 
 	ev, err := s.Poll(timeout)
@@ -100,7 +100,7 @@ func TestEventAfterSubscribeIgnoreMask(t *testing.T) {
 	go l.Serve()
 
 	s := l.Subscribe(DeviceDisconnected)
-	defer l.Unsubscribe(s)
+	defer s.Unsubscribe()
 	l.Log(DeviceConnected, "foo")
 
 	_, err := s.Poll(timeout)
@@ -115,7 +115,7 @@ func TestBufferOverflow(t *testing.T) {
 	go l.Serve()
 
 	s := l.Subscribe(AllEvents)
-	defer l.Unsubscribe(s)
+	defer s.Unsubscribe()
 
 	// The first BufferSize events will be logged pretty much
 	// instantaneously. The next BufferSize events will each block for up to
@@ -147,7 +147,7 @@ func TestUnsubscribe(t *testing.T) {
 		t.Fatal("Unexpected error:", err)
 	}
 
-	l.Unsubscribe(s)
+	s.Unsubscribe()
 	l.Log(DeviceConnected, "foo")
 
 	_, err = s.Poll(timeout)
@@ -162,7 +162,7 @@ func TestGlobalIDs(t *testing.T) {
 	go l.Serve()
 
 	s := l.Subscribe(AllEvents)
-	defer l.Unsubscribe(s)
+	defer s.Unsubscribe()
 	l.Log(DeviceConnected, "foo")
 	l.Subscribe(AllEvents)
 	l.Log(DeviceConnected, "bar")
@@ -194,7 +194,7 @@ func TestSubscriptionIDs(t *testing.T) {
 	go l.Serve()
 
 	s := l.Subscribe(DeviceConnected)
-	defer l.Unsubscribe(s)
+	defer s.Unsubscribe()
 
 	l.Log(DeviceDisconnected, "a")
 	l.Log(DeviceConnected, "b")
@@ -236,7 +236,7 @@ func TestBufferedSub(t *testing.T) {
 	go l.Serve()
 
 	s := l.Subscribe(AllEvents)
-	defer l.Unsubscribe(s)
+	defer s.Unsubscribe()
 	bs := NewBufferedSubscription(s, 10*BufferSize)
 
 	go func() {
@@ -267,7 +267,7 @@ func BenchmarkBufferedSub(b *testing.B) {
 	go l.Serve()
 
 	s := l.Subscribe(AllEvents)
-	defer l.Unsubscribe(s)
+	defer s.Unsubscribe()
 	bufferSize := BufferSize
 	bs := NewBufferedSubscription(s, bufferSize)
 
@@ -323,7 +323,7 @@ func TestSinceUsesSubscriptionId(t *testing.T) {
 	go l.Serve()
 
 	s := l.Subscribe(DeviceConnected)
-	defer l.Unsubscribe(s)
+	defer s.Unsubscribe()
 	bs := NewBufferedSubscription(s, 10*BufferSize)
 
 	l.Log(DeviceConnected, "a") // SubscriptionID = 1
@@ -390,7 +390,7 @@ func TestUnsubscribeContention(t *testing.T) {
 			defer listenerWg.Done()
 
 			s := l.Subscribe(AllEvents)
-			defer l.Unsubscribe(s)
+			defer s.Unsubscribe()
 
 			for {
 				select {
@@ -449,7 +449,7 @@ func BenchmarkLogEvent(b *testing.B) {
 	go l.Serve()
 
 	s := l.Subscribe(AllEvents)
-	defer l.Unsubscribe(s)
+	defer s.Unsubscribe()
 	NewBufferedSubscription(s, 1) // runs in the background
 
 	for i := 0; i < b.N; i++ {

+ 6 - 6
lib/model/folder.go

@@ -77,11 +77,11 @@ type puller interface {
 	pull() bool // true when successfull and should not be retried
 }
 
-func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration) folder {
+func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, evLogger events.Logger) folder {
 	ctx, cancel := context.WithCancel(context.Background())
 
 	return folder{
-		stateTracker:              newStateTracker(cfg.ID),
+		stateTracker:              newStateTracker(cfg.ID, evLogger),
 		FolderConfiguration:       cfg,
 		FolderStatisticsReference: stats.NewFolderStatisticsReference(model.db, cfg.ID),
 
@@ -630,7 +630,7 @@ func (f *folder) monitorWatch(ctx context.Context) {
 				failTimer.Reset(time.Minute)
 				continue
 			}
-			watchaggregator.Aggregate(eventChan, f.watchChan, f.FolderConfiguration, f.model.cfg, aggrCtx)
+			watchaggregator.Aggregate(eventChan, f.watchChan, f.FolderConfiguration, f.model.cfg, f.evLogger, aggrCtx)
 			l.Debugln("Started filesystem watcher for folder", f.Description())
 		case err = <-errChan:
 			f.setWatchError(err)
@@ -669,7 +669,7 @@ func (f *folder) setWatchError(err error) {
 		if err != nil {
 			data["to"] = err.Error()
 		}
-		events.Default.Log(events.FolderWatchStateChanged, data)
+		f.evLogger.Log(events.FolderWatchStateChanged, data)
 	}
 	if err == nil {
 		return
@@ -800,7 +800,7 @@ func (f *folder) updateLocals(fs []protocol.FileInfo) {
 		filenames[i] = file.Name
 	}
 
-	events.Default.Log(events.LocalIndexUpdated, map[string]interface{}{
+	f.evLogger.Log(events.LocalIndexUpdated, map[string]interface{}{
 		"folder":    f.ID,
 		"items":     len(fs),
 		"filenames": filenames,
@@ -839,7 +839,7 @@ func (f *folder) emitDiskChangeEvents(fs []protocol.FileInfo, typeOfEvent events
 		}
 
 		// Two different events can be fired here based on what EventType is passed into function
-		events.Default.Log(typeOfEvent, map[string]string{
+		f.evLogger.Log(typeOfEvent, map[string]string{
 			"folder":     f.ID,
 			"folderID":   f.ID, // incorrect, deprecated, kept for historical compliance
 			"label":      f.Label,

+ 3 - 2
lib/model/folder_recvonly.go

@@ -12,6 +12,7 @@ import (
 
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/db"
+	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/fs"
 	"github.com/syncthing/syncthing/lib/ignore"
 	"github.com/syncthing/syncthing/lib/protocol"
@@ -56,8 +57,8 @@ type receiveOnlyFolder struct {
 	*sendReceiveFolder
 }
 
-func newReceiveOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service {
-	sr := newSendReceiveFolder(model, fset, ignores, cfg, ver, fs).(*sendReceiveFolder)
+func newReceiveOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem, evLogger events.Logger) service {
+	sr := newSendReceiveFolder(model, fset, ignores, cfg, ver, fs, evLogger).(*sendReceiveFolder)
 	sr.localFlags = protocol.FlagLocalReceiveOnly // gets propagated to the scanner, and set on locally changed files
 	return &receiveOnlyFolder{sr}
 }

+ 1 - 0
lib/model/folder_recvonly_test.go

@@ -321,6 +321,7 @@ func setupROFolder() (*model, *sendOnlyFolder) {
 
 	f := &sendOnlyFolder{
 		folder: folder{
+			stateTracker:        newStateTracker(fcfg.ID, m.evLogger),
 			fset:                m.folderFiles[fcfg.ID],
 			FolderConfiguration: fcfg,
 		},

+ 3 - 2
lib/model/folder_sendonly.go

@@ -9,6 +9,7 @@ package model
 import (
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/db"
+	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/fs"
 	"github.com/syncthing/syncthing/lib/ignore"
 	"github.com/syncthing/syncthing/lib/protocol"
@@ -24,9 +25,9 @@ type sendOnlyFolder struct {
 	folder
 }
 
-func newSendOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, _ versioner.Versioner, _ fs.Filesystem) service {
+func newSendOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, _ versioner.Versioner, _ fs.Filesystem, evLogger events.Logger) service {
 	f := &sendOnlyFolder{
-		folder: newFolder(model, fset, ignores, cfg),
+		folder: newFolder(model, fset, ignores, cfg, evLogger),
 	}
 	f.folder.puller = f
 	f.folder.Service = util.AsService(f.serve)

+ 19 - 19
lib/model/folder_sendrecv.go

@@ -108,9 +108,9 @@ type sendReceiveFolder struct {
 	pullErrorsMut sync.Mutex
 }
 
-func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service {
+func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem, evLogger events.Logger) service {
 	f := &sendReceiveFolder{
-		folder:        newFolder(model, fset, ignores, cfg),
+		folder:        newFolder(model, fset, ignores, cfg, evLogger),
 		fs:            fs,
 		versioner:     ver,
 		queue:         newJobQueue(),
@@ -211,7 +211,7 @@ func (f *sendReceiveFolder) pull() bool {
 	// errors preventing us. Flag this with a warning and
 	// wait a bit longer before retrying.
 	if errors := f.Errors(); len(errors) > 0 {
-		events.Default.Log(events.FolderErrors, map[string]interface{}{
+		f.evLogger.Log(events.FolderErrors, map[string]interface{}{
 			"folder": f.folderID,
 			"errors": errors,
 		})
@@ -544,7 +544,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<
 
 	f.resetPullError(file.Name)
 
-	events.Default.Log(events.ItemStarted, map[string]string{
+	f.evLogger.Log(events.ItemStarted, map[string]string{
 		"folder": f.folderID,
 		"item":   file.Name,
 		"type":   "dir",
@@ -552,7 +552,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<
 	})
 
 	defer func() {
-		events.Default.Log(events.ItemFinished, map[string]interface{}{
+		f.evLogger.Log(events.ItemFinished, map[string]interface{}{
 			"folder": f.folderID,
 			"item":   file.Name,
 			"error":  events.Error(err),
@@ -700,7 +700,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, dbUpdateChan c
 
 	f.resetPullError(file.Name)
 
-	events.Default.Log(events.ItemStarted, map[string]string{
+	f.evLogger.Log(events.ItemStarted, map[string]string{
 		"folder": f.folderID,
 		"item":   file.Name,
 		"type":   "symlink",
@@ -708,7 +708,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, dbUpdateChan c
 	})
 
 	defer func() {
-		events.Default.Log(events.ItemFinished, map[string]interface{}{
+		f.evLogger.Log(events.ItemFinished, map[string]interface{}{
 			"folder": f.folderID,
 			"item":   file.Name,
 			"error":  events.Error(err),
@@ -782,7 +782,7 @@ func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, dbUpdateChan chan<
 	// care not declare another err.
 	var err error
 
-	events.Default.Log(events.ItemStarted, map[string]string{
+	f.evLogger.Log(events.ItemStarted, map[string]string{
 		"folder": f.folderID,
 		"item":   file.Name,
 		"type":   "dir",
@@ -790,7 +790,7 @@ func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, dbUpdateChan chan<
 	})
 
 	defer func() {
-		events.Default.Log(events.ItemFinished, map[string]interface{}{
+		f.evLogger.Log(events.ItemFinished, map[string]interface{}{
 			"folder": f.folderID,
 			"item":   file.Name,
 			"error":  events.Error(err),
@@ -822,7 +822,7 @@ func (f *sendReceiveFolder) deleteFileWithCurrent(file, cur protocol.FileInfo, h
 
 	f.resetPullError(file.Name)
 
-	events.Default.Log(events.ItemStarted, map[string]string{
+	f.evLogger.Log(events.ItemStarted, map[string]string{
 		"folder": f.folderID,
 		"item":   file.Name,
 		"type":   "file",
@@ -833,7 +833,7 @@ func (f *sendReceiveFolder) deleteFileWithCurrent(file, cur protocol.FileInfo, h
 		if err != nil {
 			f.newPullError(file.Name, errors.Wrap(err, "delete file"))
 		}
-		events.Default.Log(events.ItemFinished, map[string]interface{}{
+		f.evLogger.Log(events.ItemFinished, map[string]interface{}{
 			"folder": f.folderID,
 			"item":   file.Name,
 			"error":  events.Error(err),
@@ -897,13 +897,13 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, db
 	// care not declare another err.
 	var err error
 
-	events.Default.Log(events.ItemStarted, map[string]string{
+	f.evLogger.Log(events.ItemStarted, map[string]string{
 		"folder": f.folderID,
 		"item":   source.Name,
 		"type":   "file",
 		"action": "delete",
 	})
-	events.Default.Log(events.ItemStarted, map[string]string{
+	f.evLogger.Log(events.ItemStarted, map[string]string{
 		"folder": f.folderID,
 		"item":   target.Name,
 		"type":   "file",
@@ -911,14 +911,14 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, db
 	})
 
 	defer func() {
-		events.Default.Log(events.ItemFinished, map[string]interface{}{
+		f.evLogger.Log(events.ItemFinished, map[string]interface{}{
 			"folder": f.folderID,
 			"item":   source.Name,
 			"error":  events.Error(err),
 			"type":   "file",
 			"action": "delete",
 		})
-		events.Default.Log(events.ItemFinished, map[string]interface{}{
+		f.evLogger.Log(events.ItemFinished, map[string]interface{}{
 			"folder": f.folderID,
 			"item":   target.Name,
 			"error":  events.Error(err),
@@ -1095,7 +1095,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
 	// Shuffle the blocks
 	rand.Shuffle(blocks)
 
-	events.Default.Log(events.ItemStarted, map[string]string{
+	f.evLogger.Log(events.ItemStarted, map[string]string{
 		"folder": f.folderID,
 		"item":   file.Name,
 		"type":   "file",
@@ -1178,7 +1178,7 @@ func (f *sendReceiveFolder) shortcutFile(file, curFile protocol.FileInfo, dbUpda
 
 	f.resetPullError(file.Name)
 
-	events.Default.Log(events.ItemStarted, map[string]string{
+	f.evLogger.Log(events.ItemStarted, map[string]string{
 		"folder": f.folderID,
 		"item":   file.Name,
 		"type":   "file",
@@ -1186,7 +1186,7 @@ func (f *sendReceiveFolder) shortcutFile(file, curFile protocol.FileInfo, dbUpda
 	})
 
 	var err error
-	defer events.Default.Log(events.ItemFinished, map[string]interface{}{
+	defer f.evLogger.Log(events.ItemFinished, map[string]interface{}{
 		"folder": f.folderID,
 		"item":   file.Name,
 		"error":  events.Error(err),
@@ -1575,7 +1575,7 @@ func (f *sendReceiveFolder) finisherRoutine(in <-chan *sharedPullerState, dbUpda
 
 			f.model.progressEmitter.Deregister(state)
 
-			events.Default.Log(events.ItemFinished, map[string]interface{}{
+			f.evLogger.Log(events.ItemFinished, map[string]interface{}{
 				"folder": f.folderID,
 				"item":   state.file.Name,
 				"error":  events.Error(err),

+ 21 - 48
lib/model/folder_sendrecv_test.go

@@ -96,7 +96,7 @@ func setupSendReceiveFolder(files ...protocol.FileInfo) (*model, *sendReceiveFol
 
 	f := &sendReceiveFolder{
 		folder: folder{
-			stateTracker:        newStateTracker("default"),
+			stateTracker:        newStateTracker("default", model.evLogger),
 			model:               model,
 			fset:                model.folderFiles[fcfg.ID],
 			initialScanFinished: make(chan struct{}),
@@ -121,6 +121,12 @@ func setupSendReceiveFolder(files ...protocol.FileInfo) (*model, *sendReceiveFol
 	return model, f
 }
 
+func cleanupSRFolder(f *sendReceiveFolder, m *model) {
+	m.evLogger.Stop()
+	os.Remove(m.cfg.ConfigPath())
+	os.Remove(f.Filesystem().URI())
+}
+
 // Layout of the files: (indexes from the above array)
 // 12345678 - Required file
 // 02005008 - Existing file (currently in the index)
@@ -137,10 +143,7 @@ func TestHandleFile(t *testing.T) {
 	requiredFile.Blocks = blocks[1:]
 
 	m, f := setupSendReceiveFolder(existingFile)
-	defer func() {
-		os.Remove(m.cfg.ConfigPath())
-		os.Remove(f.Filesystem().URI())
-	}()
+	defer cleanupSRFolder(f, m)
 
 	copyChan := make(chan copyBlocksState, 1)
 	dbUpdateChan := make(chan dbUpdateJob, 1)
@@ -183,10 +186,7 @@ func TestHandleFileWithTemp(t *testing.T) {
 	requiredFile.Blocks = blocks[1:]
 
 	m, f := setupSendReceiveFolder(existingFile)
-	defer func() {
-		os.Remove(m.cfg.ConfigPath())
-		os.Remove(f.Filesystem().URI())
-	}()
+	defer cleanupSRFolder(f, m)
 
 	if _, err := prepareTmpFile(f.Filesystem()); err != nil {
 		t.Fatal(err)
@@ -236,10 +236,7 @@ func TestCopierFinder(t *testing.T) {
 	requiredFile.Name = "file2"
 
 	m, f := setupSendReceiveFolder(existingFile)
-	defer func() {
-		os.Remove(m.cfg.ConfigPath())
-		os.Remove(f.Filesystem().URI())
-	}()
+	defer cleanupSRFolder(f, m)
 
 	if _, err := prepareTmpFile(f.Filesystem()); err != nil {
 		t.Fatal(err)
@@ -302,11 +299,8 @@ func TestCopierFinder(t *testing.T) {
 func TestWeakHash(t *testing.T) {
 	// Setup the model/pull environment
 	model, fo := setupSendReceiveFolder()
+	defer cleanupSRFolder(fo, model)
 	ffs := fo.Filesystem()
-	defer func() {
-		os.Remove(model.cfg.ConfigPath())
-		os.Remove(ffs.URI())
-	}()
 
 	tempFile := fs.TempName("weakhash")
 	var shift int64 = 10
@@ -432,10 +426,7 @@ func TestCopierCleanup(t *testing.T) {
 	// Create a file
 	file := setupFile("test", []int{0})
 	m, f := setupSendReceiveFolder(file)
-	defer func() {
-		os.Remove(m.cfg.ConfigPath())
-		os.Remove(f.Filesystem().URI())
-	}()
+	defer cleanupSRFolder(f, m)
 
 	file.Blocks = []protocol.BlockInfo{blocks[1]}
 	file.Version = file.Version.Update(myID.Short())
@@ -468,13 +459,10 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
 	file := setupFile("filex", []int{0, 2, 0, 0, 5, 0, 0, 8})
 
 	m, f := setupSendReceiveFolder()
-	defer func() {
-		os.Remove(m.cfg.ConfigPath())
-		os.Remove(f.Filesystem().URI())
-	}()
+	defer cleanupSRFolder(f, m)
 
 	// Set up our evet subscription early
-	s := events.Default.Subscribe(events.ItemFinished)
+	s := m.evLogger.Subscribe(events.ItemFinished)
 
 	// queue.Done should be called by the finisher routine
 	f.queue.Push("filex", 0, time.Time{})
@@ -558,13 +546,10 @@ func TestDeregisterOnFailInPull(t *testing.T) {
 	file := setupFile("filex", []int{0, 2, 0, 0, 5, 0, 0, 8})
 
 	m, f := setupSendReceiveFolder()
-	defer func() {
-		os.Remove(m.cfg.ConfigPath())
-		os.Remove(f.Filesystem().URI())
-	}()
+	defer cleanupSRFolder(f, m)
 
 	// Set up our evet subscription early
-	s := events.Default.Subscribe(events.ItemFinished)
+	s := m.evLogger.Subscribe(events.ItemFinished)
 
 	// queue.Done should be called by the finisher routine
 	f.queue.Push("filex", 0, time.Time{})
@@ -636,12 +621,9 @@ func TestDeregisterOnFailInPull(t *testing.T) {
 
 func TestIssue3164(t *testing.T) {
 	m, f := setupSendReceiveFolder()
+	defer cleanupSRFolder(f, m)
 	ffs := f.Filesystem()
 	tmpDir := ffs.URI()
-	defer func() {
-		os.Remove(m.cfg.ConfigPath())
-		os.Remove(tmpDir)
-	}()
 
 	ignDir := filepath.Join("issue3164", "oktodelete")
 	subDir := filepath.Join(ignDir, "foobar")
@@ -728,11 +710,8 @@ func TestDiffEmpty(t *testing.T) {
 // in the db.
 func TestDeleteIgnorePerms(t *testing.T) {
 	m, f := setupSendReceiveFolder()
+	defer cleanupSRFolder(f, m)
 	ffs := f.Filesystem()
-	defer func() {
-		os.Remove(m.cfg.ConfigPath())
-		os.Remove(ffs.URI())
-	}()
 	f.IgnorePerms = true
 
 	name := "deleteIgnorePerms"
@@ -778,7 +757,7 @@ func TestCopyOwner(t *testing.T) {
 	// filesystem.
 
 	m, f := setupSendReceiveFolder()
-	defer os.Remove(m.cfg.ConfigPath())
+	defer cleanupSRFolder(f, m)
 	f.folder.FolderConfiguration = config.NewFolderConfiguration(m.id, f.ID, f.Label, fs.FilesystemTypeFake, "/TestCopyOwner")
 	f.folder.FolderConfiguration.CopyOwnershipFromParent = true
 
@@ -867,11 +846,8 @@ func TestCopyOwner(t *testing.T) {
 // is replaced with a directory and versions are conflicting
 func TestSRConflictReplaceFileByDir(t *testing.T) {
 	m, f := setupSendReceiveFolder()
+	defer cleanupSRFolder(f, m)
 	ffs := f.Filesystem()
-	defer func() {
-		os.Remove(m.cfg.ConfigPath())
-		os.Remove(ffs.URI())
-	}()
 
 	name := "foo"
 
@@ -902,11 +878,8 @@ func TestSRConflictReplaceFileByDir(t *testing.T) {
 // is replaced with a link and versions are conflicting
 func TestSRConflictReplaceFileByLink(t *testing.T) {
 	m, f := setupSendReceiveFolder()
+	defer cleanupSRFolder(f, m)
 	ffs := f.Filesystem()
-	defer func() {
-		os.Remove(m.cfg.ConfigPath())
-		os.Remove(ffs.URI())
-	}()
 
 	name := "foo"
 

+ 7 - 5
lib/model/folder_summary.go

@@ -36,6 +36,7 @@ type folderSummaryService struct {
 	cfg       config.Wrapper
 	model     Model
 	id        protocol.DeviceID
+	evLogger  events.Logger
 	immediate chan string
 
 	// For keeping track of folders to recalculate for
@@ -47,7 +48,7 @@ type folderSummaryService struct {
 	lastEventReqMut sync.Mutex
 }
 
-func NewFolderSummaryService(cfg config.Wrapper, m Model, id protocol.DeviceID) FolderSummaryService {
+func NewFolderSummaryService(cfg config.Wrapper, m Model, id protocol.DeviceID, evLogger events.Logger) FolderSummaryService {
 	service := &folderSummaryService{
 		Supervisor: suture.New("folderSummaryService", suture.Spec{
 			PassThroughPanics: true,
@@ -55,6 +56,7 @@ func NewFolderSummaryService(cfg config.Wrapper, m Model, id protocol.DeviceID)
 		cfg:             cfg,
 		model:           m,
 		id:              id,
+		evLogger:        evLogger,
 		immediate:       make(chan string),
 		folders:         make(map[string]struct{}),
 		foldersMut:      sync.NewMutex(),
@@ -144,8 +146,8 @@ func (c *folderSummaryService) OnEventRequest() {
 // listenForUpdates subscribes to the event bus and makes note of folders that
 // need their data recalculated.
 func (c *folderSummaryService) listenForUpdates(stop chan struct{}) {
-	sub := events.Default.Subscribe(events.LocalIndexUpdated | events.RemoteIndexUpdated | events.StateChanged | events.RemoteDownloadProgress | events.DeviceConnected | events.FolderWatchStateChanged | events.DownloadProgress)
-	defer events.Default.Unsubscribe(sub)
+	sub := c.evLogger.Subscribe(events.LocalIndexUpdated | events.RemoteIndexUpdated | events.StateChanged | events.RemoteDownloadProgress | events.DeviceConnected | events.FolderWatchStateChanged | events.DownloadProgress)
+	defer sub.Unsubscribe()
 
 	for {
 		// This loop needs to be fast so we don't miss too many events.
@@ -291,7 +293,7 @@ func (c *folderSummaryService) sendSummary(folder string) {
 	if err != nil {
 		return
 	}
-	events.Default.Log(events.FolderSummary, map[string]interface{}{
+	c.evLogger.Log(events.FolderSummary, map[string]interface{}{
 		"folder":  folder,
 		"summary": data,
 	})
@@ -311,6 +313,6 @@ func (c *folderSummaryService) sendSummary(folder string) {
 		comp := c.model.Completion(devCfg.DeviceID, folder).Map()
 		comp["folder"] = folder
 		comp["device"] = devCfg.DeviceID.String()
-		events.Default.Log(events.FolderCompletion, comp)
+		c.evLogger.Log(events.FolderCompletion, comp)
 	}
 }

+ 5 - 3
lib/model/folderstate.go

@@ -42,6 +42,7 @@ func (s folderState) String() string {
 
 type stateTracker struct {
 	folderID string
+	evLogger events.Logger
 
 	mut     sync.Mutex
 	current folderState
@@ -49,9 +50,10 @@ type stateTracker struct {
 	changed time.Time
 }
 
-func newStateTracker(id string) stateTracker {
+func newStateTracker(id string, evLogger events.Logger) stateTracker {
 	return stateTracker{
 		folderID: id,
+		evLogger: evLogger,
 		mut:      sync.NewMutex(),
 	}
 }
@@ -83,7 +85,7 @@ func (s *stateTracker) setState(newState folderState) {
 		s.current = newState
 		s.changed = time.Now()
 
-		events.Default.Log(events.StateChanged, eventData)
+		s.evLogger.Log(events.StateChanged, eventData)
 	}
 	s.mut.Unlock()
 }
@@ -124,5 +126,5 @@ func (s *stateTracker) setError(err error) {
 	s.err = err
 	s.changed = time.Now()
 
-	events.Default.Log(events.StateChanged, eventData)
+	s.evLogger.Log(events.StateChanged, eventData)
 }

+ 19 - 15
lib/model/model.go

@@ -128,6 +128,7 @@ type model struct {
 	shortID           protocol.ShortID
 	cacheIgnoredFiles bool
 	protectedFiles    []string
+	evLogger          events.Logger
 
 	clientName    string
 	clientVersion string
@@ -152,7 +153,7 @@ type model struct {
 	foldersRunning int32 // for testing only
 }
 
-type folderFactory func(*model, *db.FileSet, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, fs.Filesystem) service
+type folderFactory func(*model, *db.FileSet, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, fs.Filesystem, events.Logger) service
 
 var (
 	folderFactories = make(map[config.FolderType]folderFactory)
@@ -175,7 +176,7 @@ var (
 // NewModel creates and starts a new model. The model starts in read-only mode,
 // where it sends index information to connected peers and responds to requests
 // for file data without altering the local folder in any way.
-func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, ldb *db.Lowlevel, protectedFiles []string) Model {
+func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, ldb *db.Lowlevel, protectedFiles []string, evLogger events.Logger) Model {
 	m := &model{
 		Supervisor: suture.New("model", suture.Spec{
 			Log: func(line string) {
@@ -186,11 +187,12 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
 		cfg:                 cfg,
 		db:                  ldb,
 		finder:              db.NewBlockFinder(ldb),
-		progressEmitter:     NewProgressEmitter(cfg),
+		progressEmitter:     NewProgressEmitter(cfg, evLogger),
 		id:                  id,
 		shortID:             id.Short(),
 		cacheIgnoredFiles:   cfg.Options().CacheIgnoredFiles,
 		protectedFiles:      protectedFiles,
+		evLogger:            evLogger,
 		clientName:          clientName,
 		clientVersion:       clientVersion,
 		folderCfgs:          make(map[string]config.FolderConfiguration),
@@ -310,7 +312,7 @@ func (m *model) startFolderLocked(cfg config.FolderConfiguration) {
 	ffs.Hide(".stversions")
 	ffs.Hide(".stignore")
 
-	p := folderFactory(m, fset, m.folderIgnores[folder], cfg, ver, ffs)
+	p := folderFactory(m, fset, m.folderIgnores[folder], cfg, ver, ffs, m.evLogger)
 
 	m.folderRunners[folder] = p
 
@@ -1023,7 +1025,7 @@ func (m *model) handleIndex(deviceID protocol.DeviceID, folder string, fs []prot
 	}
 	files.Update(deviceID, fs)
 
-	events.Default.Log(events.RemoteIndexUpdated, map[string]interface{}{
+	m.evLogger.Log(events.RemoteIndexUpdated, map[string]interface{}{
 		"device":  deviceID.String(),
 		"folder":  folder,
 		"items":   len(fs),
@@ -1077,7 +1079,7 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
 			}
 			m.cfg.AddOrUpdatePendingFolder(folder.ID, folder.Label, deviceID)
 			changed = true
-			events.Default.Log(events.FolderRejected, map[string]string{
+			m.evLogger.Log(events.FolderRejected, map[string]string{
 				"folder":      folder.ID,
 				"folderLabel": folder.Label,
 				"device":      deviceID.String(),
@@ -1180,6 +1182,7 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
 			fset:         fs,
 			prevSequence: startSequence,
 			dropSymlinks: dropSymlinks,
+			evLogger:     m.evLogger,
 		}
 		is.Service = util.AsService(is.serve)
 		// The token isn't tracked as the service stops when the connection
@@ -1432,7 +1435,7 @@ func (m *model) Closed(conn protocol.Connection, err error) {
 	delete(m.closed, device)
 
 	l.Infof("Connection to %s at %s closed: %v", device, conn.Name(), err)
-	events.Default.Log(events.DeviceDisconnected, map[string]string{
+	m.evLogger.Log(events.DeviceDisconnected, map[string]string{
 		"id":    device.String(),
 		"error": err.Error(),
 	})
@@ -1773,7 +1776,7 @@ func (m *model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protoco
 	if !ok {
 		m.cfg.AddOrUpdatePendingDevice(remoteID, hello.DeviceName, addr.String())
 		_ = m.cfg.Save() // best effort
-		events.Default.Log(events.DeviceRejected, map[string]string{
+		m.evLogger.Log(events.DeviceRejected, map[string]string{
 			"name":    hello.DeviceName,
 			"device":  remoteID.String(),
 			"address": addr.String(),
@@ -1859,7 +1862,7 @@ func (m *model) AddConnection(conn connections.Connection, hello protocol.HelloR
 		event["addr"] = addr.String()
 	}
 
-	events.Default.Log(events.DeviceConnected, event)
+	m.evLogger.Log(events.DeviceConnected, event)
 
 	l.Infof(`Device %s client is "%s %s" named "%s" at %s`, deviceID, hello.ClientName, hello.ClientVersion, hello.DeviceName, conn)
 
@@ -1894,7 +1897,7 @@ func (m *model) DownloadProgress(device protocol.DeviceID, folder string, update
 	downloads.Update(folder, updates)
 	state := downloads.GetBlockCounts(folder)
 
-	events.Default.Log(events.RemoteDownloadProgress, map[string]interface{}{
+	m.evLogger.Log(events.RemoteDownloadProgress, map[string]interface{}{
 		"device": device.String(),
 		"folder": folder,
 		"state":  state,
@@ -1926,6 +1929,7 @@ type indexSender struct {
 	fset         *db.FileSet
 	prevSequence int64
 	dropSymlinks bool
+	evLogger     events.Logger
 	connClosed   chan struct{}
 }
 
@@ -1941,8 +1945,8 @@ func (s *indexSender) serve(stop chan struct{}) {
 	// Subscribe to LocalIndexUpdated (we have new information to send) and
 	// DeviceDisconnected (it might be us who disconnected, so we should
 	// exit).
-	sub := events.Default.Subscribe(events.LocalIndexUpdated | events.DeviceDisconnected)
-	defer events.Default.Unsubscribe(sub)
+	sub := s.evLogger.Subscribe(events.LocalIndexUpdated | events.DeviceDisconnected)
+	defer sub.Unsubscribe()
 
 	evChan := sub.C()
 	ticker := time.NewTicker(time.Minute)
@@ -2531,7 +2535,7 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
 			if toCfg.Paused {
 				eventType = events.FolderPaused
 			}
-			events.Default.Log(eventType, map[string]string{"id": toCfg.ID, "label": toCfg.Label})
+			m.evLogger.Log(eventType, map[string]string{"id": toCfg.ID, "label": toCfg.Label})
 		}
 	}
 
@@ -2559,9 +2563,9 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
 		if toCfg.Paused {
 			l.Infoln("Pausing", deviceID)
 			m.closeConn(deviceID, errDevicePaused)
-			events.Default.Log(events.DevicePaused, map[string]string{"device": deviceID.String()})
+			m.evLogger.Log(events.DevicePaused, map[string]string{"device": deviceID.String()})
 		} else {
-			events.Default.Log(events.DeviceResumed, map[string]string{"device": deviceID.String()})
+			m.evLogger.Log(events.DeviceResumed, map[string]string{"device": deviceID.String()})
 		}
 	}
 

+ 6 - 6
lib/model/model_test.go

@@ -110,7 +110,7 @@ func createTmpWrapper(cfg config.Configuration) config.Wrapper {
 	if err != nil {
 		panic(err)
 	}
-	wrapper := config.Wrap(tmpFile.Name(), cfg)
+	wrapper := config.Wrap(tmpFile.Name(), cfg, events.NoopLogger)
 	tmpFile.Close()
 	return wrapper
 }
@@ -303,7 +303,7 @@ func TestDeviceRename(t *testing.T) {
 			DeviceID: device1,
 		},
 	}
-	cfg := config.Wrap("testdata/tmpconfig.xml", rawCfg)
+	cfg := config.Wrap("testdata/tmpconfig.xml", rawCfg, events.NoopLogger)
 
 	db := db.OpenMemory()
 	m := newModel(cfg, myID, "syncthing", "dev", db, nil)
@@ -339,7 +339,7 @@ func TestDeviceRename(t *testing.T) {
 		t.Errorf("Device name got overwritten")
 	}
 
-	cfgw, err := config.Load("testdata/tmpconfig.xml", myID)
+	cfgw, err := config.Load("testdata/tmpconfig.xml", myID, events.NoopLogger)
 	if err != nil {
 		t.Error(err)
 		return
@@ -3358,12 +3358,12 @@ func TestModTimeWindow(t *testing.T) {
 }
 
 func TestDevicePause(t *testing.T) {
-	sub := events.Default.Subscribe(events.DevicePaused)
-	defer events.Default.Unsubscribe(sub)
-
 	m, _, fcfg := setupModelWithConnection()
 	defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
 
+	sub := m.evLogger.Subscribe(events.DevicePaused)
+	defer sub.Unsubscribe()
+
 	m.pmut.RLock()
 	closed := m.closed[device1]
 	m.pmut.RUnlock()

+ 4 - 2
lib/model/progressemitter.go

@@ -29,6 +29,7 @@ type ProgressEmitter struct {
 	connections        map[protocol.DeviceID]protocol.Connection
 	foldersByConns     map[protocol.DeviceID][]string
 	disabled           bool
+	evLogger           events.Logger
 	mut                sync.Mutex
 
 	timer *time.Timer
@@ -36,13 +37,14 @@ type ProgressEmitter struct {
 
 // NewProgressEmitter creates a new progress emitter which emits
 // DownloadProgress events every interval.
-func NewProgressEmitter(cfg config.Wrapper) *ProgressEmitter {
+func NewProgressEmitter(cfg config.Wrapper, evLogger events.Logger) *ProgressEmitter {
 	t := &ProgressEmitter{
 		registry:           make(map[string]map[string]*sharedPullerState),
 		timer:              time.NewTimer(time.Millisecond),
 		sentDownloadStates: make(map[protocol.DeviceID]*sentDownloadState),
 		connections:        make(map[protocol.DeviceID]protocol.Connection),
 		foldersByConns:     make(map[protocol.DeviceID][]string),
+		evLogger:           evLogger,
 		mut:                sync.NewMutex(),
 	}
 	t.Service = util.AsService(t.serve)
@@ -107,7 +109,7 @@ func (t *ProgressEmitter) sendDownloadProgressEventLocked() {
 			output[folder][name] = puller.Progress()
 		}
 	}
-	events.Default.Log(events.DownloadProgress, output)
+	t.evLogger.Log(events.DownloadProgress, output)
 	l.Debugf("progress emitter: emitting %#v", output)
 }
 

+ 13 - 5
lib/model/progressemitter_test.go

@@ -30,7 +30,7 @@ func caller(skip int) string {
 	return fmt.Sprintf("%s:%d", filepath.Base(file), line)
 }
 
-func expectEvent(w *events.Subscription, t *testing.T, size int) {
+func expectEvent(w events.Subscription, t *testing.T, size int) {
 	event, err := w.Poll(timeout)
 	if err != nil {
 		t.Fatal("Unexpected error:", err, "at", caller(1))
@@ -44,7 +44,7 @@ func expectEvent(w *events.Subscription, t *testing.T, size int) {
 	}
 }
 
-func expectTimeout(w *events.Subscription, t *testing.T) {
+func expectTimeout(w events.Subscription, t *testing.T) {
 	_, err := w.Poll(timeout)
 	if err != events.ErrTimeout {
 		t.Fatal("Unexpected non-Timeout error:", err, "at", caller(1))
@@ -52,7 +52,11 @@ func expectTimeout(w *events.Subscription, t *testing.T) {
 }
 
 func TestProgressEmitter(t *testing.T) {
-	w := events.Default.Subscribe(events.DownloadProgress)
+	evLogger := events.NewLogger()
+	go evLogger.Serve()
+	defer evLogger.Stop()
+
+	w := evLogger.Subscribe(events.DownloadProgress)
 
 	c := createTmpWrapper(config.Configuration{})
 	defer os.Remove(c.ConfigPath())
@@ -60,7 +64,7 @@ func TestProgressEmitter(t *testing.T) {
 		ProgressUpdateIntervalS: 0,
 	})
 
-	p := NewProgressEmitter(c)
+	p := NewProgressEmitter(c, evLogger)
 	go p.Serve()
 	p.interval = 0
 
@@ -112,7 +116,11 @@ func TestSendDownloadProgressMessages(t *testing.T) {
 
 	fc := &fakeConnection{}
 
-	p := NewProgressEmitter(c)
+	evLogger := events.NewLogger()
+	go evLogger.Serve()
+	defer evLogger.Stop()
+
+	p := NewProgressEmitter(c, evLogger)
 	p.temporaryIndexSubscribe(fc, []string{"folder", "folder2"})
 	p.registry["folder"] = make(map[string]*sharedPullerState)
 	p.registry["folder2"] = make(map[string]*sharedPullerState)

+ 6 - 6
lib/model/requests_test.go

@@ -350,8 +350,8 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
 	}
 	fc.mut.Unlock()
 
-	sub := events.Default.Subscribe(events.FolderErrors)
-	defer events.Default.Unsubscribe(sub)
+	sub := m.evLogger.Subscribe(events.FolderErrors)
+	defer sub.Unsubscribe()
 
 	fc.sendIndexUpdate()
 
@@ -640,8 +640,8 @@ func TestRequestSymlinkWindows(t *testing.T) {
 		t.Fatalf("timed out before pull was finished")
 	}
 
-	sub := events.Default.Subscribe(events.StateChanged | events.LocalIndexUpdated)
-	defer events.Default.Unsubscribe(sub)
+	sub := m.evLogger.Subscribe(events.StateChanged | events.LocalIndexUpdated)
+	defer sub.Unsubscribe()
 
 	m.ScanFolder("default")
 
@@ -978,8 +978,8 @@ func TestNeedFolderFiles(t *testing.T) {
 	tmpDir := tfs.URI()
 	defer cleanupModelAndRemoveDir(m, tmpDir)
 
-	sub := events.Default.Subscribe(events.RemoteIndexUpdated)
-	defer events.Default.Unsubscribe(sub)
+	sub := m.evLogger.Subscribe(events.RemoteIndexUpdated)
+	defer sub.Unsubscribe()
 
 	errPreventSync := errors.New("you aren't getting any of this")
 	fc.mut.Lock()

+ 6 - 1
lib/model/testutils_test.go

@@ -13,6 +13,7 @@ import (
 
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/db"
+	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/fs"
 	"github.com/syncthing/syncthing/lib/protocol"
 )
@@ -117,12 +118,16 @@ func setupModel(w config.Wrapper) *model {
 }
 
 func newModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, ldb *db.Lowlevel, protectedFiles []string) *model {
-	return NewModel(cfg, id, clientName, clientVersion, ldb, protectedFiles).(*model)
+	evLogger := events.NewLogger()
+	m := NewModel(cfg, id, clientName, clientVersion, ldb, protectedFiles, evLogger).(*model)
+	go evLogger.Serve()
+	return m
 }
 
 func cleanupModel(m *model) {
 	m.Stop()
 	m.db.Close()
+	m.evLogger.Stop()
 	os.Remove(m.cfg.ConfigPath())
 }
 

+ 4 - 3
lib/rc/rc.go

@@ -26,6 +26,7 @@ import (
 
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/dialer"
+	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/protocol"
 	"github.com/syncthing/syncthing/lib/sync"
 )
@@ -455,7 +456,7 @@ func (p *Process) eventLoop() {
 		default:
 		}
 
-		events, err := p.Events(since)
+		evs, err := p.Events(since)
 		if err != nil {
 			if time.Since(start) < 5*time.Second {
 				// The API has probably not started yet, lets give it some time.
@@ -473,7 +474,7 @@ func (p *Process) eventLoop() {
 			continue
 		}
 
-		for _, ev := range events {
+		for _, ev := range evs {
 			if ev.ID != since+1 {
 				l.Warnln("Event ID jumped", since, "to", ev.ID)
 			}
@@ -493,7 +494,7 @@ func (p *Process) eventLoop() {
 				p.id = id
 
 				home := data["home"].(string)
-				w, err := config.Load(filepath.Join(home, "config.xml"), protocol.LocalDeviceID)
+				w, err := config.Load(filepath.Join(home, "config.xml"), protocol.LocalDeviceID, events.NoopLogger)
 				if err != nil {
 					log.Println("eventLoop: Starting:", err)
 					continue

+ 3 - 1
lib/scanner/walk.go

@@ -56,6 +56,8 @@ type Config struct {
 	LocalFlags uint32
 	// Modification time is to be considered unchanged if the difference is lower.
 	ModTimeWindow time.Duration
+	// Event logger to which the scan progress events are sent
+	EvLogger events.Logger
 }
 
 type CurrentFiler interface {
@@ -168,7 +170,7 @@ func (w *walker) walk(ctx context.Context) chan ScanResult {
 					current := progress.Total()
 					rate := progress.Rate()
 					l.Debugf("Walk %s %s current progress %d/%d at %.01f MiB/s (%d%%)", w.Folder, w.Subs, current, total, rate/1024/1024, current*100/total)
-					events.Default.Log(events.FolderScanProgress, map[string]interface{}{
+					w.EvLogger.Log(events.FolderScanProgress, map[string]interface{}{
 						"folder":  w.Folder,
 						"current": current,
 						"total":   total,

+ 45 - 45
lib/scanner/walk_test.go

@@ -22,6 +22,7 @@ import (
 	"testing"
 
 	"github.com/d4l3k/messagediff"
+	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/fs"
 	"github.com/syncthing/syncthing/lib/ignore"
 	"github.com/syncthing/syncthing/lib/osutil"
@@ -66,12 +67,10 @@ func TestWalkSub(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	fchan := Walk(context.TODO(), Config{
-		Filesystem: testFs,
-		Subs:       []string{"dir2"},
-		Matcher:    ignores,
-		Hashers:    2,
-	})
+	cfg := testConfig()
+	cfg.Subs = []string{"dir2"}
+	cfg.Matcher = ignores
+	fchan := Walk(context.TODO(), cfg)
 	var files []protocol.FileInfo
 	for f := range fchan {
 		if f.Err != nil {
@@ -102,11 +101,9 @@ func TestWalk(t *testing.T) {
 	}
 	t.Log(ignores)
 
-	fchan := Walk(context.TODO(), Config{
-		Filesystem: testFs,
-		Matcher:    ignores,
-		Hashers:    2,
-	})
+	cfg := testConfig()
+	cfg.Matcher = ignores
+	fchan := Walk(context.TODO(), cfg)
 
 	var tmp []protocol.FileInfo
 	for f := range fchan {
@@ -466,15 +463,14 @@ func TestWalkReceiveOnly(t *testing.T) {
 }
 
 func walkDir(fs fs.Filesystem, dir string, cfiler CurrentFiler, matcher *ignore.Matcher, localFlags uint32) []protocol.FileInfo {
-	fchan := Walk(context.TODO(), Config{
-		Filesystem:    fs,
-		Subs:          []string{dir},
-		AutoNormalize: true,
-		Hashers:       2,
-		CurrentFiler:  cfiler,
-		Matcher:       matcher,
-		LocalFlags:    localFlags,
-	})
+	cfg := testConfig()
+	cfg.Filesystem = fs
+	cfg.Subs = []string{dir}
+	cfg.AutoNormalize = true
+	cfg.CurrentFiler = cfiler
+	cfg.Matcher = matcher
+	cfg.LocalFlags = localFlags
+	fchan := Walk(context.TODO(), cfg)
 
 	var tmp []protocol.FileInfo
 	for f := range fchan {
@@ -576,11 +572,11 @@ func TestStopWalk(t *testing.T) {
 
 	const numHashers = 4
 	ctx, cancel := context.WithCancel(context.Background())
-	fchan := Walk(ctx, Config{
-		Filesystem:            fs,
-		Hashers:               numHashers,
-		ProgressTickIntervalS: -1, // Don't attempt to build the full list of files before starting to scan...
-	})
+	cfg := testConfig()
+	cfg.Filesystem = fs
+	cfg.Hashers = numHashers
+	cfg.ProgressTickIntervalS = -1 // Don't attempt to build the full list of files before starting to scan...
+	fchan := Walk(ctx, cfg)
 
 	// Receive a few entries to make sure the walker is up and running,
 	// scanning both files and dirs. Do some quick sanity tests on the
@@ -705,21 +701,17 @@ func TestIssue4841(t *testing.T) {
 	}
 	fd.Close()
 
-	fchan := Walk(context.TODO(), Config{
-		Filesystem:    fs,
-		Subs:          nil,
-		AutoNormalize: true,
-		Hashers:       2,
-		CurrentFiler: fakeCurrentFiler{
-			"foo": {
-				Name:       "foo",
-				Type:       protocol.FileInfoTypeFile,
-				LocalFlags: protocol.FlagLocalIgnored,
-				Version:    protocol.Vector{}.Update(1),
-			},
-		},
-		ShortID: protocol.LocalDeviceID.Short(),
-	})
+	cfg := testConfig()
+	cfg.Filesystem = fs
+	cfg.AutoNormalize = true
+	cfg.CurrentFiler = fakeCurrentFiler{"foo": {
+		Name:       "foo",
+		Type:       protocol.FileInfoTypeFile,
+		LocalFlags: protocol.FlagLocalIgnored,
+		Version:    protocol.Vector{}.Update(1),
+	}}
+	cfg.ShortID = protocol.LocalDeviceID.Short()
+	fchan := Walk(context.TODO(), cfg)
 
 	var files []protocol.FileInfo
 	for f := range fchan {
@@ -745,11 +737,9 @@ func TestNotExistingError(t *testing.T) {
 		t.Fatalf("Lstat returned error %v, while nothing should exist there.", err)
 	}
 
-	fchan := Walk(context.TODO(), Config{
-		Filesystem: testFs,
-		Subs:       []string{sub},
-		Hashers:    2,
-	})
+	cfg := testConfig()
+	cfg.Subs = []string{sub}
+	fchan := Walk(context.TODO(), cfg)
 	for f := range fchan {
 		t.Fatalf("Expected no result from scan, got %v", f)
 	}
@@ -793,3 +783,13 @@ func (fcf fakeCurrentFiler) CurrentFile(name string) (protocol.FileInfo, bool) {
 	f, ok := fcf[name]
 	return f, ok
 }
+
+func testConfig() Config {
+	evLogger := events.NewLogger()
+	go evLogger.Serve()
+	return Config{
+		Filesystem: testFs,
+		Hashers:    2,
+		EvLogger:   evLogger,
+	}
+}

+ 4 - 4
lib/syncthing/auditservice.go

@@ -21,13 +21,13 @@ import (
 type auditService struct {
 	suture.Service
 	w   io.Writer // audit destination
-	sub *events.Subscription
+	sub events.Subscription
 }
 
-func newAuditService(w io.Writer) *auditService {
+func newAuditService(w io.Writer, evLogger events.Logger) *auditService {
 	s := &auditService{
 		w:   w,
-		sub: events.Default.Subscribe(events.AllEvents),
+		sub: evLogger.Subscribe(events.AllEvents),
 	}
 	s.Service = util.AsService(s.serve)
 	return s
@@ -50,5 +50,5 @@ func (s *auditService) serve(stop chan struct{}) {
 // Stop stops the audit service.
 func (s *auditService) Stop() {
 	s.Service.Stop()
-	events.Default.Unsubscribe(s.sub)
+	s.sub.Unsubscribe()
 }

+ 14 - 7
lib/syncthing/auditservice_test.go

@@ -17,15 +17,22 @@ import (
 
 func TestAuditService(t *testing.T) {
 	buf := new(bytes.Buffer)
-
-	// Event sent before construction, will not be logged
-	events.Default.Log(events.ConfigSaved, "the first event")
-
-	service := newAuditService(buf)
+	evLogger := events.NewLogger()
+	go evLogger.Serve()
+	defer evLogger.Stop()
+	sub := evLogger.Subscribe(events.AllEvents)
+	defer sub.Unsubscribe()
+
+	// Event sent before start, will not be logged
+	evLogger.Log(events.ConfigSaved, "the first event")
+	// Make sure the event goes through before creating the service
+	<-sub.C()
+
+	service := newAuditService(buf, evLogger)
 	go service.Serve()
 
 	// Event that should end up in the audit log
-	events.Default.Log(events.ConfigSaved, "the second event")
+	evLogger.Log(events.ConfigSaved, "the second event")
 
 	// We need to give the events time to arrive, since the channels are buffered etc.
 	time.Sleep(10 * time.Millisecond)
@@ -33,7 +40,7 @@ func TestAuditService(t *testing.T) {
 	service.Stop()
 
 	// This event should not be logged, since we have stopped.
-	events.Default.Log(events.ConfigSaved, "the third event")
+	evLogger.Log(events.ConfigSaved, "the third event")
 
 	result := buf.String()
 	t.Log(result)

+ 22 - 20
lib/syncthing/syncthing.go

@@ -68,6 +68,7 @@ type App struct {
 	mainService *suture.Supervisor
 	cfg         config.Wrapper
 	ll          *db.Lowlevel
+	evLogger    events.Logger
 	cert        tls.Certificate
 	opts        Options
 	exitStatus  ExitStatus
@@ -78,14 +79,15 @@ type App struct {
 	stopped     chan struct{}
 }
 
-func New(cfg config.Wrapper, ll *db.Lowlevel, cert tls.Certificate, opts Options) *App {
+func New(cfg config.Wrapper, ll *db.Lowlevel, evLogger events.Logger, cert tls.Certificate, opts Options) *App {
 	return &App{
-		cfg:     cfg,
-		ll:      ll,
-		opts:    opts,
-		cert:    cert,
-		stop:    make(chan struct{}),
-		stopped: make(chan struct{}),
+		cfg:      cfg,
+		ll:       ll,
+		evLogger: evLogger,
+		opts:     opts,
+		cert:     cert,
+		stop:     make(chan struct{}),
+		stopped:  make(chan struct{}),
 	}
 }
 
@@ -120,11 +122,11 @@ func (a *App) startup() error {
 	a.mainService.ServeBackground()
 
 	if a.opts.AuditWriter != nil {
-		a.mainService.Add(newAuditService(a.opts.AuditWriter))
+		a.mainService.Add(newAuditService(a.opts.AuditWriter, a.evLogger))
 	}
 
 	if a.opts.Verbose {
-		a.mainService.Add(newVerboseService())
+		a.mainService.Add(newVerboseService(a.evLogger))
 	}
 
 	errors := logger.NewRecorder(l, logger.LevelWarn, maxSystemErrors, 0)
@@ -133,8 +135,8 @@ func (a *App) startup() error {
 	// Event subscription for the API; must start early to catch the early
 	// events. The LocalChangeDetected event might overwhelm the event
 	// receiver in some situations so we will not subscribe to it here.
-	defaultSub := events.NewBufferedSubscription(events.Default.Subscribe(api.DefaultEventMask), api.EventSubBufferSize)
-	diskSub := events.NewBufferedSubscription(events.Default.Subscribe(api.DiskEventMask), api.EventSubBufferSize)
+	defaultSub := events.NewBufferedSubscription(a.evLogger.Subscribe(api.DefaultEventMask), api.EventSubBufferSize)
+	diskSub := events.NewBufferedSubscription(a.evLogger.Subscribe(api.DiskEventMask), api.EventSubBufferSize)
 
 	// Attempt to increase the limit on number of open files to the maximum
 	// allowed, in case we have many peers. We don't really care enough to
@@ -153,7 +155,7 @@ func (a *App) startup() error {
 
 	// Emit the Starting event, now that we know who we are.
 
-	events.Default.Log(events.Starting, map[string]string{
+	a.evLogger.Log(events.Starting, map[string]string{
 		"home": locations.GetBaseDir(locations.ConfigBaseDir),
 		"myID": a.myID.String(),
 	})
@@ -228,7 +230,7 @@ func (a *App) startup() error {
 		miscDB.PutString("prevVersion", build.Version)
 	}
 
-	m := model.NewModel(a.cfg, a.myID, "syncthing", build.Version, a.ll, protectedFiles)
+	m := model.NewModel(a.cfg, a.myID, "syncthing", build.Version, a.ll, protectedFiles, a.evLogger)
 
 	if a.opts.DeadlockTimeoutS > 0 {
 		m.StartDeadlockDetector(time.Duration(a.opts.DeadlockTimeoutS) * time.Second)
@@ -265,13 +267,13 @@ func (a *App) startup() error {
 
 	// Start connection management
 
-	connectionsService := connections.NewService(a.cfg, a.myID, m, tlsCfg, cachedDiscovery, bepProtocolName, tlsDefaultCommonName)
+	connectionsService := connections.NewService(a.cfg, a.myID, m, tlsCfg, cachedDiscovery, bepProtocolName, tlsDefaultCommonName, a.evLogger)
 	a.mainService.Add(connectionsService)
 
 	if a.cfg.Options().GlobalAnnEnabled {
 		for _, srv := range a.cfg.GlobalDiscoveryServers() {
 			l.Infoln("Using discovery server", srv)
-			gd, err := discover.NewGlobal(srv, a.cert, connectionsService)
+			gd, err := discover.NewGlobal(srv, a.cert, connectionsService, a.evLogger)
 			if err != nil {
 				l.Warnln("Global discovery:", err)
 				continue
@@ -286,14 +288,14 @@ func (a *App) startup() error {
 
 	if a.cfg.Options().LocalAnnEnabled {
 		// v4 broadcasts
-		bcd, err := discover.NewLocal(a.myID, fmt.Sprintf(":%d", a.cfg.Options().LocalAnnPort), connectionsService)
+		bcd, err := discover.NewLocal(a.myID, fmt.Sprintf(":%d", a.cfg.Options().LocalAnnPort), connectionsService, a.evLogger)
 		if err != nil {
 			l.Warnln("IPv4 local discovery:", err)
 		} else {
 			cachedDiscovery.Add(bcd, 0, 0)
 		}
 		// v6 multicasts
-		mcd, err := discover.NewLocal(a.myID, a.cfg.Options().LocalAnnMCAddr, connectionsService)
+		mcd, err := discover.NewLocal(a.myID, a.cfg.Options().LocalAnnMCAddr, connectionsService, a.evLogger)
 		if err != nil {
 			l.Warnln("IPv6 local discovery:", err)
 		} else {
@@ -342,7 +344,7 @@ func (a *App) startup() error {
 		l.Warnln("Syncthing should not run as a privileged or system user. Please consider using a normal user account.")
 	}
 
-	events.Default.Log(events.StartupComplete, map[string]string{
+	a.evLogger.Log(events.StartupComplete, map[string]string{
 		"myID": a.myID.String(),
 	})
 
@@ -426,10 +428,10 @@ func (a *App) setupGUI(m model.Model, defaultSub, diskSub events.BufferedSubscri
 	cpu := newCPUService()
 	a.mainService.Add(cpu)
 
-	summaryService := model.NewFolderSummaryService(a.cfg, m, a.myID)
+	summaryService := model.NewFolderSummaryService(a.cfg, m, a.myID, a.evLogger)
 	a.mainService.Add(summaryService)
 
-	apiSvc := api.New(a.myID, a.cfg, a.opts.AssetDir, tlsDefaultCommonName, m, defaultSub, diskSub, discoverer, connectionsService, urService, summaryService, errors, systemLog, cpu, &controller{a}, a.opts.NoUpgrade)
+	apiSvc := api.New(a.myID, a.cfg, a.opts.AssetDir, tlsDefaultCommonName, m, defaultSub, diskSub, a.evLogger, discoverer, connectionsService, urService, summaryService, errors, systemLog, cpu, &controller{a}, a.opts.NoUpgrade)
 	a.mainService.Add(apiSvc)
 
 	if err := apiSvc.WaitForStart(); err != nil {

+ 3 - 2
lib/syncthing/syncthing_test.go

@@ -10,6 +10,7 @@ import (
 	"testing"
 
 	"github.com/syncthing/syncthing/lib/config"
+	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/protocol"
 )
 
@@ -19,7 +20,7 @@ func TestShortIDCheck(t *testing.T) {
 			{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 0, 0}},
 			{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 1, 1}}, // first 56 bits same, differ in the first 64 bits
 		},
-	})
+	}, events.NoopLogger)
 
 	if err := checkShortIDs(cfg); err != nil {
 		t.Error("Unexpected error:", err)
@@ -30,7 +31,7 @@ func TestShortIDCheck(t *testing.T) {
 			{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 64, 0}},
 			{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 64, 1}}, // first 64 bits same
 		},
-	})
+	}, events.NoopLogger)
 
 	if err := checkShortIDs(cfg); err == nil {
 		t.Error("Should have gotten an error")

+ 7 - 6
lib/syncthing/utils.go

@@ -17,6 +17,7 @@ import (
 
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/db"
+	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/fs"
 	"github.com/syncthing/syncthing/lib/locations"
 	"github.com/syncthing/syncthing/lib/protocol"
@@ -39,7 +40,7 @@ func LoadOrGenerateCertificate(certFile, keyFile string) (tls.Certificate, error
 	return cert, nil
 }
 
-func DefaultConfig(path string, myID protocol.DeviceID, noDefaultFolder bool) (config.Wrapper, error) {
+func DefaultConfig(path string, myID protocol.DeviceID, evLogger events.Logger, noDefaultFolder bool) (config.Wrapper, error) {
 	newCfg, err := config.NewWithFreePorts(myID)
 	if err != nil {
 		return nil, err
@@ -47,23 +48,23 @@ func DefaultConfig(path string, myID protocol.DeviceID, noDefaultFolder bool) (c
 
 	if noDefaultFolder {
 		l.Infoln("We will skip creation of a default folder on first start")
-		return config.Wrap(path, newCfg), nil
+		return config.Wrap(path, newCfg, evLogger), nil
 	}
 
 	newCfg.Folders = append(newCfg.Folders, config.NewFolderConfiguration(myID, "default", "Default Folder", fs.FilesystemTypeBasic, locations.Get(locations.DefFolder)))
 	l.Infoln("Default folder created and/or linked to new config")
-	return config.Wrap(path, newCfg), nil
+	return config.Wrap(path, newCfg, evLogger), nil
 }
 
 // LoadConfigAtStartup loads an existing config. If it doesn't yet exist, it
 // creates a default one, without the default folder if noDefaultFolder is ture.
 // Otherwise it checks the version, and archives and upgrades the config if
 // necessary or returns an error, if the version isn't compatible.
-func LoadConfigAtStartup(path string, cert tls.Certificate, allowNewerConfig, noDefaultFolder bool) (config.Wrapper, error) {
+func LoadConfigAtStartup(path string, cert tls.Certificate, evLogger events.Logger, allowNewerConfig, noDefaultFolder bool) (config.Wrapper, error) {
 	myID := protocol.NewDeviceID(cert.Certificate[0])
-	cfg, err := config.Load(path, myID)
+	cfg, err := config.Load(path, myID, evLogger)
 	if fs.IsNotExist(err) {
-		cfg, err = DefaultConfig(path, myID, noDefaultFolder)
+		cfg, err = DefaultConfig(path, myID, evLogger, noDefaultFolder)
 		if err != nil {
 			return nil, errors.Wrap(err, "failed to generate default config")
 		}

+ 4 - 4
lib/syncthing/verboseservice.go

@@ -19,12 +19,12 @@ import (
 // verbose format to the console using INFO level.
 type verboseService struct {
 	suture.Service
-	sub *events.Subscription
+	sub events.Subscription
 }
 
-func newVerboseService() *verboseService {
+func newVerboseService(evLogger events.Logger) *verboseService {
 	s := &verboseService{
-		sub: events.Default.Subscribe(events.AllEvents),
+		sub: evLogger.Subscribe(events.AllEvents),
 	}
 	s.Service = util.AsService(s.serve)
 	return s
@@ -48,7 +48,7 @@ func (s *verboseService) serve(stop chan struct{}) {
 // Stop stops the verbose logging service.
 func (s *verboseService) Stop() {
 	s.Service.Stop()
-	events.Default.Unsubscribe(s.sub)
+	s.sub.Unsubscribe()
 
 }
 

+ 5 - 5
lib/watchaggregator/aggregator.go

@@ -125,19 +125,19 @@ func newAggregator(folderCfg config.FolderConfiguration, ctx context.Context) *a
 	return a
 }
 
-func Aggregate(in <-chan fs.Event, out chan<- []string, folderCfg config.FolderConfiguration, cfg config.Wrapper, ctx context.Context) {
+func Aggregate(in <-chan fs.Event, out chan<- []string, folderCfg config.FolderConfiguration, cfg config.Wrapper, evLogger events.Logger, ctx context.Context) {
 	a := newAggregator(folderCfg, ctx)
 
 	// Necessary for unit tests where the backend is mocked
-	go a.mainLoop(in, out, cfg)
+	go a.mainLoop(in, out, cfg, evLogger)
 }
 
-func (a *aggregator) mainLoop(in <-chan fs.Event, out chan<- []string, cfg config.Wrapper) {
+func (a *aggregator) mainLoop(in <-chan fs.Event, out chan<- []string, cfg config.Wrapper, evLogger events.Logger) {
 	a.notifyTimer = time.NewTimer(a.notifyDelay)
 	defer a.notifyTimer.Stop()
 
-	inProgressItemSubscription := events.Default.Subscribe(events.ItemStarted | events.ItemFinished)
-	defer events.Default.Unsubscribe(inProgressItemSubscription)
+	inProgressItemSubscription := evLogger.Subscribe(events.ItemStarted | events.ItemFinished)
+	defer inProgressItemSubscription.Unsubscribe()
 
 	cfg.Subscribe(a)
 	defer cfg.Unsubscribe(a)

+ 16 - 8
lib/watchaggregator/aggregator_test.go

@@ -47,7 +47,7 @@ var (
 	}
 	defaultCfg = config.Wrap("", config.Configuration{
 		Folders: []config.FolderConfiguration{defaultFolderCfg},
-	})
+	}, events.NoopLogger)
 )
 
 // Represents possibly multiple (different event types) expected paths from
@@ -151,14 +151,17 @@ func TestAggregate(t *testing.T) {
 
 // TestInProgress checks that ignoring files currently edited by Syncthing works
 func TestInProgress(t *testing.T) {
+	evLogger := events.NewLogger()
+	go evLogger.Serve()
+	defer evLogger.Stop()
 	testCase := func(c chan<- fs.Event) {
-		events.Default.Log(events.ItemStarted, map[string]string{
+		evLogger.Log(events.ItemStarted, map[string]string{
 			"item": "inprogress",
 		})
 		sleepMs(100)
 		c <- fs.Event{Name: "inprogress", Type: fs.NonRemove}
 		sleepMs(1000)
-		events.Default.Log(events.ItemFinished, map[string]interface{}{
+		evLogger.Log(events.ItemFinished, map[string]interface{}{
 			"item": "inprogress",
 		})
 		sleepMs(100)
@@ -170,7 +173,7 @@ func TestInProgress(t *testing.T) {
 		{[][]string{{"notinprogress"}}, 2000, 3500},
 	}
 
-	testScenario(t, "InProgress", testCase, expectedBatches)
+	testScenario(t, "InProgress", testCase, expectedBatches, evLogger)
 }
 
 // TestDelay checks that recurring changes to the same path are delayed
@@ -208,7 +211,7 @@ func TestDelay(t *testing.T) {
 		{[][]string{{delayed}, {delAfter}}, 3600, 7000},
 	}
 
-	testScenario(t, "Delay", testCase, expectedBatches)
+	testScenario(t, "Delay", testCase, expectedBatches, nil)
 }
 
 // TestNoDelay checks that no delay occurs if there are no non-remove events
@@ -225,7 +228,7 @@ func TestNoDelay(t *testing.T) {
 		{[][]string{{mixed}, {del}}, 500, 2000},
 	}
 
-	testScenario(t, "NoDelay", testCase, expectedBatches)
+	testScenario(t, "NoDelay", testCase, expectedBatches, nil)
 }
 
 func getEventPaths(dir *eventDir, dirPath string, a *aggregator) []string {
@@ -277,8 +280,13 @@ func compareBatchToExpectedDirect(t *testing.T, batch []string, expectedPaths []
 	}
 }
 
-func testScenario(t *testing.T, name string, testCase func(c chan<- fs.Event), expectedBatches []expectedBatch) {
+func testScenario(t *testing.T, name string, testCase func(c chan<- fs.Event), expectedBatches []expectedBatch, evLogger events.Logger) {
 	t.Helper()
+
+	if evLogger == nil {
+		evLogger = events.NoopLogger
+	}
+
 	ctx, cancel := context.WithCancel(context.Background())
 	eventChan := make(chan fs.Event)
 	watchChan := make(chan []string)
@@ -289,7 +297,7 @@ func testScenario(t *testing.T, name string, testCase func(c chan<- fs.Event), e
 	a.notifyTimeout = testNotifyTimeout
 
 	startTime := time.Now()
-	go a.mainLoop(eventChan, watchChan, defaultCfg)
+	go a.mainLoop(eventChan, watchChan, defaultCfg, evLogger)
 
 	sleepMs(20)