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  }