github.com/nikandfor/tlog@v0.21.5-0.20231108111739-3ef89426a96d/agent/agent.go (about) 1 package agent 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "fmt" 7 "hash/crc32" 8 "io" 9 "io/fs" 10 "os" 11 "path/filepath" 12 "runtime/debug" 13 "strings" 14 "sync" 15 "time" 16 17 "github.com/nikandfor/errors" 18 19 "github.com/nikandfor/tlog" 20 "github.com/nikandfor/tlog/tlio" 21 "github.com/nikandfor/tlog/tlwire" 22 "github.com/nikandfor/tlog/tlz" 23 ) 24 25 type ( 26 Agent struct { //nolint:maligned 27 path string 28 29 sync.Mutex 30 31 files []*file 32 subs map[int64]*sub 33 subid int64 34 35 // end of Mutex 36 37 Partition time.Duration 38 MaxFileSize int64 39 40 KeyTimestamp string 41 42 Stderr io.Writer 43 44 d tlwire.Decoder 45 } 46 47 file struct { 48 sum uint32 49 labels []byte 50 start time.Time 51 52 sync.Mutex 53 54 io.Writer 55 56 // end of Mutex 57 58 a *Agent 59 } 60 61 sub struct { 62 id int64 63 io.Writer 64 } 65 ) 66 67 func New(db string) (*Agent, error) { 68 a := &Agent{ 69 path: db, 70 71 subs: make(map[int64]*sub), 72 73 Partition: 3 * time.Hour, 74 KeyTimestamp: tlog.KeyTimestamp, 75 76 Stderr: os.Stderr, 77 } 78 79 err := a.openFiles() 80 if err != nil { 81 return a, errors.Wrap(err, "open files") 82 } 83 84 return a, nil 85 } 86 87 func (a *Agent) openFiles() (err error) { 88 err = filepath.WalkDir(a.path, func(path string, d fs.DirEntry, err error) error { 89 if path == a.path { 90 return nil 91 } 92 if d.IsDir() { 93 return fs.SkipDir 94 } 95 96 base := filepath.Base(path) 97 ext := filepath.Ext(path) 98 99 if ext != ".tlz" || !strings.HasPrefix(base, "events_") { 100 return nil 101 } 102 103 ff, err := os.Open(path) 104 if err != nil { 105 return errors.Wrap(err, "open file") 106 } 107 108 defer func() { 109 e := ff.Close() 110 if err == nil { 111 err = errors.Wrap(e, "close file") 112 } 113 }() 114 115 dec := tlz.NewDecoder(ff) 116 sd := tlwire.NewStreamDecoder(dec) 117 118 msg, err := sd.Decode() 119 if err != nil { 120 return errors.Wrap(err, "decode message") 121 } 122 123 _, err = a.getFile(msg) 124 if err != nil { 125 return errors.Wrap(err, "get file") 126 } 127 128 return nil 129 }) 130 131 return nil 132 } 133 134 func (a *Agent) Write(p []byte) (n int, err error) { 135 defer func() { 136 perr := recover() 137 138 if err == nil && perr == nil { 139 return 140 } 141 142 if perr != nil { 143 fmt.Fprintf(a.Stderr, "panic: %v (pos %x)\n", perr, n) 144 } else { 145 fmt.Fprintf(a.Stderr, "parse error: %+v (pos %x)\n", err, n) 146 } 147 fmt.Fprintf(a.Stderr, "dump\n%v", tlwire.Dump(p)) 148 fmt.Fprintf(a.Stderr, "hex dump\n%v", hex.Dump(p)) 149 150 s := debug.Stack() 151 fmt.Fprintf(a.Stderr, "%s", s) 152 }() 153 154 f, err := a.getFile(p) 155 if err != nil { 156 return 0, err 157 } 158 159 n, err = f.Write(p) 160 161 func() { 162 defer a.Unlock() 163 a.Lock() 164 165 for _, sub := range a.subs { 166 _, _ = sub.Writer.Write(p) 167 } 168 }() 169 170 return 171 } 172 173 func (a *Agent) Close() (err error) { 174 a.Lock() 175 defer a.Unlock() 176 177 for _, f := range a.files { 178 e := f.Close() 179 if err == nil { 180 err = errors.Wrap(e, "file %v", f.sum) 181 } 182 } 183 184 return err 185 } 186 187 func (a *Agent) getFile(p []byte) (f *file, err error) { 188 if tlog.If("dump") { 189 defer func() { 190 var sum uint32 191 if f != nil { 192 sum = f.sum 193 } 194 195 tlog.Printw("message", "sum", tlog.NextAsHex, sum, "msg", tlog.RawMessage(p)) 196 }() 197 } 198 199 defer a.Unlock() 200 a.Lock() 201 202 ts, labels, err := a.parseEventHeader(p) 203 if err != nil { 204 return nil, errors.Wrap(err, "parse event header") 205 } 206 207 start := time.Unix(0, ts).UTC().Truncate(a.Partition) 208 209 f = a.getPart(labels, start) 210 211 return f, nil 212 } 213 214 func (a *Agent) getPart(labels []byte, start time.Time) *file { 215 sum := crc32.ChecksumIEEE(labels) 216 217 for _, f := range a.files { 218 if f.sum == sum && f.start.UnixNano() == start.UnixNano() && bytes.Equal(f.labels, labels) { 219 return f 220 } 221 } 222 223 f := &file{ 224 sum: sum, 225 labels: append([]byte{}, labels...), 226 start: start, 227 228 a: a, 229 } 230 231 a.files = append(a.files, f) 232 233 tlog.Printw("open file", "sum", tlog.NextAsHex, sum, "labels", tlog.RawTag(tlwire.Map, -1), tlog.RawMessage(labels), tlog.Break) 234 235 return f 236 } 237 238 func (a *Agent) parseEventHeader(p []byte) (ts int64, labels []byte, err error) { 239 tag, els, i := a.d.Tag(p, 0) 240 if tag != tlwire.Map { 241 err = errors.New("expected map") 242 return 243 } 244 245 var k []byte 246 var sub int64 247 var end int 248 249 for el := 0; els == -1 || el < int(els); el++ { 250 if els == -1 && a.d.Break(p, &i) { 251 break 252 } 253 254 st := i 255 256 k, i = a.d.Bytes(p, i) 257 if len(k) == 0 { 258 err = errors.New("empty key") 259 return 260 } 261 262 tag, sub, end = a.d.SkipTag(p, i) 263 if tag != tlwire.Semantic { 264 i = a.d.Skip(p, i) 265 continue 266 } 267 268 switch { 269 case sub == tlwire.Time && string(k) == a.KeyTimestamp: 270 ts, i = a.d.Timestamp(p, i) 271 case sub == tlog.WireLabel: 272 // labels = crc32.Update(labels, crc32.IEEETable, p[st:end]) 273 labels = append(labels, p[st:end]...) 274 } 275 276 i = end 277 } 278 279 return 280 } 281 282 func (f *file) Write(p []byte) (n int, err error) { 283 defer f.Unlock() 284 f.Lock() 285 286 if f.Writer == nil { 287 f.Writer, err = f.a.openWriter(f) 288 if err != nil { 289 return 0, errors.Wrap(err, "open writer") 290 } 291 } 292 293 n, err = f.Writer.Write(p) 294 295 return 296 } 297 298 func (f *file) Close() (err error) { 299 if c, ok := f.Writer.(io.Closer); ok { 300 e := c.Close() 301 if err == nil { 302 err = errors.Wrap(e, "close writer") 303 } 304 } 305 306 return err 307 } 308 309 func (a *Agent) openWriter(f *file) (io.Writer, error) { 310 name := filepath.Join(a.path, fmt.Sprintf("events_%08x_%s.tlz", f.sum, f.start.Format("2006-01-02T15-04"))) 311 dir := filepath.Dir(name) 312 313 err := os.MkdirAll(dir, 0o755) 314 if err != nil { 315 return nil, errors.Wrap(err, "mkdir") 316 } 317 318 ff, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) 319 if err != nil { 320 return nil, errors.Wrap(err, "open file") 321 } 322 323 w := tlz.NewEncoder(ff, tlz.MiB) 324 325 return tlio.WriteCloser{ 326 Writer: w, 327 Closer: ff, 328 }, nil 329 }