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 }