change httprouter with fasthttp and simple handler

* add http benchmark
* move HTTP query parameters parsing to http subpackage
* update dependencies
This commit is contained in:
Lawrence, Rendall
2023-03-18 00:58:35 +03:00
parent e5039131b3
commit da7de52813
18 changed files with 442 additions and 459 deletions
-167
View File
@@ -2,11 +2,6 @@ package bittorrent
import (
"context"
"errors"
"net/url"
"strconv"
"strings"
"github.com/rs/zerolog"
)
@@ -21,39 +16,9 @@ type Params interface {
// returned as a string because they are encoded in the URL as strings.
String(key string) (string, bool)
// RawPath returns the raw path from the request URL.
// The path returned can contain URL encoded data.
// For a request of the form "/announce?port=1234" this would return
// "/announce".
RawPath() string
// RawQuery returns the raw query from the request URL, excluding the
// delimiter '?'.
// For a request of the form "/announce?port=1234" this would return
// "port=1234"
RawQuery() string
zerolog.LogObjectMarshaler
}
var (
// ErrKeyNotFound is returned when a provided key has no value associated with
// it.
ErrKeyNotFound = errors.New("query: value for the provided key does not exist")
// ErrInvalidQueryEscape is returned when a query string contains invalid
// escapes.
ErrInvalidQueryEscape = ClientError("invalid query escape")
)
// QueryParams parses a URL Query and implements the Params interface with some
// additional helpers.
type QueryParams struct {
path string
query string
params map[string]string
infoHashes []InfoHash
}
type routeParamsKey struct{}
// RouteParamsKey is a key for the context of a request that
@@ -104,135 +69,3 @@ func RemapRouteParamsToBgContext(inCtx context.Context) context.Context {
}
return context.WithValue(context.Background(), RouteParamsKey, rp)
}
// ParseURLData parses a request URL or UDP URLData as defined in BEP41.
// It expects a concatenated string of the request's path and query parts as
// defined in RFC 3986. As both the udp: and http: scheme used by BitTorrent
// include an authority part the path part must always begin with a slash.
// An example of the expected URLData would be "/announce?port=1234&uploaded=0"
// or "/?auth=0x1337".
// HTTP servers should pass (*http.Request).RequestURI, UDP servers should
// pass the concatenated, unchanged URLData as defined in BEP41.
//
// Note that, in the case of a key occurring multiple times in the query, only
// the last value for that key is kept.
// The only exception to this rule is the key "info_hash" which will attempt to
// parse each value as an InfoHash and return an error if parsing fails. All
// InfoHashes are collected and can later be retrieved by calling the InfoHashes
// method.
//
// Also note that any error that is encountered during parsing is returned as a
// ClientError, as this method is expected to be used to parse client-provided
// data.
func ParseURLData(urlData string) (*QueryParams, error) {
var path, query string
queryDelim := strings.IndexAny(urlData, "?")
if queryDelim == -1 {
path = urlData
} else {
path = urlData[:queryDelim]
query = urlData[queryDelim+1:]
}
q, err := parseQuery(query)
if err != nil {
return nil, ClientError(err.Error())
}
q.path = path
return q, nil
}
// parseQuery parses a URL query into QueryParams.
// The query is expected to exclude the delimiting '?'.
func parseQuery(query string) (q *QueryParams, err error) {
// This is basically url.parseQuery, but with a map[string]string
// instead of map[string][]string for the values.
q = &QueryParams{
query: query,
infoHashes: nil,
params: make(map[string]string),
}
for query != "" {
key := query
if i := strings.IndexAny(key, "&;"); i >= 0 {
key, query = key[:i], key[i+1:]
} else {
query = ""
}
if key == "" {
continue
}
value := ""
if i := strings.Index(key, "="); i >= 0 {
key, value = key[:i], key[i+1:]
}
key, err = url.QueryUnescape(key)
if err != nil {
// QueryUnescape returns an error like "invalid escape: '%x'".
// But frontends record these errors to prometheus, which generates
// a lot of time series.
// We log it here for debugging instead.
return nil, ErrInvalidQueryEscape
}
value, err = url.QueryUnescape(value)
if err != nil {
// QueryUnescape returns an error like "invalid escape: '%x'".
// But frontends record these errors to prometheus, which generates
// a lot of time series.
// We log it here for debugging instead.
return nil, ErrInvalidQueryEscape
}
if key == "info_hash" {
if ih, err := NewInfoHash(value); err == nil {
q.infoHashes = append(q.infoHashes, ih)
} else {
return nil, err
}
} else {
q.params[strings.ToLower(key)] = value
}
}
return q, nil
}
// String returns a string parsed from a query. Every key can be returned as a
// string because they are encoded in the URL as strings.
func (qp QueryParams) String(key string) (string, bool) {
value, ok := qp.params[strings.ToLower(key)]
return value, ok
}
// Uint returns an uint parsed from a query. After being called, it is safe to
// cast the uint64 to your desired length.
func (qp QueryParams) Uint(key string, bitSize int) (uint64, error) {
str, exists := qp.params[strings.ToLower(key)]
if !exists {
return 0, ErrKeyNotFound
}
return strconv.ParseUint(str, 10, bitSize)
}
// InfoHashes returns a list of requested infohashes.
func (qp QueryParams) InfoHashes() []InfoHash {
return qp.infoHashes
}
// RawPath returns the raw path from the parsed URL.
func (qp QueryParams) RawPath() string {
return qp.path
}
// RawQuery returns the raw query from the parsed URL.
func (qp QueryParams) RawQuery() string {
return qp.query
}
// MarshalZerologObject writes fields into zerolog event
func (qp QueryParams) MarshalZerologObject(e *zerolog.Event) {
e.Str("path", qp.path).Str("query", qp.query)
}
+9 -1
View File
@@ -88,7 +88,15 @@ func NewInfoHash(data any) (InfoHash, error) {
case [InfoHashV2Len]byte:
ba = t[:]
case []byte:
ba = t
l := len(t)
if l == InfoHashV1Len*2 || l == InfoHashV2Len*2 {
ba = make([]byte, l/2)
if _, err := hex.Decode(ba, t); err != nil {
return NoneInfoHash, err
}
} else {
ba = t
}
case string:
l := len(t)
if l == InfoHashV1Len*2 || l == InfoHashV2Len*2 {
+6 -4
View File
@@ -36,11 +36,12 @@ frontends:
# Enable SO_REUSEPORT to allow starting multiple mochi instances with the same HTTP(S) port.
# You can also use this parameter to define two or more listeners or separate processes
# for the same address and port, and (possibly) increase throughput (faster queue processing
# because of multiple 'workers').
# because of multiple processes).
reuse_port: true
# Number of connection listeners to start. See reuse_port, default is 1.
workers: 1
# For http frontend it's number of concurrent connections.
# Default is 262144.
workers: 0
# The timeout durations for HTTP requests.
read_timeout: 5s
@@ -119,7 +120,8 @@ frontends:
# because of multiple 'workers').
reuse_port: true
# Number of connection listeners to start. See reuse_port, default is 1.
# For udp frontend it's number of listen goroutines to be used with reuse_port option.
# Default is 1.
workers: 1
# The leeway for a timestamp on a connection ID.
-1
View File
@@ -14,7 +14,6 @@ frontends:
tls_cert_path: ""
tls_key_path: ""
reuse_port: true
workers: 1
read_timeout: 5s
write_timeout: 5s
enable_keepalive: false
-1
View File
@@ -14,7 +14,6 @@ frontends:
tls_cert_path: ""
tls_key_path: ""
reuse_port: true
workers: 1
read_timeout: 5s
write_timeout: 5s
enable_keepalive: false
+93 -89
View File
@@ -3,16 +3,15 @@
package http
import (
"context"
"crypto/tls"
"errors"
"io"
"net/http"
"net/netip"
"path"
"sync"
"time"
"github.com/julienschmidt/httprouter"
"github.com/valyala/fasthttp"
"github.com/sot-tech/mochi/bittorrent"
"github.com/sot-tech/mochi/frontend"
@@ -37,6 +36,8 @@ func init() {
// Config represents all configurable options for an HTTP BitTorrent Frontend
type Config struct {
frontend.ListenOptions
ReadTimeout time.Duration `cfg:"read_timeout"`
WriteTimeout time.Duration `cfg:"write_timeout"`
IdleTimeout time.Duration `cfg:"idle_timeout"`
EnableKeepAlive bool `cfg:"enable_keepalive"`
UseTLS bool `cfg:"tls"`
@@ -49,6 +50,8 @@ type Config struct {
}
const (
defaultReadTimeout = 2 * time.Second
defaultWriteTimeout = 2 * time.Second
defaultIdleTimeout = 30 * time.Second
defaultAnnounceRoute = "/announce"
defaultScrapeRoute = "/scrape"
@@ -58,11 +61,30 @@ const (
// default values replacing anything that is invalid.
func (cfg Config) Validate() (validCfg Config, err error) {
validCfg = cfg
validCfg.ListenOptions = cfg.ListenOptions.Validate(false, logger)
validCfg.ListenOptions = cfg.ListenOptions.Validate(logger)
if cfg.UseTLS && (len(cfg.TLSCertPath) == 0 || len(cfg.TLSKeyPath) == 0) {
err = errTLSNotProvided
return
}
if cfg.ReadTimeout <= 0 {
validCfg.ReadTimeout = defaultReadTimeout
logger.Warn().
Str("name", "ReadTimeout").
Dur("provided", cfg.ReadTimeout).
Dur("default", validCfg.ReadTimeout).
Msg("falling back to default configuration")
}
if cfg.WriteTimeout <= 0 {
validCfg.WriteTimeout = defaultWriteTimeout
logger.Warn().
Str("name", "WriteTimeout").
Dur("provided", cfg.WriteTimeout).
Dur("default", validCfg.WriteTimeout).
Msg("falling back to default configuration")
}
if cfg.IdleTimeout <= 0 {
validCfg.IdleTimeout = defaultIdleTimeout
if cfg.EnableKeepAlive {
@@ -94,20 +116,8 @@ func (cfg Config) Validate() (validCfg Config, err error) {
return
}
// httpServer replaces http.Close method with http.Shutdown
type httpServer struct {
*http.Server
}
func (c httpServer) Close() (err error) {
if c.Server != nil {
err = c.Shutdown(context.Background())
}
return
}
type httpFE struct {
servers []httpServer
*fasthttp.Server
logic *middleware.Logic
collectTimings bool
onceCloser sync.Once
@@ -128,20 +138,17 @@ func NewFrontend(c conf.MapConfig, logic *middleware.Logic) (frontend.Frontend,
f := &httpFE{
logic: logic,
servers: make([]httpServer, cfg.Workers),
collectTimings: cfg.EnableRequestTiming,
ParseOptions: cfg.ParseOptions,
}
for i := range f.servers {
f.servers[i] = httpServer{
&http.Server{
ReadTimeout: cfg.ReadTimeout,
ReadHeaderTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout,
IdleTimeout: cfg.IdleTimeout,
},
}
Server: &fasthttp.Server{
ReadTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout,
IdleTimeout: cfg.IdleTimeout,
Concurrency: int(cfg.Workers),
DisableKeepalive: !cfg.EnableKeepAlive,
GetOnly: true,
Logger: logger,
},
}
// If TLS is enabled, create a key pair.
@@ -151,36 +158,50 @@ func NewFrontend(c conf.MapConfig, logic *middleware.Logic) (frontend.Frontend,
return nil, err
}
certs := []tls.Certificate{cert}
for i := range f.servers {
f.servers[i].TLSConfig = &tls.Config{
Certificates: certs,
MinVersion: tls.VersionTLS12,
}
f.Server.TLSConfig = &tls.Config{
Certificates: certs,
MinVersion: tls.VersionTLS12,
}
}
router := httprouter.New()
pathRouting := make(map[string]func(*fasthttp.RequestCtx),
len(cfg.AnnounceRoutes)+len(cfg.ScrapeRoutes)+len(cfg.PingRoutes))
for _, route := range cfg.AnnounceRoutes {
router.GET(route, f.announceRoute)
route = path.Clean(route)
if !path.IsAbs(route) {
route = "/" + route
}
pathRouting[route] = f.announceRoute
}
for _, route := range cfg.ScrapeRoutes {
router.GET(route, f.scrapeRoute)
route = path.Clean(route)
if !path.IsAbs(route) {
route = "/" + route
}
pathRouting[path.Clean(route)] = f.scrapeRoute
}
for _, route := range cfg.PingRoutes {
router.GET(route, f.ping)
router.HEAD(route, f.ping)
route = path.Clean(route)
if !path.IsAbs(route) {
route = "/" + route
}
pathRouting[path.Clean(route)] = f.ping
}
for _, srv := range f.servers {
srv.Handler = router
srv.SetKeepAlivesEnabled(cfg.EnableKeepAlive)
go runServer(srv, &cfg)
f.Server.Handler = func(ctx *fasthttp.RequestCtx) {
if route, exists := pathRouting[string(ctx.Path())]; exists {
route(ctx)
} else {
ctx.NotFound()
}
}
go runServer(f.Server, &cfg)
return f, nil
}
func runServer(s httpServer, cfg *Config) {
func runServer(s *fasthttp.Server, cfg *Config) {
ln, err := cfg.ListenTCP()
if err == nil {
if s.TLSConfig == nil {
@@ -197,30 +218,20 @@ func runServer(s httpServer, cfg *Config) {
// Close provides a thread-safe way to gracefully shut down a currently running Frontend.
func (f *httpFE) Close() (err error) {
f.onceCloser.Do(func() {
cls := make([]io.Closer, len(f.servers))
for i, s := range f.servers {
cls[i] = s
if f.Server != nil {
err = f.Server.Shutdown()
}
err = frontend.CloseGroup(cls)
})
return
}
func httpParamsToRouteParams(in httprouter.Params) (out bittorrent.RouteParams) {
out = make([]bittorrent.RouteParam, 0, len(in))
for _, p := range in {
out = append(out, bittorrent.RouteParam{Key: p.Key, Value: p.Value})
}
return
}
// announceRoute parses and responds to an Announce.
func (f *httpFE) announceRoute(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
func (f *httpFE) announceRoute(reqCtx *fasthttp.RequestCtx) {
var err error
var start time.Time
var addr netip.Addr
var req *bittorrent.AnnounceRequest
var aReq *bittorrent.AnnounceRequest
if f.collectTimings && metrics.Enabled() {
start = time.Now()
defer func() {
@@ -228,34 +239,32 @@ func (f *httpFE) announceRoute(w http.ResponseWriter, r *http.Request, ps httpro
}()
}
req, err = ParseAnnounce(r, f.ParseOptions)
aReq, err = ParseAnnounce(reqCtx, f.ParseOptions)
if err != nil {
WriteError(w, err)
writeErrorResponse(reqCtx, err)
return
}
addr = req.GetFirst()
addr = aReq.GetFirst()
ctx := bittorrent.InjectRouteParamsToContext(r.Context(), httpParamsToRouteParams(ps))
ctx, resp, err := f.logic.HandleAnnounce(ctx, req)
ctx := bittorrent.InjectRouteParamsToContext(reqCtx, nil)
ctx, aResp, err := f.logic.HandleAnnounce(ctx, aReq)
if err != nil {
WriteError(w, err)
writeErrorResponse(reqCtx, err)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
err = WriteAnnounceResponse(w, resp)
if err != nil {
WriteError(w, err)
return
}
reqCtx.Response.Header.Set("Content-Type", "text/plain; charset=utf-8")
writeAnnounceResponse(reqCtx, aResp)
// next actions are background and should not be canceled after http writer closed
ctx = bittorrent.RemapRouteParamsToBgContext(ctx)
go f.logic.AfterAnnounce(ctx, req, resp)
// params mapped from fasthttp.QueryArgs will in the next request
aReq.Params = nil
go f.logic.AfterAnnounce(ctx, aReq, aResp)
}
// scrapeRoute parses and responds to a Scrape.
func (f *httpFE) scrapeRoute(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
func (f *httpFE) scrapeRoute(reqCtx *fasthttp.RequestCtx) {
var err error
var start time.Time
var addr netip.Addr
@@ -266,47 +275,42 @@ func (f *httpFE) scrapeRoute(w http.ResponseWriter, r *http.Request, ps httprout
}()
}
req, err := ParseScrape(r, f.ParseOptions)
req, err := ParseScrape(reqCtx, f.ParseOptions)
if err != nil {
WriteError(w, err)
writeErrorResponse(reqCtx, err)
return
}
addr = req.GetFirst()
ctx := bittorrent.InjectRouteParamsToContext(r.Context(), httpParamsToRouteParams(ps))
ctx := bittorrent.InjectRouteParamsToContext(reqCtx, nil)
ctx, resp, err := f.logic.HandleScrape(ctx, req)
if err != nil {
WriteError(w, err)
writeErrorResponse(reqCtx, err)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
err = WriteScrapeResponse(w, resp)
if err != nil {
WriteError(w, err)
return
}
reqCtx.Response.Header.Set("Content-Type", "text/plain; charset=utf-8")
writeScrapeResponse(reqCtx, resp)
// next actions are background and should not be canceled after http writer closed
ctx = bittorrent.RemapRouteParamsToBgContext(ctx)
// params mapped from fasthttp.QueryArgs will in the next request
req.Params = nil
go f.logic.AfterScrape(ctx, req, resp)
}
func (f *httpFE) ping(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
func (f *httpFE) ping(ctx *fasthttp.RequestCtx) {
var err error
status := http.StatusOK
ctx := r.Context()
if r.Method == http.MethodGet {
err = f.logic.Ping(ctx)
}
err = f.logic.Ping(ctx)
if err != nil {
logger.Error().Err(err).Msg("ping completed with error")
status = http.StatusServiceUnavailable
}
if ctxErr := ctx.Err(); ctxErr == nil {
w.WriteHeader(status)
ctx.SetStatusCode(status)
} else {
logger.Info().Err(ctxErr).Str("ip", r.RemoteAddr).Msg("ping request cancelled")
logger.Info().Err(ctxErr).Stringer("addr", ctx.RemoteAddr()).Msg("ping request cancelled")
}
}
+131
View File
@@ -0,0 +1,131 @@
package http
import (
cr "crypto/rand"
"encoding/hex"
"errors"
"fmt"
"github.com/sot-tech/mochi/bittorrent"
"github.com/sot-tech/mochi/middleware"
"github.com/sot-tech/mochi/pkg/log"
"io"
"math/rand"
"net/http"
"net/url"
"strings"
"testing"
)
const iterations = 10000
var (
addr = fmt.Sprintf("127.0.0.1:%d", rand.Int63n(10000)+16384)
hashes = make([]string, 10)
peers = make([]string, 10)
)
func init() {
_ = log.ConfigureLogger("", "error", false, false)
for i := range hashes {
var bb []byte
if rand.Int()%2 == 0 {
bb = make([]byte, bittorrent.InfoHashV1Len)
} else {
bb = make([]byte, bittorrent.InfoHashV2Len)
}
if _, err := cr.Read(bb); err != nil {
panic(err)
}
hashes[i] = hex.EncodeToString(bb)
}
for i := range peers {
bb := make([]byte, bittorrent.PeerIDLen)
if _, err := cr.Read(bb); err != nil {
panic(err)
}
peers[i] = string(bb)
}
_, err := NewFrontend(map[string]any{
"addr": addr,
"enable_keepalive": true,
"ping_routes": []string{"ping"},
}, &middleware.Logic{})
if err != nil {
panic(err)
}
}
func runGet(u string, checkResponse bool) (err error) {
var r *http.Response
if r, err = http.Get(u); err == nil {
if r.StatusCode < 400 {
if checkResponse {
var out []byte
if out, err = io.ReadAll(r.Body); err == nil {
sout := string(out)
if strings.Contains(sout, "failure reason") {
return errors.New(sout)
}
}
} else {
_ = r.Body.Close()
}
} else {
return errors.New(r.Status)
}
}
return
}
func BenchmarkPing(b *testing.B) {
u := url.URL{
Scheme: "http",
Host: addr,
Path: "ping",
}
us := u.String()
for i := 0; i < iterations; i++ {
if err := runGet(us, false); err != nil {
b.Error(err)
}
}
}
func BenchmarkAnnounce(b *testing.B) {
for i := 0; i < iterations; i++ {
u := url.URL{
Scheme: "http",
Host: addr,
Path: defaultAnnounceRoute,
RawQuery: url.Values{
"event": []string{bittorrent.StartedStr},
"compact": []string{"1"},
"left": []string{"100"},
"downloaded": []string{"0"},
"uploaded": []string{"0"},
"numwant": []string{"1"},
"port": []string{"12345"},
"info_hash": []string{hashes[rand.Intn(len(hashes))]},
"peer_id": []string{peers[rand.Intn(len(peers))]},
}.Encode(),
}
if err := runGet(u.String(), true); err != nil {
b.Error(err)
}
}
}
func BenchmarkScrape(b *testing.B) {
for i := 0; i < iterations; i++ {
u := url.URL{
Scheme: "http",
Host: addr,
Path: defaultScrapeRoute,
RawQuery: url.Values{"info_hash": hashes[:len(hashes)/2]}.Encode(),
}
if err := runGet(u.String(), true); err != nil {
b.Error(err)
}
}
}
+66
View File
@@ -0,0 +1,66 @@
package http
import (
"bytes"
"github.com/rs/zerolog"
"github.com/sot-tech/mochi/bittorrent"
"github.com/valyala/fasthttp"
)
// queryParams parses a URL Query and implements the Params interface with some
// additional helpers.
type queryParams struct {
*fasthttp.Args
}
// parseURLData parses a request URL or UDP URLData as defined in BEP41.
// It expects a concatenated string of the request's path and query parts as
// defined in RFC 3986. As both the udp: and http: scheme used by BitTorrent
// include an authority part the path part must always begin with a slash.
// An example of the expected URLData would be "/announce?port=1234&uploaded=0"
// or "/?auth=0x1337".
// HTTP servers should pass (*http.Request).RequestURI, UDP servers should
// pass the concatenated, unchanged URLData as defined in BEP41.
//
// Note that, in the case of a key occurring multiple times in the query, only
// the last value for that key is kept.
// The only exception to this rule is the key "info_hash" which will attempt to
// parse each value as an InfoHash and return an error if parsing fails. All
// InfoHashes are collected and can later be retrieved by calling the InfoHashes
// method.
//
// Also note that any error that is encountered during parsing is returned as a
// ClientError, as this method is expected to be used to parse client-provided
// data.
func parseURLData(urlData []byte) (*queryParams, error) {
i := bytes.IndexByte(urlData, '?')
if i >= 0 {
urlData = urlData[i+1:]
}
q := &queryParams{new(fasthttp.Args)}
q.ParseBytes(urlData)
return q, nil
}
// String returns a string parsed from a query. Every key can be returned as a
// string because they are encoded in the URL as strings.
func (qp queryParams) String(key string) (string, bool) {
v := qp.Peek(key)
return string(v), v != nil
}
// InfoHashes returns a list of requested infohashes.
func (qp queryParams) InfoHashes() bittorrent.InfoHashes {
var ihs bittorrent.InfoHashes
for _, bb := range qp.PeekMulti("info_hash") {
if ih, err := bittorrent.NewInfoHash(bb); err == nil {
ihs = append(ihs, ih)
}
}
return ihs
}
// MarshalZerologObject writes fields into zerolog event
func (qp queryParams) MarshalZerologObject(e *zerolog.Event) {
e.Stringer("query", qp.Args)
}
@@ -1,4 +1,4 @@
package bittorrent
package http
import (
"net/url"
@@ -24,34 +24,34 @@ var (
{"peer_id": {""}, "compact": {""}},
}
InvalidQueries = []string{
"/announce?" + "info_hash=%0%a",
InvalidQueries = [][]byte{
[]byte("/announce?info_hash=%0%a"),
}
// See https://github.com/chihaya/chihaya/issues/334.
shouldNotPanicQueries = []string{
"/annnounce?" + "info_hash=" + testPeerID + "&a",
"/annnounce?" + "info_hash=" + testPeerID + "&=b?",
shouldNotPanicQueries = [][]byte{
[]byte("/annnounce?info_hash=" + testPeerID + "&a"),
[]byte("/annnounce?info_hash=" + testPeerID + "&=b?"),
}
)
func mapArrayEqual(boxed map[string][]string, unboxed map[string]string) bool {
if len(boxed) != len(unboxed) {
return false
func mapArrayEqual(boxed map[string][]string, unboxed *queryParams) (bool, string) {
if len(boxed) != unboxed.Len() {
return false, ""
}
for mapKey, mapVal := range boxed {
// Always expect box to hold only one element
if len(mapVal) != 1 || mapVal[0] != unboxed[mapKey] {
return false
if len(mapVal) != 1 || mapVal[0] != string(unboxed.Peek(mapKey)) {
return false, mapVal[0]
}
}
return true
return true, ""
}
func TestParseEmptyURLData(t *testing.T) {
parsedQuery, err := ParseURLData("")
parsedQuery, err := parseURLData(nil)
if err != nil {
t.Fatal(err)
}
@@ -62,49 +62,32 @@ func TestParseEmptyURLData(t *testing.T) {
func TestParseValidURLData(t *testing.T) {
for parseIndex, parseVal := range ValidAnnounceArguments {
parsedQueryObj, err := ParseURLData("/announce?" + parseVal.Encode())
parsedQueryObj, err := parseURLData([]byte("/announce?" + parseVal.Encode()))
if err != nil {
t.Fatal(err)
}
if !mapArrayEqual(parseVal, parsedQueryObj.params) {
t.Fatalf("Incorrect parse at item %d.\n Expected=%v\n Received=%v\n", parseIndex, parseVal, parsedQueryObj.params)
}
if parsedQueryObj.path != "/announce" {
t.Fatalf("Incorrect path, expected %q, got %q", "/announce", parsedQueryObj.path)
}
}
}
func TestParseInvalidURLData(t *testing.T) {
for parseIndex, parseStr := range InvalidQueries {
parsedQueryObj, err := ParseURLData(parseStr)
if err == nil {
t.Fatal("Should have produced error", parseIndex)
}
if parsedQueryObj != nil {
t.Fatal("Should be nil after error", parsedQueryObj, parseIndex)
if eq, exp := mapArrayEqual(parseVal, parsedQueryObj); !eq {
t.Fatalf("Incorrect parse at item %d.\n Expected=%v\n Received=%v\n", parseIndex, parseVal, exp)
}
}
}
func TestParseShouldNotPanicURLData(t *testing.T) {
for _, parseStr := range shouldNotPanicQueries {
_, _ = ParseURLData(parseStr)
_, _ = parseURLData(parseStr)
}
}
func BenchmarkParseQuery(b *testing.B) {
announceStrings := make([]string, 0)
announceStrings := make([][]byte, 0)
for i := range ValidAnnounceArguments {
announceStrings = append(announceStrings, ValidAnnounceArguments[i].Encode())
announceStrings = append(announceStrings, []byte(ValidAnnounceArguments[i].Encode()))
}
b.ResetTimer()
for bCount := 0; bCount < b.N; bCount++ {
i := bCount % len(announceStrings)
parsedQueryObj, err := parseQuery(announceStrings[i])
parsedQueryObj, err := parseURLData(announceStrings[i])
if err != nil {
b.Error(err, i)
b.Log(parsedQueryObj)
+32 -38
View File
@@ -1,13 +1,12 @@
package http
import (
"bytes"
"errors"
"net/http"
"net/netip"
"strings"
"github.com/sot-tech/mochi/bittorrent"
"github.com/sot-tech/mochi/frontend"
"github.com/valyala/fasthttp"
"net/netip"
)
// ParseOptions is the configuration used to parse an Announce Request.
@@ -31,16 +30,14 @@ var (
)
// ParseAnnounce parses an bittorrent.AnnounceRequest from an http.Request.
func ParseAnnounce(r *http.Request, opts ParseOptions) (*bittorrent.AnnounceRequest, error) {
qp, err := bittorrent.ParseURLData(r.RequestURI)
if err != nil {
return nil, err
}
func ParseAnnounce(r *fasthttp.RequestCtx, opts ParseOptions) (*bittorrent.AnnounceRequest, error) {
qp := &queryParams{r.QueryArgs()}
request := &bittorrent.AnnounceRequest{Params: qp}
// Attempt to parse the event from the request.
var eventStr string
var err error
eventStr, request.EventProvided = qp.String("event")
if request.EventProvided {
if request.Event, err = bittorrent.NewEvent(eventStr); err != nil {
@@ -50,10 +47,6 @@ func ParseAnnounce(r *http.Request, opts ParseOptions) (*bittorrent.AnnounceRequ
request.Event = bittorrent.None
}
// Determine if the client expects a compact response.
compactStr, _ := qp.String("compact")
request.Compact = compactStr != "" && compactStr != "0"
// Parse the info hash from the request.
infoHashes := qp.InfoHashes()
if len(infoHashes) < 1 {
@@ -62,50 +55,53 @@ func ParseAnnounce(r *http.Request, opts ParseOptions) (*bittorrent.AnnounceRequ
if len(infoHashes) > 1 {
return nil, errMultipleInfoHashes
}
//FIXME: make sure that we have a copy of InfoHash
request.InfoHash = infoHashes[0]
// Determine if the client expects a compact response.
request.Compact = qp.GetBool("compact")
// Parse the PeerID from the request.
peerID, ok := qp.String("peer_id")
if !ok {
return nil, errInvalidPeerID
}
request.ID, err = bittorrent.NewPeerID([]byte(peerID))
request.ID, err = bittorrent.NewPeerID(qp.Peek("peer_id"))
if err != nil {
return nil, errInvalidPeerID
}
// Determine the number of remaining bytes for the client.
request.Left, err = qp.Uint("left", 64)
n, err := qp.GetUint("left")
if err != nil {
return nil, errInvalidParameterLeft
}
request.Left = uint64(n)
// Determine the number of bytes downloaded by the client.
request.Downloaded, err = qp.Uint("downloaded", 64)
n, err = qp.GetUint("downloaded")
if err != nil {
return nil, errInvalidParameterDownloaded
}
request.Downloaded = uint64(n)
// Determine the number of bytes shared by the client.
request.Uploaded, err = qp.Uint("uploaded", 64)
n, err = qp.GetUint("uploaded")
if err != nil {
return nil, errInvalidParameterUploaded
}
request.Uploaded = uint64(n)
// Determine the number of peers the client wants in the response.
numWant, err := qp.Uint("numwant", 32)
if err != nil && !errors.Is(err, bittorrent.ErrKeyNotFound) {
n, err = qp.GetUint("numwant")
if err != nil && !errors.Is(err, fasthttp.ErrNoArgValue) {
return nil, errInvalidParameterNumWant
}
// If there were no errors, the user actually provided the numWant.
request.NumWantProvided = err == nil
request.NumWant = uint32(numWant)
request.NumWant = uint32(n)
// Parse the port where the client is listening.
port, err := qp.Uint("port", 16)
n, err = qp.GetUint("port")
if err != nil {
return nil, bittorrent.ErrInvalidPort
}
request.Port = uint16(port)
request.Port = uint16(n)
// Parse the IP address where the client is listening.
request.RequestAddresses = requestedIPs(r, qp, opts)
@@ -118,11 +114,8 @@ func ParseAnnounce(r *http.Request, opts ParseOptions) (*bittorrent.AnnounceRequ
}
// ParseScrape parses an bittorrent.ScrapeRequest from an http.Request.
func ParseScrape(r *http.Request, opts ParseOptions) (*bittorrent.ScrapeRequest, error) {
qp, err := bittorrent.ParseURLData(r.RequestURI)
if err != nil {
return nil, err
}
func ParseScrape(r *fasthttp.RequestCtx, opts ParseOptions) (*bittorrent.ScrapeRequest, error) {
qp := &queryParams{r.QueryArgs()}
infoHashes := qp.InfoHashes()
if len(infoHashes) < 1 {
@@ -130,18 +123,19 @@ func ParseScrape(r *http.Request, opts ParseOptions) (*bittorrent.ScrapeRequest,
}
request := &bittorrent.ScrapeRequest{
//FIXME: make sure that we have a copy of InfoHashes
InfoHashes: infoHashes,
Params: qp,
RequestAddresses: requestedIPs(r, qp, opts),
}
err = bittorrent.SanitizeScrape(request, opts.MaxScrapeInfoHashes, opts.FilterPrivateIPs)
err := bittorrent.SanitizeScrape(request, opts.MaxScrapeInfoHashes, opts.FilterPrivateIPs)
return request, err
}
// requestedIPs determines the IP address for a BitTorrent client request.
func requestedIPs(r *http.Request, p bittorrent.Params, opts ParseOptions) (addresses bittorrent.RequestAddresses) {
func requestedIPs(r *fasthttp.RequestCtx, p *queryParams, opts ParseOptions) (addresses bittorrent.RequestAddresses) {
if opts.AllowIPSpoofing {
for _, f := range []string{"ip", "ipv4", "ipv6"} {
if ipStr, ok := p.String(f); ok {
@@ -150,16 +144,16 @@ func requestedIPs(r *http.Request, p bittorrent.Params, opts ParseOptions) (addr
}
}
if ipValues := r.Header.Values(opts.RealIPHeader); len(ipValues) > 0 && opts.RealIPHeader != "" {
if ipValues := r.Request.Header.PeekAll(opts.RealIPHeader); len(ipValues) > 0 && opts.RealIPHeader != "" {
for _, ipStr := range ipValues {
for _, ipStr := range strings.Split(ipStr, ",") {
if ipStr = strings.TrimSpace(ipStr); len(ipStr) > 0 {
addresses.Add(parseRequestAddress(ipStr, false))
for _, ipStr := range bytes.Split(ipStr, []byte{','}) {
if ipStr = bytes.TrimSpace(ipStr); len(ipStr) > 0 {
addresses.Add(parseRequestAddress(string(ipStr), false))
}
}
}
} else {
addrPort, _ := netip.ParseAddrPort(r.RemoteAddr)
addrPort, _ := netip.ParseAddrPort(r.RemoteAddr().String())
addresses.Add(bittorrent.RequestAddress{
Addr: addrPort.Addr(),
Provided: false,
+23 -30
View File
@@ -3,8 +3,9 @@ package http
import (
"bytes"
"errors"
"github.com/valyala/fasthttp"
"io"
"net"
"net/http"
"strconv"
"time"
@@ -14,21 +15,18 @@ import (
var respBufferPool = bytepool.NewBufferPool()
// WriteError communicates an error to a BitTorrent client over HTTP.
func WriteError(w http.ResponseWriter, err error) {
func writeErrorResponse(w io.StringWriter, err error) {
message := "internal server error"
var clientErr bittorrent.ClientError
if errors.As(err, &clientErr) {
message = clientErr.Error()
} else {
logger.Error().Err(err).Msg("http: internal error")
logger.Error().Err(err).Msg("internal error")
}
_, _ = w.Write([]byte("d14:failure reason" + strconv.Itoa(len(message)) + ":" + message + "e"))
_, _ = w.WriteString("d14:failure reason" + strconv.Itoa(len(message)) + ":" + message + "e")
}
// WriteAnnounceResponse communicates the results of an Announce to a
// BitTorrent client over HTTP.
func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceResponse) error {
func writeAnnounceResponse(w io.Writer, resp *bittorrent.AnnounceResponse) {
bb := respBufferPool.Get()
defer respBufferPool.Put(bb)
@@ -40,13 +38,13 @@ func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceRespo
}
bb.WriteString("d8:completei")
bb.WriteString(strconv.FormatUint(uint64(resp.Complete), 10))
bb.Write(fasthttp.AppendUint(nil, int(resp.Complete)))
bb.WriteString("e10:incompletei")
bb.WriteString(strconv.FormatUint(uint64(resp.Incomplete), 10))
bb.Write(fasthttp.AppendUint(nil, int(resp.Incomplete)))
bb.WriteString("e8:intervali")
bb.WriteString(strconv.FormatUint(uint64(resp.Interval), 10))
bb.Write(fasthttp.AppendUint(nil, int(resp.Interval)))
bb.WriteString("e12:min intervali")
bb.WriteString(strconv.FormatUint(uint64(resp.MinInterval), 10))
bb.Write(fasthttp.AppendUint(nil, int(resp.MinInterval)))
bb.WriteByte('e')
// Add the peers to the dictionary in the compact format.
@@ -68,8 +66,7 @@ func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceRespo
}
bb.WriteByte('e')
_, err := bb.WriteTo(w)
return err
_, _ = bb.WriteTo(w)
}
func compactAddresses(bb *bytes.Buffer, peers bittorrent.Peers, v6 bool) {
@@ -80,13 +77,12 @@ func compactAddresses(bb *bytes.Buffer, peers bittorrent.Peers, v6 bool) {
key, al = "6:peers6", net.IPv6len
}
bb.WriteString(key)
bb.WriteString(strconv.Itoa((al + 2) * l))
bb.Write(fasthttp.AppendUint(nil, (al+2)*l))
bb.WriteByte(':')
for _, peer := range peers {
bb.Write(peer.Addr().AsSlice())
port := peer.Port()
bb.WriteByte(byte(port >> 8))
bb.WriteByte(byte(port))
bb.Write([]byte{byte(port >> 8), byte(port)})
}
}
}
@@ -94,35 +90,32 @@ func compactAddresses(bb *bytes.Buffer, peers bittorrent.Peers, v6 bool) {
func dictAddress(bb *bytes.Buffer, peer bittorrent.Peer) {
bb.WriteString("d2:ip")
addr := peer.Addr().String()
bb.WriteString(strconv.Itoa(len(addr)))
bb.Write(fasthttp.AppendUint(nil, len(addr)))
bb.WriteByte(':')
bb.WriteString(addr)
bb.WriteString("7:peer id20:")
bb.Write(peer.ID[:])
bb.WriteString("4:porti")
bb.WriteString(strconv.FormatUint(uint64(peer.Port()), 10))
bb.WriteString("ee")
bb.Write(fasthttp.AppendUint(nil, int(peer.Port())))
bb.Write([]byte{'e', 'e'})
}
// WriteScrapeResponse communicates the results of a Scrape to a BitTorrent
// client over HTTP.
func WriteScrapeResponse(w http.ResponseWriter, resp *bittorrent.ScrapeResponse) error {
func writeScrapeResponse(w io.Writer, resp *bittorrent.ScrapeResponse) {
bb := respBufferPool.Get()
defer respBufferPool.Put(bb)
bb.WriteString("d5:filesd")
for _, scrape := range resp.Files {
bb.WriteString(strconv.Itoa(len(scrape.InfoHash)))
bb.Write(fasthttp.AppendUint(nil, len(scrape.InfoHash)))
bb.WriteByte(':')
bb.Write([]byte(scrape.InfoHash))
bb.WriteString("d8:completei")
bb.WriteString(strconv.FormatUint(uint64(scrape.Complete), 10))
bb.Write(fasthttp.AppendUint(nil, int(scrape.Complete)))
bb.WriteString("e10:downloadedi")
bb.WriteString(strconv.FormatUint(uint64(scrape.Snatches), 10))
bb.Write(fasthttp.AppendUint(nil, int(scrape.Snatches)))
bb.WriteString("e10:incompletei")
bb.WriteString(strconv.FormatUint(uint64(scrape.Incomplete), 10))
bb.Write(fasthttp.AppendUint(nil, int(scrape.Incomplete)))
bb.WriteString("ee")
}
bb.WriteString("ee")
_, err := bb.WriteTo(w)
return err
bb.Write([]byte{'e', 'e'})
_, _ = bb.WriteTo(w)
}
+2 -2
View File
@@ -26,7 +26,7 @@ func TestWriteError(t *testing.T) {
for _, tt := range table {
t.Run(fmt.Sprintf("%s expecting %s", tt.reason, tt.expected), func(t *testing.T) {
r := httptest.NewRecorder()
WriteError(r, bittorrent.ClientError(tt.reason))
writeErrorResponse(r, bittorrent.ClientError(tt.reason))
require.Equal(t, r.Body.String(), tt.expected)
})
}
@@ -42,7 +42,7 @@ func TestWriteStatus(t *testing.T) {
for _, tt := range table {
t.Run(fmt.Sprintf("%s expecting %s", tt.reason, tt.expected), func(t *testing.T) {
r := httptest.NewRecorder()
WriteError(r, bittorrent.ClientError(tt.reason))
writeErrorResponse(r, bittorrent.ClientError(tt.reason))
require.Equal(t, r.Body.String(), tt.expected)
})
}
+4 -38
View File
@@ -2,18 +2,12 @@ package frontend
import (
"errors"
"net"
"time"
"github.com/libp2p/go-reuseport"
"github.com/sot-tech/mochi/pkg/log"
"net"
)
const (
defaultReadTimeout = 2 * time.Second
defaultWriteTimeout = 2 * time.Second
defaultListenAddress = ":6969"
)
const defaultListenAddress = ":6969"
var errUnexpectedListenerType = errors.New("unexpected listener type")
@@ -22,14 +16,12 @@ type ListenOptions struct {
Addr string
ReusePort bool `cfg:"reuse_port"`
Workers uint
ReadTimeout time.Duration `cfg:"read_timeout"`
WriteTimeout time.Duration `cfg:"write_timeout"`
EnableRequestTiming bool `cfg:"enable_request_timing"`
EnableRequestTiming bool `cfg:"enable_request_timing"`
}
// Validate checks if listen address provided and sets default
// timeout options if needed
func (lo ListenOptions) Validate(ignoreTimeouts bool, logger *log.Logger) (validOptions ListenOptions) {
func (lo ListenOptions) Validate(logger *log.Logger) (validOptions ListenOptions) {
validOptions = lo
if len(lo.Addr) == 0 {
validOptions.Addr = defaultListenAddress
@@ -39,32 +31,6 @@ func (lo ListenOptions) Validate(ignoreTimeouts bool, logger *log.Logger) (valid
Str("default", validOptions.Addr).
Msg("falling back to default configuration")
}
if lo.Workers == 0 {
validOptions.Workers = 1
}
if lo.Workers > 1 && !lo.ReusePort {
validOptions.ReusePort = true
logger.Warn().Msg("forcibly enabling ReusePort because Workers > 1")
}
if !ignoreTimeouts {
if lo.ReadTimeout <= 0 {
validOptions.ReadTimeout = defaultReadTimeout
logger.Warn().
Str("name", "ReadTimeout").
Dur("provided", lo.ReadTimeout).
Dur("default", validOptions.ReadTimeout).
Msg("falling back to default configuration")
}
if lo.WriteTimeout <= 0 {
validOptions.WriteTimeout = defaultWriteTimeout
logger.Warn().
Str("name", "WriteTimeout").
Dur("provided", lo.WriteTimeout).
Dur("default", validOptions.WriteTimeout).
Msg("falling back to default configuration")
}
}
return
}
+9 -1
View File
@@ -54,7 +54,15 @@ type Config struct {
// default values replacing anything that is invalid.
func (cfg Config) Validate() (validCfg Config) {
validCfg = cfg
validCfg.ListenOptions = cfg.ListenOptions.Validate(true, logger)
validCfg.ListenOptions = cfg.ListenOptions.Validate(logger)
if cfg.Workers == 0 {
cfg.Workers = 1
}
if cfg.Workers > 1 && !cfg.ReusePort {
cfg.ReusePort = true
logger.Warn().Msg("forcibly enabling ReusePort because Workers > 1")
}
// Generate a private key if one isn't provided by the user.
if cfg.PrivateKey == "" {
+14 -11
View File
@@ -3,13 +3,12 @@ module github.com/sot-tech/mochi
go 1.19
require (
code.cloudfoundry.org/go-diodes v0.0.0-20230222233121-2712486f12be
code.cloudfoundry.org/go-diodes v0.0.0-20230317203753-49f1af6d2f1a
github.com/MicahParks/keyfunc v1.9.0
github.com/anacrolix/torrent v1.48.0
github.com/cespare/xxhash/v2 v2.2.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/jackc/pgx/v5 v5.3.1
github.com/julienschmidt/httprouter v1.3.0
github.com/libp2p/go-reuseport v0.2.0
github.com/minio/sha256-simd v1.0.0
github.com/mitchellh/mapstructure v1.5.0
@@ -17,6 +16,7 @@ require (
github.com/redis/go-redis/v9 v9.0.2
github.com/rs/zerolog v1.29.0
github.com/stretchr/testify v1.8.2
github.com/valyala/fasthttp v1.45.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -25,31 +25,34 @@ require (
github.com/anacrolix/log v0.13.2-0.20221123232138-02e2764801c3 // indirect
github.com/anacrolix/missinggo v1.3.0 // indirect
github.com/anacrolix/missinggo/v2 v2.7.1 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/pprof v0.0.0-20230309165930-d61513b1440d // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.0 // indirect
github.com/klauspost/compress v1.16.3 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/onsi/ginkgo/v2 v2.8.4 // indirect
github.com/onsi/ginkgo/v2 v2.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.41.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
golang.org/x/crypto v0.6.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
)
+30 -24
View File
@@ -1,7 +1,7 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
code.cloudfoundry.org/go-diodes v0.0.0-20230222233121-2712486f12be h1:giafIIx83xPMKSFXt/RsSiZz3dGmENtrs+1DPTrwOqM=
code.cloudfoundry.org/go-diodes v0.0.0-20230222233121-2712486f12be/go.mod h1:7XxQ+IiO+UoOqgMzMXCQ5t0kvlEEq7QXaA7evlE20cU=
code.cloudfoundry.org/go-diodes v0.0.0-20230317203753-49f1af6d2f1a h1:ci46gsKkiAiqv9LjVgbVWRZer1Z5zxbQt1IGMkCjksE=
code.cloudfoundry.org/go-diodes v0.0.0-20230317203753-49f1af6d2f1a/go.mod h1:XfKqABWo2YXnsg9cp39TNwzSj0xPx/k2SbgNPE5Fgpc=
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk=
crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -42,6 +42,8 @@ github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pm
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
github.com/anacrolix/torrent v1.48.0 h1:OQe1aQb8WnhDzpcI7r3yWoHzHWKyPbfhXGfO9Q/pvbY=
github.com/anacrolix/torrent v1.48.0/go.mod h1:3UtkJ8BnxXDRwvk+eT+uwiZalfFJ8YzAhvxe4QRPSJI=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -110,8 +112,8 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -124,8 +126,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso=
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/google/pprof v0.0.0-20230309165930-d61513b1440d h1:um9/pc7tKMINFfP1eE7Wv6PRGXlcCSJkVajF7KJw3uQ=
github.com/google/pprof v0.0.0-20230309165930-d61513b1440d/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@@ -152,9 +154,9 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
@@ -192,10 +194,10 @@ github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOl
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo/v2 v2.8.4 h1:gf5mIQ8cLFieruNLAdgijHF1PYfLphKm2dxxcUtcqK0=
github.com/onsi/ginkgo/v2 v2.8.4/go.mod h1:427dEDQZkDKsBvCjc2A/ZPefhKxsTTrsQegMlayL730=
github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk=
github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754=
github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@@ -220,8 +222,8 @@ github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3d
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/common v0.41.0 h1:npo01n6vUlRViIj5fgwiK8vlNIh8bnoxqh3gypKsyAw=
github.com/prometheus/common v0.41.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -261,6 +263,10 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.45.0 h1:zPkkzpIn8tdHZUrVa6PzYd0i5verqiPSkgTd3bSUcpA=
github.com/valyala/fasthttp v1.45.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
@@ -268,8 +274,8 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -285,7 +291,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -311,20 +317,20 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@@ -342,8 +348,8 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-8
View File
@@ -61,14 +61,6 @@ func (p params) String(key string) (out string, found bool) {
return
}
func (params) RawPath() (s string) {
return
}
func (params) RawQuery() (s string) {
return
}
func (params) MarshalZerologObject(*zerolog.Event) {}
func init() {
+3 -7
View File
@@ -21,15 +21,11 @@ import (
var (
root = zl.Logger
rootWg = sync.WaitGroup{}
rootMu = sync.Mutex{}
customOut io.WriteCloser
customOutMu = sync.Mutex{}
)
func init() {
rootWg.Add(1)
}
// ConfigureLogger initializes root and all child loggers.
// NOTE: this function MUST be called before any child log call
//
@@ -70,9 +66,10 @@ func ConfigureLogger(output, level string, formatted, colored bool) (err error)
return err
}
}
rootMu.Lock()
defer rootMu.Unlock()
root = zerolog.New(w).With().Timestamp().Logger()
zerolog.SetGlobalLevel(lvl)
rootWg.Done()
return nil
}
@@ -87,7 +84,6 @@ type Logger struct {
func (l *Logger) init() {
l.zlOnce.Do(func() {
rootWg.Wait()
l.Logger = root.With().Str("component", l.comp).Logger()
})
}