Initial commit

This commit is contained in:
2026-03-05 11:21:18 +03:00
commit 056e2ad529
18 changed files with 729 additions and 0 deletions

17
.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
/bin/*
contrib/dbg/.*
debug
debug.test
__debug_bin
__debug_bin*
*.sublime-project
*.sublime-workspace
*.un~
*.swp
.idea/
*.iml
*.log
/logs
static
testdata/
/payouts.properties

20
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"cwd": "${workspaceFolder}/config",
"program": "${workspaceFolder}/cmd/payouts",
"env": {
"CONFIG_PATH": "${workspaceFolder}/payouts.properties"
}
}
]
}

19
cmd/payouts/main.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import (
"go.uber.org/fx"
"payouts/internal/api"
"payouts/internal/config"
"payouts/internal/log"
)
func main() {
app := fx.New(
api.Module,
config.Module,
log.Module,
)
app.Run()
}

22
config/payouts.properties Normal file
View File

@@ -0,0 +1,22 @@
Server.Port = :8080
Server.WriteTimeout = 35s
Server.ReadTimeout = 35s
Server.EnablePProfEndpoints = false
Socket.MaxHttpBufferSize = 2097152
Socket.PingInterval = 25s
Socket.PingTimeout = 20s
Socket.Debug = false
# Prometheus settings
Metrics.Endpoint = /metrics
Metrics.HistogramBuckets = 0.001,0.002,0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10
Metrics.Http.HistogramEnabled = true
Metrics.Http.Buckets = 0.001,0.002,0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10
Log.Level = DEBUG
Log.FilePath = ./logs/payouts.log
Log.TextOutput = false
Log.StdoutEnabled = true
Log.FileEnabled = false

42
go.mod Normal file
View File

@@ -0,0 +1,42 @@
module payouts
go 1.24.4
require (
github.com/go-viper/encoding/javaproperties v0.1.0
github.com/go-viper/mapstructure/v2 v2.5.0
github.com/gorilla/mux v1.8.1
github.com/ogier/pflag v0.0.1
github.com/prometheus/client_golang v1.23.2
github.com/samber/slog-multi v1.7.1
github.com/spf13/viper v1.21.0
go.uber.org/fx v1.24.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/samber/lo v1.52.0 // indirect
github.com/samber/slog-common v0.20.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/dig v1.19.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.26.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
)

93
go.sum Normal file
View File

@@ -0,0 +1,93 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/encoding/javaproperties v0.1.0 h1:4pQN/pez/rMy9ITZ++SgLH6VIN3zWzNNuWFHKjrpn6w=
github.com/go-viper/encoding/javaproperties v0.1.0/go.mod h1:LGaThjx5J/GFdQRJscxLMQsYt0XKAM7IW9YzsJTv6jw=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ogier/pflag v0.0.1 h1:RW6JSWSu/RkSatfcLtogGfFgpim5p7ARQ10ECk5O750=
github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc=
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
github.com/samber/slog-multi v1.7.1 h1:aCLXHRxgU+2v0PVlEOh7phynzM7CRo89ZgFtOwaqVEE=
github.com/samber/slog-multi v1.7.1/go.mod h1:A4KQC99deqfkCDJcL/cO3kX6McX7FffQAx/8QHink+c=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=
go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

95
internal/api/module.go Normal file
View File

@@ -0,0 +1,95 @@
package api
import (
"context"
"fmt"
"log/slog"
"net/http"
"net/http/pprof"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/fx"
"payouts/internal/api/version"
appConfig "payouts/internal/config"
"payouts/internal/service/monitoring"
)
// Module is a fx module
var Module = fx.Options(
version.Module,
monitoring.Module,
fx.Invoke(RegisterRoutes),
)
const BaseRoute = "/api/v1"
// Params represents the module input params
type Params struct {
fx.In
Logger *slog.Logger
AppConfig *appConfig.App
Version version.Handler
Metrics monitoring.Metrics
}
// RegisterRoutes registers the api routes and starts the http server
func RegisterRoutes(p Params, lc fx.Lifecycle) {
router := mux.NewRouter()
router.StrictSlash(true)
router.HandleFunc(version.Route, p.Version.VersionHandler).Methods(http.MethodGet)
if p.AppConfig.Server.EnablePProfEndpoints {
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
router.HandleFunc("/debug/pprof/trace", pprof.Trace)
router.NewRoute().PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
}
apiRouter := router.PathPrefix(BaseRoute).Subrouter()
apiRouter.HandleFunc("/test", func(http.ResponseWriter, *http.Request) {
slog.Info("Test called", slog.String("sample", "value"))
})
// data
apiRouter.Use(p.Metrics.GetMiddleware())
router.Handle(p.AppConfig.Metrics.Endpoint, promhttp.Handler())
srv := http.Server{
Handler: router,
Addr: p.AppConfig.Server.Port,
WriteTimeout: p.AppConfig.Server.WriteTimeout,
ReadTimeout: p.AppConfig.Server.ReadTimeout,
}
lc.Append(fx.Hook{
OnStart: func(c context.Context) error {
go func() {
var err error
slog.Info(fmt.Sprintf("Starting server on port %s", p.AppConfig.Server.Port))
if p.AppConfig.Server.Tls.Enabled {
err = srv.ListenAndServeTLS(p.AppConfig.Server.Tls.CertFile, p.AppConfig.Server.Tls.KeyFile)
} else {
err = srv.ListenAndServe()
}
if err != nil {
panic(err)
}
}()
return nil
},
OnStop: func(ctx context.Context) error {
return srv.Shutdown(ctx)
},
})
}

View File

@@ -0,0 +1,45 @@
package version
import (
"io"
"net/http"
"go.uber.org/fx"
// import embedded version
"payouts/internal/version"
)
// Route version route
const Route = "/version"
// Module is a fx module
var Module = fx.Options(
fx.Provide(New),
)
// New constructs a new config Handler.
func New() (Handler, error) {
return &handler{
version.Version,
}, nil
}
// Handler config handler interface
type Handler interface {
VersionHandler(http.ResponseWriter, *http.Request)
}
type handler struct {
version string
}
// VersionHandler handles the version requests
func (h *handler) VersionHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
io.WriteString(w, h.version)
}

12
internal/config/app.go Normal file
View File

@@ -0,0 +1,12 @@
package config
import (
logging "payouts/internal/log/config"
monitoring "payouts/internal/service/monitoring/config"
)
type App struct {
Server Server
Metrics monitoring.Metrics
Log logging.Log
}

86
internal/config/module.go Normal file
View File

@@ -0,0 +1,86 @@
package config
import (
"fmt"
"path/filepath"
"strings"
"github.com/go-viper/encoding/javaproperties"
"github.com/go-viper/mapstructure/v2"
"github.com/ogier/pflag"
"github.com/spf13/viper"
"go.uber.org/fx"
monitoring "payouts/internal/service/monitoring/config"
)
const (
ConfigPathArg = "config-path"
ConfigPathDefault = "./payouts.properties"
)
var Module = fx.Provide(NewAppConfig)
func getConfigData(filePath string) (string, string, string) {
dir, file := filepath.Split(filePath)
base := filepath.Base(file)
ext := filepath.Ext(base)
confPath, _ := filepath.Abs(dir)
confName := strings.TrimSuffix(base, ext)
confType := strings.Trim(ext, ".")
return confPath, confName, confType
}
func NewAppConfig() (*App, error) {
mainConfig := &App{}
configPaths := []string{ConfigPathDefault}
configPath := pflag.String(ConfigPathArg, "", "")
pflag.Parse()
configPaths = append(configPaths, *configPath)
codecRegistry := viper.NewCodecRegistry()
codec := &javaproperties.Codec{}
codecRegistry.RegisterCodec("properties", codec)
codecRegistry.RegisterCodec("props", codec)
codecRegistry.RegisterCodec("prop", codec)
conf := viper.New()
for num, path := range configPaths {
if len(path) < 1 {
continue
}
tempConf := viper.NewWithOptions(
viper.WithCodecRegistry(codecRegistry),
)
confPath, confName, confType := getConfigData(path)
tempConf.AddConfigPath(confPath)
tempConf.SetConfigName(confName)
tempConf.SetConfigType(confType)
err := tempConf.ReadInConfig()
if err != nil {
// complain on missed non-default config
if num > 0 {
fmt.Printf("Can't read config from %v, Error: %v\n", path, err)
}
} else {
_ = conf.MergeConfigMap(tempConf.AllSettings())
}
}
err := conf.Unmarshal(mainConfig, viper.DecodeHook(
mapstructure.ComposeDecodeHookFunc(
mapstructure.TextUnmarshallerHookFunc(),
mapstructure.StringToSliceHookFunc(","),
mapstructure.StringToTimeDurationHookFunc(),
monitoring.CommaSeparatedFloat64SliceHookFunc(),
)))
return mainConfig, err
}

20
internal/config/server.go Normal file
View File

@@ -0,0 +1,20 @@
package config
import (
"time"
)
type Tls struct {
Enabled bool
CertFile string
KeyFile string
}
// Server represents the server configiration
type Server struct {
Tls Tls
Port string
WriteTimeout time.Duration
ReadTimeout time.Duration
EnablePProfEndpoints bool
}

View File

@@ -0,0 +1,14 @@
package config
import "log/slog"
type Log struct {
Level slog.Level
FilePath string
TextOutput bool
StdoutEnabled bool
FileEnabled bool
FluentEnabled bool
}

56
internal/log/module.go Normal file
View File

@@ -0,0 +1,56 @@
package log
import (
"log/slog"
"os"
slogmulti "github.com/samber/slog-multi"
"go.uber.org/fx"
"payouts/internal/config"
)
var Module = fx.Options(
fx.Provide(NewLogger),
)
// Params represents the module input params
type Params struct {
fx.In
AppConfig *config.App
}
func NewLogger(p Params) (*slog.Logger, error) {
logConfig := p.AppConfig.Log
opts := &slog.HandlerOptions{
Level: logConfig.Level,
}
handlers := []slog.Handler{}
if logConfig.StdoutEnabled {
if logConfig.TextOutput {
handlers = append(handlers, slog.NewTextHandler(os.Stdout, opts))
} else {
handlers = append(handlers, slog.NewJSONHandler(os.Stdout, opts))
}
}
if logConfig.FileEnabled {
file, err := os.OpenFile(logConfig.FilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return nil, err
}
if logConfig.TextOutput {
handlers = append(handlers, slog.NewTextHandler(file, opts))
} else {
handlers = append(handlers, slog.NewJSONHandler(file, opts))
}
}
logger := slog.New(slogmulti.Fanout(handlers...))
slog.SetDefault(logger)
return logger, nil
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) New Cloud Technologies, Ltd., 2013-2026
*
* You can not use the contents of the file in any way without New Cloud Technologies Ltd. written permission.
* To obtain such a permit, you should contact New Cloud Technologies, Ltd. at https://myoffice.ru/contacts/
*/
package config
import (
"reflect"
"strconv"
"strings"
"github.com/go-viper/mapstructure/v2"
)
type CommaSeparatedFloat64Slice []float64
func CommaSeparatedFloat64SliceHookFunc() mapstructure.DecodeHookFuncType {
return func(
f reflect.Type,
t reflect.Type,
data interface{},
) (interface{}, error) {
// Check that the data is string
if f.Kind() != reflect.String {
return data, nil
}
// Check that the target type is our custom type
if t != reflect.TypeOf(CommaSeparatedFloat64Slice{}) {
return data, nil
}
stringSlice := strings.Split(data.(string), ",")
floatSlice := make([]float64, 0)
for _, str := range stringSlice {
val, err := strconv.ParseFloat(strings.TrimSpace(str), 64)
if err != nil {
return nil, err
}
floatSlice = append(floatSlice, val)
}
// Return the parsed value
return CommaSeparatedFloat64Slice(floatSlice), nil
}
}
// HttpMetrics configuration properties for http monitoring
type HttpMetrics struct {
HistogramEnabled bool
Buckets CommaSeparatedFloat64Slice
}
// Metrics configuration properties for monitoring
type Metrics struct {
Endpoint string
HistogramBuckets CommaSeparatedFloat64Slice
Http HttpMetrics
}

View File

@@ -0,0 +1,95 @@
package monitoring
import (
"net/http"
"strconv"
"time"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"payouts/internal/service/monitoring/config"
)
const (
METRICS_NAMESPACE = "payouts"
)
// Metrics represents the metrics service
type Metrics interface {
GetMiddleware() func(next http.Handler) http.Handler
}
// metrics represents the metrics service implementation
type metrics struct {
httpDuration prometheus.ObserverVec
}
// NewMetrics instantiates metrics service
func NewMetrics(config config.Metrics) (Metrics, error) {
var httpDuration prometheus.ObserverVec
if config.Http.HistogramEnabled {
httpDuration = CreateHttpHistogram(config.Http.Buckets)
} else {
httpDuration = CreateHttpSummary(config.Http.Buckets)
}
return &metrics{
httpDuration: httpDuration,
}, nil
}
// GetMiddleware returns the middleware to be used in mux router
func (m *metrics) GetMiddleware() func(next http.Handler) http.Handler {
return GetMiddleware(m.httpDuration)
}
func CreateHttpSummary(buckets []float64) *prometheus.SummaryVec {
return promauto.NewSummaryVec(prometheus.SummaryOpts{
Name: "http_server_requests_seconds",
Help: "Duration of HTTP requests.",
}, []string{"uri", "method", "code"})
}
func CreateHttpHistogram(buckets []float64) *prometheus.HistogramVec {
return promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "http_server_requests_seconds",
Help: "Duration of HTTP requests.",
Buckets: buckets,
}, []string{"uri", "method", "code"})
}
func GetMiddleware(histogramVec prometheus.ObserverVec) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
route := mux.CurrentRoute(r)
path, _ := route.GetPathTemplate()
wd := &writerDelegate{
ResponseWriter: w,
statusCode: http.StatusOK,
}
start := time.Now()
defer func() {
duration := time.Since(start).Seconds()
code := strconv.Itoa(wd.statusCode)
histogramVec.WithLabelValues(path, r.Method, code).Observe(duration)
}()
next.ServeHTTP(wd, r)
})
}
}
type writerDelegate struct {
http.ResponseWriter
statusCode int
}
// override the WriteHeader method
func (w *writerDelegate) WriteHeader(statusCode int) {
w.statusCode = statusCode
w.ResponseWriter.WriteHeader(statusCode)
}

View File

@@ -0,0 +1,24 @@
package monitoring
import (
"go.uber.org/fx"
"payouts/internal/config"
)
// Module is a fx module
var Module = fx.Options(
fx.Provide(New),
)
// Params represents the module input params
type Params struct {
fx.In
AppConfig *config.App
}
// New instantiates the metrics service
func New(p Params) (Metrics, error) {
return NewMetrics(p.AppConfig.Metrics)
}

View File

@@ -0,0 +1,6 @@
package version
import _ "embed"
//go:embed version.txt
var Version string

View File

@@ -0,0 +1 @@
unknown