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