golang.org/x/build@v0.0.0-20240506185731-218518f32b70/maintner/logger.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package maintner 6 7 import ( 8 "context" 9 "fmt" 10 "os" 11 "path/filepath" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/golang/protobuf/proto" 17 "golang.org/x/build/maintner/maintpb" 18 "golang.org/x/build/maintner/reclog" 19 ) 20 21 // A MutationLogger logs mutations. 22 type MutationLogger interface { 23 Log(*maintpb.Mutation) error 24 } 25 26 // DiskMutationLogger logs mutations to disk. 27 type DiskMutationLogger struct { 28 directory string 29 30 mu sync.Mutex 31 done bool // true after first GetMutations 32 } 33 34 // NewDiskMutationLogger creates a new DiskMutationLogger, which will create 35 // mutations in the given directory. 36 func NewDiskMutationLogger(directory string) *DiskMutationLogger { 37 if directory == "" { 38 panic("empty directory") 39 } 40 return &DiskMutationLogger{directory: directory} 41 } 42 43 // filename returns the filename to write to. The oldest filename must come 44 // first in lexical order. 45 func (d *DiskMutationLogger) filename() string { 46 now := time.Now().UTC() 47 return filepath.Join(d.directory, fmt.Sprintf("maintner-%s.mutlog", now.Format("2006-01-02"))) 48 } 49 50 // Log will write m to disk. If a mutation file does not exist for the current 51 // day, it will be created. 52 func (d *DiskMutationLogger) Log(m *maintpb.Mutation) error { 53 data, err := proto.Marshal(m) 54 if err != nil { 55 return err 56 } 57 d.mu.Lock() 58 defer d.mu.Unlock() 59 return reclog.AppendRecordToFile(d.filename(), data) 60 } 61 62 func (d *DiskMutationLogger) ForeachFile(fn func(fullPath string, fi os.FileInfo) error) error { 63 d.mu.Lock() 64 defer d.mu.Unlock() 65 if d.directory == "" { 66 panic("empty directory") 67 } 68 // Walk guarantees that files are walked in lexical order, which we depend on. 69 return filepath.Walk(d.directory, func(path string, fi os.FileInfo, err error) error { 70 if err != nil { 71 return err 72 } 73 if fi.IsDir() && path != filepath.Clean(d.directory) { 74 return filepath.SkipDir 75 } 76 if !strings.HasPrefix(fi.Name(), "maintner-") { 77 return nil 78 } 79 if !strings.HasSuffix(fi.Name(), ".mutlog") { 80 return nil 81 } 82 return fn(path, fi) 83 }) 84 } 85 86 func (d *DiskMutationLogger) GetMutations(ctx context.Context) <-chan MutationStreamEvent { 87 d.mu.Lock() 88 wasDone := d.done 89 d.done = true 90 d.mu.Unlock() 91 92 if wasDone { 93 // TODO: support subsequent Update? for now we only 94 // support the initial loading. The network mutation 95 // source is the new implementation with Update 96 // support. 97 return nil 98 } 99 100 ch := make(chan MutationStreamEvent, 50) // buffered: overlap gunzip/unmarshal with loading 101 102 go func() { 103 err := d.ForeachFile(func(fullPath string, fi os.FileInfo) error { 104 return reclog.ForeachFileRecord(fullPath, func(off int64, hdr, rec []byte) error { 105 m := new(maintpb.Mutation) 106 if err := proto.Unmarshal(rec, m); err != nil { 107 return err 108 } 109 select { 110 case ch <- MutationStreamEvent{Mutation: m}: 111 return nil 112 case <-ctx.Done(): 113 return ctx.Err() 114 } 115 }) 116 }) 117 final := MutationStreamEvent{Err: err} 118 if err == nil { 119 final.End = true 120 } 121 select { 122 case ch <- final: 123 case <-ctx.Done(): 124 } 125 }() 126 return ch 127 }