github.com/nikandfor/tlog@v0.21.3/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  	"strings"
    11  
    12  	"github.com/nikandfor/errors"
    13  
    14  	"github.com/nikandfor/tlog"
    15  	"github.com/nikandfor/tlog/convert"
    16  	"github.com/nikandfor/tlog/tlio"
    17  	"github.com/nikandfor/tlog/tlwire"
    18  	"github.com/nikandfor/tlog/tlz"
    19  )
    20  
    21  type (
    22  	FileOpener func(name string, flags int, mode os.FileMode) (interface{}, error)
    23  )
    24  
    25  var (
    26  	OpenFileWriter = OSOpenFile
    27  	OpenFileReader = OpenFileReReader(OSOpenFile)
    28  )
    29  
    30  func OpenWriter(dst string) (wc io.WriteCloser, err error) {
    31  	var ws tlio.MultiWriter
    32  
    33  	defer func() {
    34  		if err == nil {
    35  			return
    36  		}
    37  
    38  		_ = ws.Close()
    39  	}()
    40  
    41  	for _, d := range strings.Split(dst, ",") {
    42  		if d == "" {
    43  			continue
    44  		}
    45  
    46  		u, err := ParseURL(d)
    47  		if err != nil {
    48  			return nil, errors.Wrap(err, "parse %v", d)
    49  		}
    50  
    51  		// tlog.Printw(d, "scheme", u.Scheme, "host", u.Host, "path", u.Path, "query", u.RawQuery, "from", loc.Caller(1))
    52  
    53  		w, err := openw(u)
    54  		if err != nil {
    55  			return nil, errors.Wrap(err, "%v", d)
    56  		}
    57  
    58  		ws = append(ws, w)
    59  	}
    60  
    61  	if len(ws) == 1 {
    62  		var ok bool
    63  		if wc, ok = ws[0].(io.WriteCloser); ok {
    64  			return wc, nil
    65  		}
    66  	}
    67  
    68  	return ws, nil
    69  }
    70  
    71  func openw(u *url.URL) (io.Writer, error) {
    72  	//	fmt.Fprintf(os.Stderr, "openw %q\n", fname)
    73  
    74  	w, c, err := openwc(u, u.Path)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	return writeCloser(w, c), nil
    80  }
    81  
    82  func openwc(u *url.URL, base string, wrap ...func(io.Writer, io.Closer) (io.Writer, io.Closer, error)) (w io.Writer, c io.Closer, err error) {
    83  	ext := filepath.Ext(base)
    84  	base = strings.TrimSuffix(base, ext)
    85  
    86  	//	fmt.Fprintf(os.Stderr, "openwc %q %q\n", base, ext)
    87  
    88  	// w := os.Create("file.json.ez")
    89  	// w = tlz.NewEncoder(w)
    90  	// w = convert.NewJSON(w)
    91  
    92  	switch ext {
    93  	case ".tlog", ".tl", ".tlogdump", ".tldump", ".log", "":
    94  	case ".tlz":
    95  	case ".json", ".logfmt", ".html":
    96  	case ".eazy", ".ez":
    97  		wrap = append(wrap, func(w io.Writer, c io.Closer) (io.Writer, io.Closer, error) {
    98  			w = tlz.NewEncoder(w, tlz.MiB)
    99  
   100  			return w, c, nil
   101  		})
   102  
   103  		return openwc(u, base, wrap...)
   104  	case ".eazydump", ".ezdump":
   105  	default:
   106  		return nil, nil, errors.New("unsupported format: %v", ext)
   107  	}
   108  
   109  	w, c, err = openwriter(u, base)
   110  	if err != nil {
   111  		return nil, nil, err
   112  	}
   113  
   114  	for _, wrap := range wrap {
   115  		w, c, err = wrap(w, c)
   116  		if err != nil {
   117  			return
   118  		}
   119  	}
   120  
   121  	switch ext {
   122  	case ".tlog", ".tl":
   123  	case ".tlz":
   124  		blockSize := tlz.MiB
   125  
   126  		w = tlz.NewEncoder(w, blockSize)
   127  	case ".log", "":
   128  		ff := tlog.LstdFlags
   129  		ff = updateConsoleFlags(ff, u.RawQuery)
   130  
   131  		w = tlog.NewConsoleWriter(w, ff)
   132  	case ".json":
   133  		w = convert.NewJSON(w)
   134  	case ".logfmt":
   135  		w = convert.NewLogfmt(w)
   136  	case ".html":
   137  		wc := writeCloser(w, c)
   138  		w = convert.NewWeb(wc)
   139  		c, _ = w.(io.Closer)
   140  	case ".tlogdump", ".tldump":
   141  		w = tlwire.NewDumper(w)
   142  	case ".eazydump", ".ezdump":
   143  		w = tlz.NewDumper(w)
   144  		w = tlz.NewEncoder(w, tlz.MiB)
   145  	default:
   146  		panic(ext)
   147  	}
   148  
   149  	return w, c, nil
   150  }
   151  
   152  func openwriter(u *url.URL, base string) (w io.Writer, c io.Closer, err error) {
   153  	switch base {
   154  	case "-", "stdout":
   155  		return os.Stdout, nil, nil
   156  	case "", "stderr":
   157  		return os.Stderr, nil, nil
   158  	case "discard":
   159  		return io.Discard, nil, nil
   160  	}
   161  
   162  	var f interface{}
   163  
   164  	if u.Scheme != "" {
   165  		f, err = openwurl(u)
   166  	} else {
   167  		f, err = openwfile(u)
   168  	}
   169  	if err != nil {
   170  		return nil, nil, err
   171  	}
   172  
   173  	w = f.(io.Writer)
   174  	c, _ = f.(io.Closer)
   175  
   176  	return w, c, nil
   177  }
   178  
   179  func openwfile(u *url.URL) (interface{}, error) {
   180  	fname := u.Path
   181  
   182  	of := os.O_APPEND | os.O_WRONLY | os.O_CREATE
   183  	of = updateFileFlags(of, u.RawQuery)
   184  
   185  	mode := os.FileMode(0o644)
   186  
   187  	return OpenFileWriter(fname, of, mode)
   188  }
   189  
   190  func openwurl(u *url.URL) (f interface{}, err error) {
   191  	if u.Scheme == "file" {
   192  		return openwfile(u)
   193  	}
   194  
   195  	switch u.Scheme {
   196  	case "unix", "unixgram":
   197  	default:
   198  		return nil, errors.New("unsupported scheme: %v", u.Scheme)
   199  	}
   200  
   201  	return tlio.NewReWriter(func(w io.Writer, err error) (io.Writer, error) {
   202  		if c, ok := w.(io.Closer); ok {
   203  			_ = c.Close()
   204  		}
   205  
   206  		return net.Dial(u.Scheme, u.Path)
   207  	}), nil
   208  }
   209  
   210  func OpenReader(src string) (rc io.ReadCloser, err error) {
   211  	u, err := ParseURL(src)
   212  	if err != nil {
   213  		return nil, errors.Wrap(err, "parse %v", src)
   214  	}
   215  
   216  	r, err := openr(u)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	var ok bool
   222  	if rc, ok = r.(io.ReadCloser); ok {
   223  		return rc, nil
   224  	}
   225  
   226  	return tlio.NopCloser{
   227  		Reader: r,
   228  	}, nil
   229  }
   230  
   231  func openr(u *url.URL) (io.Reader, error) {
   232  	r, c, err := openrc(u, u.Path)
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  
   237  	if c == nil {
   238  		if _, ok := r.(io.Closer); ok {
   239  			return tlio.NopCloser{Reader: r}, nil
   240  		}
   241  
   242  		return r, nil
   243  	}
   244  
   245  	if r.(interface{}) == c.(interface{}) {
   246  		return r, nil
   247  	}
   248  
   249  	return tlio.ReadCloser{
   250  		Reader: r,
   251  		Closer: c,
   252  	}, nil
   253  }
   254  
   255  func openrc(u *url.URL, base string, wrap ...func(io.Reader) (io.Reader, error)) (r io.Reader, c io.Closer, err error) {
   256  	ext := filepath.Ext(base)
   257  	base = strings.TrimSuffix(base, ext)
   258  
   259  	switch ext {
   260  	case ".tlog", ".tl", "":
   261  	case ".tlz":
   262  	case ".eazy", ".ez":
   263  		wrap = append(wrap, func(r io.Reader) (io.Reader, error) {
   264  			return tlz.NewDecoder(r), nil
   265  		})
   266  
   267  		return openrc(u, base, wrap...)
   268  	default:
   269  		return nil, nil, errors.New("unsupported format: %v", ext)
   270  	}
   271  
   272  	r, c, err = openreader(u, base)
   273  	if err != nil {
   274  		return nil, nil, err
   275  	}
   276  
   277  	for _, wrap := range wrap {
   278  		r, err = wrap(r)
   279  		if err != nil {
   280  			return
   281  		}
   282  	}
   283  
   284  	switch ext {
   285  	case ".tlog", ".tl", "":
   286  	case ".tlz":
   287  		r = tlz.NewDecoder(r)
   288  	default:
   289  		panic(ext)
   290  	}
   291  
   292  	return r, c, nil
   293  }
   294  
   295  func openreader(u *url.URL, base string) (r io.Reader, c io.Closer, err error) {
   296  	switch base {
   297  	case "-", "", "stdin":
   298  		return os.Stdin, nil, nil
   299  	}
   300  
   301  	var f interface{}
   302  
   303  	if u.Scheme != "" {
   304  		f, err = openrurl(u)
   305  	} else {
   306  		f, err = openrfile(u)
   307  	}
   308  	if err != nil {
   309  		return nil, nil, err
   310  	}
   311  
   312  	r = f.(io.Reader)
   313  	c, _ = f.(io.Closer)
   314  
   315  	return r, c, nil
   316  }
   317  
   318  func openrfile(u *url.URL) (interface{}, error) {
   319  	return OpenFileReader(u.Path, os.O_RDONLY, 0)
   320  }
   321  
   322  func openrurl(u *url.URL) (interface{}, error) {
   323  	if u.Scheme == "file" {
   324  		return openrfile(u)
   325  	}
   326  
   327  	switch u.Scheme { //nolint:gocritic
   328  	default:
   329  		return nil, errors.New("unsupported scheme: %v", u.Scheme)
   330  	}
   331  }
   332  
   333  func updateFileFlags(of int, q string) int {
   334  	for _, c := range q {
   335  		if c == '0' {
   336  			of |= os.O_TRUNC
   337  		}
   338  	}
   339  
   340  	return of
   341  }
   342  
   343  func updateConsoleFlags(ff int, q string) int {
   344  	for _, c := range q {
   345  		switch c {
   346  		case 'd':
   347  			ff |= tlog.LdetFlags
   348  		case 'm':
   349  			ff |= tlog.Lmilliseconds
   350  		case 'M':
   351  			ff |= tlog.Lmicroseconds
   352  		case 'n':
   353  			ff |= tlog.Lfuncname
   354  		case 'f':
   355  			ff &^= tlog.Llongfile
   356  			ff |= tlog.Lshortfile
   357  		case 'F':
   358  			ff &^= tlog.Lshortfile
   359  			ff |= tlog.Llongfile
   360  		case 'U':
   361  			ff |= tlog.LUTC
   362  		}
   363  	}
   364  
   365  	return ff
   366  }
   367  
   368  func OSOpenFile(name string, flags int, mode os.FileMode) (interface{}, error) {
   369  	return os.OpenFile(name, flags, mode)
   370  }
   371  
   372  func OpenFileReReader(open FileOpener) FileOpener {
   373  	return func(name string, flags int, mode os.FileMode) (interface{}, error) {
   374  		f, err := open(name, flags, mode)
   375  		if err != nil {
   376  			return nil, err
   377  		}
   378  
   379  		rs := f.(tlio.ReadSeeker)
   380  
   381  		r, err := tlio.NewReReader(rs)
   382  		if err != nil {
   383  			return nil, errors.Wrap(err, "open ReReader")
   384  		}
   385  
   386  		r.Hook = func(old, cur int64) {
   387  			tlog.Printw("file truncated", "name", name, "old_len", old)
   388  		}
   389  
   390  		c, ok := f.(io.Closer)
   391  		if !ok {
   392  			return r, nil
   393  		}
   394  
   395  		return tlio.ReadCloser{
   396  			Reader: r,
   397  			Closer: c,
   398  		}, nil
   399  	}
   400  }
   401  
   402  func OpenFileDumpReader(open FileOpener) FileOpener {
   403  	tr := tlog.Start("read dumper")
   404  
   405  	return func(name string, flags int, mode os.FileMode) (interface{}, error) {
   406  		f, err := open(name, flags, mode)
   407  		if err != nil {
   408  			return nil, err
   409  		}
   410  
   411  		r := f.(io.Reader)
   412  
   413  		r = &tlio.DumpReader{
   414  			Reader: r,
   415  			Span:   tr.Spawn("open dump reader", "file", name),
   416  		}
   417  
   418  		return r, nil
   419  	}
   420  }
   421  
   422  func ParseURL(d string) (u *url.URL, err error) {
   423  	u, err = url.Parse(d)
   424  	if err != nil {
   425  		return nil, err
   426  	}
   427  
   428  	if (u.Scheme == "file" || u.Scheme == "unix" || u.Scheme == "unixgram") && u.Host != "" {
   429  		u.Path = path.Join(u.Host, u.Path)
   430  		u.Host = ""
   431  	}
   432  
   433  	return u, nil
   434  }
   435  
   436  func DumpWriter(tr tlog.Span, w io.Writer) {
   437  	dumpWriter(tr, w, 0)
   438  }
   439  
   440  func dumpWriter(tr tlog.Span, w io.Writer, d int) {
   441  	switch w := w.(type) {
   442  	case tlio.MultiWriter:
   443  		tr.Printw("writer", "d", d, "typ", tlog.NextAsType, w)
   444  
   445  		for _, w := range w {
   446  			dumpWriter(tr, w, d+1)
   447  		}
   448  	case *tlog.ConsoleWriter:
   449  		tr.Printw("writer", "d", d, "typ", tlog.NextAsType, w)
   450  
   451  		dumpWriter(tr, w.Writer, d+1)
   452  	case *os.File:
   453  		tr.Printw("writer", "d", d, "typ", tlog.NextAsType, w, "name", w.Name())
   454  
   455  	case *net.UnixConn:
   456  		f, err := w.File()
   457  
   458  		tr.Printw("writer", "d", d, "typ", tlog.NextAsType, w, "get_file_err", err)
   459  
   460  		dumpWriter(tr, f, d+1)
   461  
   462  	default:
   463  		tr.Printw("writer", "d", d, "typ", tlog.NextAsType, w)
   464  	}
   465  }
   466  
   467  func writeCloser(w io.Writer, c io.Closer) io.Writer {
   468  	if c == nil {
   469  		if _, ok := w.(io.Closer); ok {
   470  			return tlio.NopCloser{Writer: w}
   471  		}
   472  
   473  		return w
   474  	}
   475  
   476  	if w.(interface{}) == c.(interface{}) {
   477  		return w
   478  	}
   479  
   480  	return tlio.WriteCloser{
   481  		Writer: w,
   482  		Closer: c,
   483  	}
   484  }