github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/cmd/util/ledger/reporters/reporter_output.go (about)

     1  package reporters
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"path"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/rs/zerolog"
    13  )
    14  
    15  // TODO janezp: we should be able to switch the report writer to write to a database.
    16  type ReportWriterFactory interface {
    17  	ReportWriter(dataNamespace string) ReportWriter
    18  }
    19  
    20  type ReportFileWriterFactory struct {
    21  	fileSuffix int32
    22  	outputDir  string
    23  	log        zerolog.Logger
    24  }
    25  
    26  func NewReportFileWriterFactory(outputDir string, log zerolog.Logger) *ReportFileWriterFactory {
    27  	return &ReportFileWriterFactory{
    28  		fileSuffix: int32(time.Now().Unix()),
    29  		outputDir:  outputDir,
    30  		log:        log,
    31  	}
    32  }
    33  
    34  func (r *ReportFileWriterFactory) Filename(dataNamespace string) string {
    35  	return path.Join(r.outputDir, fmt.Sprintf("%s_%d.json", dataNamespace, r.fileSuffix))
    36  }
    37  
    38  func (r *ReportFileWriterFactory) ReportWriter(dataNamespace string) ReportWriter {
    39  	fn := r.Filename(dataNamespace)
    40  
    41  	return NewReportFileWriter(fn, r.log)
    42  }
    43  
    44  var _ ReportWriterFactory = &ReportFileWriterFactory{}
    45  
    46  // ReportWriter writes data from reports
    47  type ReportWriter interface {
    48  	Write(dataPoint interface{})
    49  	Close()
    50  }
    51  
    52  // ReportNilWriter does nothing. Can be used as the final fallback writer
    53  type ReportNilWriter struct {
    54  }
    55  
    56  var _ ReportWriter = &ReportNilWriter{}
    57  
    58  func (r ReportNilWriter) Write(_ interface{}) {
    59  }
    60  
    61  func (r ReportNilWriter) Close() {
    62  }
    63  
    64  var _ ReportWriter = &ReportFileWriter{}
    65  
    66  type ReportFileWriter struct {
    67  	f          *os.File
    68  	fileName   string
    69  	wg         *sync.WaitGroup
    70  	writeChan  chan interface{}
    71  	writer     *bufio.Writer
    72  	log        zerolog.Logger
    73  	faulty     bool
    74  	firstWrite bool
    75  }
    76  
    77  const reportFileWriteBufferSize = 100
    78  
    79  func NewReportFileWriter(fileName string, log zerolog.Logger) ReportWriter {
    80  	f, err := os.Create(fileName)
    81  	if err != nil {
    82  		log.Warn().Err(err).Msg("Error creating ReportFileWriter, defaulting to ReportNilWriter")
    83  		return ReportNilWriter{}
    84  	}
    85  
    86  	writer := bufio.NewWriter(f)
    87  
    88  	// open a json array
    89  	_, err = writer.WriteRune('[')
    90  
    91  	if err != nil {
    92  		log.Warn().Err(err).Msg("Error opening json array")
    93  		// time to clean up
    94  		err = writer.Flush()
    95  		if err != nil {
    96  			log.Error().Err(err).Msg("Error closing flushing writer")
    97  			panic(err)
    98  		}
    99  
   100  		err = f.Close()
   101  		if err != nil {
   102  			log.Error().Err(err).Msg("Error closing report file")
   103  			panic(err)
   104  		}
   105  		return ReportNilWriter{}
   106  	}
   107  
   108  	fw := &ReportFileWriter{
   109  		f:          f,
   110  		fileName:   fileName,
   111  		writer:     writer,
   112  		log:        log,
   113  		firstWrite: true,
   114  		writeChan:  make(chan interface{}, reportFileWriteBufferSize),
   115  		wg:         &sync.WaitGroup{},
   116  	}
   117  
   118  	fw.wg.Add(1)
   119  	go func() {
   120  
   121  		for d := range fw.writeChan {
   122  			fw.write(d)
   123  		}
   124  		fw.wg.Done()
   125  	}()
   126  
   127  	return fw
   128  }
   129  
   130  func (r *ReportFileWriter) Write(dataPoint interface{}) {
   131  	r.writeChan <- dataPoint
   132  }
   133  
   134  func (r *ReportFileWriter) write(dataPoint interface{}) {
   135  	if r.faulty {
   136  		return
   137  	}
   138  	tc, err := json.Marshal(dataPoint)
   139  	if err != nil {
   140  		r.log.Warn().Err(err).Msg("Error converting data point to json")
   141  		r.faulty = true
   142  	}
   143  
   144  	// delimit the json records with commas
   145  	if !r.firstWrite {
   146  		_, err = r.writer.WriteRune(',')
   147  		if err != nil {
   148  			r.log.Warn().Err(err).Msg("Error Writing json to file")
   149  			r.faulty = true
   150  		}
   151  	} else {
   152  		r.firstWrite = false
   153  	}
   154  
   155  	_, err = r.writer.Write(tc)
   156  	if err != nil {
   157  		r.log.Warn().Err(err).Msg("Error Writing json to file")
   158  		r.faulty = true
   159  	}
   160  }
   161  
   162  func (r *ReportFileWriter) Close() {
   163  	close(r.writeChan)
   164  	r.wg.Wait()
   165  
   166  	_, err := r.writer.WriteRune(']')
   167  	if err != nil {
   168  		r.log.Warn().Err(err).Msg("Error finishing json array")
   169  		// nothing to do, we will be closing the file now
   170  	}
   171  	err = r.writer.Flush()
   172  	if err != nil {
   173  		r.log.Error().Err(err).Msg("Error closing flushing writer")
   174  		panic(err)
   175  	}
   176  
   177  	err = r.f.Close()
   178  	if err != nil {
   179  		r.log.Error().Err(err).Msg("Error closing report file")
   180  		panic(err)
   181  	}
   182  
   183  	r.log.Info().Str("filename", r.fileName).Msg("Created report file")
   184  }