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