shards: log a summary of shards in toLoad

We don't log every shard we load due to logspam on startup. However,
while zoekt-webserver is running it is useful to see more detailed
information since we are normally only loading a small number of shards
at a time.

This commit strikes a balance by only logging 5 of the shard names in
the list of shards to load.

Additionally for the unloading log line we only mention the base of the
path since it should always be in the indexdir.

Change-Id: I444a594cf5ea3359772b5f42651ee860b2a0fc1f
diff --git a/shards/watcher.go b/shards/watcher.go
index 572f8cd..62f2067 100644
--- a/shards/watcher.go
+++ b/shards/watcher.go
@@ -20,6 +20,8 @@
 	"os"
 	"path/filepath"
 	"runtime"
+	"sort"
+	"strings"
 	"sync"
 	"time"
 
@@ -112,10 +114,10 @@
 	}
 
 	if len(toDrop) > 0 {
-		log.Printf("unloading %d shards", len(toDrop))
+		log.Printf("unloading %d shard(s)", len(toDrop))
 	}
 	for _, t := range toDrop {
-		log.Printf("unloading: %s", t)
+		log.Printf("unloading: %s", filepath.Base(t))
 		s.loader.drop(t)
 	}
 
@@ -123,7 +125,7 @@
 		return nil
 	}
 
-	log.Printf("loading %d shards", len(toLoad))
+	log.Printf("loading %d shard(s): %s", len(toLoad), humanTruncateList(toLoad, 5))
 
 	// Limit amount of concurrent shard loads.
 	throttle := make(chan struct{}, runtime.GOMAXPROCS(0))
@@ -148,6 +150,22 @@
 	return nil
 }
 
+func humanTruncateList(paths []string, max int) string {
+	sort.Strings(paths)
+	var b strings.Builder
+	for i, p := range paths {
+		if i >= max {
+			fmt.Fprintf(&b, "... %d more", len(paths)-i)
+			break
+		}
+		if i > 0 {
+			b.WriteString(", ")
+		}
+		b.WriteString(filepath.Base(p))
+	}
+	return b.String()
+}
+
 func (s *DirectoryWatcher) watch() error {
 	watcher, err := fsnotify.NewWatcher()
 	if err != nil {
diff --git a/shards/watcher_test.go b/shards/watcher_test.go
index 1b16617..5aad02f 100644
--- a/shards/watcher_test.go
+++ b/shards/watcher_test.go
@@ -105,3 +105,25 @@
 	default:
 	}
 }
+
+func TestHumanTruncateList(t *testing.T) {
+	paths := []string{
+		"dir/1",
+		"dir/2",
+		"dir/3",
+		"dir/4",
+	}
+
+	assert := func(max int, want string) {
+		got := humanTruncateList(paths, max)
+		if got != want {
+			t.Errorf("unexpected humanTruncateList max=%d.\ngot:  %s\nwant: %s", max, got, want)
+		}
+	}
+
+	assert(1, "1... 3 more")
+	assert(2, "1, 2... 2 more")
+	assert(3, "1, 2, 3... 1 more")
+	assert(4, "1, 2, 3, 4")
+	assert(5, "1, 2, 3, 4")
+}