tlog.app/go/tlog@v0.23.1/ext/tlflag/flag.go (about)

     1  package tlflag
     2  
     3  import (
     4  	"io"
     5  	"net"
     6  	"net/url"
     7  	"os"
     8  	"path"
     9  	"path/filepath"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"tlog.app/go/eazy"
    15  	"tlog.app/go/errors"
    16  	"tlog.app/go/loc"
    17  
    18  	"tlog.app/go/tlog"
    19  	"tlog.app/go/tlog/convert"
    20  	"tlog.app/go/tlog/rotating"
    21  	"tlog.app/go/tlog/tlio"
    22  	"tlog.app/go/tlog/tlwire"
    23  )
    24  
    25  type (
    26  	FileOpener = func(name string, flags int, mode os.FileMode) (interface{}, error)
    27  
    28  	writerWrapper func(io.Writer, io.Closer) (io.Writer, io.Closer, error)
    29  	readerWrapper func(io.Reader, io.Closer) (io.Reader, io.Closer, error)
    30  )
    31  
    32  var (
    33  	OpenFileWriter FileOpener = OSOpenFile
    34  	OpenFileReader FileOpener = OpenFileReReader(OSOpenFile)
    35  
    36  	EazyBlockSize = eazy.MiB
    37  	EazyHTable    = 2 * 1024
    38  )
    39  
    40  func OpenWriter(dst string) (wc io.WriteCloser, err error) {
    41  	var ws tlio.MultiWriter
    42  
    43  	defer func() {
    44  		if err == nil {
    45  			return
    46  		}
    47  
    48  		_ = ws.Close()
    49  	}()
    50  
    51  	for _, d := range strings.Split(dst, ",") {
    52  		if d == "" {
    53  			continue
    54  		}
    55  
    56  		u, err := ParseURL(d)
    57  		if err != nil {
    58  			return nil, errors.Wrap(err, "parse %v", d)
    59  		}
    60  
    61  		tlog.V("writer_url").Printw(d, "scheme", u.Scheme, "host", u.Host, "path", u.Path, "query", u.RawQuery, "from", loc.Caller(1))
    62  
    63  		w, err := openw(u)
    64  		if err != nil {
    65  			return nil, errors.Wrap(err, "%v", d)
    66  		}
    67  
    68  		ws = append(ws, w)
    69  	}
    70  
    71  	if len(ws) == 1 {
    72  		var ok bool
    73  		if wc, ok = ws[0].(io.WriteCloser); ok {
    74  			return wc, nil
    75  		}
    76  	}
    77  
    78  	return ws, nil
    79  }
    80  
    81  func openw(u *url.URL) (io.Writer, error) {
    82  	//	fmt.Fprintf(os.Stderr, "openw %q\n", fname)
    83  
    84  	w, c, err := openwc(u, u.Path)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	return writeCloser(w, c), nil
    90  }
    91  
    92  func openwc(u *url.URL, base string) (w io.Writer, c io.Closer, err error) {
    93  	//	fmt.Fprintf(os.Stderr, "openwc %q %q\n", base, ext)
    94  
    95  	// w := os.Create("file.json.ez")
    96  	// w = tlz.NewEncoder(w)
    97  	// w = convert.NewJSON(w)
    98  
    99  	var wrap []writerWrapper
   100  
   101  more:
   102  	ext := filepath.Ext(base)
   103  	base = strings.TrimSuffix(base, ext)
   104  
   105  	switch ext {
   106  	case ".tlog", ".tl":
   107  	case ".tlogdump", ".tldump":
   108  		wrap = append(wrap, func(w io.Writer, c io.Closer) (io.Writer, io.Closer, error) {
   109  			w = tlwire.NewDumper(w)
   110  
   111  			return w, c, nil
   112  		})
   113  	case ".log", "":
   114  		wrap = append(wrap, func(w io.Writer, c io.Closer) (io.Writer, io.Closer, error) {
   115  			ff := tlog.LstdFlags
   116  			ff = updateConsoleFlags(ff, u.RawQuery)
   117  
   118  			w = tlog.NewConsoleWriter(w, ff)
   119  
   120  			return w, c, nil
   121  		})
   122  	case ".tlz", ".eazy", ".ez":
   123  		wrap = append(wrap, func(w io.Writer, c io.Closer) (io.Writer, io.Closer, error) {
   124  			if f, ok := w.(*rotating.File); ok {
   125  				f.OpenFile = RotatedTLZFileOpener(f.OpenFile)
   126  
   127  				return f, c, nil
   128  			}
   129  
   130  			w = eazy.NewWriter(w, EazyBlockSize, EazyHTable)
   131  
   132  			return w, c, nil
   133  		})
   134  
   135  		if ext == ".eazy" || ext == ".ez" {
   136  			goto more
   137  		}
   138  	case ".json":
   139  		wrap = append(wrap, func(w io.Writer, c io.Closer) (io.Writer, io.Closer, error) {
   140  			w = convert.NewJSON(w)
   141  
   142  			return w, c, nil
   143  		})
   144  	case ".logfmt":
   145  		wrap = append(wrap, func(w io.Writer, c io.Closer) (io.Writer, io.Closer, error) {
   146  			w = convert.NewLogfmt(w)
   147  
   148  			return w, c, nil
   149  		})
   150  	case ".html":
   151  		wrap = append(wrap, func(w io.Writer, c io.Closer) (io.Writer, io.Closer, error) {
   152  			wc := writeCloser(w, c)
   153  			w = convert.NewWeb(wc)
   154  			c, _ = w.(io.Closer)
   155  
   156  			return w, c, nil
   157  		})
   158  	case ".eazydump", ".ezdump":
   159  		wrap = append(wrap, func(w io.Writer, c io.Closer) (io.Writer, io.Closer, error) {
   160  			w = eazy.NewDumper(w)
   161  			w = eazy.NewWriter(w, EazyBlockSize, EazyHTable)
   162  
   163  			return w, c, nil
   164  		})
   165  	default:
   166  		return nil, nil, errors.New("unsupported format: %v", ext)
   167  	}
   168  
   169  	w, c, err = openwriter(u, base)
   170  	if err != nil {
   171  		return nil, nil, err
   172  	}
   173  
   174  	for _, wrap := range wrap {
   175  		w, c, err = wrap(w, c)
   176  		if err != nil {
   177  			return
   178  		}
   179  	}
   180  
   181  	return w, c, nil
   182  }
   183  
   184  func openwriter(u *url.URL, base string) (w io.Writer, c io.Closer, err error) {
   185  	switch base {
   186  	case "-", "stdout":
   187  		return os.Stdout, nil, nil
   188  	case "", "stderr":
   189  		return os.Stderr, nil, nil
   190  	case "discard":
   191  		return io.Discard, nil, nil
   192  	}
   193  
   194  	var f interface{}
   195  
   196  	if u.Scheme != "" {
   197  		f, err = openwurl(u)
   198  	} else {
   199  		f, err = openwfile(u)
   200  	}
   201  	if err != nil {
   202  		return nil, nil, err
   203  	}
   204  
   205  	w = f.(io.Writer)
   206  	c, _ = f.(io.Closer)
   207  
   208  	return w, c, nil
   209  }
   210  
   211  func openwfile(u *url.URL) (interface{}, error) {
   212  	fname := u.Path
   213  
   214  	of := os.O_APPEND | os.O_WRONLY | os.O_CREATE
   215  	of = updateFileFlags(of, u.RawQuery)
   216  
   217  	mode := os.FileMode(0o644)
   218  
   219  	q := u.Query()
   220  
   221  	if rotating.IsPattern(filepath.Base(fname)) || q.Get("rotating") != "" {
   222  		f := rotating.Create(fname)
   223  		f.Flags = of
   224  		f.Mode = mode
   225  		f.OpenFile = openFileWriter
   226  
   227  		if v := q.Get("rotating_max_file_size"); v != "" {
   228  			x, err := ParseBytes(v)
   229  			if err == nil {
   230  				f.MaxFileSize = x
   231  			}
   232  		}
   233  
   234  		if v := q.Get("rotating_max_file_age"); v != "" {
   235  			x, err := time.ParseDuration(v)
   236  			if err == nil {
   237  				f.MaxFileAge = x
   238  			}
   239  		}
   240  
   241  		if v := q.Get("rotating_max_total_size"); v != "" {
   242  			x, err := ParseBytes(v)
   243  			if err == nil {
   244  				f.MaxTotalSize = x
   245  			}
   246  		}
   247  
   248  		if v := q.Get("rotating_max_total_age"); v != "" {
   249  			x, err := time.ParseDuration(v)
   250  			if err == nil {
   251  				f.MaxTotalAge = x
   252  			}
   253  		}
   254  
   255  		return f, nil
   256  	}
   257  
   258  	return OpenFileWriter(fname, of, mode)
   259  }
   260  
   261  func openwurl(u *url.URL) (f interface{}, err error) {
   262  	if u.Scheme == "file" {
   263  		return openwfile(u)
   264  	}
   265  
   266  	switch u.Scheme {
   267  	case "unix", "unixgram":
   268  	default:
   269  		return nil, errors.New("unsupported scheme: %v", u.Scheme)
   270  	}
   271  
   272  	return tlio.NewReWriter(func(w io.Writer, err error) (io.Writer, error) {
   273  		if c, ok := w.(io.Closer); ok {
   274  			_ = c.Close()
   275  		}
   276  
   277  		return net.Dial(u.Scheme, u.Path)
   278  	}), nil
   279  }
   280  
   281  func OpenReader(src string) (rc io.ReadCloser, err error) {
   282  	u, err := ParseURL(src)
   283  	if err != nil {
   284  		return nil, errors.Wrap(err, "parse %v", src)
   285  	}
   286  
   287  	r, err := openr(u)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  
   292  	var ok bool
   293  	if rc, ok = r.(io.ReadCloser); ok {
   294  		return rc, nil
   295  	}
   296  
   297  	return tlio.NopCloser{
   298  		Reader: r,
   299  	}, nil
   300  }
   301  
   302  func openr(u *url.URL) (io.Reader, error) {
   303  	r, c, err := openrc(u, u.Path)
   304  	if err != nil {
   305  		return nil, err
   306  	}
   307  
   308  	if c == nil {
   309  		if _, ok := r.(io.Closer); ok {
   310  			return tlio.NopCloser{Reader: r}, nil
   311  		}
   312  
   313  		return r, nil
   314  	}
   315  
   316  	if r.(interface{}) == c.(interface{}) {
   317  		return r, nil
   318  	}
   319  
   320  	return tlio.ReadCloser{
   321  		Reader: r,
   322  		Closer: c,
   323  	}, nil
   324  }
   325  
   326  func openrc(u *url.URL, base string) (r io.Reader, c io.Closer, err error) {
   327  	var wrap []readerWrapper
   328  
   329  more:
   330  	ext := filepath.Ext(base)
   331  	base = strings.TrimSuffix(base, ext)
   332  
   333  	switch ext {
   334  	case ".tlog", ".tl", "":
   335  	case ".tlz", ".eazy", ".ez":
   336  		wrap = append(wrap, func(r io.Reader, c io.Closer) (io.Reader, io.Closer, error) {
   337  			r = eazy.NewReader(r)
   338  
   339  			return r, c, nil
   340  		})
   341  
   342  		if ext == ".eazy" || ext == ".ez" {
   343  			goto more
   344  		}
   345  	default:
   346  		return nil, nil, errors.New("unsupported format: %v", ext)
   347  	}
   348  
   349  	r, c, err = openreader(u, base)
   350  	if err != nil {
   351  		return nil, nil, err
   352  	}
   353  
   354  	for _, wrap := range wrap {
   355  		r, c, err = wrap(r, c)
   356  		if err != nil {
   357  			return
   358  		}
   359  	}
   360  
   361  	return r, c, nil
   362  }
   363  
   364  func openreader(u *url.URL, base string) (r io.Reader, c io.Closer, err error) {
   365  	switch base {
   366  	case "-", "", "stdin":
   367  		return os.Stdin, nil, nil
   368  	}
   369  
   370  	var f interface{}
   371  
   372  	if u.Scheme != "" {
   373  		f, err = openrurl(u)
   374  	} else {
   375  		f, err = openrfile(u)
   376  	}
   377  	if err != nil {
   378  		return nil, nil, err
   379  	}
   380  
   381  	if rc, ok := f.(tlio.ReadCloser); ok {
   382  		return rc.Reader, rc.Closer, nil
   383  	}
   384  
   385  	r = f.(io.Reader)
   386  	c, _ = f.(io.Closer)
   387  
   388  	return r, c, nil
   389  }
   390  
   391  func openrfile(u *url.URL) (interface{}, error) {
   392  	return OpenFileReader(u.Path, os.O_RDONLY, 0)
   393  }
   394  
   395  func openrurl(u *url.URL) (interface{}, error) {
   396  	if u.Scheme == "file" {
   397  		return openrfile(u)
   398  	}
   399  
   400  	switch u.Scheme { //nolint:gocritic
   401  	default:
   402  		return nil, errors.New("unsupported scheme: %v", u.Scheme)
   403  	}
   404  }
   405  
   406  func updateFileFlags(of int, q string) int {
   407  	for _, c := range q {
   408  		if c == '0' {
   409  			of |= os.O_TRUNC
   410  		}
   411  	}
   412  
   413  	return of
   414  }
   415  
   416  func updateConsoleFlags(ff int, q string) int {
   417  	for _, c := range q {
   418  		switch c {
   419  		case 'd':
   420  			ff |= tlog.LdetFlags
   421  		case 'm':
   422  			ff |= tlog.Lmilliseconds
   423  		case 'M':
   424  			ff |= tlog.Lmicroseconds
   425  		case 'n':
   426  			ff |= tlog.Lfuncname
   427  		case 'f':
   428  			ff &^= tlog.Llongfile
   429  			ff |= tlog.Lshortfile
   430  		case 'F':
   431  			ff &^= tlog.Lshortfile
   432  			ff |= tlog.Llongfile
   433  		case 'U':
   434  			ff |= tlog.LUTC
   435  		}
   436  	}
   437  
   438  	return ff
   439  }
   440  
   441  func OSOpenFile(name string, flags int, mode os.FileMode) (interface{}, error) {
   442  	return os.OpenFile(name, flags, mode)
   443  }
   444  
   445  func OpenFileReReader(open FileOpener) FileOpener {
   446  	return func(name string, flags int, mode os.FileMode) (interface{}, error) {
   447  		f, err := open(name, flags, mode)
   448  		if err != nil {
   449  			return nil, err
   450  		}
   451  
   452  		rs := f.(tlio.ReadSeeker)
   453  
   454  		r, err := tlio.NewReReader(rs)
   455  		if err != nil {
   456  			return nil, errors.Wrap(err, "open ReReader")
   457  		}
   458  
   459  		r.Hook = func(old, cur int64) {
   460  			tlog.Printw("file truncated", "name", name, "old_len", old)
   461  		}
   462  
   463  		c, ok := f.(io.Closer)
   464  		if !ok {
   465  			return r, nil
   466  		}
   467  
   468  		return tlio.ReadCloser{
   469  			Reader: r,
   470  			Closer: c,
   471  		}, nil
   472  	}
   473  }
   474  
   475  func OpenFileDumpReader(open FileOpener) FileOpener {
   476  	tr := tlog.Start("read dumper")
   477  
   478  	return func(name string, flags int, mode os.FileMode) (interface{}, error) {
   479  		f, err := open(name, flags, mode)
   480  		if err != nil {
   481  			return nil, err
   482  		}
   483  
   484  		r := f.(io.Reader)
   485  
   486  		r = &tlio.DumpReader{
   487  			Reader: r,
   488  			Span:   tr.Spawn("open dump reader", "file", name),
   489  		}
   490  
   491  		return r, nil
   492  	}
   493  }
   494  
   495  func RotatedTLZFileOpener(below rotating.FileOpener) rotating.FileOpener {
   496  	return func(name string, flags int, mode os.FileMode) (io.Writer, error) {
   497  		w, err := below(name, flags, mode)
   498  		if err != nil {
   499  			return nil, errors.Wrap(err, "")
   500  		}
   501  
   502  		s := tlio.NewSandwichWriter(w)
   503  
   504  		s.Writer = eazy.NewWriter(s.Inner(), EazyBlockSize, EazyHTable)
   505  
   506  		return s, nil
   507  	}
   508  }
   509  
   510  func openFileWriter(name string, flags int, mode os.FileMode) (io.Writer, error) {
   511  	file, err := OpenFileWriter(name, flags, mode)
   512  	if err != nil {
   513  		return nil, err
   514  	}
   515  
   516  	return file.(io.Writer), nil
   517  }
   518  
   519  func ParseURL(d string) (u *url.URL, err error) {
   520  	u, err = url.Parse(d)
   521  	if err != nil {
   522  		return nil, err
   523  	}
   524  
   525  	if u.Opaque != "" {
   526  		return nil, errors.New("unexpected opaque url")
   527  	}
   528  
   529  	if (u.Scheme == "file" || u.Scheme == "unix" || u.Scheme == "unixgram") && u.Host != "" {
   530  		u.Path = path.Join(u.Host, u.Path)
   531  		u.Host = ""
   532  	}
   533  
   534  	return u, nil
   535  }
   536  
   537  func ParseBytes(s string) (int64, error) {
   538  	base := 10
   539  	neg := false
   540  
   541  	for strings.HasPrefix(s, "-") {
   542  		neg = !neg
   543  		s = s[1:]
   544  	}
   545  
   546  	if strings.HasPrefix(s, "0x") {
   547  		s = s[2:]
   548  		base = 16
   549  	}
   550  
   551  	l := 0
   552  
   553  	for l < len(s) && s[l] >= '0' && (s[l] <= '9' || base == 16 && (s[l] >= 'a' && s[l] <= 'f' || s[l] >= 'A' && s[l] <= 'F')) {
   554  		l++
   555  	}
   556  
   557  	if l == 0 {
   558  		return 0, errors.New("bad size")
   559  	}
   560  
   561  	var m int64
   562  
   563  	switch strings.ToLower(s[l:]) {
   564  	case "b", "":
   565  		m = 1
   566  	case "kb", "k":
   567  		m = 1000
   568  	case "kib", "ki":
   569  		m = 1024
   570  	case "mb", "m":
   571  		m = 1e6
   572  	case "mib", "mi":
   573  		m = 1 << 20
   574  	case "gb", "g":
   575  		m = 1e9
   576  	case "gib", "gi":
   577  		m = 1 << 30
   578  	case "tb", "t":
   579  		m = 1e12
   580  	case "tib", "ti":
   581  		m = 1 << 40
   582  	default:
   583  		return 0, errors.New("unsupported suffix: %v", s[l:])
   584  	}
   585  
   586  	x, err := strconv.ParseInt(s[:l], base, 64)
   587  	if err != nil {
   588  		return 0, err
   589  	}
   590  
   591  	if neg {
   592  		x = -x
   593  	}
   594  
   595  	return x * m, nil
   596  }
   597  
   598  func Describe(tr tlog.Span, x interface{}) {
   599  	describe(tr, x, 0)
   600  }
   601  
   602  func describe(tr tlog.Span, x interface{}, d int) {
   603  	switch x := x.(type) {
   604  	case tlio.MultiWriter:
   605  		tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x)
   606  
   607  		for _, w := range x {
   608  			describe(tr, w, d+1)
   609  		}
   610  	case tlio.ReadCloser:
   611  		tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x)
   612  
   613  		describe(tr, x.Reader, d+1)
   614  		describe(tr, x.Closer, d+1)
   615  	case *tlio.ReReader:
   616  		tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x)
   617  
   618  		describe(tr, x.ReadSeeker, d+1)
   619  	case *eazy.Reader:
   620  		tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x)
   621  
   622  		describe(tr, x.Reader, d+1)
   623  	case *eazy.Writer:
   624  		tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x)
   625  
   626  		describe(tr, x.Writer, d+1)
   627  	case *tlog.ConsoleWriter:
   628  		tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x)
   629  
   630  		describe(tr, x.Writer, d+1)
   631  	case *os.File:
   632  		tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x, "name", x.Name())
   633  
   634  	case *net.UnixConn:
   635  		f, err := x.File()
   636  
   637  		tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x, "get_file_err", err)
   638  
   639  		describe(tr, f, d+1)
   640  	default:
   641  		tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x)
   642  	}
   643  }
   644  
   645  func writeCloser(w io.Writer, c io.Closer) io.Writer {
   646  	if c == nil {
   647  		if _, ok := w.(io.Closer); ok {
   648  			return tlio.NopCloser{Writer: w}
   649  		}
   650  
   651  		return w
   652  	}
   653  
   654  	if w.(interface{}) == c.(interface{}) {
   655  		return w
   656  	}
   657  
   658  	return tlio.WriteCloser{
   659  		Writer: w,
   660  		Closer: c,
   661  	}
   662  }