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 }