diff --git a/.gitignore b/.gitignore index d091be8..e2798b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ -data/config.json -data/settings_db/** -aleesa-jabber-go +/data/Config.json +/data/config.json +/data/settings_db/** +/aleesa-jabber-go +/aleesa-jabber-go.exe diff --git a/.golangci.yml b/.golangci.yml index 2cae5e9..4135c21 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,11 +1,8 @@ run: tests: false - skip-dirs: - - data - - vendor modules-download-mode: readonly allow-parallel-runners: true - go: '1.22' + go: '1.25' output: sort-results: true linters-settings: @@ -14,6 +11,9 @@ linters-settings: # Оно не используется для повышения безопасности или каких-то таких секъюрых вещей, оно используется для выборки # произвольного элемента из массива, так что ИДИ НАХУЙ с этой проверкой и не еби мозги липовой безопасностью. - G404 # Insecure random number source (rand) +issues.exclude-dirs: + - data + - vendor linters: disable-all: true enable: @@ -28,19 +28,18 @@ linters: - bodyclose - containedctx - contextcheck + - copyloopvar - decorder - durationcheck - errchkjson - errorlint - exhaustive - exhaustruct - - exportloopref - - gci - ginkgolinter - gocheckcompilerdirectives - gocritic - godot - - goerr113 + - err113 - gofmt - goimports - gosec diff --git a/Makefile b/Makefile index 9362b85..b00d0b4 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,34 @@ #!/usr/bin/env gmake -f -BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath -FILELIST=collection.go commands.go event_parser.go globals.go lib.go main.go read_config.go redis.go types.go \ - settings-db-util.go +GOOPTS=CGO_ENABLED=0 +BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath -buildvcs=false +MYNAME=aleesa-jabber-go +BINARY=${MYNAME} +UNIX_BINARY=${MYNAME} +WINDOWS_BINARY=${MYNAME}.exe +RMCMD=rm -rf -BINARY=aleesa-jabber-go +# На windows имя бинарника может зависеть не только от платформы, но и от выбранной цели, для linux-а суффикс .exe +# не нужен +ifeq ($(OS),Windows_NT) +ifdef GOOS +ifeq ($(GOOS),windows) +BINARY=${WINDOWS_BINARY} +else # not ifeq ($(GOOS),windows) +BINARY=${MYNAME} +endif # ifeq ($(GOOS),windows) +else # not ifdef GOOS +BINARY=${WINDOWS_BINARY} +endif # ifdef GOOS +ifeq ($(SHELL), sh.exe) +RMCMD=DEL /Q /F +endif +endif + +# Явно определяем символ новой строки, чтобы избежать неоднозначности на windows +define IFS + +endef all: clean build @@ -12,38 +36,41 @@ all: clean build build: ifeq ($(OS),Windows_NT) -# powershell -ifeq ($(SHELL),sh.exe) - SET CGO_ENABLED=0 - go build ${BUILDOPTS} -o ${BINARY} ${FILELIST} -else -# jetbrains golang - CGO_ENABLED=0 - go build ${BUILDOPTS} -o ${BINARY} ${FILELIST} -endif -# bash/git bash -else - CGO_ENABLED=0 go build ${BUILDOPTS} -o ${BINARY} ${FILELIST} -endif +# Looks like on windows gnu make explicitly set SHELL to sh.exe, if it was not set. +ifeq ($(SHELL), sh.exe) +# # Vanilla cmd.exe / powershell. + SET "CGO_ENABLED=0" + go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME} +else ifeq (,$(findstring(Git/usr/bin/sh.exe, $(SHELL)))) +# # git-bash + CGO_ENABLED=0 go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME} +else # not ifeq (,$(findstring(Git/usr/bin/sh.exe, $(SHELL)))) +# # Some other shell. +# # TODO: handle it. + $(info "-- Dunno how to handle this shell: ${SHELL}") +endif # ifeq (,$(findstring(Git/usr/bin/sh.exe, $(SHELL)))) +else # not ($(OS),Windows_NT) + CGO_ENABLED=0 go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME} +endif # ifeq ($(OS),Windows_NT) clean: - go clean +ifeq ($(OS),Windows_NT) +ifeq ($(SHELL),sh.exe) +# # Vanilla cmd.exe / powershell. + if exist ${WINDOWS_BINARY} ${RMCMD} ${WINDOWS_BINARY} + if exist ${UNIX_BINARY} ${RMCMD} ${UNIX_BINARY} +else # not ifeq ($(SHELL),sh.exe) + ${RMCMD} ./${WINDOWS_BINARY} + ${RMCMD} ./${UNIX_BINARY} +endif # ifeq ($(SHELL),sh.exe) +else # not ifeq ($(OS),Windows_NT) + ${RMCMD} ./${BINARY} +endif upgrade: -ifeq ($(OS),Windows_NT) -# jetbrains golang, powershell -ifeq ($(SHELL),sh.exe) - if exist vendor del /F /S /Q vendor >nul -# git bash case -else - $(RM) -r vendor -endif -else - $(RM) -r vendor -endif - go get -d -u -t ./... + go get -u ./... go mod tidy go mod vendor diff --git a/README.md b/README.md index e14c7fb..561ca97 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ pubsub. make ``` -в итоге получится бинарник **aleesa-jabber-go**. После создания data/config.json из data/config_sample.json, можно +в итоге получится бинарник **aleesa-jabber-go**. После создания data/Config.json из data/config_sample.json, можно запускать приложение. Предполагается, что конфиг написан в формате [human json](https://hjson.github.io), который допускает коментарии и @@ -28,12 +28,12 @@ make ~/.aleesa-jabber-go.json ~/aleesa-jabber-go.json /etc/aleesa-jabber-go.json -data/config.json +data/Config.json ``` В каталоге **contrib** находится скрипт для _alpine linux_, системы инициализации _openrc_. Этот скрипт достаточно положить в **/etc/init.d/aleesa-jabber-go**. После чего скопировать бинарник в каталог **/var/lib/aleesa-jabber-go**, -положить конфиг в одну из дефолтных локаций (тестировалась /var/lib/aleesa-jabber-go/data/config.json) и после этого +положить конфиг в одну из дефолтных локаций (тестировалась /var/lib/aleesa-jabber-go/data/Config.json) и после этого сервис запускается командой: ```bash diff --git a/cmd/aleesa-jabber-go/main.go b/cmd/aleesa-jabber-go/main.go new file mode 100644 index 0000000..6c41c0f --- /dev/null +++ b/cmd/aleesa-jabber-go/main.go @@ -0,0 +1,190 @@ +package main + +import ( + "crypto/tls" + "fmt" + "os" + "os/signal" + "path/filepath" + "time" + + "aleesa-jabber-go/internal/anycollection" + "aleesa-jabber-go/internal/jabber" + "aleesa-jabber-go/internal/log" + + "github.com/eleksir/go-xmpp" + "github.com/go-redis/redis/v8" + "gopkg.in/tomb.v2" +) + +func main() { + var ( + err error + logfile *os.File + logVebosity string + verboseClient = false + ) + + // Добавим динамически формируемый путь до конфига в список возможных положений конфига в системе. + executablePath, err := os.Executable() + + if err != nil { + log.Errorf("unable to get current executable path: %s", err) + + os.Exit(1) + } + + configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath)) + jabber.ConfigLocations = append(jabber.ConfigLocations, configJSONPath) + + if err := jabber.ReadConfig(); err != nil { + log.Errorf("%s", err) + + os.Exit(1) + } + + // Откроем лог и скормим его логгеру + if jabber.Config.Log == "" { + logfile = os.Stderr + } else { + logfile, err = os.OpenFile(jabber.Config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + + if err != nil { + log.Errorf("Unable to open log file %s: %s", jabber.Config.Log, err) + os.Exit(1) + } + } + + log.Init(jabber.Config.Loglevel, logfile) + logVebosity = log.GetLevel() + log.Warnf("Loglevel set to %v", logVebosity) + + for { + jabber.SigChan = make(chan os.Signal, 1) + jabber.Talk = new(xmpp.Client) + jabber.Options = new(xmpp.Options) + + // Через tomb попробуем сделать выход горутинок управляемым. + jabber.GTomb = tomb.Tomb{} + + // Иницализируем redis-клиента. + jabber.RedisClient = redis.NewClient(&redis.Options{ //nolint:exhaustruct + Addr: fmt.Sprintf("%s:%d", jabber.Config.Redis.Server, jabber.Config.Redis.Port), + }) + + log.Debugf("Lazy connect() to redis at %s:%d", jabber.Config.Redis.Server, jabber.Config.Redis.Port) + jabber.Subscriber = jabber.RedisClient.Subscribe(jabber.Ctx, jabber.Config.Redis.MyChannel) + + if logVebosity == "debug" { + verboseClient = true + } + + // github.com/mattn/go-xmpp пишет в stdio, нам этого не надо, ловим выхлоп его в logrus с уровнем trace. + xmpp.DebugWriter = log.Writer + + // Хэндлер сигналов не надо трогать, он нужен для завершения программы целиком. + jabber.GTomb.Go(jabber.SigHandler) + signal.Notify(jabber.SigChan, os.Interrupt) + + jabber.GTomb.Go(func() error { return jabber.RedisLoop(jabber.Subscriber.Channel()) }) + + jabber.Options = &xmpp.Options{ //nolint:exhaustruct + Host: fmt.Sprintf("%s:%d", jabber.Config.Jabber.Server, jabber.Config.Jabber.Port), + User: jabber.Config.Jabber.User, + Password: jabber.Config.Jabber.Password, + Resource: jabber.Config.Jabber.Resource, + NoTLS: !jabber.Config.Jabber.Ssl, + StartTLS: jabber.Config.Jabber.StartTLS, + TLSConfig: &tls.Config{ //nolint:exhaustruct + ServerName: jabber.Config.Jabber.Server, + InsecureSkipVerify: !jabber.Config.Jabber.SslVerify, //nolint:gosec + }, + InsecureAllowUnencryptedAuth: jabber.Config.Jabber.InsecureAllowUnencryptedAuth, + Debug: verboseClient, + Session: false, + Status: "xa", + StatusMessage: jabber.RandomPhrase(jabber.Config.Jabber.StartupStatus), + DialTimeout: time.Duration(jabber.Config.Jabber.ConnectionTimeout) * time.Second, + } + + // Устанавливаем соединение и гребём события, посылаемые сервером. + jabber.GTomb.Go(myLoop) + + // Ловим первый же kill и не дождаемся остальных, хотя формально надо бы. + <-jabber.GTomb.Dying() + + time.Sleep(time.Duration(jabber.Config.Jabber.ReconnectDelay) * time.Second) + } +} + +func myLoop() error { + for { + // Зададим начальное значение глобальным переменным. + jabber.ServerPingTimestampRx = 0 + jabber.ServerPingTimestampTx = 0 + jabber.RoomsConnected = make([]string, 1) + jabber.LastActivity = 0 + jabber.LastServerActivity = 0 + jabber.LastMucActivity = anycollection.NewCollection() + jabber.ServerCapsQueried = false + jabber.ServerCapsList = anycollection.NewCollection() + jabber.MucCapsList = anycollection.NewCollection() + jabber.RoomPresences = anycollection.NewCollection() + + // Установим коннект. + if err := jabber.EstablishConnection(); err != nil { + log.Errorf("%s", err) + + return err + } + + jabber.ServerPingTimestampRx = time.Now().Unix() // Считаем, что если коннект запустился, то первый пинг успешен. + + // Тыкаем сервер палочкой, проверяем, что коннект жив и вываливаемся из mainLoop, если он не жив. + jabber.GTomb.Go( + func() error { + err := jabber.ProbeServerLiveness() + log.Errorf("%s", err) + + return err + }, + ) + + // Тыкаем muc-и палочкой, проверяем, что они живы и пере-заходим в них, если пинги пропали. + // Если пинги до комнаты пропали, то это фактически значит, что либо сервер потерял связь с MUC-компонентом, + // либо у нас какой-то wire error. + // GTomb.Go() тут зовётся внутри на каждую комнату, если у нас ошибка, эта горутинка рано или поздно выйдет. + go jabber.ProbeMUCLiveness() + + // Гребём сообщения... + for { + // Стриггерилось завершение работы приложения, или соединение не установлено (порвалось, например). + // грести не надо. + if jabber.Shutdown { + break + } + + if !jabber.IsConnected { + // Tight loop - это наверно не очень хорошо, думаю, ничего страшного не будет, если мы поспим 100мс. + time.Sleep(100 * time.Millisecond) + + continue + } + + // Вынимаем ивент из "провода". + chat, err := jabber.Talk.Recv() + + if err != nil { + log.Errorf("Unable to get recieve data from socket: %s", err) + + return err + } + + jabber.ParseEvent(chat) + } + + return nil + } +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/go.mod b/go.mod index d5e07ac..c18bd92 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module aleesa-jabber-go -go 1.22 +go 1.25.1 require ( github.com/cockroachdb/errors v1.11.3 @@ -9,7 +9,6 @@ require ( github.com/eleksir/go-xmpp v1.6.0 github.com/go-redis/redis/v8 v8.11.5 github.com/hjson/hjson-go v3.3.0+incompatible - github.com/sirupsen/logrus v1.9.3 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 ) diff --git a/go.sum b/go.sum index 4aa7ef0..14ae07d 100644 --- a/go.sum +++ b/go.sum @@ -19,7 +19,6 @@ github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= @@ -76,10 +75,6 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -107,7 +102,6 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -124,13 +118,11 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs= gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/collection.go b/internal/anycollection/anycollection.go similarity index 78% rename from collection.go rename to internal/anycollection/anycollection.go index 9ec783a..2d6efaa 100644 --- a/collection.go +++ b/internal/anycollection/anycollection.go @@ -1,4 +1,4 @@ -package main +package anycollection import ( "sync" @@ -12,12 +12,13 @@ type Collection struct { // Item представляет собой интерфейс для хранения произвольных данных. type item struct { - data interface{} + data any } // NewCollection создаёт инстанс коллекции. func NewCollection() *Collection { - c := &Collection{ //nolint:exhaustruct + c := &Collection{ + items: sync.Map{}, close: make(chan struct{}), } @@ -25,7 +26,7 @@ func NewCollection() *Collection { } // Get достаёт данные по заданному ключу из коллекции. -func (collection *Collection) Get(key interface{}) (interface{}, bool) { +func (collection *Collection) Get(key any) (any, bool) { obj, exists := collection.items.Load(key) if !exists { @@ -38,15 +39,15 @@ func (collection *Collection) Get(key interface{}) (interface{}, bool) { } // Set сохраняет данные с заданным ключом в коллекцию. -func (collection *Collection) Set(key interface{}, value interface{}) { +func (collection *Collection) Set(key any, value any) { collection.items.Store(key, item{ data: value, }) } // Range применяет функцию f ко всем ключам в коллекции. -func (collection *Collection) Range(f func(key, value interface{}) bool) { - fn := func(key, value interface{}) bool { +func (collection *Collection) Range(f func(key, value any) bool) { + fn := func(key, value any) bool { item := value.(item) return f(key, item.data) @@ -56,7 +57,7 @@ func (collection *Collection) Range(f func(key, value interface{}) bool) { } // Delete удаляет ключ и значение из коллекции данных. -func (collection *Collection) Delete(key interface{}) { +func (collection *Collection) Delete(key any) { collection.items.Delete(key) } diff --git a/commands.go b/internal/jabber/commands.go similarity index 80% rename from commands.go rename to internal/jabber/commands.go index ffe6306..8f76dc3 100644 --- a/commands.go +++ b/internal/jabber/commands.go @@ -1,4 +1,4 @@ -package main +package jabber import ( "encoding/json" @@ -7,8 +7,9 @@ import ( "slices" "strings" + "aleesa-jabber-go/internal/log" + "github.com/eleksir/go-xmpp" - log "github.com/sirupsen/logrus" ) func cmd(v xmpp.Chat) error { @@ -18,10 +19,10 @@ func cmd(v xmpp.Chat) error { data []byte ) - message.From = config.Redis.MyChannel + message.From = Config.Redis.MyChannel message.Threadid = "" // Тредиков в jabber-е нету, поэтому это поле отправляем пустым message.Message = v.Text - message.Plugin = config.Redis.MyChannel + message.Plugin = Config.Redis.MyChannel // Список команд, публично доступных, без каких-то специальных настроек cmds := []string{"ping", "пинг", "пинх", "pong", "понг", "понх", "coin", "монетка", "roll", "dice", "кости", @@ -58,7 +59,7 @@ func cmd(v xmpp.Chat) error { message.Misc.Answer = 0 message.Misc.Fwdcnt = 0 - message.Misc.Csign = config.CSign + message.Misc.Csign = Config.CSign message.Misc.Username = userNick message.Misc.Botnick = getBotNickFromRoomConfig(message.Chatid) message.Misc.Msgformat = 0 @@ -66,9 +67,9 @@ func cmd(v xmpp.Chat) error { msgLen := len(v.Text) // TODO: а вот тут надо впилить парсер команд - if (msgLen > len(config.CSign)) && (v.Text[:len(config.CSign)] == config.CSign) { + if (msgLen > len(Config.CSign)) && (v.Text[:len(Config.CSign)] == Config.CSign) { var ( - cmd = v.Text[len(config.CSign):] + cmd = v.Text[len(Config.CSign):] room = message.Chatid answer string ) @@ -76,55 +77,55 @@ func cmd(v xmpp.Chat) error { // И таких кейсов у нас минимум на команды !help и семейство команд !admin, а также на те команды, которые не всегда работают switch { case cmd == "help" || cmd == "помощь": - answer += fmt.Sprintf("%shelp | %sпомощь - это сообщение\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%sanek | %sанек | %sанекдот - рандомный анекдот с anekdot.ru\n", config.CSign, config.CSign, config.CSign) - answer += fmt.Sprintf("%sbuni - комикс-стрип hapi buni\n", config.CSign) - answer += fmt.Sprintf("%sbunny - кролик\n", config.CSign) - answer += fmt.Sprintf("%srabbit | %sкролик - кролик\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%scat | %sкис - кошечка\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%sdice | %sroll | %sкости - бросить кости\n", config.CSign, config.CSign, config.CSign) - answer += fmt.Sprintf("%sdig | %sкопать - заняться археологией\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%sdrink | %sпраздник - какой сегодня праздник?\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%sfish | %sfisher - порыбачить\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%sрыба | %sрыбка | %sрыбалка - порыбачить\n", config.CSign, config.CSign, config.CSign) - answer += fmt.Sprintf("%sf | %sф - рандомная фраза из сборника цитат fortune_mod\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%sfortune | %sфортунка - рандомная фраза из сборника цитат fortune_mod\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%sfox | %sлис - лисичка\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%sfriday | %sпятница - а не пятница ли сегодня?\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%sfrog | %sлягушка - лягушка\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%shorse | %sлошадь | %sлошадка - лошадка\n", config.CSign, config.CSign, config.CSign) - answer += fmt.Sprintf("%skarma фраза - посмотреть карму фразы\n", config.CSign) - answer += fmt.Sprintf("%sкарма фраза - посмотреть карму фразы\n", config.CSign) + answer += fmt.Sprintf("%shelp | %sпомощь - это сообщение\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sanek | %sанек | %sанекдот - рандомный анекдот с anekdot.ru\n", Config.CSign, Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sbuni - комикс-стрип hapi buni\n", Config.CSign) + answer += fmt.Sprintf("%sbunny - кролик\n", Config.CSign) + answer += fmt.Sprintf("%srabbit | %sкролик - кролик\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%scat | %sкис - кошечка\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sdice | %sroll | %sкости - бросить кости\n", Config.CSign, Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sdig | %sкопать - заняться археологией\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sdrink | %sпраздник - какой сегодня праздник?\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sfish | %sfisher - порыбачить\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sрыба | %sрыбка | %sрыбалка - порыбачить\n", Config.CSign, Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sf | %sф - рандомная фраза из сборника цитат fortune_mod\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sfortune | %sфортунка - рандомная фраза из сборника цитат fortune_mod\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sfox | %sлис - лисичка\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sfriday | %sпятница - а не пятница ли сегодня?\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sfrog | %sлягушка - лягушка\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%shorse | %sлошадь | %sлошадка - лошадка\n", Config.CSign, Config.CSign, Config.CSign) + answer += fmt.Sprintf("%skarma фраза - посмотреть карму фразы\n", Config.CSign) + answer += fmt.Sprintf("%sкарма фраза - посмотреть карму фразы\n", Config.CSign) answer += fmt.Sprintln("фраза++ | фраза-- - повысить или понизить карму фразы") - answer += fmt.Sprintf("%slat | %sлат - сгенерировать фразу из крылатого латинского выражения\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%smonkeyuser - комикс-стрип MonkeyUser\n", config.CSign) - answer += fmt.Sprintf("%sowl | %sсова - сова\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%sping | %sпинг - попинговать бота\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%sproverb | %sпословица - рандомная русская пословица\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%ssnail | %sулитка - улитка\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%ssome_brew - выдать соответствующий напиток, бармен может налить rum, ром, vodka, водку, tequila, текила\n", config.CSign) - answer += fmt.Sprintf("%sver | %sversion - написать что-то про версию ПО\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%sверсия - написать что-то про версию ПО\n", config.CSign) - answer += fmt.Sprintf("%sw <город> | %sп <город> - погода в городе\n", config.CSign, config.CSign) - answer += fmt.Sprintf("%sxkcd - комикс-стрип с xkcb.ru\n", config.CSign) + answer += fmt.Sprintf("%slat | %sлат - сгенерировать фразу из крылатого латинского выражения\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%smonkeyuser - комикс-стрип MonkeyUser\n", Config.CSign) + answer += fmt.Sprintf("%sowl | %sсова - сова\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sping | %sпинг - попинговать бота\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sproverb | %sпословица - рандомная русская пословица\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%ssnail | %sулитка - улитка\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%ssome_brew - выдать соответствующий напиток, бармен может налить rum, ром, vodka, водку, tequila, текила\n", Config.CSign) + answer += fmt.Sprintf("%sver | %sversion - написать что-то про версию ПО\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sверсия - написать что-то про версию ПО\n", Config.CSign) + answer += fmt.Sprintf("%sw <город> | %sп <город> - погода в городе\n", Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sxkcd - комикс-стрип с xkcb.ru\n", Config.CSign) // Для овнера или админа надо показывать команду admin if isMucAdmin(v.Remote) { - answer += fmt.Sprintf("%sadmin - настройки некоторых плагинов бота для комнаты\n", config.CSign) + answer += fmt.Sprintf("%sadmin - настройки некоторых плагинов бота для комнаты\n", Config.CSign) } - _, err = talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct + _, err = Talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct return err case cmd == "admin": if isMucAdmin(v.Remote) { - answer += fmt.Sprintf("%sadmin oboobs # - где 1 - вкл, 0 - выкл плагина oboobs\n", config.CSign) - answer += fmt.Sprintf("%sadmin oboobs - показываем ли сисечки по просьбе участников чата (команды %stits, %stities, %sboobs, %sboobies, %sсиси, %sсисечки)\n", config.CSign, config.CSign, config.CSign, config.CSign, config.CSign, config.CSign, config.CSign) - answer += fmt.Sprintf("%sadmin obutts # - где 1 - вкл, 0 - выкл плагина obutts\n", config.CSign) - answer += fmt.Sprintf("%sadmin obutts - показываем ли попки по просьбе участников чата (команды %sass, %sbutt, %sbooty, %sпопа, %sпопка)\n", config.CSign, config.CSign, config.CSign, config.CSign, config.CSign, config.CSign) + answer += fmt.Sprintf("%sadmin oboobs # - где 1 - вкл, 0 - выкл плагина oboobs\n", Config.CSign) + answer += fmt.Sprintf("%sadmin oboobs - показываем ли сисечки по просьбе участников чата (команды %stits, %stities, %sboobs, %sboobies, %sсиси, %sсисечки)\n", Config.CSign, Config.CSign, Config.CSign, Config.CSign, Config.CSign, Config.CSign, Config.CSign) + answer += fmt.Sprintf("%sadmin obutts # - где 1 - вкл, 0 - выкл плагина obutts\n", Config.CSign) + answer += fmt.Sprintf("%sadmin obutts - показываем ли попки по просьбе участников чата (команды %sass, %sbutt, %sbooty, %sпопа, %sпопка)\n", Config.CSign, Config.CSign, Config.CSign, Config.CSign, Config.CSign, Config.CSign) - _, err = talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct + _, err = Talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct return err } @@ -145,7 +146,7 @@ func cmd(v xmpp.Chat) error { answer = "Плагин oboobs выключен" } - _, err = talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct + _, err = Talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct return err } @@ -160,7 +161,7 @@ func cmd(v xmpp.Chat) error { answer = "Плагин oboobs включен" } - _, err = talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct + _, err = Talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct return err } @@ -170,7 +171,7 @@ func cmd(v xmpp.Chat) error { _ = saveSetting(room, "oboobs", "0") answer = "Плагин oboobs выключен" - _, err = talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct + _, err = Talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct return err } @@ -189,7 +190,7 @@ func cmd(v xmpp.Chat) error { answer = "Плагин obutts выключен" } - _, err = talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct + _, err = Talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct return err } @@ -204,7 +205,7 @@ func cmd(v xmpp.Chat) error { answer = "Плагин obutts включен" } - _, err = talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct + _, err = Talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct return err } @@ -214,7 +215,7 @@ func cmd(v xmpp.Chat) error { _ = saveSetting(room, "obutts", "0") answer = "Плагин obutts выключен" - _, err = talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct + _, err = Talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct return err } @@ -289,7 +290,7 @@ func cmd(v xmpp.Chat) error { } else { answer := fmt.Sprintf("Я тут не вижу участника с ником %s", userNick) - _, err = talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct + _, err = Talk.Send(xmpp.Chat{Remote: room, Type: v.Type, Text: answer}) //nolint:exhaustruct return err } @@ -351,9 +352,9 @@ func cmd(v xmpp.Chat) error { message.Misc.Answer = 1 message.Misc.Fwdcnt = 0 - message.Misc.Csign = config.CSign + message.Misc.Csign = Config.CSign message.Misc.Username = message.Userid - message.Misc.Botnick = config.Jabber.Nick + message.Misc.Botnick = Config.Jabber.Nick message.Misc.Msgformat = 0 data, err = json.Marshal(message) @@ -364,11 +365,11 @@ func cmd(v xmpp.Chat) error { } // Заталкиваем наш json в редиску - if err := redisClient.Publish(ctx, config.Redis.Channel, data).Err(); err != nil { - return fmt.Errorf("unable to send data to redis channel %s: %w", config.Redis.Channel, err) + if err := RedisClient.Publish(Ctx, Config.Redis.Channel, data).Err(); err != nil { + return fmt.Errorf("unable to send data to redis channel %s: %w", Config.Redis.Channel, err) } - log.Debugf("Sent msg to redis channel %s: %s", config.Redis.Channel, string(data)) + log.Debugf("Sent msg to redis channel %s: %s", Config.Redis.Channel, string(data)) return err } diff --git a/event_parser.go b/internal/jabber/event_parser.go similarity index 86% rename from event_parser.go rename to internal/jabber/event_parser.go index c31f192..03ce197 100644 --- a/event_parser.go +++ b/internal/jabber/event_parser.go @@ -1,4 +1,4 @@ -package main +package jabber import ( "encoding/json" @@ -8,15 +8,16 @@ import ( "strings" "time" + "aleesa-jabber-go/internal/log" + "github.com/davecgh/go-spew/spew" "github.com/eleksir/go-xmpp" - log "github.com/sirupsen/logrus" "golang.org/x/exp/slices" ) -// parseEvent парсит ивенты, прилетающие из модуля xmpp (изменения presence, фразы участников, ошибки). -func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo - lastServerActivity = time.Now().Unix() +// ParseEvent парсит ивенты, прилетающие из модуля xmpp (изменения presence, фразы участников, ошибки). +func ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo + LastServerActivity = time.Now().Unix() switch v := e.(type) { // Сообщение в чяти @@ -43,15 +44,15 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo } if err := cmd(v); err != nil { - gTomb.Kill(err) + GTomb.Kill(err) return } - lastActivity = lastServerActivity + LastActivity = LastServerActivity if muc, _ := strings.CutSuffix(v.Remote, "/"); muc != "" { - lastMucActivity.Set(muc, lastServerActivity) + LastMucActivity.Set(muc, LastServerActivity) } // Приватный чятик. @@ -62,12 +63,12 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo log.Debugf("Private message: %s", v.Text) if err := cmd(v); err != nil { - gTomb.Kill(err) + GTomb.Kill(err) return } - lastActivity = lastServerActivity + LastActivity = LastServerActivity // Внезапно, ошибки. По идее они должны ассоциироваться с отправляемыми сообщениями, но по ходу это // не реализовано, поэтому мы получаем ошибки в форме отдельного чятика. @@ -92,8 +93,8 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo log.Debug("Looks like IQ get query") if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" { - if slices.Contains(roomsConnected, muc) { - lastMucActivity.Set(muc, lastServerActivity) + if slices.Contains(RoomsConnected, muc) { + LastMucActivity.Set(muc, LastServerActivity) } } @@ -109,7 +110,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo case "jabber:iq:version": log.Infof("Got IQ get request for version from %s", v.From) - if id, err := talk.IqVersionResponse( + if id, err := Talk.IqVersionResponse( v, "aleesa-jabber-go", "4.с_чем-то", @@ -121,7 +122,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo err, ) - gTomb.Kill(err) + GTomb.Kill(err) return } @@ -132,14 +133,14 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo case "urn:xmpp:ping": log.Infof("Got IQ get request for pong from %s", v.From) - if id, err := talk.PingResponse(v); err != nil { + if id, err := Talk.PingResponse(v); err != nil { err := fmt.Errorf( "unable to send pong to jabber server: id=%s, err=%w", id, err, ) - gTomb.Kill(err) + GTomb.Kill(err) return } @@ -151,14 +152,14 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo case "jabber:iq:last": log.Infof("Got IQ get last activity time request from %s", v.From) - if id, err := talk.JabberIqLastResponse(v, lastActivity); err != nil { + if id, err := Talk.JabberIqLastResponse(v, LastActivity); err != nil { err := fmt.Errorf( "unable to send last activity time to jabber server: id=%s, err=%w", id, err, ) - gTomb.Kill(err) + GTomb.Kill(err) return } @@ -177,7 +178,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo answer += "" answer += "" - if id, err := talk.RawInformation( + if id, err := Talk.RawInformation( v.To, v.From, v.ID, @@ -190,7 +191,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo err, ) - gTomb.Kill(err) + GTomb.Kill(err) return } @@ -201,7 +202,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo case xmpp.XMPPNS_DISCO_ITEMS: log.Infof("Got IQ get disco#items request from %s, answer service unavailable", v.From) - if _, err := talk.ErrorServiceUnavailable( + if _, err := Talk.ErrorServiceUnavailable( v, "http://jabber.org/protocol/disco#info", "http://jabber.org/protocol/commands", @@ -212,7 +213,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo err, ) - gTomb.Kill(err) + GTomb.Kill(err) return } @@ -239,14 +240,14 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo case "urn:xmpp:time": log.Infof("Got IQ get time request from %s", v.From) - if id, err := talk.UrnXMPPTimeResponse(v, "+00:00"); err != nil { + if id, err := Talk.UrnXMPPTimeResponse(v, "+00:00"); err != nil { err := fmt.Errorf( "unable to send urn:xmpp:time to jabber server: id=%s, err=%w", id, err, ) - gTomb.Kill(err) + GTomb.Kill(err) return } @@ -273,7 +274,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo case xmpp.XMPPNS_PUBSUB: log.Infof("Got IQ get pubsub request from %s, answer feature unimplemented", v.From) - if id, err := talk.ErrorNotImplemented( + if id, err := Talk.ErrorNotImplemented( v, "http://jabber.org/protocol/pubsub#errors", "subscribe", @@ -283,7 +284,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo err, ) - gTomb.Kill(err) + GTomb.Kill(err) return } @@ -309,14 +310,14 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo if err := xml.Unmarshal(v.Query, &iqStruct); err == nil { log.Debugf("Got IQ get request (actually, response) for MUC Self-Ping from %s", v.From) - if id, err := talk.PingResponse(v); err != nil { + if id, err := Talk.PingResponse(v); err != nil { err := fmt.Errorf( "unable to send pong to jabber server: id=%s, err=%w", id, err, ) - gTomb.Kill(err) + GTomb.Kill(err) return } @@ -333,27 +334,27 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo case xmpp.IQTypeResult: if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" { - if slices.Contains(roomsConnected, muc) { - lastMucActivity.Set(muc, lastServerActivity) + if slices.Contains(RoomsConnected, muc) { + LastMucActivity.Set(muc, LastServerActivity) } } switch { // Похоже на pong от сервера (по стандарту в ответе нету query, но go-xmpp нам подсовывает это) - case v.From == config.Jabber.Server && v.To == talk.JID() && string(v.Query) == "": + case v.From == Config.Jabber.Server && v.To == Talk.JID() && string(v.Query) == "": log.Debugf("Got S2C pong answer from %s to %s", v.From, v.To) - serverPingTimestampRx = time.Now().Unix() //nolint:wsl + ServerPingTimestampRx = time.Now().Unix() //nolint:wsl // Похоже на понг второй стадии xep-0410 MUC-Ping-а, который у нас не реализован - case v.To == talk.JID() && string(v.Query) == "": - mucNameMatch := slices.Contains(roomsConnected, v.From) + case v.To == Talk.JID() && string(v.Query) == "": + mucNameMatch := slices.Contains(RoomsConnected, v.From) if mucNameMatch { log.Debugf("Got server-optimized MUC pong answer (xep-0410) from %s to %s", v.From, v.To) } else { mucNickMatch := false - for _, room := range roomsConnected { + for _, room := range RoomsConnected { mucNick := fmt.Sprintf("%s/%s", room, getBotNickFromRoomConfig(room)) if v.From == mucNick { @@ -379,14 +380,14 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo // Этот бот не управляется со стороны сервера, поэтому все попытки порулить игнорируем. case xmpp.IQTypeSet: if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" { - if slices.Contains(roomsConnected, muc) { - lastMucActivity.Set(muc, lastServerActivity) + if slices.Contains(RoomsConnected, muc) { + LastMucActivity.Set(muc, LastServerActivity) } } log.Info("Got an IQ request for set something. Answer not implemented") - if id, err := talk.ErrorNotImplemented( + if id, err := Talk.ErrorNotImplemented( v, "http://jabber.org/protocol/commands", xmpp.IQTypeSet, @@ -397,7 +398,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo err, ) - gTomb.Kill(err) + GTomb.Kill(err) return } @@ -415,12 +416,12 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo if err := xml.Unmarshal(v.Query, &iqPingStruct); err == nil { if iqPingStruct.Xmlns == "urn:xmpp:ping" { - if v.From == config.Jabber.Server { + if v.From == Config.Jabber.Server { msg := "Server announced that it can answer c2s ping, but gives us an error to such query, " msg += "fallback to keepalive whitespace pings" log.Error(msg) - serverCapsList.Set("urn:xmpp:ping", false) + ServerCapsList.Set("urn:xmpp:ping", false) } else { log.Errorf("Got 'ping unsupported' message from: %s to: %s", v.From, v.To) } @@ -437,27 +438,27 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo var iqErrorCancelNotAcceptable jabberIqErrorCancelNotAcceptable if err := xml.Unmarshal(v.Query, &iqErrorCancelNotAcceptable); err == nil { - if v.To == talk.JID() { + if v.To == Talk.JID() { var ( room = strings.SplitN(v.From, "/", 2)[0] nick = strings.SplitN(v.From, "/", 2)[1] ) - if slices.Contains(roomsConnected, iqErrorCancelNotAcceptable.By) && nick == getBotNickFromRoomConfig(room) { + if slices.Contains(RoomsConnected, iqErrorCancelNotAcceptable.By) && nick == getBotNickFromRoomConfig(room) { log.Errorf( "Got Iq error message from: %s to: %s. Looks like i'm not in MUC anymore", v.From, v.To, ) - time.Sleep(time.Duration(config.Jabber.MucRejoinDelay) * time.Second) + time.Sleep(time.Duration(Config.Jabber.MucRejoinDelay) * time.Second) - if _, err := talk.JoinMUCNoHistory(iqErrorCancelNotAcceptable.By, nick); err != nil { + if _, err := Talk.JoinMUCNoHistory(iqErrorCancelNotAcceptable.By, nick); err != nil { err := fmt.Errorf( "looks like connection to server also lost err=%w", err, ) - gTomb.Kill(err) + GTomb.Kill(err) return } @@ -493,8 +494,8 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo // Смена статуса участника case xmpp.Presence: if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" { - if slices.Contains(roomsConnected, muc) { - lastMucActivity.Set(muc, lastServerActivity) + if slices.Contains(RoomsConnected, muc) { + LastMucActivity.Set(muc, LastServerActivity) } } @@ -506,12 +507,12 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo // Однако, это не значит, что такую ситуацию мы не должны корректным образом обрабатывать. if v.Type == "unavailable" { // Считаем, что мы больше не в комнате, поэтому не знаем, кто там есть. - roomPresences.Delete(v.From) + RoomPresences.Delete(v.From) log.Error("Presence notification - looks like another instance of client leaves room") - if slices.Contains(roomsConnected, v.From) { - go joinMuc(v.From) + if slices.Contains(RoomsConnected, v.From) { + go joinMuc(v.From) //nolint: errcheck } } else { log.Errorf("Presence notification, Type: %s, From: %s, Show: %s, Status: %s", @@ -535,18 +536,18 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo // Это наш собственный Presence if v.Show == "" && v.Status == "" { - if nick == config.Jabber.Nick { - roomsConnected = append(roomsConnected, room) + if nick == Config.Jabber.Nick { + RoomsConnected = append(RoomsConnected, room) // На всякий случай дедуплицируем список комнат, к которым мы заджойнились. - sort.Strings(roomsConnected) - slices.Compact(roomsConnected) + sort.Strings(RoomsConnected) + slices.Compact(RoomsConnected) } } switch v.Role { // Участник ушёл case "none": - if presenceJSONInterface, present := roomPresences.Get(room); present { + if presenceJSONInterface, present := RoomPresences.Get(room); present { presenceJSONStrings := interfaceToStringSlice(presenceJSONInterface) var newPresenceJSONStrings []string @@ -562,7 +563,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo newPresenceJSONStrings = append(newPresenceJSONStrings, presenceJSONstring) } - roomPresences.Set(room, newPresenceJSONStrings) + RoomPresences.Set(room, newPresenceJSONStrings) } // Участник пришёл default: @@ -572,7 +573,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo presenceJSONBytes []byte ) - if presenceJSONInterface, present := roomPresences.Get(room); present { + if presenceJSONInterface, present := RoomPresences.Get(room); present { presenceJSONStrings = interfaceToStringSlice(presenceJSONInterface) } @@ -591,15 +592,15 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo presenceJSONBytes, _ = json.Marshal(v) //nolint:errchkjson newPresenceJSONStrings = append(newPresenceJSONStrings, string(presenceJSONBytes)) - roomPresences.Set(room, newPresenceJSONStrings) + RoomPresences.Set(room, newPresenceJSONStrings) } } // Ответ на запрос поддерживаемых фич, который "http://jabber.org/protocol/disco#info" case xmpp.DiscoResult: if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" { - if slices.Contains(roomsConnected, muc) { - lastMucActivity.Set(muc, lastServerActivity) + if slices.Contains(RoomsConnected, muc) { + LastMucActivity.Set(muc, LastServerActivity) } } @@ -611,10 +612,10 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo // Конкретно сейчас нас интересует только поддержка c2s ping for _, feature := range v.Features { log.Debugf("Server %s announced that it supports feature: %s", v.From, feature) - serverCapsList.Set(feature, true) + ServerCapsList.Set(feature, true) } - serverCapsQueried = true + ServerCapsQueried = true case "conference": mucCaps := make(map[string]bool) @@ -624,7 +625,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo mucCaps[feature] = true //nolint:wsl } - mucCapsList.Set(v.From, mucCaps) + MucCapsList.Set(v.From, mucCaps) case "pubsub": log.Debugf("PubSub component %s reply to disco#info, skipping", v.From) diff --git a/globals.go b/internal/jabber/globals.go similarity index 78% rename from globals.go rename to internal/jabber/globals.go index 78b1d93..87691da 100644 --- a/globals.go +++ b/internal/jabber/globals.go @@ -1,6 +1,7 @@ -package main +package jabber import ( + "aleesa-jabber-go/internal/anycollection" "context" "os" @@ -11,79 +12,79 @@ import ( ) // Config - это у нас глобальная штука :) . -var config myConfig +var Config myConfig // Статичные пути, по которым у нас лежат конфиги. При запуске сюда дописываются и динамические пути тоже. -var configLocations = []string{ +var ConfigLocations = []string{ "~/.aleesa-jabber-go.json", "~/aleesa-jabber-go.json", "/etc/aleesa-jabber-go.json", } -// To break circular message forwarding we must set some sane default, it can be overridden via config. +// To break circular message forwarding we must set some sane default, it can be overridden via Config. var forwardMax int64 = 5 // Ставится в true, если мы получили сигнал на выключение. -var shutdown = false +var Shutdown = false // Чтобы не организовывать драку за установку коннекта. var connecting = false // Глобальное состояние соединения. -var isConnected = false +var IsConnected = false // Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов. -var sigChan chan os.Signal +var SigChan chan os.Signal // Основной инстанс xmpp-клиента. -var talk *xmpp.Client +var Talk *xmpp.Client // Опции подключения к xmpp-серверу. -var options *xmpp.Options +var Options *xmpp.Options // Список комнат, в которых находится бот. -var roomsConnected []string +var RoomsConnected []string // Время последней активности, нужно для jabber:iq:last. -var lastActivity int64 +var LastActivity int64 // Время последней активности, нужно для c2s пингов - посылаем пинги, только если давненько ничего не приходило с // сервера. -var lastServerActivity int64 +var LastServerActivity int64 // Время последней активности MUC-ов, нужно для пингов - посылаем пинги, только если давненько ничего не приходило из // muc-ов. -var lastMucActivity *Collection +var LastMucActivity *anycollection.Collection // Получен ли ответ на запрос disco#info к серверу. -var serverCapsQueried bool +var ServerCapsQueried bool // sync.Map-ка с капабилити сервера. -var serverCapsList *Collection +var ServerCapsList *anycollection.Collection // sync.Map-ка с комнатами и их capability. -var mucCapsList *Collection +var MucCapsList *anycollection.Collection // Время, когда был отправлен c2s ping. -var serverPingTimestampTx int64 +var ServerPingTimestampTx int64 // Время, когда был принят s2c pong. -var serverPingTimestampRx int64 +var ServerPingTimestampRx int64 // Объектик для хранения стейта утилизатора горутинок. -var gTomb tomb.Tomb +var GTomb tomb.Tomb // sync.Map-ка со списком участников конференций (в json-формате, согласно структуре xmpp.Presence, "room".[]json). -var roomPresences *Collection +var RoomPresences *anycollection.Collection // Объектики клиента-редиски. -var redisClient *redis.Client -var subscriber *redis.PubSub +var RedisClient *redis.Client +var Subscriber *redis.PubSub // Мапка с открытыми дескрипторами баз с настройками. var settingsDB = make(map[string]*pebble.DB) // Main context. -var ctx = context.Background() +var Ctx = context.Background() /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/lib.go b/internal/jabber/lib.go similarity index 57% rename from lib.go rename to internal/jabber/lib.go index 49d84b5..9f04218 100644 --- a/lib.go +++ b/internal/jabber/lib.go @@ -1,138 +1,63 @@ -package main +package jabber import ( "encoding/json" "fmt" "math/rand" - "os" "reflect" "strings" - "syscall" "time" + "aleesa-jabber-go/internal/log" + "github.com/eleksir/go-xmpp" - log "github.com/sirupsen/logrus" + "golang.org/x/exp/slices" ) -// sigHandler Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения. -func sigHandler() error { - log.Debug("Installing signal handler") - - for s := range sigChan { - switch s { - case syscall.SIGINT: - log.Infoln("Got SIGINT, quitting") - case syscall.SIGTERM: - log.Infoln("Got SIGTERM, quitting") - case syscall.SIGQUIT: - log.Infoln("Got SIGQUIT, quitting") - - // Заходим на новую итерацию, если у нас "неинтересный" сигнал. - default: - continue - } - - var err error - - // Чтобы не срать в логи ошибками, проставим shutdown state приложения в true. - shutdown = true - - // Отпишемся от всех каналов и закроем коннект к редиске - if err = subscriber.Unsubscribe(ctx); err != nil { - log.Errorf("Unable to unsubscribe from redis channels cleanly: %s", err) - } else { - log.Debug("Unsubscribe from all redis channels") - } - - if err = subscriber.Close(); err != nil { - log.Errorf("Unable to close redis connection cleanly: %s", err) - } else { - log.Debug("Close redis connection") - } - - if isConnected && !shutdown { - log.Debug("Try to set our presence to Unavailable and status to Offline") - - // Вот тут понадобится коллекция известных пользователей, чтобы им разослать presence, что бот свалил в offline - // Пока за неимением лучшего сообщим об этом самим себе. - for _, room := range roomsConnected { - if _, err := talk.SendPresence( - xmpp.Presence{ //nolint:exhaustruct - To: room, - Status: "Offline", - Type: "unavailable", - }, - ); err != nil { - log.Infof("Unable to send presence to jabber server: %s", err) - } - } - - // И закрываем соединение. - log.Infoln("Closing connection to jabber server") - - if err := talk.Close(); err != nil { - log.Infof("Unable to close connection to jabber server: %s", err) - } - } - - if len(settingsDB) > 0 { - log.Debug("Closing runtime jabber room settings db") - - for _, db := range settingsDB { - _ = db.Close() - } - } - - os.Exit(0) - } - - return nil -} - -// establishConnection устанавливает соединение с jabber-сервером. -func establishConnection() error { +// EstablishConnection устанавливает соединение с jabber-сервером. +func EstablishConnection() error { var err error - if connecting && !isConnected { + if connecting && !IsConnected { return nil } // Проставляем глобальные переменные. connecting = true - isConnected = false - roomsConnected = make([]string, 0) + IsConnected = false + RoomsConnected = make([]string, 0) - talk, err = options.NewClient() + Talk, err = Options.NewClient() if err != nil { - return fmt.Errorf("Unable to connect to %s: %w", options.Host, err) + return fmt.Errorf("unable to connect to %s: %w", Options.Host, err) } // По идее keepalive должен же проходить только, если мы уже на сервере, так? - if _, err := talk.SendKeepAlive(); err != nil { - return fmt.Errorf("Try to send initial KeepAlive, got error: %w", err) + if _, err := Talk.SendKeepAlive(); err != nil { + return fmt.Errorf("try to send initial KeepAlive, got error: %w", err) } log.Info("Connected") // Джойнимся к чятикам, но делаем это в фоне, чтобы не блочиться на ошибках, например, если бота забанили - for _, room := range config.Jabber.Channels { - gTomb.Go(func() error { return joinMuc(room.Name) }) + for _, room := range Config.Jabber.Channels { + GTomb.Go(func() error { return joinMuc(room.Name) }) } - gTomb.Go(func() error { return RotateStatus("") }) + GTomb.Go(func() error { return RotateStatus("") }) - lastActivity = time.Now().Unix() + LastActivity = time.Now().Unix() connecting = false - isConnected = true + IsConnected = true - log.Debugf("Sending disco#info to %s", config.Jabber.Server) + log.Debugf("Sending disco#info to %s", Config.Jabber.Server) - _, err = talk.DiscoverInfo(talk.JID(), config.Jabber.Server) + _, err = Talk.DiscoverInfo(Talk.JID(), Config.Jabber.Server) if err != nil { - return fmt.Errorf("Unable to send disco#info to jabber server: %s", err) + return fmt.Errorf("unable to send disco#info to jabber server: %w", err) } return nil @@ -140,14 +65,14 @@ func establishConnection() error { // joinMu джойнится к конференциям/каналам/комнатам в джаббере. func joinMuc(room string) error { - log.Debugf("Sending disco#info from %s to %s", talk.JID(), room) + log.Debugf("Sending disco#info from %s to %s", Talk.JID(), room) - if _, err := talk.DiscoverInfo(talk.JID(), room); err != nil { - return fmt.Errorf("Unable to send disco#info to MUC %s: %s", room, err) + if _, err := Talk.DiscoverInfo(Talk.JID(), room); err != nil { + return fmt.Errorf("unable to send disco#info to MUC %s: %w", room, err) } // Ждём, пока muc нам вернёт список фичей. - for i := 0; i < (20 * int(config.Jabber.ConnectionTimeout)); i++ { + for i := 0; i < (20 * int(Config.Jabber.ConnectionTimeout)); i++ { var ( myRoom interface{} supported bool @@ -156,7 +81,7 @@ func joinMuc(room string) error { time.Sleep(50 * time.Millisecond) - if myRoom, exist = mucCapsList.Get(room); !exist { + if myRoom, exist = MucCapsList.Get(room); !exist { // Пока не задискаверилась continue } @@ -173,8 +98,8 @@ func joinMuc(room string) error { } // Пытаемся зайти в комнату - if _, err := talk.JoinMUCNoHistory(room, getBotNickFromRoomConfig(room)); err != nil { - return fmt.Errorf("Unable to join to MUC: %s, %w", room, err) + if _, err := Talk.JoinMUCNoHistory(room, getBotNickFromRoomConfig(room)); err != nil { + return fmt.Errorf("unable to join to MUC: %s, %w", room, err) } log.Infof("Joining to MUC: %s", room) @@ -182,10 +107,10 @@ func joinMuc(room string) error { // Ждём, когда прилетит presence из комнаты, тогда мы точно знаем, что мы вошли. entered := false - for i := 0; i < (20 * int(config.Jabber.ConnectionTimeout)); i++ { + for i := 0; i < (20 * int(Config.Jabber.ConnectionTimeout)); i++ { time.Sleep(50 * time.Millisecond) - if slices.Contains(roomsConnected, room) { + if slices.Contains(RoomsConnected, room) { entered = true break @@ -196,7 +121,7 @@ func joinMuc(room string) error { log.Errorf( "Unable to enter to MUC %s, join timeout after %d seconds (server does not return my presence for this room)", room, - 20*int(config.Jabber.ConnectionTimeout)+1, + 20*int(Config.Jabber.ConnectionTimeout)+1, ) return nil @@ -205,54 +130,54 @@ func joinMuc(room string) error { // Вот теперь точно можно слать статус. log.Infof("Joined to MUC: %s", room) - gTomb.Go(func() error { return RotateStatus(room) }) + GTomb.Go(func() error { return RotateStatus(room) }) return nil } -// probeServerLiveness проверяет живость соединения с сервером. Для многих серверов обязательная штука, без которой +// ProbeServerLiveness проверяет живость соединения с сервером. Для многих серверов обязательная штука, без которой // они выкидывают клиента через некоторое время неактивности. -func probeServerLiveness() error { //nolint:gocognit +func ProbeServerLiveness() error { //nolint:gocognit for { for { - if shutdown { + if Shutdown { return nil } - sleepTime := time.Duration(config.Jabber.ServerPingDelay) * 1000 * time.Millisecond - sleepTime += time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond + sleepTime := time.Duration(Config.Jabber.ServerPingDelay) * 1000 * time.Millisecond + sleepTime += time.Duration(rand.Int63n(1000*Config.Jabber.PingSplayDelay)) * time.Millisecond time.Sleep(sleepTime) - if !isConnected { + if !IsConnected { continue } - // Пингуем, только если не было никакой активности в течение > config.Jabber.ServerPingDelay, - // в худшем случе это будет ~ (config.Jabber.PingSplayDelay * 2) + config.Jabber.PingSplayDelay - // if (time.Now().Unix() - lastServerActivity) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) { + // Пингуем, только если не было никакой активности в течение > Config.Jabber.ServerPingDelay, + // в худшем случе это будет ~ (Config.Jabber.PingSplayDelay * 2) + Config.Jabber.PingSplayDelay + // if (time.Now().Unix() - lastServerActivity) < (Config.Jabber.ServerPingDelay + Config.Jabber.PingSplayDelay) { // continue // } - if serverCapsQueried { // Сервер ответил на disco#info + if ServerCapsQueried { // Сервер ответил на disco#info var ( value interface{} exist bool ) - value, exist = serverCapsList.Get("urn:xmpp:ping") + value, exist = ServerCapsList.Get("urn:xmpp:ping") switch { // Сервер анонсировал, что умеет в c2s пинги case exist && value.(bool): // Таймаут c2s пинга. Возьмём сумму задержки между пингами, добавим таймаут коннекта и добавим // максимальную корректировку разброса. - txTimeout := config.Jabber.ServerPingDelay + config.Jabber.ConnectionTimeout - txTimeout += config.Jabber.PingSplayDelay + txTimeout := Config.Jabber.ServerPingDelay + Config.Jabber.ConnectionTimeout + txTimeout += Config.Jabber.PingSplayDelay rxTimeout := txTimeout - rxTimeAgo := time.Now().Unix() - serverPingTimestampRx + rxTimeAgo := time.Now().Unix() - ServerPingTimestampRx - if serverPingTimestampTx > 0 { // Первая пуля от нас ушла... + if ServerPingTimestampTx > 0 { // Первая пуля от нас ушла... switch { // Давненько мы не получали понгов от сервера, вероятно, соединение с сервером утеряно? case rxTimeAgo > (rxTimeout * 2): @@ -265,36 +190,36 @@ func probeServerLiveness() error { //nolint:gocognit // По-умолчанию, мы отправляем c2s пинг default: - log.Debugf("Sending c2s ping from %s to %s", talk.JID(), config.Jabber.Server) + log.Debugf("Sending c2s ping from %s to %s", Talk.JID(), Config.Jabber.Server) - if err := talk.PingC2S(talk.JID(), config.Jabber.Server); err != nil { + if err := Talk.PingC2S(Talk.JID(), Config.Jabber.Server); err != nil { return err } - serverPingTimestampTx = time.Now().Unix() + ServerPingTimestampTx = time.Now().Unix() } } else { // Первая пуля пока не вылетела, отправляем - log.Debugf("Sending first c2s ping from %s to %s", talk.JID(), config.Jabber.Server) + log.Debugf("Sending first c2s ping from %s to %s", Talk.JID(), Config.Jabber.Server) - if err := talk.PingC2S(talk.JID(), config.Jabber.Server); err != nil { + if err := Talk.PingC2S(Talk.JID(), Config.Jabber.Server); err != nil { return err } - serverPingTimestampTx = time.Now().Unix() + ServerPingTimestampTx = time.Now().Unix() } // Сервер не анонсировал, что умеет в c2s пинги default: log.Debug("Sending keepalive whitespace ping") - if _, err := talk.SendKeepAlive(); err != nil { + if _, err := Talk.SendKeepAlive(); err != nil { return err } } } else { // Сервер не ответил на disco#info log.Debug("Sending keepalive whitespace ping") - if _, err := talk.SendKeepAlive(); err != nil { + if _, err := Talk.SendKeepAlive(); err != nil { return err } } @@ -302,31 +227,31 @@ func probeServerLiveness() error { //nolint:gocognit } } -// probeMUCLiveness Пингует MUC-и, нужно для проверки, что клиент ещё находится в MUC-е. -func probeMUCLiveness() error { //nolint:gocognit +// ProbeMUCLiveness Пингует MUC-и, нужно для проверки, что клиент ещё находится в MUC-е. +func ProbeMUCLiveness() { //nolint:gocognit for { for { - for _, room := range roomsConnected { + for _, room := range RoomsConnected { var ( exist bool lastActivityTS interface{} ) // Если записи про комнату нету, то пинговать её бессмысленно. - if lastActivityTS, exist = lastMucActivity.Get(room); !exist { + if lastActivityTS, exist = LastMucActivity.Get(room); !exist { continue } // Если время последней активности в чятике не превысило - // config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay, ничего не пингуем. - if (time.Now().Unix() - lastActivityTS.(int64)) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) { + // Config.Jabber.ServerPingDelay + Config.Jabber.PingSplayDelay, ничего не пингуем. + if (time.Now().Unix() - lastActivityTS.(int64)) < (Config.Jabber.ServerPingDelay + Config.Jabber.PingSplayDelay) { continue } /* Пинг MUC-а по сценарию без серверной оптимизации мы реализовывать не будем. Это как-то не надёжно. go func(room string) { // Небольшая рандомная задержка перед пингом комнаты - sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec + sleepTime := time.Duration(rand.Int63n(1000*Config.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec time.Sleep(sleepTime) if err := talk.PingS2S(talk.JID(), room+"/"+getBotNickFromRoomConfig(room)); err != nil { @@ -338,21 +263,21 @@ func probeMUCLiveness() error { //nolint:gocognit var roomMap interface{} - roomMap, exist = mucCapsList.Get(room) + roomMap, exist = MucCapsList.Get(room) // Пинги комнаты проводим, только если она записана, как прошедшая disco#info и поддерживающая // Server Optimization. if exist && roomMap.(map[string]bool)["http://jabber.org/protocol/muc#self-ping-optimization"] { - gTomb.Go( + GTomb.Go( func() error { // Небольшая рандомная задержка перед пингом комнаты. - sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond + sleepTime := time.Duration(rand.Int63n(1000*Config.Jabber.PingSplayDelay)) * time.Millisecond time.Sleep(sleepTime) - log.Debugf("Sending MUC ping from %s to %s", talk.JID(), room) + log.Debugf("Sending MUC ping from %s to %s", Talk.JID(), room) - if err := talk.PingS2S(talk.JID(), room); err != nil { - return fmt.Errorf("unable to ping MUC %s: %w", room, err) + if err := Talk.PingS2S(Talk.JID(), room); err != nil { + log.Errorf("unable to ping MUC %s: %s", room, err) } return nil @@ -361,7 +286,7 @@ func probeMUCLiveness() error { //nolint:gocognit } } - time.Sleep(time.Duration(config.Jabber.MucPingDelay) * time.Second) + time.Sleep(time.Duration(Config.Jabber.MucPingDelay) * time.Second) } } } @@ -370,11 +295,11 @@ func probeMUCLiveness() error { //nolint:gocognit func RotateStatus(room string) error { for { // TODO: Переделать на ticker-ы - totalSleepTime := time.Duration(config.Jabber.RuntimeStatus.RotationTime) * time.Second - totalSleepTime += time.Duration(config.Jabber.RuntimeStatus.RotationSplayTime) * time.Second + totalSleepTime := time.Duration(Config.Jabber.RuntimeStatus.RotationTime) * time.Second + totalSleepTime += time.Duration(Config.Jabber.RuntimeStatus.RotationSplayTime) * time.Second for { - status := randomPhrase(config.Jabber.RuntimeStatus.Text) + status := RandomPhrase(Config.Jabber.RuntimeStatus.Text) log.Debugf("Set status for MUC: %s to: %s", room, status) var p xmpp.Presence @@ -390,12 +315,12 @@ func RotateStatus(room string) error { } } - if _, err := talk.SendPresence(p); err != nil { - return fmt.Errorf("Unable to send presence to MUC %s: %s", room, err) + if _, err := Talk.SendPresence(p); err != nil { + return fmt.Errorf("unable to send presence to MUC %s: %w", room, err) } // Если мы не хотим ротировать, то цикл нам тут не нужен, просто выходим. - if config.Jabber.RuntimeStatus.RotationTime == 0 { + if Config.Jabber.RuntimeStatus.RotationTime == 0 { return nil } @@ -404,8 +329,8 @@ func RotateStatus(room string) error { } } -// randomPhrase Выдаёт одну рандомную фразу из даденного списка фраз. -func randomPhrase(list []string) string { +// RandomPhrase Выдаёт одну рандомную фразу из даденного списка фраз. +func RandomPhrase(list []string) string { phrase := "" if listLen := len(list); listLen > 0 { @@ -441,7 +366,7 @@ func getRealJIDfromNick(fullNick string) string { room := (strings.SplitN(fullNick, "/", 2))[0] // Достанем presence участника - presenceJSONInterface, present := roomPresences.Get(room) + presenceJSONInterface, present := RoomPresences.Get(room) // Никого нет дома if !present { @@ -467,8 +392,8 @@ func isMucAdmin(fullNick string) bool { room := (strings.SplitN(fullNick, "/", 2))[0] - // Достанем presence участника - presenceJSONInterface, present := roomPresences.Get(room) + // Достанем presence участника. + presenceJSONInterface, present := RoomPresences.Get(room) // Никого нет дома if !present { @@ -498,7 +423,7 @@ func getMucNames(room string) []string { ) // Достанем presence участников - presenceJSONInterface, present := roomPresences.Get(room) + presenceJSONInterface, present := RoomPresences.Get(room) if !present { return names @@ -516,15 +441,15 @@ func getMucNames(room string) []string { return names } -// getBotNickFromRoomConfig достаёт (короткий) ник бота из настроек чата +// getBotNickFromRoomConfig достаёт (короткий) ник бота из настроек чата. func getBotNickFromRoomConfig(room string) string { - for _, roomStruct := range config.Jabber.Channels { + for _, roomStruct := range Config.Jabber.Channels { if roomStruct.Name == room { return roomStruct.Nick } } - return config.Jabber.Nick + return Config.Jabber.Nick } /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/read_config.go b/internal/jabber/read_config.go similarity index 96% rename from read_config.go rename to internal/jabber/read_config.go index 9fdb6e8..4c55d66 100644 --- a/read_config.go +++ b/internal/jabber/read_config.go @@ -1,4 +1,4 @@ -package main +package jabber import ( "encoding/json" @@ -6,19 +6,20 @@ import ( "fmt" "os" + "aleesa-jabber-go/internal/log" + "github.com/hjson/hjson-go" - log "github.com/sirupsen/logrus" ) -// readConfig читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге +// ReadConfig читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге // нет. -func readConfig() error { //nolint:gocognit,gocyclo +func ReadConfig() error { //nolint:gocognit,gocyclo var ( err error configLoaded = false ) - for _, location := range configLocations { + for _, location := range ConfigLocations { fileInfo, err := os.Stat(location) // Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём. @@ -189,7 +190,7 @@ func readConfig() error { //nolint:gocognit,gocyclo for n, channel := range sampleConfig.Jabber.Channels { if channel.Name == "" { - return fmt.Errorf("no \"name\" entry in jabber channel config") + return fmt.Errorf("no \"name\" entry in jabber channel config") //nolint: err113 } if channel.Nick == "" { @@ -232,7 +233,7 @@ func readConfig() error { //nolint:gocognit,gocyclo // sampleConfig.Log = "" if not set - config = sampleConfig + Config = sampleConfig configLoaded = true log.Infof("Using %s as config file", location) diff --git a/redis.go b/internal/jabber/redis.go similarity index 91% rename from redis.go rename to internal/jabber/redis.go index 901a82c..43c4233 100644 --- a/redis.go +++ b/internal/jabber/redis.go @@ -1,23 +1,25 @@ -package main +package jabber import ( "encoding/json" "fmt" + "aleesa-jabber-go/internal/log" + "github.com/eleksir/go-xmpp" "github.com/go-redis/redis/v8" - log "github.com/sirupsen/logrus" ) -func redisLoop(redisMsgChan <-chan *redis.Message) error { +// RedisLoop вычитывает сообщения из редиски. +func RedisLoop(redisMsgChan <-chan *redis.Message) error { // Обработчик событий от редиски for msg := range redisMsgChan { - if shutdown { + if Shutdown { return nil } if err := redisMsgParser(msg.Payload); err != nil { - log.Warn(err) + log.Warnf("%s", err) } } @@ -28,7 +30,7 @@ func redisLoop(redisMsgChan <-chan *redis.Message) error { func redisMsgParser(msg string) error { var err error - if shutdown { + if Shutdown { // Если мы завершаем работу программы, то нам ничего обрабатывать не надо. return err } @@ -90,7 +92,7 @@ func redisMsgParser(msg string) error { // j.Misc.BotNick тоже можно не передавать, тогда будет записана пустая строка // j.Misc.CSign если нам его не передали, возьмём значение из конфига if exist := j.Misc.Csign; exist == "" { - j.Misc.Csign = config.CSign + j.Misc.Csign = Config.CSign } // j.Misc.FwdCnt если нам его не передали, то будет 0 @@ -107,7 +109,7 @@ func redisMsgParser(msg string) error { switch j.Mode { case "public": // Отправляем сообщение в чятик, а не тому, кто прислал нам исходное сообщение - if _, err := talk.Send( + if _, err := Talk.Send( xmpp.Chat{ //nolint:exhaustruct Remote: j.Chatid, Type: "groupchat", @@ -121,7 +123,7 @@ func redisMsgParser(msg string) error { case "private": // Отправляем сообщение тому, кто прислал сообщение - if _, err := talk.Send( + if _, err := Talk.Send( xmpp.Chat{ //nolint:exhaustruct Remote: j.From, Type: "chat", diff --git a/settings-db-util.go b/internal/jabber/settings-db-util.go similarity index 95% rename from settings-db-util.go rename to internal/jabber/settings-db-util.go index 0652ee6..19f2bd5 100644 --- a/settings-db-util.go +++ b/internal/jabber/settings-db-util.go @@ -1,4 +1,4 @@ -package main +package jabber import ( "crypto/sha256" @@ -6,9 +6,10 @@ import ( "fmt" "io/fs" + "aleesa-jabber-go/internal/log" + "github.com/cockroachdb/errors/oserror" "github.com/cockroachdb/pebble" - log "github.com/sirupsen/logrus" ) // StoreKV сохраняет в указанной бд ключ и значение. @@ -58,7 +59,7 @@ func getSetting(chatID string, setting string) string { // настройками порождается огромное количество файлов. Умолчальное ограничение на количество файлов - 500 штук, // что нас не устраивает, поэтому немного снизим эту цифру до более приемлемых значений options.L0CompactionFileThreshold = 8 - settingsDB[database], err = pebble.Open(config.DataDir+"/"+database, &options) + settingsDB[database], err = pebble.Open(Config.DataDir+"/"+database, &options) if err != nil { log.Errorf("Unable to open settings db, %s: %s\n", database, err) @@ -107,7 +108,7 @@ func saveSetting(chatID string, setting string, value string) error { // что нас не устраивает, поэтому немного снизим эту цифру до более приемлемых значений options.L0CompactionFileThreshold = 8 - settingsDB[database], err = pebble.Open(config.DataDir+"/"+database, &options) + settingsDB[database], err = pebble.Open(Config.DataDir+"/"+database, &options) if err != nil { log.Errorf("Unable to open settings db, %s: %s\n", database, err) diff --git a/internal/jabber/sighandler.go b/internal/jabber/sighandler.go new file mode 100644 index 0000000..b1cdf93 --- /dev/null +++ b/internal/jabber/sighandler.go @@ -0,0 +1,86 @@ +package jabber + +import ( + "aleesa-jabber-go/internal/log" + "os" + "syscall" + + "github.com/eleksir/go-xmpp" +) + +// SigHandler Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения. +func SigHandler() error { + log.Debug("Installing signal handler") + + for s := range SigChan { + switch s { + case syscall.SIGINT: + log.Info("Got SIGINT, quitting") + case syscall.SIGTERM: + log.Info("Got SIGTERM, quitting") + case syscall.SIGQUIT: + log.Info("Got SIGQUIT, quitting") + + // Заходим на новую итерацию, если у нас "неинтересный" сигнал. + default: + continue + } + + var err error + + // Чтобы не срать в логи ошибками, проставим shutdown state приложения в true. + Shutdown = true + + // Отпишемся от всех каналов и закроем коннект к редиске + if err = Subscriber.Unsubscribe(Ctx); err != nil { + log.Errorf("Unable to unsubscribe from redis channels cleanly: %s", err) + } else { + log.Debug("Unsubscribe from all redis channels") + } + + if err = Subscriber.Close(); err != nil { + log.Errorf("Unable to close redis connection cleanly: %s", err) + } else { + log.Debug("Close redis connection") + } + + if IsConnected && !Shutdown { + log.Debug("Try to set our presence to Unavailable and status to Offline") + + // Вот тут понадобится коллекция известных пользователей, чтобы им разослать presence, что бот свалил в offline + // Пока за неимением лучшего сообщим об этом самим себе. + for _, room := range RoomsConnected { + if _, err := Talk.SendPresence( + xmpp.Presence{ //nolint:exhaustruct + To: room, + Status: "Offline", + Type: "unavailable", + }, + ); err != nil { + log.Infof("Unable to send presence to jabber server: %s", err) + } + } + + // И закрываем соединение. + log.Info("Closing connection to jabber server") + + if err := Talk.Close(); err != nil { + log.Infof("Unable to close connection to jabber server: %s", err) + } + } + + if len(settingsDB) > 0 { + log.Debug("Closing runtime jabber room settings db") + + for _, db := range settingsDB { + _ = db.Close() + } + } + + os.Exit(0) + } + + return nil +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/types.go b/internal/jabber/types.go similarity index 99% rename from types.go rename to internal/jabber/types.go index 2a05674..ecd250c 100644 --- a/types.go +++ b/internal/jabber/types.go @@ -1,4 +1,4 @@ -package main +package jabber import ( "encoding/xml" diff --git a/internal/log/log.go b/internal/log/log.go new file mode 100644 index 0000000..b10e667 --- /dev/null +++ b/internal/log/log.go @@ -0,0 +1,158 @@ +package log + +import ( + "bufio" + "context" + "errors" + "fmt" + "io" + "log/slog" + "os" + "time" +) + +// Handler exposes slog handler struct. +var Handler slog.Handler +var Ctx = context.Background() +var Writer io.Writer +var reader io.Reader + +// Init setup logger stuff. +// level can be error, warn, info, debug if something other supplied info level selected. +// fileDescriptor should be opened before supplying it to Init(). +func Init(level string, fileDescriptor *os.File) { + var loglevel slog.Level + + // no panic, no trace. + switch level { + case "debug": + loglevel = slog.LevelDebug + + case "info": + loglevel = slog.LevelInfo + + case "warn": + loglevel = slog.LevelWarn + + case "error": + loglevel = slog.LevelError + + default: + loglevel = slog.LevelInfo + } + + opts := &slog.HandlerOptions{ //nolint: exhaustruct + // Use the ReplaceAttr function on the handler options + // to be able to replace any single attribute in the log output + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { //nolint: revive + // check that we are handling the time key + if a.Key != slog.TimeKey { + return a + } + + t := a.Value.Time() + + // change the value from a time.Time to a String + // where the string has the correct time format. + a.Value = slog.StringValue(t.Format(time.DateTime)) + + return a + }, + + Level: loglevel, + } + + Handler = slog.NewTextHandler(fileDescriptor, opts) + + slog.SetDefault( + slog.New(Handler), + ) + + reader, Writer = io.Pipe() + + go readIoPipe() +} + +// readIoPipe reads pipe and writes it to slog. +func readIoPipe() { + scanner := bufio.NewScanner(reader) + + for { + for scanner.Scan() { + line := scanner.Text() // Get the current line as a string + slog.Debug(line) + } + + if err := scanner.Err(); err != nil { + // io.EOF is expected when done reading, e.g. on exit. + if errors.Is(err, io.EOF) { + return + } + + // something weird happen, we should it log to guess later what it was. + Errorf("Error during scanning log writer pipe: %v\n", err) + } + } +} + +// GetLevel returns current loglevel. +func GetLevel() string { + switch { + case Handler.Enabled(Ctx, slog.LevelDebug): + return "debug" + + case Handler.Enabled(Ctx, slog.LevelInfo): + return "info" + + case Handler.Enabled(Ctx, slog.LevelWarn): + return "warn" + + case Handler.Enabled(Ctx, slog.LevelError): + return "error" + + default: + return "unknown" + } +} + +// Error passes message directly to slog.Error(). +func Error(message string) { + slog.Error(message) +} + +// Errorf logs message on error log level and allows to supply arguments in printf() style. +func Errorf(format string, a ...any) { + slog.Error(fmt.Sprintf(format, a...)) +} + +// Warn passes message directly to slog.Warn(). +func Warn(message string) { + slog.Warn(message) +} + +// Warnf logs message on warn log level and allows to supply arguments in printf() style. +func Warnf(format string, a ...any) { + slog.Warn(fmt.Sprintf(format, a...)) +} + +// Info passes message directly to slog.Info(). +func Info(message string) { + slog.Info(message) +} + +// Infof logs message on info log level and allows to supply arguments in printf() style. +func Infof(format string, a ...any) { + slog.Info(fmt.Sprintf(format, a...)) +} + +// Debug passes message directly to slog.Debug(). +func Debug(message string) { + slog.Debug(message) +} + +// Debugf logs message on debug log level and allows to supply arguments in printf() style. +func Debugf(format string, a ...any) { + slog.Debug(fmt.Sprintf(format, a...)) +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/main.go b/main.go deleted file mode 100644 index 011588b..0000000 --- a/main.go +++ /dev/null @@ -1,207 +0,0 @@ -package main - -import ( - "crypto/tls" - "fmt" - "os" - "os/signal" - "path/filepath" - "time" - - "github.com/eleksir/go-xmpp" - "github.com/go-redis/redis/v8" - log "github.com/sirupsen/logrus" - "gopkg.in/tomb.v2" -) - -func main() { - log.SetFormatter(&log.TextFormatter{ //nolint:exhaustruct - DisableQuote: true, - DisableLevelTruncation: false, - DisableColors: true, - FullTimestamp: true, - TimestampFormat: "2006-01-02 15:04:05", - }) - - // Добавим динамически формируемый путь до конфига в список возможных положений конфига в системе. - executablePath, err := os.Executable() - - if err != nil { - log.Errorf("unable to get current executable path: %s", err) - - os.Exit(1) - } - - for { - sigChan = make(chan os.Signal, 1) - talk = new(xmpp.Client) - options = new(xmpp.Options) - - configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath)) - configLocations = append(configLocations, configJSONPath) - - if err := readConfig(); err != nil { - log.Error(err) - - os.Exit(1) - } - - // no panic - switch config.Loglevel { - case "fatal": - log.SetLevel(log.FatalLevel) - case "error": - log.SetLevel(log.ErrorLevel) - case "warn": - log.SetLevel(log.WarnLevel) - case "info": - log.SetLevel(log.InfoLevel) - case "debug": - log.SetLevel(log.DebugLevel) - case "trace": - log.SetLevel(log.TraceLevel) - default: - log.SetLevel(log.InfoLevel) - } - - // Откроем лог и скормим его логгеру - if config.Log != "" { - logfile, err := os.OpenFile(config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) - - if err != nil { - log.Fatalf("Unable to open log file %s: %s", config.Log, err) - } - - log.SetOutput(logfile) - } - - myLogLevel := log.GetLevel() - log.Warnf("Loglevel set to %v", myLogLevel) - - // Через tomb попробуем сделать выход горутинок управляемым. - gTomb := tomb.Tomb{} - - // Иницализируем redis-клиента. - redisClient = redis.NewClient(&redis.Options{ //nolint:exhaustruct - Addr: fmt.Sprintf("%s:%d", config.Redis.Server, config.Redis.Port), - }) - - log.Debugf("Lazy connect() to redis at %s:%d", config.Redis.Server, config.Redis.Port) - subscriber = redisClient.Subscribe(ctx, config.Redis.MyChannel) - - verboseClient := false - - if myLogLevel == log.TraceLevel { - verboseClient = true - } - - // github.com/mattn/go-xmpp пишет в stdio, нам этого не надо, ловим выхлоп его в logrus с уровнем trace. - xmpp.DebugWriter = log.WithFields(log.Fields{"logger": "stdlib"}).WriterLevel(log.TraceLevel) - - // Хэндлер сигналов не надо трогать, он нужен для завершения программы целиком. - gTomb.Go(func() error { return sigHandler() }) - signal.Notify(sigChan, os.Interrupt) - - gTomb.Go(func() error { return redisLoop(subscriber.Channel()) }) - - options = &xmpp.Options{ //nolint:exhaustruct - Host: fmt.Sprintf("%s:%d", config.Jabber.Server, config.Jabber.Port), - User: config.Jabber.User, - Password: config.Jabber.Password, - Resource: config.Jabber.Resource, - NoTLS: !config.Jabber.Ssl, - StartTLS: config.Jabber.StartTLS, - TLSConfig: &tls.Config{ //nolint:exhaustruct - ServerName: config.Jabber.Server, - InsecureSkipVerify: !config.Jabber.SslVerify, //nolint:gosec - }, - InsecureAllowUnencryptedAuth: config.Jabber.InsecureAllowUnencryptedAuth, - Debug: verboseClient, - Session: false, - Status: "xa", - StatusMessage: randomPhrase(config.Jabber.StartupStatus), - DialTimeout: time.Duration(config.Jabber.ConnectionTimeout) * time.Second, - } - - // Устанавливаем соединение и гребём события, посылаемые сервером. - gTomb.Go(func() error { return myLoop() }) - - // Ловим первый же kill и не дождаемся остальных, хотя формально надо бы. - _ = <-gTomb.Dying() - - time.Sleep(time.Duration(config.Jabber.ReconnectDelay) * time.Second) - } -} - -func myLoop() error { - for { - // Зададим начальное значение глобальным переменным. - serverPingTimestampRx = 0 - serverPingTimestampTx = 0 - roomsConnected = make([]string, 1) - lastActivity = 0 - lastServerActivity = 0 - lastMucActivity = NewCollection() - serverCapsQueried = false - serverCapsList = NewCollection() - mucCapsList = NewCollection() - serverPingTimestampTx = 0 - serverPingTimestampRx = 0 - roomPresences = NewCollection() - - // Установим коннект. - if err := establishConnection(); err != nil { - log.Error(err) - - return err - } - - serverPingTimestampRx = time.Now().Unix() // Считаем, что если коннект запустился, то первый пинг успешен. - - // Тыкаем сервер палочкой, проверяем, что коннект жив и вываливаемся из mainLoop, если он не жив. - gTomb.Go( - func() error { - err := probeServerLiveness() - log.Error(err) - return err - }, - ) - - // Тыкаем muc-и палочкой, проверяем, что они живы и пере-заходим в них, если пинги пропали. - // Если пинги до комнаты пропали, то это фактически значит, что либо сервер потерял связь с MUC-компонентом, - // либо у нас какой-то wire error. - // gTomb.Go() тут зовётся внутри на каждую комнату, если у нас ошибка, эта горутинка рано или поздно выйдет. - go probeMUCLiveness() - - // Гребём сообщения... - for { - // Стриггерилось завершение работы приложения, или соединение не установлено (порвалось, например). - // грести не надо. - if shutdown { - break - } - - if !isConnected { - // Tight loop - это наверно не очень хорошо, думаю, ничего страшного не будет, если мы поспим 100мс. - time.Sleep(100 * time.Millisecond) - - continue - } - - // Вынимаем ивент из "провода". - chat, err := talk.Recv() - - if err != nil { - log.Errorf("Unable to get recieve data from socket: %s", err) - - return err - } - - parseEvent(chat) - } - - return nil - } -} - -/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/vendor/github.com/sirupsen/logrus/.gitignore b/vendor/github.com/sirupsen/logrus/.gitignore deleted file mode 100644 index 1fb13ab..0000000 --- a/vendor/github.com/sirupsen/logrus/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -logrus -vendor - -.idea/ diff --git a/vendor/github.com/sirupsen/logrus/.golangci.yml b/vendor/github.com/sirupsen/logrus/.golangci.yml deleted file mode 100644 index 65dc285..0000000 --- a/vendor/github.com/sirupsen/logrus/.golangci.yml +++ /dev/null @@ -1,40 +0,0 @@ -run: - # do not run on test files yet - tests: false - -# all available settings of specific linters -linters-settings: - errcheck: - # report about not checking of errors in type assetions: `a := b.(MyStruct)`; - # default is false: such cases aren't reported by default. - check-type-assertions: false - - # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; - # default is false: such cases aren't reported by default. - check-blank: false - - lll: - line-length: 100 - tab-width: 4 - - prealloc: - simple: false - range-loops: false - for-loops: false - - whitespace: - multi-if: false # Enforces newlines (or comments) after every multi-line if statement - multi-func: false # Enforces newlines (or comments) after every multi-line function signature - -linters: - enable: - - megacheck - - govet - disable: - - maligned - - prealloc - disable-all: false - presets: - - bugs - - unused - fast: false diff --git a/vendor/github.com/sirupsen/logrus/.travis.yml b/vendor/github.com/sirupsen/logrus/.travis.yml deleted file mode 100644 index c1dbd5a..0000000 --- a/vendor/github.com/sirupsen/logrus/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: go -go_import_path: github.com/sirupsen/logrus -git: - depth: 1 -env: - - GO111MODULE=on -go: 1.15.x -os: linux -install: - - ./travis/install.sh -script: - - cd ci - - go run mage.go -v -w ../ crossBuild - - go run mage.go -v -w ../ lint - - go run mage.go -v -w ../ test diff --git a/vendor/github.com/sirupsen/logrus/CHANGELOG.md b/vendor/github.com/sirupsen/logrus/CHANGELOG.md deleted file mode 100644 index 7567f61..0000000 --- a/vendor/github.com/sirupsen/logrus/CHANGELOG.md +++ /dev/null @@ -1,259 +0,0 @@ -# 1.8.1 -Code quality: - * move magefile in its own subdir/submodule to remove magefile dependency on logrus consumer - * improve timestamp format documentation - -Fixes: - * fix race condition on logger hooks - - -# 1.8.0 - -Correct versioning number replacing v1.7.1. - -# 1.7.1 - -Beware this release has introduced a new public API and its semver is therefore incorrect. - -Code quality: - * use go 1.15 in travis - * use magefile as task runner - -Fixes: - * small fixes about new go 1.13 error formatting system - * Fix for long time race condiction with mutating data hooks - -Features: - * build support for zos - -# 1.7.0 -Fixes: - * the dependency toward a windows terminal library has been removed - -Features: - * a new buffer pool management API has been added - * a set of `Fn()` functions have been added - -# 1.6.0 -Fixes: - * end of line cleanup - * revert the entry concurrency bug fix whic leads to deadlock under some circumstances - * update dependency on go-windows-terminal-sequences to fix a crash with go 1.14 - -Features: - * add an option to the `TextFormatter` to completely disable fields quoting - -# 1.5.0 -Code quality: - * add golangci linter run on travis - -Fixes: - * add mutex for hooks concurrent access on `Entry` data - * caller function field for go1.14 - * fix build issue for gopherjs target - -Feature: - * add an hooks/writer sub-package whose goal is to split output on different stream depending on the trace level - * add a `DisableHTMLEscape` option in the `JSONFormatter` - * add `ForceQuote` and `PadLevelText` options in the `TextFormatter` - -# 1.4.2 - * Fixes build break for plan9, nacl, solaris -# 1.4.1 -This new release introduces: - * Enhance TextFormatter to not print caller information when they are empty (#944) - * Remove dependency on golang.org/x/crypto (#932, #943) - -Fixes: - * Fix Entry.WithContext method to return a copy of the initial entry (#941) - -# 1.4.0 -This new release introduces: - * Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848). - * Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter` (#909, #911) - * Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919). - -Fixes: - * Fix wrong method calls `Logger.Print` and `Logger.Warningln` (#893). - * Update `Entry.Logf` to not do string formatting unless the log level is enabled (#903) - * Fix infinite recursion on unknown `Level.String()` (#907) - * Fix race condition in `getCaller` (#916). - - -# 1.3.0 -This new release introduces: - * Log, Logf, Logln functions for Logger and Entry that take a Level - -Fixes: - * Building prometheus node_exporter on AIX (#840) - * Race condition in TextFormatter (#468) - * Travis CI import path (#868) - * Remove coloured output on Windows (#862) - * Pointer to func as field in JSONFormatter (#870) - * Properly marshal Levels (#873) - -# 1.2.0 -This new release introduces: - * A new method `SetReportCaller` in the `Logger` to enable the file, line and calling function from which the trace has been issued - * A new trace level named `Trace` whose level is below `Debug` - * A configurable exit function to be called upon a Fatal trace - * The `Level` object now implements `encoding.TextUnmarshaler` interface - -# 1.1.1 -This is a bug fix release. - * fix the build break on Solaris - * don't drop a whole trace in JSONFormatter when a field param is a function pointer which can not be serialized - -# 1.1.0 -This new release introduces: - * several fixes: - * a fix for a race condition on entry formatting - * proper cleanup of previously used entries before putting them back in the pool - * the extra new line at the end of message in text formatter has been removed - * a new global public API to check if a level is activated: IsLevelEnabled - * the following methods have been added to the Logger object - * IsLevelEnabled - * SetFormatter - * SetOutput - * ReplaceHooks - * introduction of go module - * an indent configuration for the json formatter - * output colour support for windows - * the field sort function is now configurable for text formatter - * the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater - -# 1.0.6 - -This new release introduces: - * a new api WithTime which allows to easily force the time of the log entry - which is mostly useful for logger wrapper - * a fix reverting the immutability of the entry given as parameter to the hooks - a new configuration field of the json formatter in order to put all the fields - in a nested dictionnary - * a new SetOutput method in the Logger - * a new configuration of the textformatter to configure the name of the default keys - * a new configuration of the text formatter to disable the level truncation - -# 1.0.5 - -* Fix hooks race (#707) -* Fix panic deadlock (#695) - -# 1.0.4 - -* Fix race when adding hooks (#612) -* Fix terminal check in AppEngine (#635) - -# 1.0.3 - -* Replace example files with testable examples - -# 1.0.2 - -* bug: quote non-string values in text formatter (#583) -* Make (*Logger) SetLevel a public method - -# 1.0.1 - -* bug: fix escaping in text formatter (#575) - -# 1.0.0 - -* Officially changed name to lower-case -* bug: colors on Windows 10 (#541) -* bug: fix race in accessing level (#512) - -# 0.11.5 - -* feature: add writer and writerlevel to entry (#372) - -# 0.11.4 - -* bug: fix undefined variable on solaris (#493) - -# 0.11.3 - -* formatter: configure quoting of empty values (#484) -* formatter: configure quoting character (default is `"`) (#484) -* bug: fix not importing io correctly in non-linux environments (#481) - -# 0.11.2 - -* bug: fix windows terminal detection (#476) - -# 0.11.1 - -* bug: fix tty detection with custom out (#471) - -# 0.11.0 - -* performance: Use bufferpool to allocate (#370) -* terminal: terminal detection for app-engine (#343) -* feature: exit handler (#375) - -# 0.10.0 - -* feature: Add a test hook (#180) -* feature: `ParseLevel` is now case-insensitive (#326) -* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308) -* performance: avoid re-allocations on `WithFields` (#335) - -# 0.9.0 - -* logrus/text_formatter: don't emit empty msg -* logrus/hooks/airbrake: move out of main repository -* logrus/hooks/sentry: move out of main repository -* logrus/hooks/papertrail: move out of main repository -* logrus/hooks/bugsnag: move out of main repository -* logrus/core: run tests with `-race` -* logrus/core: detect TTY based on `stderr` -* logrus/core: support `WithError` on logger -* logrus/core: Solaris support - -# 0.8.7 - -* logrus/core: fix possible race (#216) -* logrus/doc: small typo fixes and doc improvements - - -# 0.8.6 - -* hooks/raven: allow passing an initialized client - -# 0.8.5 - -* logrus/core: revert #208 - -# 0.8.4 - -* formatter/text: fix data race (#218) - -# 0.8.3 - -* logrus/core: fix entry log level (#208) -* logrus/core: improve performance of text formatter by 40% -* logrus/core: expose `LevelHooks` type -* logrus/core: add support for DragonflyBSD and NetBSD -* formatter/text: print structs more verbosely - -# 0.8.2 - -* logrus: fix more Fatal family functions - -# 0.8.1 - -* logrus: fix not exiting on `Fatalf` and `Fatalln` - -# 0.8.0 - -* logrus: defaults to stderr instead of stdout -* hooks/sentry: add special field for `*http.Request` -* formatter/text: ignore Windows for colors - -# 0.7.3 - -* formatter/\*: allow configuration of timestamp layout - -# 0.7.2 - -* formatter/text: Add configuration option for time format (#158) diff --git a/vendor/github.com/sirupsen/logrus/LICENSE b/vendor/github.com/sirupsen/logrus/LICENSE deleted file mode 100644 index f090cb4..0000000 --- a/vendor/github.com/sirupsen/logrus/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Simon Eskildsen - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/github.com/sirupsen/logrus/README.md b/vendor/github.com/sirupsen/logrus/README.md deleted file mode 100644 index d1d4a85..0000000 --- a/vendor/github.com/sirupsen/logrus/README.md +++ /dev/null @@ -1,515 +0,0 @@ -# Logrus :walrus: [![Build Status](https://github.com/sirupsen/logrus/workflows/CI/badge.svg)](https://github.com/sirupsen/logrus/actions?query=workflow%3ACI) [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![Go Reference](https://pkg.go.dev/badge/github.com/sirupsen/logrus.svg)](https://pkg.go.dev/github.com/sirupsen/logrus) - -Logrus is a structured logger for Go (golang), completely API compatible with -the standard library logger. - -**Logrus is in maintenance-mode.** We will not be introducing new features. It's -simply too hard to do in a way that won't break many people's projects, which is -the last thing you want from your Logging library (again...). - -This does not mean Logrus is dead. Logrus will continue to be maintained for -security, (backwards compatible) bug fixes, and performance (where we are -limited by the interface). - -I believe Logrus' biggest contribution is to have played a part in today's -widespread use of structured logging in Golang. There doesn't seem to be a -reason to do a major, breaking iteration into Logrus V2, since the fantastic Go -community has built those independently. Many fantastic alternatives have sprung -up. Logrus would look like those, had it been re-designed with what we know -about structured logging in Go today. Check out, for example, -[Zerolog][zerolog], [Zap][zap], and [Apex][apex]. - -[zerolog]: https://github.com/rs/zerolog -[zap]: https://github.com/uber-go/zap -[apex]: https://github.com/apex/log - -**Seeing weird case-sensitive problems?** It's in the past been possible to -import Logrus as both upper- and lower-case. Due to the Go package environment, -this caused issues in the community and we needed a standard. Some environments -experienced problems with the upper-case variant, so the lower-case was decided. -Everything using `logrus` will need to use the lower-case: -`github.com/sirupsen/logrus`. Any package that isn't, should be changed. - -To fix Glide, see [these -comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437). -For an in-depth explanation of the casing issue, see [this -comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276). - -Nicely color-coded in development (when a TTY is attached, otherwise just -plain text): - -![Colored](http://i.imgur.com/PY7qMwd.png) - -With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash -or Splunk: - -```text -{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the -ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"} - -{"level":"warning","msg":"The group's number increased tremendously!", -"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"} - -{"animal":"walrus","level":"info","msg":"A giant walrus appears!", -"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"} - -{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.", -"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"} - -{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true, -"time":"2014-03-10 19:57:38.562543128 -0400 EDT"} -``` - -With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not -attached, the output is compatible with the -[logfmt](http://godoc.org/github.com/kr/logfmt) format: - -```text -time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8 -time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10 -time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true -time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4 -time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009 -time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true -``` -To ensure this behaviour even if a TTY is attached, set your formatter as follows: - -```go - log.SetFormatter(&log.TextFormatter{ - DisableColors: true, - FullTimestamp: true, - }) -``` - -#### Logging Method Name - -If you wish to add the calling method as a field, instruct the logger via: -```go -log.SetReportCaller(true) -``` -This adds the caller as 'method' like so: - -```json -{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by", -"time":"2014-03-10 19:57:38.562543129 -0400 EDT"} -``` - -```text -time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin -``` -Note that this does add measurable overhead - the cost will depend on the version of Go, but is -between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your -environment via benchmarks: -``` -go test -bench=.*CallerTracing -``` - - -#### Case-sensitivity - -The organization's name was changed to lower-case--and this will not be changed -back. If you are getting import conflicts due to case sensitivity, please use -the lower-case import: `github.com/sirupsen/logrus`. - -#### Example - -The simplest way to use Logrus is simply the package-level exported logger: - -```go -package main - -import ( - log "github.com/sirupsen/logrus" -) - -func main() { - log.WithFields(log.Fields{ - "animal": "walrus", - }).Info("A walrus appears") -} -``` - -Note that it's completely api-compatible with the stdlib logger, so you can -replace your `log` imports everywhere with `log "github.com/sirupsen/logrus"` -and you'll now have the flexibility of Logrus. You can customize it all you -want: - -```go -package main - -import ( - "os" - log "github.com/sirupsen/logrus" -) - -func init() { - // Log as JSON instead of the default ASCII formatter. - log.SetFormatter(&log.JSONFormatter{}) - - // Output to stdout instead of the default stderr - // Can be any io.Writer, see below for File example - log.SetOutput(os.Stdout) - - // Only log the warning severity or above. - log.SetLevel(log.WarnLevel) -} - -func main() { - log.WithFields(log.Fields{ - "animal": "walrus", - "size": 10, - }).Info("A group of walrus emerges from the ocean") - - log.WithFields(log.Fields{ - "omg": true, - "number": 122, - }).Warn("The group's number increased tremendously!") - - log.WithFields(log.Fields{ - "omg": true, - "number": 100, - }).Fatal("The ice breaks!") - - // A common pattern is to re-use fields between logging statements by re-using - // the logrus.Entry returned from WithFields() - contextLogger := log.WithFields(log.Fields{ - "common": "this is a common field", - "other": "I also should be logged always", - }) - - contextLogger.Info("I'll be logged with common and other field") - contextLogger.Info("Me too") -} -``` - -For more advanced usage such as logging to multiple locations from the same -application, you can also create an instance of the `logrus` Logger: - -```go -package main - -import ( - "os" - "github.com/sirupsen/logrus" -) - -// Create a new instance of the logger. You can have any number of instances. -var log = logrus.New() - -func main() { - // The API for setting attributes is a little different than the package level - // exported logger. See Godoc. - log.Out = os.Stdout - - // You could set this to any `io.Writer` such as a file - // file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) - // if err == nil { - // log.Out = file - // } else { - // log.Info("Failed to log to file, using default stderr") - // } - - log.WithFields(logrus.Fields{ - "animal": "walrus", - "size": 10, - }).Info("A group of walrus emerges from the ocean") -} -``` - -#### Fields - -Logrus encourages careful, structured logging through logging fields instead of -long, unparseable error messages. For example, instead of: `log.Fatalf("Failed -to send event %s to topic %s with key %d")`, you should log the much more -discoverable: - -```go -log.WithFields(log.Fields{ - "event": event, - "topic": topic, - "key": key, -}).Fatal("Failed to send event") -``` - -We've found this API forces you to think about logging in a way that produces -much more useful logging messages. We've been in countless situations where just -a single added field to a log statement that was already there would've saved us -hours. The `WithFields` call is optional. - -In general, with Logrus using any of the `printf`-family functions should be -seen as a hint you should add a field, however, you can still use the -`printf`-family functions with Logrus. - -#### Default Fields - -Often it's helpful to have fields _always_ attached to log statements in an -application or parts of one. For example, you may want to always log the -`request_id` and `user_ip` in the context of a request. Instead of writing -`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on -every line, you can create a `logrus.Entry` to pass around instead: - -```go -requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip}) -requestLogger.Info("something happened on that request") # will log request_id and user_ip -requestLogger.Warn("something not great happened") -``` - -#### Hooks - -You can add hooks for logging levels. For example to send errors to an exception -tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to -multiple places simultaneously, e.g. syslog. - -Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in -`init`: - -```go -import ( - log "github.com/sirupsen/logrus" - "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake" - logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" - "log/syslog" -) - -func init() { - - // Use the Airbrake hook to report errors that have Error severity or above to - // an exception tracker. You can create custom hooks, see the Hooks section. - log.AddHook(airbrake.NewHook(123, "xyz", "production")) - - hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") - if err != nil { - log.Error("Unable to connect to local syslog daemon") - } else { - log.AddHook(hook) - } -} -``` -Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md). - -A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks) - - -#### Level logging - -Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic. - -```go -log.Trace("Something very low level.") -log.Debug("Useful debugging information.") -log.Info("Something noteworthy happened!") -log.Warn("You should probably take a look at this.") -log.Error("Something failed but I'm not quitting.") -// Calls os.Exit(1) after logging -log.Fatal("Bye.") -// Calls panic() after logging -log.Panic("I'm bailing.") -``` - -You can set the logging level on a `Logger`, then it will only log entries with -that severity or anything above it: - -```go -// Will log anything that is info or above (warn, error, fatal, panic). Default. -log.SetLevel(log.InfoLevel) -``` - -It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose -environment if your application has that. - -Note: If you want different log levels for global (`log.SetLevel(...)`) and syslog logging, please check the [syslog hook README](hooks/syslog/README.md#different-log-levels-for-local-and-remote-logging). - -#### Entries - -Besides the fields added with `WithField` or `WithFields` some fields are -automatically added to all logging events: - -1. `time`. The timestamp when the entry was created. -2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after - the `AddFields` call. E.g. `Failed to send event.` -3. `level`. The logging level. E.g. `info`. - -#### Environments - -Logrus has no notion of environment. - -If you wish for hooks and formatters to only be used in specific environments, -you should handle that yourself. For example, if your application has a global -variable `Environment`, which is a string representation of the environment you -could do: - -```go -import ( - log "github.com/sirupsen/logrus" -) - -func init() { - // do something here to set environment depending on an environment variable - // or command-line flag - if Environment == "production" { - log.SetFormatter(&log.JSONFormatter{}) - } else { - // The TextFormatter is default, you don't actually have to do this. - log.SetFormatter(&log.TextFormatter{}) - } -} -``` - -This configuration is how `logrus` was intended to be used, but JSON in -production is mostly only useful if you do log aggregation with tools like -Splunk or Logstash. - -#### Formatters - -The built-in logging formatters are: - -* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise - without colors. - * *Note:* to force colored output when there is no TTY, set the `ForceColors` - field to `true`. To force no colored output even if there is a TTY set the - `DisableColors` field to `true`. For Windows, see - [github.com/mattn/go-colorable](https://github.com/mattn/go-colorable). - * When colors are enabled, levels are truncated to 4 characters by default. To disable - truncation set the `DisableLevelTruncation` field to `true`. - * When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text. - * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter). -* `logrus.JSONFormatter`. Logs fields as JSON. - * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter). - -Third party logging formatters: - -* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine. -* [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html). -* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events. -* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout. -* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the Power of Zalgo. -* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure. -* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Sava log to files. -* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added. - -You can define your formatter by implementing the `Formatter` interface, -requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a -`Fields` type (`map[string]interface{}`) with all your fields as well as the -default ones (see Entries section above): - -```go -type MyJSONFormatter struct { -} - -log.SetFormatter(new(MyJSONFormatter)) - -func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) { - // Note this doesn't include Time, Level and Message which are available on - // the Entry. Consult `godoc` on information about those fields or read the - // source of the official loggers. - serialized, err := json.Marshal(entry.Data) - if err != nil { - return nil, fmt.Errorf("Failed to marshal fields to JSON, %w", err) - } - return append(serialized, '\n'), nil -} -``` - -#### Logger as an `io.Writer` - -Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it. - -```go -w := logger.Writer() -defer w.Close() - -srv := http.Server{ - // create a stdlib log.Logger that writes to - // logrus.Logger. - ErrorLog: log.New(w, "", 0), -} -``` - -Each line written to that writer will be printed the usual way, using formatters -and hooks. The level for those entries is `info`. - -This means that we can override the standard library logger easily: - -```go -logger := logrus.New() -logger.Formatter = &logrus.JSONFormatter{} - -// Use logrus for standard log output -// Note that `log` here references stdlib's log -// Not logrus imported under the name `log`. -log.SetOutput(logger.Writer()) -``` - -#### Rotation - -Log rotation is not provided with Logrus. Log rotation should be done by an -external program (like `logrotate(8)`) that can compress and delete old log -entries. It should not be a feature of the application-level logger. - -#### Tools - -| Tool | Description | -| ---- | ----------- | -|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will be generated with different configs in different environments.| -|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) | - -#### Testing - -Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides: - -* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just adds the `test` hook -* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any): - -```go -import( - "github.com/sirupsen/logrus" - "github.com/sirupsen/logrus/hooks/test" - "github.com/stretchr/testify/assert" - "testing" -) - -func TestSomething(t*testing.T){ - logger, hook := test.NewNullLogger() - logger.Error("Helloerror") - - assert.Equal(t, 1, len(hook.Entries)) - assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level) - assert.Equal(t, "Helloerror", hook.LastEntry().Message) - - hook.Reset() - assert.Nil(t, hook.LastEntry()) -} -``` - -#### Fatal handlers - -Logrus can register one or more functions that will be called when any `fatal` -level message is logged. The registered handlers will be executed before -logrus performs an `os.Exit(1)`. This behavior may be helpful if callers need -to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted. - -``` -... -handler := func() { - // gracefully shutdown something... -} -logrus.RegisterExitHandler(handler) -... -``` - -#### Thread safety - -By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs. -If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking. - -Situation when locking is not needed includes: - -* You have no hooks registered, or hooks calling is already thread-safe. - -* Writing to logger.Out is already thread-safe, for example: - - 1) logger.Out is protected by locks. - - 2) logger.Out is an os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allows multi-thread/multi-process writing) - - (Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/) diff --git a/vendor/github.com/sirupsen/logrus/alt_exit.go b/vendor/github.com/sirupsen/logrus/alt_exit.go deleted file mode 100644 index 8fd189e..0000000 --- a/vendor/github.com/sirupsen/logrus/alt_exit.go +++ /dev/null @@ -1,76 +0,0 @@ -package logrus - -// The following code was sourced and modified from the -// https://github.com/tebeka/atexit package governed by the following license: -// -// Copyright (c) 2012 Miki Tebeka . -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import ( - "fmt" - "os" -) - -var handlers = []func(){} - -func runHandler(handler func()) { - defer func() { - if err := recover(); err != nil { - fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err) - } - }() - - handler() -} - -func runHandlers() { - for _, handler := range handlers { - runHandler(handler) - } -} - -// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code) -func Exit(code int) { - runHandlers() - os.Exit(code) -} - -// RegisterExitHandler appends a Logrus Exit handler to the list of handlers, -// call logrus.Exit to invoke all handlers. The handlers will also be invoked when -// any Fatal log entry is made. -// -// This method is useful when a caller wishes to use logrus to log a fatal -// message but also needs to gracefully shutdown. An example usecase could be -// closing database connections, or sending a alert that the application is -// closing. -func RegisterExitHandler(handler func()) { - handlers = append(handlers, handler) -} - -// DeferExitHandler prepends a Logrus Exit handler to the list of handlers, -// call logrus.Exit to invoke all handlers. The handlers will also be invoked when -// any Fatal log entry is made. -// -// This method is useful when a caller wishes to use logrus to log a fatal -// message but also needs to gracefully shutdown. An example usecase could be -// closing database connections, or sending a alert that the application is -// closing. -func DeferExitHandler(handler func()) { - handlers = append([]func(){handler}, handlers...) -} diff --git a/vendor/github.com/sirupsen/logrus/appveyor.yml b/vendor/github.com/sirupsen/logrus/appveyor.yml deleted file mode 100644 index df9d65c..0000000 --- a/vendor/github.com/sirupsen/logrus/appveyor.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: "{build}" -platform: x64 -clone_folder: c:\gopath\src\github.com\sirupsen\logrus -environment: - GOPATH: c:\gopath -branches: - only: - - master -install: - - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% - - go version -build_script: - - go get -t - - go test diff --git a/vendor/github.com/sirupsen/logrus/buffer_pool.go b/vendor/github.com/sirupsen/logrus/buffer_pool.go deleted file mode 100644 index c7787f7..0000000 --- a/vendor/github.com/sirupsen/logrus/buffer_pool.go +++ /dev/null @@ -1,43 +0,0 @@ -package logrus - -import ( - "bytes" - "sync" -) - -var ( - bufferPool BufferPool -) - -type BufferPool interface { - Put(*bytes.Buffer) - Get() *bytes.Buffer -} - -type defaultPool struct { - pool *sync.Pool -} - -func (p *defaultPool) Put(buf *bytes.Buffer) { - p.pool.Put(buf) -} - -func (p *defaultPool) Get() *bytes.Buffer { - return p.pool.Get().(*bytes.Buffer) -} - -// SetBufferPool allows to replace the default logrus buffer pool -// to better meets the specific needs of an application. -func SetBufferPool(bp BufferPool) { - bufferPool = bp -} - -func init() { - SetBufferPool(&defaultPool{ - pool: &sync.Pool{ - New: func() interface{} { - return new(bytes.Buffer) - }, - }, - }) -} diff --git a/vendor/github.com/sirupsen/logrus/doc.go b/vendor/github.com/sirupsen/logrus/doc.go deleted file mode 100644 index da67aba..0000000 --- a/vendor/github.com/sirupsen/logrus/doc.go +++ /dev/null @@ -1,26 +0,0 @@ -/* -Package logrus is a structured logger for Go, completely API compatible with the standard library logger. - - -The simplest way to use Logrus is simply the package-level exported logger: - - package main - - import ( - log "github.com/sirupsen/logrus" - ) - - func main() { - log.WithFields(log.Fields{ - "animal": "walrus", - "number": 1, - "size": 10, - }).Info("A walrus appears") - } - -Output: - time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10 - -For a full guide visit https://github.com/sirupsen/logrus -*/ -package logrus diff --git a/vendor/github.com/sirupsen/logrus/entry.go b/vendor/github.com/sirupsen/logrus/entry.go deleted file mode 100644 index 71cdbbc..0000000 --- a/vendor/github.com/sirupsen/logrus/entry.go +++ /dev/null @@ -1,442 +0,0 @@ -package logrus - -import ( - "bytes" - "context" - "fmt" - "os" - "reflect" - "runtime" - "strings" - "sync" - "time" -) - -var ( - - // qualified package name, cached at first use - logrusPackage string - - // Positions in the call stack when tracing to report the calling method - minimumCallerDepth int - - // Used for caller information initialisation - callerInitOnce sync.Once -) - -const ( - maximumCallerDepth int = 25 - knownLogrusFrames int = 4 -) - -func init() { - // start at the bottom of the stack before the package-name cache is primed - minimumCallerDepth = 1 -} - -// Defines the key when adding errors using WithError. -var ErrorKey = "error" - -// An entry is the final or intermediate Logrus logging entry. It contains all -// the fields passed with WithField{,s}. It's finally logged when Trace, Debug, -// Info, Warn, Error, Fatal or Panic is called on it. These objects can be -// reused and passed around as much as you wish to avoid field duplication. -type Entry struct { - Logger *Logger - - // Contains all the fields set by the user. - Data Fields - - // Time at which the log entry was created - Time time.Time - - // Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic - // This field will be set on entry firing and the value will be equal to the one in Logger struct field. - Level Level - - // Calling method, with package name - Caller *runtime.Frame - - // Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic - Message string - - // When formatter is called in entry.log(), a Buffer may be set to entry - Buffer *bytes.Buffer - - // Contains the context set by the user. Useful for hook processing etc. - Context context.Context - - // err may contain a field formatting error - err string -} - -func NewEntry(logger *Logger) *Entry { - return &Entry{ - Logger: logger, - // Default is three fields, plus one optional. Give a little extra room. - Data: make(Fields, 6), - } -} - -func (entry *Entry) Dup() *Entry { - data := make(Fields, len(entry.Data)) - for k, v := range entry.Data { - data[k] = v - } - return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err} -} - -// Returns the bytes representation of this entry from the formatter. -func (entry *Entry) Bytes() ([]byte, error) { - return entry.Logger.Formatter.Format(entry) -} - -// Returns the string representation from the reader and ultimately the -// formatter. -func (entry *Entry) String() (string, error) { - serialized, err := entry.Bytes() - if err != nil { - return "", err - } - str := string(serialized) - return str, nil -} - -// Add an error as single field (using the key defined in ErrorKey) to the Entry. -func (entry *Entry) WithError(err error) *Entry { - return entry.WithField(ErrorKey, err) -} - -// Add a context to the Entry. -func (entry *Entry) WithContext(ctx context.Context) *Entry { - dataCopy := make(Fields, len(entry.Data)) - for k, v := range entry.Data { - dataCopy[k] = v - } - return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx} -} - -// Add a single field to the Entry. -func (entry *Entry) WithField(key string, value interface{}) *Entry { - return entry.WithFields(Fields{key: value}) -} - -// Add a map of fields to the Entry. -func (entry *Entry) WithFields(fields Fields) *Entry { - data := make(Fields, len(entry.Data)+len(fields)) - for k, v := range entry.Data { - data[k] = v - } - fieldErr := entry.err - for k, v := range fields { - isErrField := false - if t := reflect.TypeOf(v); t != nil { - switch { - case t.Kind() == reflect.Func, t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Func: - isErrField = true - } - } - if isErrField { - tmp := fmt.Sprintf("can not add field %q", k) - if fieldErr != "" { - fieldErr = entry.err + ", " + tmp - } else { - fieldErr = tmp - } - } else { - data[k] = v - } - } - return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context} -} - -// Overrides the time of the Entry. -func (entry *Entry) WithTime(t time.Time) *Entry { - dataCopy := make(Fields, len(entry.Data)) - for k, v := range entry.Data { - dataCopy[k] = v - } - return &Entry{Logger: entry.Logger, Data: dataCopy, Time: t, err: entry.err, Context: entry.Context} -} - -// getPackageName reduces a fully qualified function name to the package name -// There really ought to be to be a better way... -func getPackageName(f string) string { - for { - lastPeriod := strings.LastIndex(f, ".") - lastSlash := strings.LastIndex(f, "/") - if lastPeriod > lastSlash { - f = f[:lastPeriod] - } else { - break - } - } - - return f -} - -// getCaller retrieves the name of the first non-logrus calling function -func getCaller() *runtime.Frame { - // cache this package's fully-qualified name - callerInitOnce.Do(func() { - pcs := make([]uintptr, maximumCallerDepth) - _ = runtime.Callers(0, pcs) - - // dynamic get the package name and the minimum caller depth - for i := 0; i < maximumCallerDepth; i++ { - funcName := runtime.FuncForPC(pcs[i]).Name() - if strings.Contains(funcName, "getCaller") { - logrusPackage = getPackageName(funcName) - break - } - } - - minimumCallerDepth = knownLogrusFrames - }) - - // Restrict the lookback frames to avoid runaway lookups - pcs := make([]uintptr, maximumCallerDepth) - depth := runtime.Callers(minimumCallerDepth, pcs) - frames := runtime.CallersFrames(pcs[:depth]) - - for f, again := frames.Next(); again; f, again = frames.Next() { - pkg := getPackageName(f.Function) - - // If the caller isn't part of this package, we're done - if pkg != logrusPackage { - return &f //nolint:scopelint - } - } - - // if we got here, we failed to find the caller's context - return nil -} - -func (entry Entry) HasCaller() (has bool) { - return entry.Logger != nil && - entry.Logger.ReportCaller && - entry.Caller != nil -} - -func (entry *Entry) log(level Level, msg string) { - var buffer *bytes.Buffer - - newEntry := entry.Dup() - - if newEntry.Time.IsZero() { - newEntry.Time = time.Now() - } - - newEntry.Level = level - newEntry.Message = msg - - newEntry.Logger.mu.Lock() - reportCaller := newEntry.Logger.ReportCaller - bufPool := newEntry.getBufferPool() - newEntry.Logger.mu.Unlock() - - if reportCaller { - newEntry.Caller = getCaller() - } - - newEntry.fireHooks() - buffer = bufPool.Get() - defer func() { - newEntry.Buffer = nil - buffer.Reset() - bufPool.Put(buffer) - }() - buffer.Reset() - newEntry.Buffer = buffer - - newEntry.write() - - newEntry.Buffer = nil - - // To avoid Entry#log() returning a value that only would make sense for - // panic() to use in Entry#Panic(), we avoid the allocation by checking - // directly here. - if level <= PanicLevel { - panic(newEntry) - } -} - -func (entry *Entry) getBufferPool() (pool BufferPool) { - if entry.Logger.BufferPool != nil { - return entry.Logger.BufferPool - } - return bufferPool -} - -func (entry *Entry) fireHooks() { - var tmpHooks LevelHooks - entry.Logger.mu.Lock() - tmpHooks = make(LevelHooks, len(entry.Logger.Hooks)) - for k, v := range entry.Logger.Hooks { - tmpHooks[k] = v - } - entry.Logger.mu.Unlock() - - err := tmpHooks.Fire(entry.Level, entry) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) - } -} - -func (entry *Entry) write() { - entry.Logger.mu.Lock() - defer entry.Logger.mu.Unlock() - serialized, err := entry.Logger.Formatter.Format(entry) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err) - return - } - if _, err := entry.Logger.Out.Write(serialized); err != nil { - fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) - } -} - -// Log will log a message at the level given as parameter. -// Warning: using Log at Panic or Fatal level will not respectively Panic nor Exit. -// For this behaviour Entry.Panic or Entry.Fatal should be used instead. -func (entry *Entry) Log(level Level, args ...interface{}) { - if entry.Logger.IsLevelEnabled(level) { - entry.log(level, fmt.Sprint(args...)) - } -} - -func (entry *Entry) Trace(args ...interface{}) { - entry.Log(TraceLevel, args...) -} - -func (entry *Entry) Debug(args ...interface{}) { - entry.Log(DebugLevel, args...) -} - -func (entry *Entry) Print(args ...interface{}) { - entry.Info(args...) -} - -func (entry *Entry) Info(args ...interface{}) { - entry.Log(InfoLevel, args...) -} - -func (entry *Entry) Warn(args ...interface{}) { - entry.Log(WarnLevel, args...) -} - -func (entry *Entry) Warning(args ...interface{}) { - entry.Warn(args...) -} - -func (entry *Entry) Error(args ...interface{}) { - entry.Log(ErrorLevel, args...) -} - -func (entry *Entry) Fatal(args ...interface{}) { - entry.Log(FatalLevel, args...) - entry.Logger.Exit(1) -} - -func (entry *Entry) Panic(args ...interface{}) { - entry.Log(PanicLevel, args...) -} - -// Entry Printf family functions - -func (entry *Entry) Logf(level Level, format string, args ...interface{}) { - if entry.Logger.IsLevelEnabled(level) { - entry.Log(level, fmt.Sprintf(format, args...)) - } -} - -func (entry *Entry) Tracef(format string, args ...interface{}) { - entry.Logf(TraceLevel, format, args...) -} - -func (entry *Entry) Debugf(format string, args ...interface{}) { - entry.Logf(DebugLevel, format, args...) -} - -func (entry *Entry) Infof(format string, args ...interface{}) { - entry.Logf(InfoLevel, format, args...) -} - -func (entry *Entry) Printf(format string, args ...interface{}) { - entry.Infof(format, args...) -} - -func (entry *Entry) Warnf(format string, args ...interface{}) { - entry.Logf(WarnLevel, format, args...) -} - -func (entry *Entry) Warningf(format string, args ...interface{}) { - entry.Warnf(format, args...) -} - -func (entry *Entry) Errorf(format string, args ...interface{}) { - entry.Logf(ErrorLevel, format, args...) -} - -func (entry *Entry) Fatalf(format string, args ...interface{}) { - entry.Logf(FatalLevel, format, args...) - entry.Logger.Exit(1) -} - -func (entry *Entry) Panicf(format string, args ...interface{}) { - entry.Logf(PanicLevel, format, args...) -} - -// Entry Println family functions - -func (entry *Entry) Logln(level Level, args ...interface{}) { - if entry.Logger.IsLevelEnabled(level) { - entry.Log(level, entry.sprintlnn(args...)) - } -} - -func (entry *Entry) Traceln(args ...interface{}) { - entry.Logln(TraceLevel, args...) -} - -func (entry *Entry) Debugln(args ...interface{}) { - entry.Logln(DebugLevel, args...) -} - -func (entry *Entry) Infoln(args ...interface{}) { - entry.Logln(InfoLevel, args...) -} - -func (entry *Entry) Println(args ...interface{}) { - entry.Infoln(args...) -} - -func (entry *Entry) Warnln(args ...interface{}) { - entry.Logln(WarnLevel, args...) -} - -func (entry *Entry) Warningln(args ...interface{}) { - entry.Warnln(args...) -} - -func (entry *Entry) Errorln(args ...interface{}) { - entry.Logln(ErrorLevel, args...) -} - -func (entry *Entry) Fatalln(args ...interface{}) { - entry.Logln(FatalLevel, args...) - entry.Logger.Exit(1) -} - -func (entry *Entry) Panicln(args ...interface{}) { - entry.Logln(PanicLevel, args...) -} - -// Sprintlnn => Sprint no newline. This is to get the behavior of how -// fmt.Sprintln where spaces are always added between operands, regardless of -// their type. Instead of vendoring the Sprintln implementation to spare a -// string allocation, we do the simplest thing. -func (entry *Entry) sprintlnn(args ...interface{}) string { - msg := fmt.Sprintln(args...) - return msg[:len(msg)-1] -} diff --git a/vendor/github.com/sirupsen/logrus/exported.go b/vendor/github.com/sirupsen/logrus/exported.go deleted file mode 100644 index 017c30c..0000000 --- a/vendor/github.com/sirupsen/logrus/exported.go +++ /dev/null @@ -1,270 +0,0 @@ -package logrus - -import ( - "context" - "io" - "time" -) - -var ( - // std is the name of the standard logger in stdlib `log` - std = New() -) - -func StandardLogger() *Logger { - return std -} - -// SetOutput sets the standard logger output. -func SetOutput(out io.Writer) { - std.SetOutput(out) -} - -// SetFormatter sets the standard logger formatter. -func SetFormatter(formatter Formatter) { - std.SetFormatter(formatter) -} - -// SetReportCaller sets whether the standard logger will include the calling -// method as a field. -func SetReportCaller(include bool) { - std.SetReportCaller(include) -} - -// SetLevel sets the standard logger level. -func SetLevel(level Level) { - std.SetLevel(level) -} - -// GetLevel returns the standard logger level. -func GetLevel() Level { - return std.GetLevel() -} - -// IsLevelEnabled checks if the log level of the standard logger is greater than the level param -func IsLevelEnabled(level Level) bool { - return std.IsLevelEnabled(level) -} - -// AddHook adds a hook to the standard logger hooks. -func AddHook(hook Hook) { - std.AddHook(hook) -} - -// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key. -func WithError(err error) *Entry { - return std.WithField(ErrorKey, err) -} - -// WithContext creates an entry from the standard logger and adds a context to it. -func WithContext(ctx context.Context) *Entry { - return std.WithContext(ctx) -} - -// WithField creates an entry from the standard logger and adds a field to -// it. If you want multiple fields, use `WithFields`. -// -// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal -// or Panic on the Entry it returns. -func WithField(key string, value interface{}) *Entry { - return std.WithField(key, value) -} - -// WithFields creates an entry from the standard logger and adds multiple -// fields to it. This is simply a helper for `WithField`, invoking it -// once for each field. -// -// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal -// or Panic on the Entry it returns. -func WithFields(fields Fields) *Entry { - return std.WithFields(fields) -} - -// WithTime creates an entry from the standard logger and overrides the time of -// logs generated with it. -// -// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal -// or Panic on the Entry it returns. -func WithTime(t time.Time) *Entry { - return std.WithTime(t) -} - -// Trace logs a message at level Trace on the standard logger. -func Trace(args ...interface{}) { - std.Trace(args...) -} - -// Debug logs a message at level Debug on the standard logger. -func Debug(args ...interface{}) { - std.Debug(args...) -} - -// Print logs a message at level Info on the standard logger. -func Print(args ...interface{}) { - std.Print(args...) -} - -// Info logs a message at level Info on the standard logger. -func Info(args ...interface{}) { - std.Info(args...) -} - -// Warn logs a message at level Warn on the standard logger. -func Warn(args ...interface{}) { - std.Warn(args...) -} - -// Warning logs a message at level Warn on the standard logger. -func Warning(args ...interface{}) { - std.Warning(args...) -} - -// Error logs a message at level Error on the standard logger. -func Error(args ...interface{}) { - std.Error(args...) -} - -// Panic logs a message at level Panic on the standard logger. -func Panic(args ...interface{}) { - std.Panic(args...) -} - -// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1. -func Fatal(args ...interface{}) { - std.Fatal(args...) -} - -// TraceFn logs a message from a func at level Trace on the standard logger. -func TraceFn(fn LogFunction) { - std.TraceFn(fn) -} - -// DebugFn logs a message from a func at level Debug on the standard logger. -func DebugFn(fn LogFunction) { - std.DebugFn(fn) -} - -// PrintFn logs a message from a func at level Info on the standard logger. -func PrintFn(fn LogFunction) { - std.PrintFn(fn) -} - -// InfoFn logs a message from a func at level Info on the standard logger. -func InfoFn(fn LogFunction) { - std.InfoFn(fn) -} - -// WarnFn logs a message from a func at level Warn on the standard logger. -func WarnFn(fn LogFunction) { - std.WarnFn(fn) -} - -// WarningFn logs a message from a func at level Warn on the standard logger. -func WarningFn(fn LogFunction) { - std.WarningFn(fn) -} - -// ErrorFn logs a message from a func at level Error on the standard logger. -func ErrorFn(fn LogFunction) { - std.ErrorFn(fn) -} - -// PanicFn logs a message from a func at level Panic on the standard logger. -func PanicFn(fn LogFunction) { - std.PanicFn(fn) -} - -// FatalFn logs a message from a func at level Fatal on the standard logger then the process will exit with status set to 1. -func FatalFn(fn LogFunction) { - std.FatalFn(fn) -} - -// Tracef logs a message at level Trace on the standard logger. -func Tracef(format string, args ...interface{}) { - std.Tracef(format, args...) -} - -// Debugf logs a message at level Debug on the standard logger. -func Debugf(format string, args ...interface{}) { - std.Debugf(format, args...) -} - -// Printf logs a message at level Info on the standard logger. -func Printf(format string, args ...interface{}) { - std.Printf(format, args...) -} - -// Infof logs a message at level Info on the standard logger. -func Infof(format string, args ...interface{}) { - std.Infof(format, args...) -} - -// Warnf logs a message at level Warn on the standard logger. -func Warnf(format string, args ...interface{}) { - std.Warnf(format, args...) -} - -// Warningf logs a message at level Warn on the standard logger. -func Warningf(format string, args ...interface{}) { - std.Warningf(format, args...) -} - -// Errorf logs a message at level Error on the standard logger. -func Errorf(format string, args ...interface{}) { - std.Errorf(format, args...) -} - -// Panicf logs a message at level Panic on the standard logger. -func Panicf(format string, args ...interface{}) { - std.Panicf(format, args...) -} - -// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1. -func Fatalf(format string, args ...interface{}) { - std.Fatalf(format, args...) -} - -// Traceln logs a message at level Trace on the standard logger. -func Traceln(args ...interface{}) { - std.Traceln(args...) -} - -// Debugln logs a message at level Debug on the standard logger. -func Debugln(args ...interface{}) { - std.Debugln(args...) -} - -// Println logs a message at level Info on the standard logger. -func Println(args ...interface{}) { - std.Println(args...) -} - -// Infoln logs a message at level Info on the standard logger. -func Infoln(args ...interface{}) { - std.Infoln(args...) -} - -// Warnln logs a message at level Warn on the standard logger. -func Warnln(args ...interface{}) { - std.Warnln(args...) -} - -// Warningln logs a message at level Warn on the standard logger. -func Warningln(args ...interface{}) { - std.Warningln(args...) -} - -// Errorln logs a message at level Error on the standard logger. -func Errorln(args ...interface{}) { - std.Errorln(args...) -} - -// Panicln logs a message at level Panic on the standard logger. -func Panicln(args ...interface{}) { - std.Panicln(args...) -} - -// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1. -func Fatalln(args ...interface{}) { - std.Fatalln(args...) -} diff --git a/vendor/github.com/sirupsen/logrus/formatter.go b/vendor/github.com/sirupsen/logrus/formatter.go deleted file mode 100644 index 4088837..0000000 --- a/vendor/github.com/sirupsen/logrus/formatter.go +++ /dev/null @@ -1,78 +0,0 @@ -package logrus - -import "time" - -// Default key names for the default fields -const ( - defaultTimestampFormat = time.RFC3339 - FieldKeyMsg = "msg" - FieldKeyLevel = "level" - FieldKeyTime = "time" - FieldKeyLogrusError = "logrus_error" - FieldKeyFunc = "func" - FieldKeyFile = "file" -) - -// The Formatter interface is used to implement a custom Formatter. It takes an -// `Entry`. It exposes all the fields, including the default ones: -// -// * `entry.Data["msg"]`. The message passed from Info, Warn, Error .. -// * `entry.Data["time"]`. The timestamp. -// * `entry.Data["level"]. The level the entry was logged at. -// -// Any additional fields added with `WithField` or `WithFields` are also in -// `entry.Data`. Format is expected to return an array of bytes which are then -// logged to `logger.Out`. -type Formatter interface { - Format(*Entry) ([]byte, error) -} - -// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when -// dumping it. If this code wasn't there doing: -// -// logrus.WithField("level", 1).Info("hello") -// -// Would just silently drop the user provided level. Instead with this code -// it'll logged as: -// -// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."} -// -// It's not exported because it's still using Data in an opinionated way. It's to -// avoid code duplication between the two default formatters. -func prefixFieldClashes(data Fields, fieldMap FieldMap, reportCaller bool) { - timeKey := fieldMap.resolve(FieldKeyTime) - if t, ok := data[timeKey]; ok { - data["fields."+timeKey] = t - delete(data, timeKey) - } - - msgKey := fieldMap.resolve(FieldKeyMsg) - if m, ok := data[msgKey]; ok { - data["fields."+msgKey] = m - delete(data, msgKey) - } - - levelKey := fieldMap.resolve(FieldKeyLevel) - if l, ok := data[levelKey]; ok { - data["fields."+levelKey] = l - delete(data, levelKey) - } - - logrusErrKey := fieldMap.resolve(FieldKeyLogrusError) - if l, ok := data[logrusErrKey]; ok { - data["fields."+logrusErrKey] = l - delete(data, logrusErrKey) - } - - // If reportCaller is not set, 'func' will not conflict. - if reportCaller { - funcKey := fieldMap.resolve(FieldKeyFunc) - if l, ok := data[funcKey]; ok { - data["fields."+funcKey] = l - } - fileKey := fieldMap.resolve(FieldKeyFile) - if l, ok := data[fileKey]; ok { - data["fields."+fileKey] = l - } - } -} diff --git a/vendor/github.com/sirupsen/logrus/hooks.go b/vendor/github.com/sirupsen/logrus/hooks.go deleted file mode 100644 index 3f151cd..0000000 --- a/vendor/github.com/sirupsen/logrus/hooks.go +++ /dev/null @@ -1,34 +0,0 @@ -package logrus - -// A hook to be fired when logging on the logging levels returned from -// `Levels()` on your implementation of the interface. Note that this is not -// fired in a goroutine or a channel with workers, you should handle such -// functionality yourself if your call is non-blocking and you don't wish for -// the logging calls for levels returned from `Levels()` to block. -type Hook interface { - Levels() []Level - Fire(*Entry) error -} - -// Internal type for storing the hooks on a logger instance. -type LevelHooks map[Level][]Hook - -// Add a hook to an instance of logger. This is called with -// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface. -func (hooks LevelHooks) Add(hook Hook) { - for _, level := range hook.Levels() { - hooks[level] = append(hooks[level], hook) - } -} - -// Fire all the hooks for the passed level. Used by `entry.log` to fire -// appropriate hooks for a log entry. -func (hooks LevelHooks) Fire(level Level, entry *Entry) error { - for _, hook := range hooks[level] { - if err := hook.Fire(entry); err != nil { - return err - } - } - - return nil -} diff --git a/vendor/github.com/sirupsen/logrus/json_formatter.go b/vendor/github.com/sirupsen/logrus/json_formatter.go deleted file mode 100644 index c96dc56..0000000 --- a/vendor/github.com/sirupsen/logrus/json_formatter.go +++ /dev/null @@ -1,128 +0,0 @@ -package logrus - -import ( - "bytes" - "encoding/json" - "fmt" - "runtime" -) - -type fieldKey string - -// FieldMap allows customization of the key names for default fields. -type FieldMap map[fieldKey]string - -func (f FieldMap) resolve(key fieldKey) string { - if k, ok := f[key]; ok { - return k - } - - return string(key) -} - -// JSONFormatter formats logs into parsable json -type JSONFormatter struct { - // TimestampFormat sets the format used for marshaling timestamps. - // The format to use is the same than for time.Format or time.Parse from the standard - // library. - // The standard Library already provides a set of predefined format. - TimestampFormat string - - // DisableTimestamp allows disabling automatic timestamps in output - DisableTimestamp bool - - // DisableHTMLEscape allows disabling html escaping in output - DisableHTMLEscape bool - - // DataKey allows users to put all the log entry parameters into a nested dictionary at a given key. - DataKey string - - // FieldMap allows users to customize the names of keys for default fields. - // As an example: - // formatter := &JSONFormatter{ - // FieldMap: FieldMap{ - // FieldKeyTime: "@timestamp", - // FieldKeyLevel: "@level", - // FieldKeyMsg: "@message", - // FieldKeyFunc: "@caller", - // }, - // } - FieldMap FieldMap - - // CallerPrettyfier can be set by the user to modify the content - // of the function and file keys in the json data when ReportCaller is - // activated. If any of the returned value is the empty string the - // corresponding key will be removed from json fields. - CallerPrettyfier func(*runtime.Frame) (function string, file string) - - // PrettyPrint will indent all json logs - PrettyPrint bool -} - -// Format renders a single log entry -func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { - data := make(Fields, len(entry.Data)+4) - for k, v := range entry.Data { - switch v := v.(type) { - case error: - // Otherwise errors are ignored by `encoding/json` - // https://github.com/sirupsen/logrus/issues/137 - data[k] = v.Error() - default: - data[k] = v - } - } - - if f.DataKey != "" { - newData := make(Fields, 4) - newData[f.DataKey] = data - data = newData - } - - prefixFieldClashes(data, f.FieldMap, entry.HasCaller()) - - timestampFormat := f.TimestampFormat - if timestampFormat == "" { - timestampFormat = defaultTimestampFormat - } - - if entry.err != "" { - data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err - } - if !f.DisableTimestamp { - data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat) - } - data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message - data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String() - if entry.HasCaller() { - funcVal := entry.Caller.Function - fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) - if f.CallerPrettyfier != nil { - funcVal, fileVal = f.CallerPrettyfier(entry.Caller) - } - if funcVal != "" { - data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal - } - if fileVal != "" { - data[f.FieldMap.resolve(FieldKeyFile)] = fileVal - } - } - - var b *bytes.Buffer - if entry.Buffer != nil { - b = entry.Buffer - } else { - b = &bytes.Buffer{} - } - - encoder := json.NewEncoder(b) - encoder.SetEscapeHTML(!f.DisableHTMLEscape) - if f.PrettyPrint { - encoder.SetIndent("", " ") - } - if err := encoder.Encode(data); err != nil { - return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err) - } - - return b.Bytes(), nil -} diff --git a/vendor/github.com/sirupsen/logrus/logger.go b/vendor/github.com/sirupsen/logrus/logger.go deleted file mode 100644 index 5ff0aef..0000000 --- a/vendor/github.com/sirupsen/logrus/logger.go +++ /dev/null @@ -1,417 +0,0 @@ -package logrus - -import ( - "context" - "io" - "os" - "sync" - "sync/atomic" - "time" -) - -// LogFunction For big messages, it can be more efficient to pass a function -// and only call it if the log level is actually enables rather than -// generating the log message and then checking if the level is enabled -type LogFunction func() []interface{} - -type Logger struct { - // The logs are `io.Copy`'d to this in a mutex. It's common to set this to a - // file, or leave it default which is `os.Stderr`. You can also set this to - // something more adventurous, such as logging to Kafka. - Out io.Writer - // Hooks for the logger instance. These allow firing events based on logging - // levels and log entries. For example, to send errors to an error tracking - // service, log to StatsD or dump the core on fatal errors. - Hooks LevelHooks - // All log entries pass through the formatter before logged to Out. The - // included formatters are `TextFormatter` and `JSONFormatter` for which - // TextFormatter is the default. In development (when a TTY is attached) it - // logs with colors, but to a file it wouldn't. You can easily implement your - // own that implements the `Formatter` interface, see the `README` or included - // formatters for examples. - Formatter Formatter - - // Flag for whether to log caller info (off by default) - ReportCaller bool - - // The logging level the logger should log at. This is typically (and defaults - // to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be - // logged. - Level Level - // Used to sync writing to the log. Locking is enabled by Default - mu MutexWrap - // Reusable empty entry - entryPool sync.Pool - // Function to exit the application, defaults to `os.Exit()` - ExitFunc exitFunc - // The buffer pool used to format the log. If it is nil, the default global - // buffer pool will be used. - BufferPool BufferPool -} - -type exitFunc func(int) - -type MutexWrap struct { - lock sync.Mutex - disabled bool -} - -func (mw *MutexWrap) Lock() { - if !mw.disabled { - mw.lock.Lock() - } -} - -func (mw *MutexWrap) Unlock() { - if !mw.disabled { - mw.lock.Unlock() - } -} - -func (mw *MutexWrap) Disable() { - mw.disabled = true -} - -// Creates a new logger. Configuration should be set by changing `Formatter`, -// `Out` and `Hooks` directly on the default logger instance. You can also just -// instantiate your own: -// -// var log = &logrus.Logger{ -// Out: os.Stderr, -// Formatter: new(logrus.TextFormatter), -// Hooks: make(logrus.LevelHooks), -// Level: logrus.DebugLevel, -// } -// -// It's recommended to make this a global instance called `log`. -func New() *Logger { - return &Logger{ - Out: os.Stderr, - Formatter: new(TextFormatter), - Hooks: make(LevelHooks), - Level: InfoLevel, - ExitFunc: os.Exit, - ReportCaller: false, - } -} - -func (logger *Logger) newEntry() *Entry { - entry, ok := logger.entryPool.Get().(*Entry) - if ok { - return entry - } - return NewEntry(logger) -} - -func (logger *Logger) releaseEntry(entry *Entry) { - entry.Data = map[string]interface{}{} - logger.entryPool.Put(entry) -} - -// WithField allocates a new entry and adds a field to it. -// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to -// this new returned entry. -// If you want multiple fields, use `WithFields`. -func (logger *Logger) WithField(key string, value interface{}) *Entry { - entry := logger.newEntry() - defer logger.releaseEntry(entry) - return entry.WithField(key, value) -} - -// Adds a struct of fields to the log entry. All it does is call `WithField` for -// each `Field`. -func (logger *Logger) WithFields(fields Fields) *Entry { - entry := logger.newEntry() - defer logger.releaseEntry(entry) - return entry.WithFields(fields) -} - -// Add an error as single field to the log entry. All it does is call -// `WithError` for the given `error`. -func (logger *Logger) WithError(err error) *Entry { - entry := logger.newEntry() - defer logger.releaseEntry(entry) - return entry.WithError(err) -} - -// Add a context to the log entry. -func (logger *Logger) WithContext(ctx context.Context) *Entry { - entry := logger.newEntry() - defer logger.releaseEntry(entry) - return entry.WithContext(ctx) -} - -// Overrides the time of the log entry. -func (logger *Logger) WithTime(t time.Time) *Entry { - entry := logger.newEntry() - defer logger.releaseEntry(entry) - return entry.WithTime(t) -} - -func (logger *Logger) Logf(level Level, format string, args ...interface{}) { - if logger.IsLevelEnabled(level) { - entry := logger.newEntry() - entry.Logf(level, format, args...) - logger.releaseEntry(entry) - } -} - -func (logger *Logger) Tracef(format string, args ...interface{}) { - logger.Logf(TraceLevel, format, args...) -} - -func (logger *Logger) Debugf(format string, args ...interface{}) { - logger.Logf(DebugLevel, format, args...) -} - -func (logger *Logger) Infof(format string, args ...interface{}) { - logger.Logf(InfoLevel, format, args...) -} - -func (logger *Logger) Printf(format string, args ...interface{}) { - entry := logger.newEntry() - entry.Printf(format, args...) - logger.releaseEntry(entry) -} - -func (logger *Logger) Warnf(format string, args ...interface{}) { - logger.Logf(WarnLevel, format, args...) -} - -func (logger *Logger) Warningf(format string, args ...interface{}) { - logger.Warnf(format, args...) -} - -func (logger *Logger) Errorf(format string, args ...interface{}) { - logger.Logf(ErrorLevel, format, args...) -} - -func (logger *Logger) Fatalf(format string, args ...interface{}) { - logger.Logf(FatalLevel, format, args...) - logger.Exit(1) -} - -func (logger *Logger) Panicf(format string, args ...interface{}) { - logger.Logf(PanicLevel, format, args...) -} - -// Log will log a message at the level given as parameter. -// Warning: using Log at Panic or Fatal level will not respectively Panic nor Exit. -// For this behaviour Logger.Panic or Logger.Fatal should be used instead. -func (logger *Logger) Log(level Level, args ...interface{}) { - if logger.IsLevelEnabled(level) { - entry := logger.newEntry() - entry.Log(level, args...) - logger.releaseEntry(entry) - } -} - -func (logger *Logger) LogFn(level Level, fn LogFunction) { - if logger.IsLevelEnabled(level) { - entry := logger.newEntry() - entry.Log(level, fn()...) - logger.releaseEntry(entry) - } -} - -func (logger *Logger) Trace(args ...interface{}) { - logger.Log(TraceLevel, args...) -} - -func (logger *Logger) Debug(args ...interface{}) { - logger.Log(DebugLevel, args...) -} - -func (logger *Logger) Info(args ...interface{}) { - logger.Log(InfoLevel, args...) -} - -func (logger *Logger) Print(args ...interface{}) { - entry := logger.newEntry() - entry.Print(args...) - logger.releaseEntry(entry) -} - -func (logger *Logger) Warn(args ...interface{}) { - logger.Log(WarnLevel, args...) -} - -func (logger *Logger) Warning(args ...interface{}) { - logger.Warn(args...) -} - -func (logger *Logger) Error(args ...interface{}) { - logger.Log(ErrorLevel, args...) -} - -func (logger *Logger) Fatal(args ...interface{}) { - logger.Log(FatalLevel, args...) - logger.Exit(1) -} - -func (logger *Logger) Panic(args ...interface{}) { - logger.Log(PanicLevel, args...) -} - -func (logger *Logger) TraceFn(fn LogFunction) { - logger.LogFn(TraceLevel, fn) -} - -func (logger *Logger) DebugFn(fn LogFunction) { - logger.LogFn(DebugLevel, fn) -} - -func (logger *Logger) InfoFn(fn LogFunction) { - logger.LogFn(InfoLevel, fn) -} - -func (logger *Logger) PrintFn(fn LogFunction) { - entry := logger.newEntry() - entry.Print(fn()...) - logger.releaseEntry(entry) -} - -func (logger *Logger) WarnFn(fn LogFunction) { - logger.LogFn(WarnLevel, fn) -} - -func (logger *Logger) WarningFn(fn LogFunction) { - logger.WarnFn(fn) -} - -func (logger *Logger) ErrorFn(fn LogFunction) { - logger.LogFn(ErrorLevel, fn) -} - -func (logger *Logger) FatalFn(fn LogFunction) { - logger.LogFn(FatalLevel, fn) - logger.Exit(1) -} - -func (logger *Logger) PanicFn(fn LogFunction) { - logger.LogFn(PanicLevel, fn) -} - -func (logger *Logger) Logln(level Level, args ...interface{}) { - if logger.IsLevelEnabled(level) { - entry := logger.newEntry() - entry.Logln(level, args...) - logger.releaseEntry(entry) - } -} - -func (logger *Logger) Traceln(args ...interface{}) { - logger.Logln(TraceLevel, args...) -} - -func (logger *Logger) Debugln(args ...interface{}) { - logger.Logln(DebugLevel, args...) -} - -func (logger *Logger) Infoln(args ...interface{}) { - logger.Logln(InfoLevel, args...) -} - -func (logger *Logger) Println(args ...interface{}) { - entry := logger.newEntry() - entry.Println(args...) - logger.releaseEntry(entry) -} - -func (logger *Logger) Warnln(args ...interface{}) { - logger.Logln(WarnLevel, args...) -} - -func (logger *Logger) Warningln(args ...interface{}) { - logger.Warnln(args...) -} - -func (logger *Logger) Errorln(args ...interface{}) { - logger.Logln(ErrorLevel, args...) -} - -func (logger *Logger) Fatalln(args ...interface{}) { - logger.Logln(FatalLevel, args...) - logger.Exit(1) -} - -func (logger *Logger) Panicln(args ...interface{}) { - logger.Logln(PanicLevel, args...) -} - -func (logger *Logger) Exit(code int) { - runHandlers() - if logger.ExitFunc == nil { - logger.ExitFunc = os.Exit - } - logger.ExitFunc(code) -} - -//When file is opened with appending mode, it's safe to -//write concurrently to a file (within 4k message on Linux). -//In these cases user can choose to disable the lock. -func (logger *Logger) SetNoLock() { - logger.mu.Disable() -} - -func (logger *Logger) level() Level { - return Level(atomic.LoadUint32((*uint32)(&logger.Level))) -} - -// SetLevel sets the logger level. -func (logger *Logger) SetLevel(level Level) { - atomic.StoreUint32((*uint32)(&logger.Level), uint32(level)) -} - -// GetLevel returns the logger level. -func (logger *Logger) GetLevel() Level { - return logger.level() -} - -// AddHook adds a hook to the logger hooks. -func (logger *Logger) AddHook(hook Hook) { - logger.mu.Lock() - defer logger.mu.Unlock() - logger.Hooks.Add(hook) -} - -// IsLevelEnabled checks if the log level of the logger is greater than the level param -func (logger *Logger) IsLevelEnabled(level Level) bool { - return logger.level() >= level -} - -// SetFormatter sets the logger formatter. -func (logger *Logger) SetFormatter(formatter Formatter) { - logger.mu.Lock() - defer logger.mu.Unlock() - logger.Formatter = formatter -} - -// SetOutput sets the logger output. -func (logger *Logger) SetOutput(output io.Writer) { - logger.mu.Lock() - defer logger.mu.Unlock() - logger.Out = output -} - -func (logger *Logger) SetReportCaller(reportCaller bool) { - logger.mu.Lock() - defer logger.mu.Unlock() - logger.ReportCaller = reportCaller -} - -// ReplaceHooks replaces the logger hooks and returns the old ones -func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks { - logger.mu.Lock() - oldHooks := logger.Hooks - logger.Hooks = hooks - logger.mu.Unlock() - return oldHooks -} - -// SetBufferPool sets the logger buffer pool. -func (logger *Logger) SetBufferPool(pool BufferPool) { - logger.mu.Lock() - defer logger.mu.Unlock() - logger.BufferPool = pool -} diff --git a/vendor/github.com/sirupsen/logrus/logrus.go b/vendor/github.com/sirupsen/logrus/logrus.go deleted file mode 100644 index 2f16224..0000000 --- a/vendor/github.com/sirupsen/logrus/logrus.go +++ /dev/null @@ -1,186 +0,0 @@ -package logrus - -import ( - "fmt" - "log" - "strings" -) - -// Fields type, used to pass to `WithFields`. -type Fields map[string]interface{} - -// Level type -type Level uint32 - -// Convert the Level to a string. E.g. PanicLevel becomes "panic". -func (level Level) String() string { - if b, err := level.MarshalText(); err == nil { - return string(b) - } else { - return "unknown" - } -} - -// ParseLevel takes a string level and returns the Logrus log level constant. -func ParseLevel(lvl string) (Level, error) { - switch strings.ToLower(lvl) { - case "panic": - return PanicLevel, nil - case "fatal": - return FatalLevel, nil - case "error": - return ErrorLevel, nil - case "warn", "warning": - return WarnLevel, nil - case "info": - return InfoLevel, nil - case "debug": - return DebugLevel, nil - case "trace": - return TraceLevel, nil - } - - var l Level - return l, fmt.Errorf("not a valid logrus Level: %q", lvl) -} - -// UnmarshalText implements encoding.TextUnmarshaler. -func (level *Level) UnmarshalText(text []byte) error { - l, err := ParseLevel(string(text)) - if err != nil { - return err - } - - *level = l - - return nil -} - -func (level Level) MarshalText() ([]byte, error) { - switch level { - case TraceLevel: - return []byte("trace"), nil - case DebugLevel: - return []byte("debug"), nil - case InfoLevel: - return []byte("info"), nil - case WarnLevel: - return []byte("warning"), nil - case ErrorLevel: - return []byte("error"), nil - case FatalLevel: - return []byte("fatal"), nil - case PanicLevel: - return []byte("panic"), nil - } - - return nil, fmt.Errorf("not a valid logrus level %d", level) -} - -// A constant exposing all logging levels -var AllLevels = []Level{ - PanicLevel, - FatalLevel, - ErrorLevel, - WarnLevel, - InfoLevel, - DebugLevel, - TraceLevel, -} - -// These are the different logging levels. You can set the logging level to log -// on your instance of logger, obtained with `logrus.New()`. -const ( - // PanicLevel level, highest level of severity. Logs and then calls panic with the - // message passed to Debug, Info, ... - PanicLevel Level = iota - // FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the - // logging level is set to Panic. - FatalLevel - // ErrorLevel level. Logs. Used for errors that should definitely be noted. - // Commonly used for hooks to send errors to an error tracking service. - ErrorLevel - // WarnLevel level. Non-critical entries that deserve eyes. - WarnLevel - // InfoLevel level. General operational entries about what's going on inside the - // application. - InfoLevel - // DebugLevel level. Usually only enabled when debugging. Very verbose logging. - DebugLevel - // TraceLevel level. Designates finer-grained informational events than the Debug. - TraceLevel -) - -// Won't compile if StdLogger can't be realized by a log.Logger -var ( - _ StdLogger = &log.Logger{} - _ StdLogger = &Entry{} - _ StdLogger = &Logger{} -) - -// StdLogger is what your logrus-enabled library should take, that way -// it'll accept a stdlib logger and a logrus logger. There's no standard -// interface, this is the closest we get, unfortunately. -type StdLogger interface { - Print(...interface{}) - Printf(string, ...interface{}) - Println(...interface{}) - - Fatal(...interface{}) - Fatalf(string, ...interface{}) - Fatalln(...interface{}) - - Panic(...interface{}) - Panicf(string, ...interface{}) - Panicln(...interface{}) -} - -// The FieldLogger interface generalizes the Entry and Logger types -type FieldLogger interface { - WithField(key string, value interface{}) *Entry - WithFields(fields Fields) *Entry - WithError(err error) *Entry - - Debugf(format string, args ...interface{}) - Infof(format string, args ...interface{}) - Printf(format string, args ...interface{}) - Warnf(format string, args ...interface{}) - Warningf(format string, args ...interface{}) - Errorf(format string, args ...interface{}) - Fatalf(format string, args ...interface{}) - Panicf(format string, args ...interface{}) - - Debug(args ...interface{}) - Info(args ...interface{}) - Print(args ...interface{}) - Warn(args ...interface{}) - Warning(args ...interface{}) - Error(args ...interface{}) - Fatal(args ...interface{}) - Panic(args ...interface{}) - - Debugln(args ...interface{}) - Infoln(args ...interface{}) - Println(args ...interface{}) - Warnln(args ...interface{}) - Warningln(args ...interface{}) - Errorln(args ...interface{}) - Fatalln(args ...interface{}) - Panicln(args ...interface{}) - - // IsDebugEnabled() bool - // IsInfoEnabled() bool - // IsWarnEnabled() bool - // IsErrorEnabled() bool - // IsFatalEnabled() bool - // IsPanicEnabled() bool -} - -// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is -// here for consistancy. Do not use. Use Logger or Entry instead. -type Ext1FieldLogger interface { - FieldLogger - Tracef(format string, args ...interface{}) - Trace(args ...interface{}) - Traceln(args ...interface{}) -} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go b/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go deleted file mode 100644 index 2403de9..0000000 --- a/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build appengine - -package logrus - -import ( - "io" -) - -func checkIfTerminal(w io.Writer) bool { - return true -} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go b/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go deleted file mode 100644 index 4997899..0000000 --- a/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go +++ /dev/null @@ -1,13 +0,0 @@ -// +build darwin dragonfly freebsd netbsd openbsd -// +build !js - -package logrus - -import "golang.org/x/sys/unix" - -const ioctlReadTermios = unix.TIOCGETA - -func isTerminal(fd int) bool { - _, err := unix.IoctlGetTermios(fd, ioctlReadTermios) - return err == nil -} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_js.go b/vendor/github.com/sirupsen/logrus/terminal_check_js.go deleted file mode 100644 index ebdae3e..0000000 --- a/vendor/github.com/sirupsen/logrus/terminal_check_js.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build js - -package logrus - -func isTerminal(fd int) bool { - return false -} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go b/vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go deleted file mode 100644 index 97af92c..0000000 --- a/vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build js nacl plan9 - -package logrus - -import ( - "io" -) - -func checkIfTerminal(w io.Writer) bool { - return false -} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go b/vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go deleted file mode 100644 index 3293fb3..0000000 --- a/vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go +++ /dev/null @@ -1,17 +0,0 @@ -// +build !appengine,!js,!windows,!nacl,!plan9 - -package logrus - -import ( - "io" - "os" -) - -func checkIfTerminal(w io.Writer) bool { - switch v := w.(type) { - case *os.File: - return isTerminal(int(v.Fd())) - default: - return false - } -} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_solaris.go b/vendor/github.com/sirupsen/logrus/terminal_check_solaris.go deleted file mode 100644 index f6710b3..0000000 --- a/vendor/github.com/sirupsen/logrus/terminal_check_solaris.go +++ /dev/null @@ -1,11 +0,0 @@ -package logrus - -import ( - "golang.org/x/sys/unix" -) - -// IsTerminal returns true if the given file descriptor is a terminal. -func isTerminal(fd int) bool { - _, err := unix.IoctlGetTermio(fd, unix.TCGETA) - return err == nil -} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_unix.go b/vendor/github.com/sirupsen/logrus/terminal_check_unix.go deleted file mode 100644 index 04748b8..0000000 --- a/vendor/github.com/sirupsen/logrus/terminal_check_unix.go +++ /dev/null @@ -1,13 +0,0 @@ -// +build linux aix zos -// +build !js - -package logrus - -import "golang.org/x/sys/unix" - -const ioctlReadTermios = unix.TCGETS - -func isTerminal(fd int) bool { - _, err := unix.IoctlGetTermios(fd, ioctlReadTermios) - return err == nil -} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_windows.go b/vendor/github.com/sirupsen/logrus/terminal_check_windows.go deleted file mode 100644 index 2879eb5..0000000 --- a/vendor/github.com/sirupsen/logrus/terminal_check_windows.go +++ /dev/null @@ -1,27 +0,0 @@ -// +build !appengine,!js,windows - -package logrus - -import ( - "io" - "os" - - "golang.org/x/sys/windows" -) - -func checkIfTerminal(w io.Writer) bool { - switch v := w.(type) { - case *os.File: - handle := windows.Handle(v.Fd()) - var mode uint32 - if err := windows.GetConsoleMode(handle, &mode); err != nil { - return false - } - mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING - if err := windows.SetConsoleMode(handle, mode); err != nil { - return false - } - return true - } - return false -} diff --git a/vendor/github.com/sirupsen/logrus/text_formatter.go b/vendor/github.com/sirupsen/logrus/text_formatter.go deleted file mode 100644 index be2c6ef..0000000 --- a/vendor/github.com/sirupsen/logrus/text_formatter.go +++ /dev/null @@ -1,339 +0,0 @@ -package logrus - -import ( - "bytes" - "fmt" - "os" - "runtime" - "sort" - "strconv" - "strings" - "sync" - "time" - "unicode/utf8" -) - -const ( - red = 31 - yellow = 33 - blue = 36 - gray = 37 -) - -var baseTimestamp time.Time - -func init() { - baseTimestamp = time.Now() -} - -// TextFormatter formats logs into text -type TextFormatter struct { - // Set to true to bypass checking for a TTY before outputting colors. - ForceColors bool - - // Force disabling colors. - DisableColors bool - - // Force quoting of all values - ForceQuote bool - - // DisableQuote disables quoting for all values. - // DisableQuote will have a lower priority than ForceQuote. - // If both of them are set to true, quote will be forced on all values. - DisableQuote bool - - // Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/ - EnvironmentOverrideColors bool - - // Disable timestamp logging. useful when output is redirected to logging - // system that already adds timestamps. - DisableTimestamp bool - - // Enable logging the full timestamp when a TTY is attached instead of just - // the time passed since beginning of execution. - FullTimestamp bool - - // TimestampFormat to use for display when a full timestamp is printed. - // The format to use is the same than for time.Format or time.Parse from the standard - // library. - // The standard Library already provides a set of predefined format. - TimestampFormat string - - // The fields are sorted by default for a consistent output. For applications - // that log extremely frequently and don't use the JSON formatter this may not - // be desired. - DisableSorting bool - - // The keys sorting function, when uninitialized it uses sort.Strings. - SortingFunc func([]string) - - // Disables the truncation of the level text to 4 characters. - DisableLevelTruncation bool - - // PadLevelText Adds padding the level text so that all the levels output at the same length - // PadLevelText is a superset of the DisableLevelTruncation option - PadLevelText bool - - // QuoteEmptyFields will wrap empty fields in quotes if true - QuoteEmptyFields bool - - // Whether the logger's out is to a terminal - isTerminal bool - - // FieldMap allows users to customize the names of keys for default fields. - // As an example: - // formatter := &TextFormatter{ - // FieldMap: FieldMap{ - // FieldKeyTime: "@timestamp", - // FieldKeyLevel: "@level", - // FieldKeyMsg: "@message"}} - FieldMap FieldMap - - // CallerPrettyfier can be set by the user to modify the content - // of the function and file keys in the data when ReportCaller is - // activated. If any of the returned value is the empty string the - // corresponding key will be removed from fields. - CallerPrettyfier func(*runtime.Frame) (function string, file string) - - terminalInitOnce sync.Once - - // The max length of the level text, generated dynamically on init - levelTextMaxLength int -} - -func (f *TextFormatter) init(entry *Entry) { - if entry.Logger != nil { - f.isTerminal = checkIfTerminal(entry.Logger.Out) - } - // Get the max length of the level text - for _, level := range AllLevels { - levelTextLength := utf8.RuneCount([]byte(level.String())) - if levelTextLength > f.levelTextMaxLength { - f.levelTextMaxLength = levelTextLength - } - } -} - -func (f *TextFormatter) isColored() bool { - isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows")) - - if f.EnvironmentOverrideColors { - switch force, ok := os.LookupEnv("CLICOLOR_FORCE"); { - case ok && force != "0": - isColored = true - case ok && force == "0", os.Getenv("CLICOLOR") == "0": - isColored = false - } - } - - return isColored && !f.DisableColors -} - -// Format renders a single log entry -func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { - data := make(Fields) - for k, v := range entry.Data { - data[k] = v - } - prefixFieldClashes(data, f.FieldMap, entry.HasCaller()) - keys := make([]string, 0, len(data)) - for k := range data { - keys = append(keys, k) - } - - var funcVal, fileVal string - - fixedKeys := make([]string, 0, 4+len(data)) - if !f.DisableTimestamp { - fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime)) - } - fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel)) - if entry.Message != "" { - fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg)) - } - if entry.err != "" { - fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError)) - } - if entry.HasCaller() { - if f.CallerPrettyfier != nil { - funcVal, fileVal = f.CallerPrettyfier(entry.Caller) - } else { - funcVal = entry.Caller.Function - fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) - } - - if funcVal != "" { - fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc)) - } - if fileVal != "" { - fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile)) - } - } - - if !f.DisableSorting { - if f.SortingFunc == nil { - sort.Strings(keys) - fixedKeys = append(fixedKeys, keys...) - } else { - if !f.isColored() { - fixedKeys = append(fixedKeys, keys...) - f.SortingFunc(fixedKeys) - } else { - f.SortingFunc(keys) - } - } - } else { - fixedKeys = append(fixedKeys, keys...) - } - - var b *bytes.Buffer - if entry.Buffer != nil { - b = entry.Buffer - } else { - b = &bytes.Buffer{} - } - - f.terminalInitOnce.Do(func() { f.init(entry) }) - - timestampFormat := f.TimestampFormat - if timestampFormat == "" { - timestampFormat = defaultTimestampFormat - } - if f.isColored() { - f.printColored(b, entry, keys, data, timestampFormat) - } else { - - for _, key := range fixedKeys { - var value interface{} - switch { - case key == f.FieldMap.resolve(FieldKeyTime): - value = entry.Time.Format(timestampFormat) - case key == f.FieldMap.resolve(FieldKeyLevel): - value = entry.Level.String() - case key == f.FieldMap.resolve(FieldKeyMsg): - value = entry.Message - case key == f.FieldMap.resolve(FieldKeyLogrusError): - value = entry.err - case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller(): - value = funcVal - case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller(): - value = fileVal - default: - value = data[key] - } - f.appendKeyValue(b, key, value) - } - } - - b.WriteByte('\n') - return b.Bytes(), nil -} - -func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) { - var levelColor int - switch entry.Level { - case DebugLevel, TraceLevel: - levelColor = gray - case WarnLevel: - levelColor = yellow - case ErrorLevel, FatalLevel, PanicLevel: - levelColor = red - case InfoLevel: - levelColor = blue - default: - levelColor = blue - } - - levelText := strings.ToUpper(entry.Level.String()) - if !f.DisableLevelTruncation && !f.PadLevelText { - levelText = levelText[0:4] - } - if f.PadLevelText { - // Generates the format string used in the next line, for example "%-6s" or "%-7s". - // Based on the max level text length. - formatString := "%-" + strconv.Itoa(f.levelTextMaxLength) + "s" - // Formats the level text by appending spaces up to the max length, for example: - // - "INFO " - // - "WARNING" - levelText = fmt.Sprintf(formatString, levelText) - } - - // Remove a single newline if it already exists in the message to keep - // the behavior of logrus text_formatter the same as the stdlib log package - entry.Message = strings.TrimSuffix(entry.Message, "\n") - - caller := "" - if entry.HasCaller() { - funcVal := fmt.Sprintf("%s()", entry.Caller.Function) - fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) - - if f.CallerPrettyfier != nil { - funcVal, fileVal = f.CallerPrettyfier(entry.Caller) - } - - if fileVal == "" { - caller = funcVal - } else if funcVal == "" { - caller = fileVal - } else { - caller = fileVal + " " + funcVal - } - } - - switch { - case f.DisableTimestamp: - fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message) - case !f.FullTimestamp: - fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message) - default: - fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message) - } - for _, k := range keys { - v := data[k] - fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k) - f.appendValue(b, v) - } -} - -func (f *TextFormatter) needsQuoting(text string) bool { - if f.ForceQuote { - return true - } - if f.QuoteEmptyFields && len(text) == 0 { - return true - } - if f.DisableQuote { - return false - } - for _, ch := range text { - if !((ch >= 'a' && ch <= 'z') || - (ch >= 'A' && ch <= 'Z') || - (ch >= '0' && ch <= '9') || - ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') { - return true - } - } - return false -} - -func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) { - if b.Len() > 0 { - b.WriteByte(' ') - } - b.WriteString(key) - b.WriteByte('=') - f.appendValue(b, value) -} - -func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { - stringVal, ok := value.(string) - if !ok { - stringVal = fmt.Sprint(value) - } - - if !f.needsQuoting(stringVal) { - b.WriteString(stringVal) - } else { - b.WriteString(fmt.Sprintf("%q", stringVal)) - } -} diff --git a/vendor/github.com/sirupsen/logrus/writer.go b/vendor/github.com/sirupsen/logrus/writer.go deleted file mode 100644 index 074fd4b..0000000 --- a/vendor/github.com/sirupsen/logrus/writer.go +++ /dev/null @@ -1,102 +0,0 @@ -package logrus - -import ( - "bufio" - "io" - "runtime" - "strings" -) - -// Writer at INFO level. See WriterLevel for details. -func (logger *Logger) Writer() *io.PipeWriter { - return logger.WriterLevel(InfoLevel) -} - -// WriterLevel returns an io.Writer that can be used to write arbitrary text to -// the logger at the given log level. Each line written to the writer will be -// printed in the usual way using formatters and hooks. The writer is part of an -// io.Pipe and it is the callers responsibility to close the writer when done. -// This can be used to override the standard library logger easily. -func (logger *Logger) WriterLevel(level Level) *io.PipeWriter { - return NewEntry(logger).WriterLevel(level) -} - -// Writer returns an io.Writer that writes to the logger at the info log level -func (entry *Entry) Writer() *io.PipeWriter { - return entry.WriterLevel(InfoLevel) -} - -// WriterLevel returns an io.Writer that writes to the logger at the given log level -func (entry *Entry) WriterLevel(level Level) *io.PipeWriter { - reader, writer := io.Pipe() - - var printFunc func(args ...interface{}) - - // Determine which log function to use based on the specified log level - switch level { - case TraceLevel: - printFunc = entry.Trace - case DebugLevel: - printFunc = entry.Debug - case InfoLevel: - printFunc = entry.Info - case WarnLevel: - printFunc = entry.Warn - case ErrorLevel: - printFunc = entry.Error - case FatalLevel: - printFunc = entry.Fatal - case PanicLevel: - printFunc = entry.Panic - default: - printFunc = entry.Print - } - - // Start a new goroutine to scan the input and write it to the logger using the specified print function. - // It splits the input into chunks of up to 64KB to avoid buffer overflows. - go entry.writerScanner(reader, printFunc) - - // Set a finalizer function to close the writer when it is garbage collected - runtime.SetFinalizer(writer, writerFinalizer) - - return writer -} - -// writerScanner scans the input from the reader and writes it to the logger -func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) { - scanner := bufio.NewScanner(reader) - - // Set the buffer size to the maximum token size to avoid buffer overflows - scanner.Buffer(make([]byte, bufio.MaxScanTokenSize), bufio.MaxScanTokenSize) - - // Define a split function to split the input into chunks of up to 64KB - chunkSize := bufio.MaxScanTokenSize // 64KB - splitFunc := func(data []byte, atEOF bool) (int, []byte, error) { - if len(data) >= chunkSize { - return chunkSize, data[:chunkSize], nil - } - - return bufio.ScanLines(data, atEOF) - } - - // Use the custom split function to split the input - scanner.Split(splitFunc) - - // Scan the input and write it to the logger using the specified print function - for scanner.Scan() { - printFunc(strings.TrimRight(scanner.Text(), "\r\n")) - } - - // If there was an error while scanning the input, log an error - if err := scanner.Err(); err != nil { - entry.Errorf("Error while reading from Writer: %s", err) - } - - // Close the reader when we are done - reader.Close() -} - -// WriterFinalizer is a finalizer function that closes then given writer when it is garbage collected -func writerFinalizer(writer *io.PipeWriter) { - writer.Close() -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 8e9a9da..417dbdf 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -163,9 +163,6 @@ github.com/prometheus/procfs/internal/util # github.com/rogpeppe/go-internal v1.12.0 ## explicit; go 1.20 github.com/rogpeppe/go-internal/fmtsort -# github.com/sirupsen/logrus v1.9.3 -## explicit; go 1.13 -github.com/sirupsen/logrus # golang.org/x/crypto v0.25.0 ## explicit; go 1.20 golang.org/x/crypto/pbkdf2