tlog.app/go/tlog@v0.23.1/cmd/tlog/tlogcmd/main.go (about) 1 package tlogcmd 2 3 import ( 4 "context" 5 "io" 6 "io/fs" 7 "net" 8 "net/http" 9 _ "net/http/pprof" 10 "os" 11 "os/signal" 12 "path/filepath" 13 "strings" 14 "sync" 15 "time" 16 17 "github.com/fsnotify/fsnotify" 18 "github.com/nikandfor/hacked/hnet" 19 "nikand.dev/go/cli" 20 "nikand.dev/go/cli/flag" 21 "nikand.dev/go/graceful" 22 "tlog.app/go/eazy" 23 "tlog.app/go/errors" 24 25 "tlog.app/go/tlog" 26 "tlog.app/go/tlog/agent" 27 "tlog.app/go/tlog/ext/tlclick" 28 "tlog.app/go/tlog/ext/tlflag" 29 "tlog.app/go/tlog/tlio" 30 "tlog.app/go/tlog/tlwire" 31 "tlog.app/go/tlog/web" 32 ) 33 34 type ( 35 filereader struct { 36 n string 37 f *os.File 38 } 39 40 perrWriter struct { 41 io.WriteCloser 42 } 43 44 listenerClose struct { 45 net.Listener 46 def []io.Closer 47 } 48 ) 49 50 func App() *cli.Command { 51 catCmd := &cli.Command{ 52 Name: "convert,cat,c", 53 Description: "read tlog encoded logs", 54 Action: cat, 55 Args: cli.Args{}, 56 Flags: []*cli.Flag{ 57 cli.NewFlag("output,out,o", "-?dm", "output file (empty is stderr, - is stdout)"), 58 cli.NewFlag("follow,f", false, "wait for changes until terminated"), 59 cli.NewFlag("head", 0, "skip all except first n events"), 60 cli.NewFlag("tail", 0, "skip all except last n events"), 61 // cli.NewFlag("filter", "", "span filter"), 62 // cli.NewFlag("filter-depth", 0, "span filter max depth"), 63 }, 64 } 65 66 tlzCmd := &cli.Command{ 67 Name: "tlz,eazy", 68 Description: "compressor/decompressor", 69 Flags: []*cli.Flag{ 70 cli.NewFlag("output,o", "-", "output file (or stdout)"), 71 }, 72 Commands: []*cli.Command{{ 73 Name: "compress,c", 74 Action: tlzRun, 75 Args: cli.Args{}, 76 Flags: []*cli.Flag{ 77 cli.NewFlag("block-size,block,bs,b", 1*eazy.MiB, "compression block size (window)"), 78 cli.NewFlag("hash-table,ht", 1*1024, "hash table size"), 79 }, 80 }, { 81 Name: "decompress,d", 82 Action: tlzRun, 83 Args: cli.Args{}, 84 }, { 85 Name: "dump", 86 Action: tlzRun, 87 Args: cli.Args{}, 88 Flags: []*cli.Flag{ 89 cli.NewFlag("base", -1, "global offset"), 90 }, 91 }}, 92 } 93 94 agentCmd := &cli.Command{ 95 Name: "agent,run", 96 Description: "run agent", 97 Before: beforeAgent, 98 Action: agentRun, 99 Flags: []*cli.Flag{ 100 cli.NewFlag("db", "", "path to logs db"), 101 cli.NewFlag("db-partition", 3*time.Hour, "db partition size"), 102 cli.NewFlag("db-file-size", int64(eazy.GiB), "db file size"), 103 cli.NewFlag("db-block-size", int64(16*eazy.MiB), "db file block size"), 104 105 cli.NewFlag("clickdb", "", "clickhouse dsn"), 106 107 cli.NewFlag("listen,l", []string(nil), "listen url"), 108 109 cli.NewFlag("http", ":8000", "http listen address"), 110 cli.NewFlag("http-net", "tcp", "http listen network"), 111 cli.NewFlag("http-fs", "", "http templates fs"), 112 113 cli.NewFlag("labels", "service=tlog-agent", "service labels"), 114 }, 115 } 116 117 app := &cli.Command{ 118 Name: "tlog", 119 Description: "tlog cli", 120 Before: before, 121 Flags: []*cli.Flag{ 122 cli.NewFlag("log", "stderr?dm", "log output file (or stderr)"), 123 cli.NewFlag("verbosity,v", "", "logger verbosity topics"), 124 cli.NewFlag("debug", "", "debug address", flag.Hidden), 125 cli.FlagfileFlag, 126 cli.HelpFlag, 127 }, 128 Commands: []*cli.Command{ 129 agentCmd, 130 catCmd, 131 tlzCmd, 132 { 133 Name: "ticker", 134 Description: "simple test app that prints current time once in an interval", 135 Action: ticker, 136 Flags: []*cli.Flag{ 137 cli.NewFlag("output,o", "-", "output file (or stdout)"), 138 cli.NewFlag("interval,int,i", time.Second, "interval to tick on"), 139 cli.NewFlag("labels", "service=ticker,_pid", "labels"), 140 }, 141 }, 142 { 143 Name: "test", 144 Action: test, 145 Args: cli.Args{}, 146 Hidden: true, 147 }, 148 }, 149 } 150 151 return app 152 } 153 154 func SubApp() *cli.Command { 155 app := App() 156 157 app.Before = nil 158 app.After = nil 159 app.Flags = nil 160 161 return app 162 } 163 164 func before(c *cli.Command) error { 165 w, err := tlflag.OpenWriter(c.String("log")) 166 if err != nil { 167 return errors.Wrap(err, "open log file") 168 } 169 170 tlog.DefaultLogger = tlog.New(w) 171 172 tlog.SetVerbosity(c.String("verbosity")) 173 174 if q := c.String("debug"); q != "" { 175 l, err := net.Listen("tcp", q) 176 if err != nil { 177 return errors.Wrap(err, "listen debug") 178 } 179 180 tlog.Printw("start debug interface", "addr", l.Addr()) 181 182 go func() { 183 err := http.Serve(l, nil) 184 if err != nil { 185 tlog.Printw("debug", "addr", q, "err", err, "", tlog.Fatal) 186 panic(err) 187 } 188 }() 189 } 190 191 if tlog.If("dump_file_reader") { 192 tlflag.OpenFileReader = tlflag.OSOpenFile 193 tlflag.OpenFileReader = tlflag.OpenFileDumpReader(tlflag.OpenFileReader) 194 tlflag.OpenFileReader = tlflag.OpenFileReReader(tlflag.OpenFileReader) 195 } 196 197 return nil 198 } 199 200 func beforeAgent(c *cli.Command) error { 201 if f := c.Flag("labels"); f != nil { 202 if ls, ok := f.Value.(string); ok { 203 tlog.SetLabels(tlog.ParseLabels(ls)...) 204 } 205 } 206 207 return nil 208 } 209 210 func agentRun(c *cli.Command) (err error) { 211 ctx := context.Background() 212 ctx = tlog.ContextWithSpan(ctx, tlog.Root()) 213 214 if (c.String("db") != "") == (c.String("clickdb") != "") { 215 return errors.New("exactly one of db and clickdb must be set") 216 } 217 218 var a interface { 219 io.Writer 220 web.Agent 221 } 222 223 if q := c.String("db"); q != "" { 224 x, err := agent.New(q) 225 if err != nil { 226 return errors.Wrap(err, "new agent") 227 } 228 229 x.Partition = c.Duration("db-partition") 230 x.FileSize = c.Int64("db-file-size") 231 x.BlockSize = c.Int64("db-block-size") 232 233 a = x 234 } else if q := c.String("clickdb"); q != "" { 235 opts := tlclick.DefaultPoolOptions(q) 236 237 pool, err := tlclick.NewPool(ctx, opts) 238 if err != nil { 239 return errors.Wrap(err, "new click pool") 240 } 241 242 ch := tlclick.New(pool) 243 244 err = ch.CreateTables(ctx) 245 if err != nil { 246 return errors.Wrap(err, "create click tables") 247 } 248 249 a = ch 250 } 251 252 group := graceful.New() 253 254 if q := c.String("http"); q != "" { 255 l, err := net.Listen(c.String("http-net"), q) 256 if err != nil { 257 return errors.Wrap(err, "listen http") 258 } 259 260 s, err := web.New(a) 261 if err != nil { 262 return errors.Wrap(err, "new web server") 263 } 264 265 if q := c.String("http-fs"); q != "" { 266 s.FS = http.Dir(q) 267 } 268 269 group.Add(func(ctx context.Context) (err error) { 270 tr := tlog.SpawnFromContext(ctx, "web_server", "addr", l.Addr()) 271 defer tr.Finish("err", &err) 272 273 ctx = tlog.ContextWithSpan(ctx, tr) 274 275 err = s.Serve(ctx, l, func(ctx context.Context, c net.Conn) (err error) { 276 tr, ctx := tlog.SpawnFromContextAndWrap(ctx, "web_request", "remote_addr", c.RemoteAddr(), "local_addr", c.LocalAddr()) 277 defer tr.Finish("err", &err) 278 279 return s.HandleConn(ctx, c) 280 }) 281 if errors.Is(err, context.Canceled) { 282 err = nil 283 } 284 285 return errors.Wrap(err, "serve http") 286 }) 287 } 288 289 for _, lurl := range c.Flag("listen").Value.([]string) { 290 u, err := tlflag.ParseURL(lurl) 291 if err != nil { 292 return errors.Wrap(err, "parse %v", lurl) 293 } 294 295 tlog.Printw("listen", "scheme", u.Scheme, "host", u.Host, "path", u.Path, "query", u.RawQuery) 296 297 host := u.Host 298 if u.Scheme == "unix" || u.Scheme == "unixgram" { 299 host = u.Path 300 } 301 302 l, p, err := listen(u.Scheme, host) 303 if err != nil { 304 return errors.Wrap(err, "listen %v", host) 305 } 306 307 switch { 308 case u.Scheme == "unix", u.Scheme == "tcp": 309 group.Add(func(ctx context.Context) error { 310 var wg sync.WaitGroup 311 312 defer wg.Wait() 313 314 for { 315 c, err := hnet.Accept(ctx, l) 316 if errors.Is(err, context.Canceled) { 317 return nil 318 } 319 if err != nil { 320 return errors.Wrap(err, "accept") 321 } 322 323 wg.Add(1) 324 325 tr := tlog.SpawnFromContext(ctx, "agent_writer", "local_addr", c.LocalAddr(), "remote_addr", c.RemoteAddr()) 326 327 go func() { 328 defer wg.Done() 329 330 var err error 331 332 defer tr.Finish("err", &err) 333 334 defer closeWrap(c, &err, "close conn") 335 336 if f, ok := a.(tlio.Flusher); ok { 337 defer doWrap(f.Flush, &err, "flush db") 338 } 339 340 rr := tlwire.NewStreamDecoder(c) 341 342 _, err = rr.WriteTo(a) 343 }() 344 } 345 }, graceful.WithStop(func(ctx context.Context) error { 346 return l.Close() 347 })) 348 case u.Scheme == "unixgram", u.Scheme == "udp": 349 group.Add(func(ctx context.Context) error { 350 buf := make([]byte, 0x1000) 351 352 for { 353 n, _, err := hnet.ReadFrom(ctx, p, buf) 354 if err != nil { 355 return errors.Wrap(err, "read") 356 } 357 358 _, _ = a.Write(buf[:n]) 359 } 360 }) 361 default: 362 return errors.New("unsupported listener: %v", u.Scheme) 363 } 364 } 365 366 return group.Run(ctx, graceful.IgnoreErrors(context.Canceled)) 367 } 368 369 func cat(c *cli.Command) (err error) { 370 w, err := tlflag.OpenWriter(c.String("out")) 371 if err != nil { 372 return err 373 } 374 375 defer func() { 376 e := w.Close() 377 if err == nil { 378 err = e 379 } 380 }() 381 382 if tlog.If("describe,describe_writer") { 383 tlflag.Describe(tlog.Root(), w) 384 } 385 386 var fs *fsnotify.Watcher //nolint:gocritic 387 388 if c.Bool("follow") { 389 fs, err = fsnotify.NewWatcher() 390 if err != nil { 391 return errors.Wrap(err, "create fs watcher") 392 } 393 394 defer func() { 395 e := fs.Close() 396 if err == nil { 397 err = errors.Wrap(e, "close watcher") 398 } 399 }() 400 } 401 402 rs := make(map[string]io.WriterTo, c.Args.Len()) 403 defer func() { 404 for name, r := range rs { 405 tlio.CloseWrap(r, name, &err) 406 } 407 }() 408 409 var addFile func(a string) error 410 addFile = func(a string) (err error) { 411 a = filepath.Clean(a) 412 413 inf, err := os.Stat(a) 414 if err != nil && !errors.Is(err, os.ErrNotExist) { 415 return errors.Wrap(err, "stat %v", a) 416 } 417 418 if err == nil { 419 if fs != nil { 420 err = fs.Add(a) 421 tlog.V("watch").Printw("watch file", "name", a, "err", err) 422 if err != nil { 423 return errors.Wrap(err, "watch") 424 } 425 } 426 427 if inf.IsDir() { 428 files, err := os.ReadDir(a) 429 if err != nil { 430 return errors.Wrap(err, "readdir %v", a) 431 } 432 433 for _, f := range files { 434 if strings.HasPrefix(f.Name(), ".") { 435 continue 436 } 437 if !f.Type().IsRegular() { 438 continue 439 } 440 441 err = addFile(filepath.Join(a, f.Name())) 442 if err != nil { 443 return err 444 } 445 } 446 447 return nil 448 } 449 } 450 451 rc, err := tlflag.OpenReader(a) 452 if err != nil { 453 return errors.Wrap(err, "open reader") 454 } 455 456 if tlog.If("describe,describe_reader") { 457 tlflag.Describe(tlog.Root(), rc) 458 } 459 460 rs[a] = tlwire.NewStreamDecoder(rc) 461 462 var w0 io.Writer = w 463 464 if f := c.Flag("tail"); f.IsSet { 465 w0 = tlio.NewTailWriter(w0, f.Value.(int)) 466 } 467 468 if f := c.Flag("head"); f.IsSet { 469 fl, _ := w0.(tlio.Flusher) 470 471 w0 = tlio.NewHeadWriter(w0, f.Value.(int)) 472 473 if _, ok := w0.(tlio.Flusher); !ok && fl != nil { 474 w0 = tlio.WriteFlusher{ 475 Writer: w0, 476 Flusher: fl, 477 } 478 } 479 } 480 481 _, err = rs[a].WriteTo(w0) 482 if errors.Is(err, io.EOF) { 483 err = nil 484 } 485 486 if f, ok := w0.(tlio.Flusher); ok { 487 e := f.Flush() 488 if err == nil { 489 err = errors.Wrap(e, "flush: %v", a) 490 } 491 } 492 493 if err != nil { 494 return errors.Wrap(err, "copy: %v", a) 495 } 496 497 return nil 498 } 499 500 for _, a := range c.Args { 501 err = addFile(a) 502 if err != nil { 503 return err 504 } 505 } 506 507 if !c.Bool("follow") { 508 return nil 509 } 510 511 sigc := make(chan os.Signal, 3) 512 signal.Notify(sigc, os.Interrupt) 513 514 var ev fsnotify.Event 515 for { 516 select { 517 case ev = <-fs.Events: 518 case <-sigc: 519 return nil 520 case err = <-fs.Errors: 521 return errors.Wrap(err, "watch") 522 } 523 524 tlog.V("fsevent").Printw("fs event", "name", ev.Name, "op", ev.Op) 525 526 switch { 527 case ev.Op&fsnotify.Create != 0: 528 err = addFile(ev.Name) 529 if err != nil { 530 return errors.Wrap(err, "add created") 531 } 532 case ev.Op&fsnotify.Remove != 0: 533 // err = fs.Remove(ev.Name) 534 // if err != nil { 535 // return errors.Wrap(err, "remove watch") 536 // } 537 case ev.Op&fsnotify.Write != 0: 538 r, ok := rs[ev.Name] 539 if !ok { 540 tlog.V("unexpected_event").Printw("unexpected event", "file", ev.Name, "map", rs) 541 break 542 // return errors.New("unexpected event: %v (%v)", ev.Name, rs) 543 } 544 545 _, err = r.WriteTo(w) 546 switch { 547 case errors.Is(err, io.EOF): 548 case errors.Is(err, io.ErrUnexpectedEOF): 549 tlog.V("unexpected_eof").Printw("unexpected EOF", "file", ev.Name) 550 case err != nil: 551 return errors.Wrap(err, "copy: %v", ev.Name) 552 } 553 } 554 } 555 } 556 557 func tlzRun(c *cli.Command) (err error) { 558 var rs []io.Reader 559 for _, a := range c.Args { 560 if a == "-" { 561 rs = append(rs, os.Stdin) 562 } else { 563 rs = append(rs, &filereader{n: a}) 564 } 565 } 566 567 if len(rs) == 0 { 568 rs = append(rs, os.Stdin) 569 } 570 571 var w io.Writer 572 if q := c.String("output"); q == "" || q == "-" { 573 w = os.Stdout 574 } else { 575 f, err := os.Create(q) 576 if err != nil { 577 return errors.Wrap(err, "open output") 578 } 579 defer func() { 580 e := f.Close() 581 if err == nil { 582 err = e 583 } 584 }() 585 586 w = f 587 } 588 589 switch c.MainName() { 590 case "compress": 591 e := eazy.NewWriter(w, c.Int("block"), c.Int("hash-table")) 592 593 for _, r := range rs { 594 _, err = io.Copy(e, r) 595 if err != nil { 596 return errors.Wrap(err, "copy") 597 } 598 } 599 case "decompress": 600 d := eazy.NewReader(io.MultiReader(rs...)) 601 602 _, err = io.Copy(w, d) 603 if err != nil { 604 return errors.Wrap(err, "copy") 605 } 606 case "dump": 607 d := eazy.NewDumper(w) // BUG: dumper does not work with writes not aligned to tags 608 609 d.GlobalOffset = int64(c.Int("base")) 610 611 data, err := io.ReadAll(io.MultiReader(rs...)) 612 if err != nil { 613 return errors.Wrap(err, "read all") 614 } 615 616 _, err = d.Write(data) 617 if err != nil { 618 return errors.Wrap(err, "dumper") 619 } 620 621 err = d.Close() 622 if err != nil { 623 return errors.Wrap(err, "close dumper") 624 } 625 default: 626 return errors.New("unexpected command: %v", c.MainName()) 627 } 628 629 return nil 630 } 631 632 func ticker(c *cli.Command) error { 633 w, err := tlflag.OpenWriter(c.String("output")) 634 if err != nil { 635 return errors.Wrap(err, "open output") 636 } 637 638 if tlog.If("describe,describe_writer") { 639 tlflag.Describe(tlog.Root(), w) 640 } 641 642 w = perrWriter{ 643 WriteCloser: w, 644 } 645 646 l := tlog.New(w) 647 648 t := time.NewTicker(c.Duration("interval")) 649 defer t.Stop() 650 651 ls := tlog.ParseLabels(c.String("labels")) 652 653 l.SetLabels(ls...) 654 655 var first time.Time 656 dur := c.Duration("interval") 657 drift := 0. 658 i := 0 659 660 const alpha = 0.0001 661 662 for t := range t.C { 663 if i == 0 { 664 first = t 665 } 666 667 diff := t.Sub(first) - time.Duration(i)*dur 668 drift := drift*(1-alpha) + float64(diff)*alpha 669 670 l.Printw("tick", "i", i, "time", t, "diff", diff, "drift", time.Duration(drift)) 671 672 i++ 673 } 674 675 return nil 676 } 677 678 func test(c *cli.Command) error { 679 return nil 680 } 681 682 func (f *filereader) Read(p []byte) (n int, err error) { 683 if f.f == nil { 684 f.f, err = os.Open(f.n) 685 if err != nil { 686 return 0, errors.Wrap(err, "") 687 // return 0, errors.Wrap(err, "open %v", f.n) 688 } 689 } 690 691 n, err = f.f.Read(p) 692 693 if err != nil { 694 _ = f.f.Close() 695 } 696 697 return 698 } 699 700 func (w perrWriter) Write(p []byte) (n int, err error) { 701 n, err = w.WriteCloser.Write(p) 702 703 if err != nil { 704 tlog.Printw("write", "err", err) 705 } 706 707 return 708 } 709 710 func (w perrWriter) Close() (err error) { 711 err = w.WriteCloser.Close() 712 713 if err != nil { 714 tlog.Printw("close", "err", err) 715 } 716 717 return 718 } 719 720 func isDir(name string) bool { 721 inf, err := os.Stat(name) 722 if err != nil { 723 return false 724 } 725 726 return inf.IsDir() 727 } 728 729 func isFifo(name string) bool { 730 inf, err := os.Stat(name) 731 if err != nil { 732 return false 733 } 734 735 mode := inf.Mode() 736 737 return mode&fs.ModeNamedPipe != 0 738 } 739 740 func listen(netw, addr string) (l net.Listener, p net.PacketConn, err error) { 741 switch netw { 742 case "unix", "unixgram": 743 _ = os.Remove(addr) 744 } 745 746 switch netw { 747 case "tcp", "unix": 748 l, err = net.Listen(netw, addr) 749 if err != nil { 750 return nil, nil, errors.Wrap(err, "listen") 751 } 752 753 switch l := l.(type) { 754 case *net.UnixListener: 755 l.SetUnlinkOnClose(true) 756 default: 757 return nil, nil, errors.New("unsupported listener type: %T", l) 758 } 759 case "udp", "unixgram": 760 p, err = net.ListenPacket(netw, addr) 761 if err != nil { 762 return nil, nil, errors.Wrap(err, "listen packet") 763 } 764 default: 765 return nil, nil, errors.New("unsupported network type: %v", netw) 766 } 767 768 return l, p, nil 769 } 770 771 func (p listenerClose) SetDeadline(t time.Time) error { 772 return p.Listener.(interface{ SetDeadline(time.Time) error }).SetDeadline(t) 773 } 774 775 func (p listenerClose) Close() (err error) { 776 err = p.Listener.Close() 777 778 for i := len(p.def) - 1; i >= 0; i-- { 779 e := p.def[i].Close() 780 if err == nil { 781 err = e 782 } 783 } 784 785 return 786 } 787 788 func closeWrap(c io.Closer, errp *error, msg string) { 789 doWrap(c.Close, errp, msg) 790 } 791 792 func flushWrap(x interface{}, errp *error, msg string) { 793 if f, ok := x.(tlio.Flusher); ok { 794 doWrap(f.Flush, errp, msg) 795 } 796 } 797 798 func doWrap(f func() error, errp *error, msg string) { 799 e := f() 800 if *errp == nil { 801 *errp = errors.Wrap(e, msg) 802 } 803 } 804 805 func closeIfErr(c io.Closer, errp *error) { 806 if *errp == nil { 807 return 808 } 809 810 _ = c.Close() 811 }