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  }