github.com/nikandfor/tlog@v0.21.5-0.20231108111739-3ef89426a96d/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 "syscall" 16 "time" 17 18 "github.com/ClickHouse/clickhouse-go/v2" 19 "github.com/fsnotify/fsnotify" 20 "github.com/nikandfor/cli" 21 "github.com/nikandfor/cli/flag" 22 "github.com/nikandfor/errors" 23 "github.com/nikandfor/graceful" 24 "github.com/nikandfor/hacked/hnet" 25 26 "github.com/nikandfor/tlog" 27 "github.com/nikandfor/tlog/agent" 28 "github.com/nikandfor/tlog/ext/tlclick" 29 "github.com/nikandfor/tlog/ext/tlflag" 30 "github.com/nikandfor/tlog/tlio" 31 "github.com/nikandfor/tlog/tlwire" 32 "github.com/nikandfor/tlog/tlz" 33 "github.com/nikandfor/tlog/web" 34 ) 35 36 type ( 37 filereader struct { 38 n string 39 f *os.File 40 } 41 42 perrWriter struct { 43 io.WriteCloser 44 } 45 46 listenerClose struct { 47 net.Listener 48 def []io.Closer 49 } 50 ) 51 52 func App() *cli.Command { 53 catCmd := &cli.Command{ 54 Name: "convert,cat,c", 55 Description: "read tlog encoded logs", 56 Action: cat, 57 Args: cli.Args{}, 58 Flags: []*cli.Flag{ 59 cli.NewFlag("output,out,o", "-?dm", "output file (empty is stderr, - is stdout)"), 60 cli.NewFlag("follow,f", false, "wait for changes until terminated"), 61 cli.NewFlag("head", 0, "skip all except first n events"), 62 cli.NewFlag("tail", 0, "skip all except last n events"), 63 // cli.NewFlag("filter", "", "span filter"), 64 // cli.NewFlag("filter-depth", 0, "span filter max depth"), 65 }, 66 } 67 68 tlzCmd := &cli.Command{ 69 Name: "tlz,eazy", 70 Description: "logs compressor/decompressor", 71 Flags: []*cli.Flag{ 72 cli.NewFlag("output,o", "-", "output file (or stdout)"), 73 }, 74 Commands: []*cli.Command{{ 75 Name: "compress,c", 76 Action: tlzRun, 77 Args: cli.Args{}, 78 Flags: []*cli.Flag{ 79 cli.NewFlag("block,b", 1*tlz.MiB, "compression block size"), 80 cli.NewFlag("hash-table,ht,h", 16*1024, "hash table size"), 81 }, 82 }, { 83 Name: "decompress,d", 84 Action: tlzRun, 85 Args: cli.Args{}, 86 }, { 87 Name: "dump", 88 Action: tlzRun, 89 Args: cli.Args{}, 90 Flags: []*cli.Flag{ 91 cli.NewFlag("base", -1, "global offset"), 92 }, 93 }}, 94 } 95 96 agentCmd := &cli.Command{ 97 Name: "agent,run", 98 Description: "run agent", 99 Before: beforeAgent, 100 Action: agentRun, 101 Flags: []*cli.Flag{ 102 cli.NewFlag("db", "", "path to logs db"), 103 cli.NewFlag("clickdb", "", "clickhouse dsn"), 104 105 cli.NewFlag("listen,l", []string(nil), "listen url"), 106 107 cli.NewFlag("http", ":8000", "http listen address"), 108 cli.NewFlag("http-net", "tcp", "http listen network"), 109 cli.NewFlag("http-fs", "", "http templates fs"), 110 111 cli.NewFlag("labels", "service=tlog-agent", "service labels"), 112 }, 113 } 114 115 app := &cli.Command{ 116 Name: "tlog", 117 Description: "tlog cli", 118 Before: before, 119 Flags: []*cli.Flag{ 120 cli.NewFlag("log", "stderr?dm", "log output file (or stderr)"), 121 cli.NewFlag("verbosity,v", "", "logger verbosity topics"), 122 cli.NewFlag("debug", "", "debug address", flag.Hidden), 123 cli.FlagfileFlag, 124 cli.HelpFlag, 125 }, 126 Commands: []*cli.Command{ 127 agentCmd, 128 catCmd, 129 tlzCmd, 130 { 131 Name: "ticker", 132 Description: "simple test app that prints current time once in an interval", 133 Action: ticker, 134 Flags: []*cli.Flag{ 135 cli.NewFlag("output,o", "-", "output file (or stdout)"), 136 cli.NewFlag("interval,int,i", time.Second, "interval to tick on"), 137 cli.NewFlag("labels", "service=ticker,_pid", "labels"), 138 }, 139 }, 140 { 141 Name: "test", 142 Action: test, 143 Args: cli.Args{}, 144 Hidden: true, 145 }, 146 }, 147 } 148 149 return app 150 } 151 152 func SubApp() *cli.Command { 153 app := App() 154 155 app.Before = nil 156 app.After = nil 157 app.Flags = nil 158 159 return app 160 } 161 162 func before(c *cli.Command) error { 163 w, err := tlflag.OpenWriter(c.String("log")) 164 if err != nil { 165 return errors.Wrap(err, "open log file") 166 } 167 168 tlog.DefaultLogger = tlog.New(w) 169 170 tlog.SetVerbosity(c.String("verbosity")) 171 172 if q := c.String("debug"); q != "" { 173 l, err := net.Listen("tcp", q) 174 if err != nil { 175 return errors.Wrap(err, "listen debug") 176 } 177 178 go func() { 179 tlog.Printw("start debug interface", "addr", l.Addr()) 180 181 err := http.Serve(l, nil) 182 if err != nil { 183 tlog.Printw("debug", "addr", q, "err", err, "", tlog.Fatal) 184 panic(err) 185 } 186 }() 187 } 188 189 if tlog.If("dump_file_reader") { 190 tlflag.OpenFileReader = tlflag.OSOpenFile 191 tlflag.OpenFileReader = tlflag.OpenFileDumpReader(tlflag.OpenFileReader) 192 tlflag.OpenFileReader = tlflag.OpenFileReReader(tlflag.OpenFileReader) 193 } 194 195 return nil 196 } 197 198 func beforeAgent(c *cli.Command) error { 199 if f := c.Flag("labels"); f != nil { 200 if ls, ok := f.Value.(string); ok { 201 tlog.SetLabels(tlog.ParseLabels(ls)...) 202 } 203 } 204 205 return nil 206 } 207 208 func agentRun(c *cli.Command) (err error) { 209 ctx := context.Background() 210 ctx = tlog.ContextWithSpan(ctx, tlog.Root()) 211 212 if (c.String("db") != "") == (c.String("clickdb") != "") { 213 return errors.New("exactly one of db and clickdb must be set") 214 } 215 216 var a interface { 217 io.Writer 218 web.Agent 219 } 220 221 if q := c.String("db"); q != "" { 222 a, err = agent.New(q) 223 if err != nil { 224 return errors.Wrap(err, "new agent") 225 } 226 } else if q := c.String("clickdb"); q != "" { 227 opts, err := clickhouse.ParseDSN(q) 228 if err != nil { 229 return errors.Wrap(err, "parse clickhouse dsn") 230 } 231 232 tr := tlog.Start("clickhouse") 233 defer tr.Finish() 234 235 ctx := tlog.ContextWithSpan(ctx, tr) 236 237 opts.Debug = tr.If("clickhouse") 238 opts.Debugf = func(format string, args ...interface{}) { 239 tr.Printf(format, args...) 240 } 241 242 conn, err := clickhouse.Open(opts) 243 if err != nil { 244 return errors.Wrap(err, "open clickhouse") 245 } 246 247 ch := tlclick.New(conn) 248 249 err = ch.CreateTables(ctx) 250 if err != nil { 251 return errors.Wrap(err, "create click tables") 252 } 253 254 a = ch 255 } 256 257 group := graceful.New() 258 259 if q := c.String("http"); q != "" { 260 l, err := net.Listen(c.String("http-net"), q) 261 if err != nil { 262 return errors.Wrap(err, "listen http") 263 } 264 265 s, err := web.New(a) 266 if err != nil { 267 return errors.Wrap(err, "new web server") 268 } 269 270 if q := c.String("http-fs"); q != "" { 271 s.FS = http.Dir(q) 272 } 273 274 group.Add(func(ctx context.Context) (err error) { 275 tr := tlog.SpawnFromContext(ctx, "web_server", "addr", l.Addr()) 276 defer tr.Finish("err", &err) 277 278 ctx = tlog.ContextWithSpan(ctx, tr) 279 280 err = s.Serve(ctx, l, func(ctx context.Context, c net.Conn) (err error) { 281 tr, ctx := tlog.SpawnFromContextAndWrap(ctx, "web_request", "remote_addr", c.RemoteAddr(), "local_addr", c.LocalAddr()) 282 defer tr.Finish("err", &err) 283 284 return s.HandleConn(ctx, c) 285 }) 286 if errors.Is(err, context.Canceled) { 287 err = nil 288 } 289 290 return errors.Wrap(err, "serve http") 291 }) 292 } 293 294 for _, lurl := range c.Flag("listen").Value.([]string) { 295 u, err := tlflag.ParseURL(lurl) 296 if err != nil { 297 return errors.Wrap(err, "parse %v", lurl) 298 } 299 300 tlog.Printw("listen", "scheme", u.Scheme, "host", u.Host, "path", u.Path, "query", u.RawQuery) 301 302 host := u.Host 303 if u.Scheme == "unix" || u.Scheme == "unixgram" { 304 host = u.Path 305 } 306 307 l, p, err := listen(u.Scheme, host) 308 if err != nil { 309 return errors.Wrap(err, "listen %v", host) 310 } 311 312 switch { 313 case u.Scheme == "unix", u.Scheme == "tcp": 314 group.Add(func(ctx context.Context) error { 315 var wg sync.WaitGroup 316 317 defer wg.Wait() 318 319 for { 320 c, err := hnet.Accept(ctx, l) 321 if errors.Is(err, context.Canceled) { 322 return nil 323 } 324 if err != nil { 325 return errors.Wrap(err, "accept") 326 } 327 328 wg.Add(1) 329 330 tr := tlog.SpawnFromContext(ctx, "agent_writer", "local_addr", c.LocalAddr(), "remote_addr", c.RemoteAddr()) 331 332 go func() { 333 defer wg.Done() 334 335 var err error 336 337 defer func() { tr.Finish("err", err) }() 338 defer closeWrap(c, &err, "close conn") 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 := tlz.NewEncoder(w, c.Int("block")) 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 := tlz.NewDecoder(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 := tlz.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, "copy") 619 } 620 default: 621 return errors.New("unexpected command: %v", c.MainName()) 622 } 623 624 return nil 625 } 626 627 func ticker(c *cli.Command) error { 628 w, err := tlflag.OpenWriter(c.String("output")) 629 if err != nil { 630 return errors.Wrap(err, "open output") 631 } 632 633 if tlog.If("describe,describe_writer") { 634 tlflag.Describe(tlog.Root(), w) 635 } 636 637 w = perrWriter{ 638 WriteCloser: w, 639 } 640 641 l := tlog.New(w) 642 643 t := time.NewTicker(c.Duration("interval")) 644 defer t.Stop() 645 646 ls := tlog.ParseLabels(c.String("labels")) 647 648 l.SetLabels(ls...) 649 650 var first time.Time 651 dur := c.Duration("interval") 652 drift := 0. 653 i := 0 654 655 const alpha = 0.0001 656 657 for t := range t.C { 658 if i == 0 { 659 first = t 660 } 661 662 diff := t.Sub(first) - time.Duration(i)*dur 663 drift := drift*(1-alpha) + float64(diff)*alpha 664 665 l.Printw("tick", "i", i, "time", t, "diff", diff, "drift", time.Duration(drift)) 666 667 i++ 668 } 669 670 return nil 671 } 672 673 func test(c *cli.Command) error { 674 return nil 675 } 676 677 func (f *filereader) Read(p []byte) (n int, err error) { 678 if f.f == nil { 679 f.f, err = os.Open(f.n) 680 if err != nil { 681 return 0, errors.Wrap(err, "") 682 // return 0, errors.Wrap(err, "open %v", f.n) 683 } 684 } 685 686 n, err = f.f.Read(p) 687 688 if err != nil { 689 _ = f.f.Close() 690 } 691 692 return 693 } 694 695 func (w perrWriter) Write(p []byte) (n int, err error) { 696 n, err = w.WriteCloser.Write(p) 697 698 if err != nil { 699 tlog.Printw("write", "err", err) 700 } 701 702 return 703 } 704 705 func (w perrWriter) Close() (err error) { 706 err = w.WriteCloser.Close() 707 708 if err != nil { 709 tlog.Printw("close", "err", err) 710 } 711 712 return 713 } 714 715 func isDir(name string) bool { 716 inf, err := os.Stat(name) 717 if err != nil { 718 return false 719 } 720 721 return inf.IsDir() 722 } 723 724 func isFifo(name string) bool { 725 inf, err := os.Stat(name) 726 if err != nil { 727 return false 728 } 729 730 mode := inf.Mode() 731 732 return mode&fs.ModeNamedPipe != 0 733 } 734 735 func listen(netw, addr string) (l net.Listener, p net.PacketConn, err error) { 736 switch netw { 737 case "unix", "unixgram": 738 _ = os.Remove(addr) 739 } 740 741 switch netw { 742 case "tcp", "unix": 743 l, err = net.Listen(netw, addr) 744 if err != nil { 745 return nil, nil, errors.Wrap(err, "listen") 746 } 747 748 switch l := l.(type) { 749 case *net.UnixListener: 750 l.SetUnlinkOnClose(true) 751 default: 752 return nil, nil, errors.New("unsupported listener type: %T", l) 753 } 754 case "udp", "unixgram": 755 p, err = net.ListenPacket(netw, addr) 756 if err != nil { 757 return nil, nil, errors.Wrap(err, "listen packet") 758 } 759 default: 760 return nil, nil, errors.New("unsupported network type: %v", netw) 761 } 762 763 return l, p, nil 764 } 765 766 func listen0(netw, addr string) (l net.Listener, err error) { 767 unix := strings.HasPrefix(netw, "unix") 768 769 var def []io.Closer 770 771 if unix { 772 cl, err := flockLock(addr + ".lock") 773 if err != nil { 774 return nil, errors.Wrap(err, "lock") 775 } 776 777 def = append(def, cl) 778 779 err = os.Remove(addr) 780 if err != nil && !os.IsNotExist(err) { 781 return nil, errors.Wrap(err, "remove old socket file") 782 } 783 } 784 785 l, err = net.Listen(netw, addr) 786 if err != nil { 787 return nil, errors.Wrap(err, "listen: %v", addr) 788 } 789 790 tlog.Printw("listen", "net", netw, "addr", addr, "l_type", tlog.NextAsType, l) 791 792 // unix listener removed by close 793 794 if def != nil { 795 return listenerClose{ 796 Listener: l, 797 def: def, 798 }, nil 799 } 800 801 return l, nil 802 } 803 804 func flockLock(addr string) (_ io.Closer, err error) { 805 lock, err := os.OpenFile(addr, os.O_CREATE, 0o644) 806 if err != nil { 807 return nil, errors.Wrap(err, "open lock") 808 } 809 810 err = syscall.Flock(int(lock.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) 811 if err != nil { 812 return nil, errors.Wrap(err, "flock") 813 } 814 815 cl := func() (err error) { 816 err = lock.Close() 817 if err != nil { 818 return errors.Wrap(err, "close lock") 819 } 820 821 err = os.Remove(addr) 822 if err != nil { 823 return errors.Wrap(err, "remove lock") 824 } 825 826 return nil 827 } 828 829 return tlio.CloserFunc(cl), nil 830 } 831 832 func (p listenerClose) SetDeadline(t time.Time) error { 833 return p.Listener.(interface{ SetDeadline(time.Time) error }).SetDeadline(t) 834 } 835 836 func (p listenerClose) Close() (err error) { 837 err = p.Listener.Close() 838 839 for i := len(p.def) - 1; i >= 0; i-- { 840 e := p.def[i].Close() 841 if err == nil { 842 err = e 843 } 844 } 845 846 return 847 } 848 849 func closeWrap(c io.Closer, errp *error, msg string) { 850 doWrap(c.Close, errp, msg) 851 } 852 853 func flushWrap(x interface{}, errp *error, msg string) { 854 if f, ok := x.(tlio.Flusher); ok { 855 doWrap(f.Flush, errp, msg) 856 } 857 } 858 859 func doWrap(f func() error, errp *error, msg string) { 860 e := f() 861 if *errp == nil { 862 *errp = errors.Wrap(e, msg) 863 } 864 } 865 866 func closeIfErr(c io.Closer, errp *error) { 867 if *errp == nil { 868 return 869 } 870 871 _ = c.Close() 872 }