diff --git a/cmd/alice/flags.go b/cmd/alice/flags.go index 3294b59..092214d 100644 --- a/cmd/alice/flags.go +++ b/cmd/alice/flags.go @@ -37,6 +37,7 @@ func flagsInitialization() []cli.Flag { Category: "Syslog settings", Usage: "optional setting; more information in syslog RFC", Value: "", + Hidden: true, }, // fiber-server settings @@ -63,6 +64,7 @@ func flagsInitialization() []cli.Flag { if enabled, the application will need to be ran through a shell because prefork mode sets environment variables; EXPERIMENTAL! USE CAREFULLY!`, + Hidden: true, DisableDefaultText: true, }, &cli.DurationFlag{ @@ -103,28 +105,33 @@ func flagsInitialization() []cli.Flag { &cli.BoolFlag{ Name: "limiter-enable", Category: "Limiter settings", + Hidden: true, DisableDefaultText: true, }, &cli.BoolFlag{ Name: "limiter-use-bbolt", Category: "Limiter settings", Usage: "use bbolt key\value file database instead of memory database", + Hidden: true, DisableDefaultText: true, }, &cli.BoolFlag{ Name: "limiter-bbolt-reset", Category: "Limiter settings", Usage: "if bbolt used as storage, reset all limited IPs on startup", + Hidden: true, DisableDefaultText: true, }, &cli.IntFlag{ Name: "limiter-max-req", Category: "Limiter settings", + Hidden: true, Value: 200, }, &cli.DurationFlag{ Name: "limiter-records-duration", Category: "Limiter settings", + Hidden: true, Value: 5 * time.Minute, }, @@ -258,8 +265,68 @@ func flagsInitialization() []cli.Flag { // "https://download.maxmind.com/geoip/databases/GeoLite2-Country/download?suffix=tar.gz.sha256" }, &cli.BoolFlag{ - Name: "geoip-download-sha256-skip", - Category: "GeoIP", + Name: "geoip-download-sha256-skip", + Category: "GeoIP", + DisableDefaultText: true, + }, + + // anilibria release randomizer + &cli.BoolFlag{ + Name: "randomizer-enable", + Category: "Release randomizer", + Usage: `alice has its own function for randomizing releases; it optimizes random_release (apiv1) + and public/random.php (www site), ensuring the high performance of these methods; + if disabled, all reuqests will be cached in shared cache pool with another methods`, + }, + &cli.StringFlag{ + Name: "randomizer-redis-host", + Category: "Release randomizer", + Value: "127.0.0.1:6279", + }, + &cli.StringFlag{ + Name: "randomizer-redis-password", + Category: "Release randomizer", + Value: "", + }, + &cli.IntFlag{ + Name: "randomizer-redis-database", + Category: "Release randomizer", + Value: 0, + }, + &cli.StringFlag{ + Name: "randomizer-releaseskey", + Category: "Release randomizer", + Usage: "the feature of the legacy", + Value: "apiInfo", + }, + &cli.DurationFlag{ + Name: "randomizer-update-frequency", + Category: "Release randomizer", + Value: 3 * time.Minute, + }, + &cli.IntFlag{ + Name: "redis-client-maxretries", + Category: "Release randomizer", + Hidden: true, + Value: 3, + }, + &cli.DurationFlag{ + Name: "redis-client-dialtimeout", + Category: "Release randomizer", + Hidden: true, + Value: 5 * time.Second, + }, + &cli.DurationFlag{ + Name: "redis-client-readtimeout", + Category: "Release randomizer", + Hidden: true, + Value: 3 * time.Second, + }, + &cli.DurationFlag{ + Name: "redis-client-writetimeout", + Category: "Release randomizer", + Hidden: true, + Value: 3 * time.Second, }, // custom settings @@ -267,6 +334,7 @@ func flagsInitialization() []cli.Flag { Name: "anilibrix-cmpb-mode", Category: "Feature flags", Usage: "avoiding 'Cannot POST //public/api/index.php' errors with req rewrite", + Hidden: true, DisableDefaultText: true, }, } diff --git a/go.mod b/go.mod index 2061882..2242167 100644 --- a/go.mod +++ b/go.mod @@ -8,17 +8,21 @@ require ( github.com/gofiber/storage/bbolt/v2 v2.0.0 github.com/jedib0t/go-pretty/v6 v6.5.9 github.com/klauspost/compress v1.17.9 + github.com/leodido/go-syslog/v4 v4.1.0 github.com/mailru/easyjson v0.7.7 github.com/oschwald/maxminddb-golang v1.13.1 + github.com/redis/go-redis/v9 v9.6.1 github.com/rs/zerolog v1.33.0 - github.com/urfave/cli/v2 v2.27.2 + github.com/urfave/cli/v2 v2.27.3 github.com/valyala/bytebufferpool v1.0.0 github.com/valyala/fasthttp v1.55.0 ) require ( github.com/andybalholm/brotli v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/gofiber/utils/v2 v2.0.0-beta.3 // indirect github.com/google/uuid v1.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -30,7 +34,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/tinylib/msgp v1.1.8 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect go.etcd.io/bbolt v1.3.9 // indirect golang.org/x/sys v0.21.0 // indirect ) diff --git a/go.sum b/go.sum index 90ad68c..fb71f10 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,16 @@ github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3b github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= @@ -21,6 +27,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/leodido/go-syslog/v4 v4.1.0 h1:Wsl194qyWXr7V6DrGWC3xmxA9Ra6XgWO+toNt2fmCaI= +github.com/leodido/go-syslog/v4 v4.1.0/go.mod h1:eJ8rUfDN5OS6dOkCOBYlg2a+hbAg6pJa99QXXgMrd98= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -37,6 +45,8 @@ github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= +github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -47,16 +57,16 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= -github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= -github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= +github.com/urfave/cli/v2 v2.27.3 h1:/POWahRmdh7uztQ3CYnaDddk0Rm90PyOgIxgW2rr41M= +github.com/urfave/cli/v2 v2.27.3/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ= 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.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8= github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= -github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= diff --git a/internal/anilibria/anilibria.go b/internal/anilibria/anilibria.go new file mode 100644 index 0000000..7c0358d --- /dev/null +++ b/internal/anilibria/anilibria.go @@ -0,0 +1,16 @@ +package anilibria + +type ( + Releases map[string]*Release + Release struct { + Id uint + Code string + BlockedInfo *ReleaseBlockedInfo `json:"blockedInfo"` + } + ReleaseBlockedInfo struct { + Blocked bool + Reason string + IsBlockedInGeo []string `json:"is_blocked_in_geo"` + IsBlockedByCopyrights bool `json:"is_blocked_by_copyrights"` + } +) diff --git a/internal/anilibria/randomizer.go b/internal/anilibria/randomizer.go new file mode 100644 index 0000000..e169a60 --- /dev/null +++ b/internal/anilibria/randomizer.go @@ -0,0 +1,171 @@ +package anilibria + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strconv" + "sync" + "time" + + "github.com/anilibria/alice/internal/utils" + futils "github.com/gofiber/fiber/v2/utils" + "github.com/redis/go-redis/v9" + "github.com/rs/zerolog" + "github.com/urfave/cli/v2" +) + +type Randomizer struct { + rctx context.Context + rclient *redis.Client + + releasesKey string + + log *zerolog.Logger + done func() <-chan struct{} + + mu sync.RWMutex + ready bool + releases []string +} + +func New(c context.Context) (_ *Randomizer) { + cli, log := + c.Value(utils.CKCliCtx).(*cli.Context), + c.Value(utils.CKLogger).(*zerolog.Logger) + + r := &Randomizer{} + + r.log, r.done = log, c.Done + + r.rctx = context.Background() + r.rclient = redis.NewClient(&redis.Options{ + Addr: cli.String("randomizer-redis-host"), + Password: cli.String("randomizer-redis-password"), + DB: cli.Int("randomizer-redis-database"), + + ClientName: fmt.Sprintf("%s/%s", cli.App.Name, cli.App.Version), + + MaxRetries: cli.Int("redis-client-maxretries"), + DialTimeout: cli.Duration("redis-client-dialtimeout"), + ReadTimeout: cli.Duration("redis-client-readtimeout"), + WriteTimeout: cli.Duration("redis-client-writetimeout"), + }) + + r.releasesKey = cli.String("randomizer-releaseskey") + r.releases = make([]string, 0) + + // + // + if _, e := r.lookupReleases(); e != nil { + r.log.Error().Msg(e.Error()) + r.done() + } + // + // + + return r +} + +func (m *Randomizer) Bootstrap() (e error) { + // add ping + // add timer + return m.destroy() +} + +func (*Randomizer) IsReady() bool { + return false +} + +// + +func (*Randomizer) loop() { + +} + +func (m *Randomizer) destroy() error { + return m.rclient.Close() +} + +func (m *Randomizer) peekReleaseKeyChunks() (_ int, e error) { + var res string + if res, e = m.rclient.Get(m.rctx, m.releasesKey).Result(); e == redis.Nil { + e = errors.New("no such release key in redis; is it correct - " + m.releasesKey) + return + } else if e != nil { + return + } else if res == "" { + e = errors.New("redis client respond with an empty string; is release key is alive?") + return + } + + return strconv.Atoi(res) +} + +func (m *Randomizer) lookupReleases() (_ []string, e error) { + var chunks int + if chunks, e = m.peekReleaseKeyChunks(); e != nil { + return + } else if chunks == 0 { + e = errors.New("invalid chunks count was responded by redis client or converted by golang") + return + } + m.log.Trace().Msgf("release key says about %d chunks", chunks) + + // avoid mass allocs + started := time.Now() + releases := make([]string, len(m.releases)) + res, errs, total, banned := "", []string{}, 0, 0 + + for i := 0; i < chunks; i++ { + m.log.Trace().Msgf("parsing chunk %d/%d...", i, chunks) + + if res, e = m.rclient.Get(m.rctx, m.releasesKey+strconv.Itoa(i)).Result(); e == redis.Nil { + e = errors.New(fmt.Sprintf("given chunk number %d is not exists", i)) + m.log.Warn().Msg(e.Error()) + errs = append(errs, e.Error()) + continue + } else if e != nil { + m.log.Warn().Msg("an error occured while peeking a releases chunk - " + e.Error()) + errs = append(errs, e.Error()) + continue + } + + var releasesChunk Releases + if e = json.Unmarshal(futils.UnsafeBytes(res), &releasesChunk); e != nil { + m.log.Warn().Msg("an error occured while unmarshal release chunk - " + e.Error()) + errs = append(errs, e.Error()) + continue + } + + for _, release := range releasesChunk { + if release.BlockedInfo != nil && release.BlockedInfo.IsBlockedByCopyrights { + m.log.Debug().Msgf("release %d (%s) worldwide banned, skip it...", release.Id, release.Code) + banned++ + continue + } + + if zerolog.GlobalLevel() <= zerolog.DebugLevel { + m.log.Trace().Msgf("release %d with code %s found", release.Id, release.Code) + } + + total++ + releases = append(releases, release.Code) + } + + } + + if errslen := len(errs); errslen != 0 { + m.log.Error().Msgf("%d chunks were corrupted, data from them did not get into the cache", errslen) + m.log.Error().Msg("release redis extraction process errors:") + + for _, err := range errs { + m.log.Error().Msg(err) + } + } + + m.log.Info().Msgf("in %s from %d (of %d) chunks added %d releases and %d skipped because of WW ban", + time.Since(started).String(), chunks-len(errs), chunks, total, banned) + return releases, nil +} diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index e20f95a..2a1420b 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -106,6 +106,8 @@ func (m *Proxy) doRequest(c *fiber.Ctx, req *fasthttp.Request, rsp *fasthttp.Res return } + rlog(c).Debug().Msg(rsp.String() + rsp.String() + rsp.String() + rsp.String()) + var ok bool if ok, e = m.unmarshalApiResponse(c, rsp); e != nil { rlog(c).Warn().Msg(e.Error()) diff --git a/internal/service/router.go b/internal/service/router.go index 9103cc2..c6cf243 100644 --- a/internal/service/router.go +++ b/internal/service/router.go @@ -76,7 +76,9 @@ func (m *Service) fiberMiddlewareInitialization() { return zc.Uint64("id", c.Context().ID()) }) - c.Locals("logger", logger) + logger2 := logger.Hook(UDPSizeDiscardHook{}) + + c.Locals("logger", &logger2) e = c.Next() loggerPool.Put(logger) @@ -184,3 +186,16 @@ func (m *Service) fiberRouterInitialization() { // step3 - proxy request to upstream apiv1.Use(m.proxy.HandleProxyToDst) } + +type UDPSizeDiscardHook struct{} + +func (UDPSizeDiscardHook) Run(e *zerolog.Event, level zerolog.Level, message string) { + fmt.Println(len(message)) + if len(message) <= utils.MAX_UDP_MSG_BYTES { + return + } + + level = zerolog.ErrorLevel + e.Msgf("message was dropped because of high length %d\n%s", len(message), message) + e.Discard() +} diff --git a/internal/service/service.go b/internal/service/service.go index 3c3f3e1..ff6f4bf 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -12,6 +12,7 @@ import ( "syscall" "time" + "github.com/anilibria/alice/internal/anilibria" "github.com/anilibria/alice/internal/cache" "github.com/anilibria/alice/internal/geoip" "github.com/anilibria/alice/internal/proxy" @@ -181,6 +182,11 @@ func (m *Service) Bootstrap() (e error) { gCtx = context.WithValue(gCtx, utils.CKCache, m.cache) gofunc(&wg, m.cache.Bootstrap) + // randomizer module + if gCli.Bool("randomizer-enable") { + anilibria.New(gCtx) + } + // geoip module if gCli.Bool("geoip-enable") { if path := gCli.String("geoip-db-path"); path != "" { @@ -229,6 +235,8 @@ func (m *Service) Bootstrap() (e error) { gLog.Info().Msg("ready...") + time.Sleep(time.Second) + wg.Wait() return m.loopError } diff --git a/internal/utils/syslog_posix.go b/internal/utils/syslog_posix.go index 8669e3a..78abbd6 100644 --- a/internal/utils/syslog_posix.go +++ b/internal/utils/syslog_posix.go @@ -3,12 +3,133 @@ package utils import ( + "bytes" + "fmt" "io" "log/syslog" + "net" + "time" + gosyslog "github.com/leodido/go-syslog/v4/rfc5424" "github.com/urfave/cli/v2" ) func SetUpSyslogWriter(c *cli.Context) (_ io.Writer, e error) { - return syslog.Dial(c.String("syslog-proto"), c.String("syslog-server"), syslog.LOG_INFO, c.String("syslog-tag")) + // return syslog.Dial(c.String("syslog-proto"), c.String("syslog-server"), syslog.LOG_INFO, c.String("syslog-tag")) + // return NewWrapped(syslog.Dial(c.String("syslog-proto"), c.String("syslog-server"), syslog.LOG_INFO, c.String("syslog-tag"))) + return NewSyslog5424().Dial(c.String("syslog-proto"), c.String("syslog-server")) +} + +// the main idea of this wrapper was stolen from +// https://github.com/deis/deis/pull/4876/files + +const ( + MAX_UDP_MSG_BYTES = 65400 + // MAX_TCP_MSG_BYTES = 1048576 +) + +type SyslogWrapped struct { + *syslog.Writer +} + +func NewWrapped(w *syslog.Writer, e error) (io.Writer, error) { + if e != nil { + return nil, e + } + + return &SyslogWrapped{ + Writer: w, + }, nil +} + +func (m *SyslogWrapped) Write(b []byte) (_ int, e error) { + // Truncate the message if it's too long to fit in a single UDP packet. + // Get the bytes first. If the string has non-UTF8 chars, the number of + // bytes might exceed the number of characters and it would be good to + // know that up front. + + // dataBytes := []byte(b) + // var dataBytes []byte + + if len(b) > MAX_UDP_MSG_BYTES { + // bb := bytebufferpool.Get() + // bb.Write(append(b[:MAX_UDP_MSG_BYTES-3], "..."...)) + // fmt.Printf("cap %d len %d \n", cap(bb.B), bb.Len()) + // return m.Writer.Write(bb.B) + + // Truncate the bytes and add ellipses. + // dataBytes = append(b[:MAX_UDP_MSG_BYTES-3], "..."...) + // dataBytes = make([]byte, MAX_UDP_MSG_BYTES, MAX_UDP_MSG_BYTES) + // copy(dataBytes, b[:MAX_UDP_MSG_BYTES-3]) + // dataBytes = append(dataBytes, "..."...) + + dataBytes := bytes.NewBuffer(nil) + dataBytes.Write(b[:MAX_UDP_MSG_BYTES-3]) + dataBytes.WriteByte(byte('.')) + dataBytes.WriteByte(byte('.')) + dataBytes.WriteByte(byte('.')) + + fmt.Printf("utils/syslog_unix - the message has been truncated from %d to %d\n", len(b), dataBytes.Len()) + fmt.Printf("utils/syslog_unix - cap %d len %d\n", dataBytes.Cap(), dataBytes.Len()) + + var n int + var err error + if n, err = m.Writer.Write(dataBytes.Bytes()); err != nil { + fmt.Printf("there is error from Write - %s\n", err.Error()) + return n, err + } + + fmt.Printf("written %d bytes\n", n) + + return dataBytes.Len(), nil + } + + return m.Writer.Write(b) +} + +func (m *SyslogWrapped) Close() error { + return m.Writer.Close() +} + +type Syslog5424 struct { + conn net.Conn +} + +func NewSyslog5424() *Syslog5424 { + return &Syslog5424{} +} + +func (m *Syslog5424) Dial(network, raddr string) (io.Writer, error) { + return m, m.connect(network, raddr) +} + +func (m *Syslog5424) Write(b []byte) (_ int, e error) { + return m.write(b) +} + +// + +func (m *Syslog5424) connect(network, raddr string) (e error) { + if m.conn, e = net.Dial(network, raddr); e != nil { + return + } + + return +} + +func (m *Syslog5424) write(b []byte) (_ int, e error) { + + msg := &gosyslog.SyslogMessage{} + msg.SetMessage(string(b)) + msg.SetTimestamp(time.Now().Format(gosyslog.RFC3339MICRO)) + msg.SetPriority(191) + msg.SetVersion(1) + + res, e := msg.String() + if e != nil { + return + } + + fmt.Fprint(m.conn, res) + return len(b), e }