diff --git a/README.md b/README.md index a430edb1..fd31108a 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,7 @@ The following libraries and frameworks are used by GoToSocial, with gratitude - [gruf/go-store](https://codeberg.org/gruf/go-store); cacheing library. [MIT License](https://spdx.org/licenses/MIT.html). - [h2non/filetype](https://github.com/h2non/filetype); filetype checking. [MIT License](https://spdx.org/licenses/MIT.html). - [jackc/pgx](https://github.com/jackc/pgx); Postgres driver. [MIT License](https://spdx.org/licenses/MIT.html). +- [mcuadros/go-syslog](https://github.com/mcuadros/go-syslog); Syslog server library. [MIT License](https://spdx.org/licenses/MIT.html). - [microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday); HTML user-input sanitization. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). - [mitchellh/mapstructure](https://github.com/mitchellh/mapstructure); Go interface => struct parsing. [MIT License](https://spdx.org/licenses/MIT.html). - [modernc.org/sqlite](https://gitlab.com/cznic/sqlite); cgo-free port of SQLite. [Other License](https://gitlab.com/cznic/sqlite/-/blob/master/LICENSE). diff --git a/cmd/gotosocial/common.go b/cmd/gotosocial/common.go index 7b5a4265..3f4b1261 100644 --- a/cmd/gotosocial/common.go +++ b/cmd/gotosocial/common.go @@ -23,7 +23,6 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/log" @@ -52,12 +51,8 @@ func preRun(cmd *cobra.Command) error { // The idea here is to take a GTSAction and run it with the given // context, after initializing any last-minute things like loggers etc. func run(ctx context.Context, action action.GTSAction) error { - // if log level has been set... - if logLevel := viper.GetString(config.Keys.LogLevel); logLevel != "" { - // then try to initialize the logger to that level - if err := log.Initialize(logLevel); err != nil { - return fmt.Errorf("error initializing log: %s", err) - } + if err := log.Initialize(); err != nil { + return fmt.Errorf("error initializing log: %s", err) } return action(ctx) diff --git a/cmd/gotosocial/flag/server.go b/cmd/gotosocial/flag/server.go index 3d3e946f..aa68573a 100644 --- a/cmd/gotosocial/flag/server.go +++ b/cmd/gotosocial/flag/server.go @@ -34,6 +34,7 @@ func Server(cmd *cobra.Command, values config.Values) { OIDC(cmd, values) SMTP(cmd, values) Router(cmd, values) + Syslog(cmd, values) } // Router attaches flags pertaining to the gin router. @@ -109,3 +110,10 @@ func SMTP(cmd *cobra.Command, values config.Values) { cmd.Flags().String(config.Keys.SMTPPassword, values.SMTPPassword, usage.SMTPPassword) cmd.Flags().String(config.Keys.SMTPFrom, values.SMTPFrom, usage.SMTPFrom) } + +// Syslog attaches flags pertaining to syslog config. +func Syslog(cmd *cobra.Command, values config.Values) { + cmd.Flags().Bool(config.Keys.SyslogEnabled, values.SyslogEnabled, usage.SyslogEnabled) + cmd.Flags().String(config.Keys.SyslogProtocol, values.SyslogProtocol, usage.SyslogProtocol) + cmd.Flags().String(config.Keys.SyslogAddress, values.SyslogAddress, usage.SyslogAddress) +} diff --git a/cmd/gotosocial/flag/usage.go b/cmd/gotosocial/flag/usage.go index aa57048c..ada5ab27 100644 --- a/cmd/gotosocial/flag/usage.go +++ b/cmd/gotosocial/flag/usage.go @@ -73,6 +73,9 @@ var usage = config.KeyNames{ SMTPUsername: "Username to authenticate with the smtp server as. Eg., 'postmaster@mail.example.org'", SMTPPassword: "Password to pass to the smtp server.", SMTPFrom: "Address to use as the 'from' field of the email. Eg., 'gotosocial@example.org'", + SyslogEnabled: "Enable the syslog logging hook. Logs will be mirrored to the configured destination.", + SyslogProtocol: "Protocol to use when directing logs to syslog. Leave empty to connect to local syslog.", + SyslogAddress: "Address:port to send syslog logs to. Leave empty to connect to local syslog.", AdminAccountUsername: "the username to create/delete/etc", AdminAccountEmail: "the email address of this account", AdminAccountPassword: "the password to set for this account", diff --git a/docs/configuration/syslog.md b/docs/configuration/syslog.md new file mode 100644 index 00000000..cf983e0b --- /dev/null +++ b/docs/configuration/syslog.md @@ -0,0 +1,41 @@ +# Syslog + +GoToSocial can be configured to mirror logs to [syslog](https://en.wikipedia.org/wiki/Syslog), either via udp/tcp, or a local syslog (eg., `/var/log/syslog`). + +This is useful if you want to daemonize GtS and not handle log rotations etc yourself but rely on a proven implementation. + +Logs in syslog will look something like this: + +```text +Dec 12 17:44:03 dilettante ./gotosocial[246860]: time=2021-12-12T17:44:03+01:00 level=info msg=connected to SQLITE database +Dec 12 17:44:03 dilettante ./gotosocial[246860]: time=2021-12-12T17:44:03+01:00 level=info msg=there are no new migrations to run func=doMigration +``` + +## Settings + +```yaml +######################### +##### SYSLOG CONFIG ##### +######################### + +# Config for additional syslog log hooks. See https://en.wikipedia.org/wiki/Syslog, +# and https://github.com/sirupsen/logrus/tree/master/hooks/syslog. +# +# These settings are useful when one wants to daemonize GoToSocial and send logs +# to a specific place, either a local location or a syslog server. Most users will +# not need to touch these settings. + +# Bool. Enable the syslog logging hook. Logs will be mirrored to the configured destination. +# Options: [true, false] +# Default: false +syslog-enabled: false + +# String. Protocol to use when directing logs to syslog. Leave empty to connect to local syslog. +# Options: ["udp", "tcp", ""] +# Default: "tcp" +syslog-protocol: "udp" + +# String. Address:port to send syslog logs to. Leave empty to connect to local syslog. +# Default: "localhost:514" +syslog-address: "localhost:514" +``` diff --git a/example/config.yaml b/example/config.yaml index 4999b10f..83f68ef2 100644 --- a/example/config.yaml +++ b/example/config.yaml @@ -396,3 +396,28 @@ smtp-password: "" # Examples: ["mail@example.org"] # Default: "" smtp-from: "" + +######################### +##### SYSLOG CONFIG ##### +######################### + +# Config for additional syslog log hooks. See https://en.wikipedia.org/wiki/Syslog, +# and https://github.com/sirupsen/logrus/tree/master/hooks/syslog. +# +# These settings are useful when one wants to daemonize GoToSocial and send logs +# to a specific place, either a local location or a syslog server. Most users will +# not need to touch these settings. + +# Bool. Enable the syslog logging hook. Logs will be mirrored to the configured destination. +# Options: [true, false] +# Default: false +syslog-enabled: false + +# String. Protocol to use when directing logs to syslog. Leave empty to connect to local syslog. +# Options: ["udp", "tcp", ""] +# Default: "tcp" +syslog-protocol: "udp" + +# String. Address:port to send syslog logs to. Leave empty to connect to local syslog. +# Default: "localhost:514" +syslog-address: "localhost:514" diff --git a/go.mod b/go.mod index 7be478c6..3f6dfbb2 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 golang.org/x/text v0.3.7 + gopkg.in/mcuadros/go-syslog.v2 v2.3.0 modernc.org/sqlite v1.14.2 mvdan.cc/xurls/v2 v2.3.0 ) diff --git a/go.sum b/go.sum index e49f21fc..f49ca195 100644 --- a/go.sum +++ b/go.sum @@ -1224,6 +1224,8 @@ gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:a gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mcuadros/go-syslog.v2 v2.3.0 h1:kcsiS+WsTKyIEPABJBJtoG0KkOS6yzvJ+/eZlhD79kk= +gopkg.in/mcuadros/go-syslog.v2 v2.3.0/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= diff --git a/internal/config/defaults.go b/internal/config/defaults.go index 01eef8c8..f1666e1a 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -84,4 +84,8 @@ var Defaults = Values{ SMTPUsername: "", SMTPPassword: "", SMTPFrom: "GoToSocial", + + SyslogEnabled: false, + SyslogProtocol: "udp", + SyslogAddress: "localhost:514", } diff --git a/internal/config/keys.go b/internal/config/keys.go index 80ca3638..a451a8dc 100644 --- a/internal/config/keys.go +++ b/internal/config/keys.go @@ -95,6 +95,11 @@ type KeyNames struct { SMTPPassword string SMTPFrom string + // syslog + SyslogEnabled string + SyslogProtocol string + SyslogAddress string + // admin AdminAccountUsername string AdminAccountEmail string @@ -168,6 +173,10 @@ var Keys = KeyNames{ SMTPPassword: "smtp-password", SMTPFrom: "smtp-from", + SyslogEnabled: "syslog-enabled", + SyslogProtocol: "syslog-protocol", + SyslogAddress: "syslog-address", + AdminAccountUsername: "username", AdminAccountEmail: "email", AdminAccountPassword: "password", diff --git a/internal/config/values.go b/internal/config/values.go index 387f934d..a8ffed1a 100644 --- a/internal/config/values.go +++ b/internal/config/values.go @@ -83,6 +83,10 @@ type Values struct { SMTPPassword string SMTPFrom string + SyslogEnabled bool + SyslogProtocol string + SyslogAddress string + AdminAccountUsername string AdminAccountEmail string AdminAccountPassword string diff --git a/internal/log/log.go b/internal/log/log.go index b0614636..59c99403 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -22,31 +22,60 @@ import ( "bytes" "os" + "log/syslog" + "github.com/sirupsen/logrus" + lSyslog "github.com/sirupsen/logrus/hooks/syslog" + "github.com/spf13/viper" + "github.com/superseriousbusiness/gotosocial/internal/config" ) -// Initialize initializes the global Logrus logger to the specified level +// Initialize initializes the global Logrus logger, reading the desired +// log level from the viper store, or using a default if the level +// has not been set in viper. +// // It also sets the output to log.outputSplitter, // so you get error logs on stderr and normal logs on stdout. -func Initialize(level string) error { +// +// If syslog settings are also in viper, then Syslog will be initialized as well. +func Initialize() error { logrus.SetOutput(&outputSplitter{}) - logLevel, err := logrus.ParseLevel(level) - if err != nil { - return err - } - logrus.SetLevel(logLevel) - - if logLevel == logrus.TraceLevel { - logrus.SetReportCaller(true) - } - logrus.SetFormatter(&logrus.TextFormatter{ DisableColors: true, DisableQuote: true, FullTimestamp: true, }) + keys := config.Keys + + // check if a desired log level has been set + logLevel := viper.GetString(keys.LogLevel) + if logLevel != "" { + level, err := logrus.ParseLevel(logLevel) + if err != nil { + return err + } + logrus.SetLevel(level) + + if level == logrus.TraceLevel { + logrus.SetReportCaller(true) + } + } + + // check if syslog has been enabled, and configure it if so + if syslogEnabled := viper.GetBool(keys.SyslogEnabled); syslogEnabled { + protocol := viper.GetString(keys.SyslogProtocol) + address := viper.GetString(keys.SyslogAddress) + + hook, err := lSyslog.NewSyslogHook(protocol, address, syslog.LOG_INFO, "") + if err != nil { + return err + } + + logrus.AddHook(hook) + } + return nil } diff --git a/internal/log/syslog_test.go b/internal/log/syslog_test.go new file mode 100644 index 00000000..4b6ee14a --- /dev/null +++ b/internal/log/syslog_test.go @@ -0,0 +1,70 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package log_test + +import ( + "testing" + + "github.com/sirupsen/logrus" + "github.com/spf13/viper" + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/testrig" + "gopkg.in/mcuadros/go-syslog.v2" + "gopkg.in/mcuadros/go-syslog.v2/format" +) + +type SyslogTestSuite struct { + suite.Suite + syslogServer *syslog.Server + syslogChannel chan format.LogParts +} + +func (suite *SyslogTestSuite) SetupTest() { + testrig.InitTestConfig() + + viper.Set(config.Keys.SyslogEnabled, true) + viper.Set(config.Keys.SyslogProtocol, "udp") + viper.Set(config.Keys.SyslogAddress, "localhost:42069") + server, channel, err := testrig.InitTestSyslog() + if err != nil { + panic(err) + } + suite.syslogServer = server + suite.syslogChannel = channel + + testrig.InitTestLog() +} + +func (suite *SyslogTestSuite) TearDownTest() { + if err := suite.syslogServer.Kill(); err != nil { + panic(err) + } +} + +func (suite *SyslogTestSuite) TestSyslog() { + logrus.Warn("this is a test of the emergency broadcast system!") + + message := <-suite.syslogChannel + suite.Contains(message["content"], "this is a test of the emergency broadcast system!") +} + +func TestSyslogTestSuite(t *testing.T) { + suite.Run(t, &SyslogTestSuite{}) +} diff --git a/internal/media/util_test.go b/internal/media/util_test.go index c54fb6f2..fb36e147 100644 --- a/internal/media/util_test.go +++ b/internal/media/util_test.go @@ -19,11 +19,13 @@ package media import ( - "github.com/sirupsen/logrus" - "github.com/superseriousbusiness/gotosocial/internal/log" "io/ioutil" "testing" + "github.com/spf13/viper" + "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/log" + "github.com/stretchr/testify/suite" ) @@ -38,11 +40,11 @@ type MediaUtilTestSuite struct { // SetupSuite sets some variables on the suite that we can use as consts (more or less) throughout func (suite *MediaUtilTestSuite) SetupSuite() { // doesn't use testrig.InitTestLog() helper to prevent import cycle - err := log.Initialize(logrus.TraceLevel.String()) + viper.Set(config.Keys.LogLevel, "trace") + err := log.Initialize() if err != nil { panic(err) } - } func (suite *MediaUtilTestSuite) TearDownSuite() { diff --git a/mkdocs.yml b/mkdocs.yml index 59965ce2..4986a488 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,6 +29,7 @@ nav: - "configuration/letsencrypt.md" - "configuration/oidc.md" - "configuration/smtp.md" + - "configuration/syslog.md" - "Admin": - "admin/admin_panel.md" - "admin/cli.md" diff --git a/test/cliparsing.sh b/test/cliparsing.sh index 13e61ede..364ae8ab 100755 --- a/test/cliparsing.sh +++ b/test/cliparsing.sh @@ -5,7 +5,7 @@ set -e echo "STARTING CLI TESTS" echo "TEST_1 Make sure defaults are set correctly." -TEST_1_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"localhost","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","trusted-proxies":["127.0.0.1/32"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_1_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"localhost","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_1="$(go run ./cmd/gotosocial/... debug config)" if [ "${TEST_1}" != "${TEST_1_EXPECTED}" ]; then echo "TEST_1 not equal TEST_1_EXPECTED" @@ -15,7 +15,7 @@ else fi echo "TEST_2 Override db-address from default using cli flag." -TEST_2_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.db.address","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","trusted-proxies":["127.0.0.1/32"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_2_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.db.address","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_2="$(go run ./cmd/gotosocial/... --db-address some.db.address debug config)" if [ "${TEST_2}" != "${TEST_2_EXPECTED}" ]; then echo "TEST_2 not equal TEST_2_EXPECTED" @@ -25,7 +25,7 @@ else fi echo "TEST_3 Override db-address from default using env var." -TEST_3_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.db.address","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","trusted-proxies":["127.0.0.1/32"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_3_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.db.address","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_3="$(GTS_DB_ADDRESS=some.db.address go run ./cmd/gotosocial/... debug config)" if [ "${TEST_3}" != "${TEST_3_EXPECTED}" ]; then echo "TEST_3 not equal TEST_3_EXPECTED" @@ -35,7 +35,7 @@ else fi echo "TEST_4 Override db-address from default using both env var and cli flag. The cli flag should take priority." -TEST_4_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.other.db.address","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","trusted-proxies":["127.0.0.1/32"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_4_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.other.db.address","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_4="$(GTS_DB_ADDRESS=some.db.address go run ./cmd/gotosocial/... --db-address some.other.db.address debug config)" if [ "${TEST_4}" != "${TEST_4_EXPECTED}" ]; then echo "TEST_4 not equal TEST_4_EXPECTED" @@ -45,7 +45,7 @@ else fi echo "TEST_5 Test loading a config file by passing an env var." -TEST_5_EXPECTED='{"account-domain":"example.org","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_5_EXPECTED='{"account-domain":"example.org","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_5="$(GTS_CONFIG_PATH=./test/test.yaml go run ./cmd/gotosocial/... debug config)" if [ "${TEST_5}" != "${TEST_5_EXPECTED}" ]; then echo "TEST_5 not equal TEST_5_EXPECTED" @@ -55,7 +55,7 @@ else fi echo "TEST_6 Test loading a config file by passing cli flag." -TEST_6_EXPECTED='{"account-domain":"example.org","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_6_EXPECTED='{"account-domain":"example.org","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_6="$(go run ./cmd/gotosocial/... --config-path ./test/test.yaml debug config)" if [ "${TEST_6}" != "${TEST_6_EXPECTED}" ]; then echo "TEST_6 not equal TEST_6_EXPECTED" @@ -65,7 +65,7 @@ else fi echo "TEST_7 Test loading a config file and overriding one of the variables with a cli flag." -TEST_7_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_7_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_7="$(go run ./cmd/gotosocial/... --config-path ./test/test.yaml --account-domain '' debug config)" if [ "${TEST_7}" != "${TEST_7_EXPECTED}" ]; then echo "TEST_7 not equal TEST_7_EXPECTED" @@ -75,7 +75,7 @@ else fi echo "TEST_8 Test loading a config file and overriding one of the variables with an env var." -TEST_8_EXPECTED='{"account-domain":"peepee","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_8_EXPECTED='{"account-domain":"peepee","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_8="$(GTS_ACCOUNT_DOMAIN='peepee' go run ./cmd/gotosocial/... --config-path ./test/test.yaml debug config)" if [ "${TEST_8}" != "${TEST_8_EXPECTED}" ]; then echo "TEST_8 not equal TEST_8_EXPECTED" @@ -85,7 +85,7 @@ else fi echo "TEST_9 Test loading a config file and overriding one of the variables with both an env var and a cli flag. The cli flag should have priority." -TEST_9_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_9_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_9="$(GTS_ACCOUNT_DOMAIN='peepee' go run ./cmd/gotosocial/... --config-path ./test/test.yaml --account-domain '' debug config)" if [ "${TEST_9}" != "${TEST_9_EXPECTED}" ]; then echo "TEST_9 not equal TEST_9_EXPECTED" @@ -95,7 +95,7 @@ else fi echo "TEST_10 Test loading a config file from json." -TEST_10_EXPECTED='{"account-domain":"example.org","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.json","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_10_EXPECTED='{"account-domain":"example.org","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.json","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_10="$(go run ./cmd/gotosocial/... --config-path ./test/test.json debug config)" if [ "${TEST_10}" != "${TEST_10_EXPECTED}" ]; then echo "TEST_10 not equal TEST_10_EXPECTED" @@ -105,7 +105,7 @@ else fi echo "TEST_11 Test loading a partial config file. Default values should be used apart from those set in the config file." -TEST_11_EXPECTED='{"account-domain":"peepee.poopoo","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test2.yaml","db-address":"localhost","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"trace","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","trusted-proxies":["127.0.0.1/32"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_11_EXPECTED='{"account-domain":"peepee.poopoo","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test2.yaml","db-address":"localhost","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-level":"trace","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-base-path":"/gotosocial/storage","storage-serve-base-path":"/fileserver","storage-serve-host":"localhost","storage-serve-protocol":"https","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_11="$(go run ./cmd/gotosocial/... --config-path ./test/test2.yaml debug config)" if [ "${TEST_11}" != "${TEST_11_EXPECTED}" ]; then echo "TEST_11 not equal TEST_11_EXPECTED" diff --git a/test/test.yaml b/test/test.yaml index 467ab920..96e53a74 100644 --- a/test/test.yaml +++ b/test/test.yaml @@ -397,3 +397,28 @@ smtp-password: "smtp-password" # Examples: ["mail@example.org"] # Default: "" smtp-from: "someone@example.org" + +######################### +##### SYSLOG CONFIG ##### +######################### + +# Config for additional syslog log hooks. See https://en.wikipedia.org/wiki/Syslog, +# and https://github.com/sirupsen/logrus/tree/master/hooks/syslog. +# +# These settings are useful when one wants to daemonize GoToSocial and send logs +# to a specific place, either a local location or a syslog server. Most users will +# not need to touch these settings. + +# Bool. Enable the syslog logging hook. Logs will be mirrored to the configured destination. +# Options: [true, false] +# Default: false +syslog-enabled: false + +# String. Protocol to use when directing logs to syslog. Leave empty to connect to local syslog. +# Options: ["udp", "tcp", ""] +# Default: "tcp" +syslog-protocol: "udp" + +# String. Address:port to send syslog logs to. Leave empty to connect to local syslog. +# Default: "localhost:514" +syslog-address: "localhost:514" diff --git a/testrig/config.go b/testrig/config.go index be5efab6..3857ddb3 100644 --- a/testrig/config.go +++ b/testrig/config.go @@ -117,4 +117,8 @@ var TestDefaults = config.Values{ SMTPUsername: "", SMTPPassword: "", SMTPFrom: "GoToSocial", + + SyslogEnabled: false, + SyslogProtocol: "udp", + SyslogAddress: "localhost:514", } diff --git a/testrig/log.go b/testrig/log.go index 5db12e6e..b0b3b9da 100644 --- a/testrig/log.go +++ b/testrig/log.go @@ -19,14 +19,37 @@ package testrig import ( - "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/log" + "gopkg.in/mcuadros/go-syslog.v2" + "gopkg.in/mcuadros/go-syslog.v2/format" ) // InitTestLog sets the global logger to trace level for logging func InitTestLog() { - err := log.Initialize(logrus.TraceLevel.String()) - if err != nil { + if err := log.Initialize(); err != nil { panic(err) } } + +// InitTestSyslog returns a test syslog running on port 42069 and a channel for reading +// messages sent to the server, or an error if something goes wrong. +// +// Callers of this function should call Kill() on the server when they're finished with it! +func InitTestSyslog() (*syslog.Server, chan format.LogParts, error) { + channel := make(syslog.LogPartsChannel) + handler := syslog.NewChannelHandler(channel) + + server := syslog.NewServer() + server.SetFormat(syslog.Automatic) + server.SetHandler(handler) + + if err := server.ListenUDP("localhost:42069"); err != nil { + return nil, nil, err + } + + if err := server.Boot(); err != nil { + return nil, nil, err + } + + return server, channel, nil +} diff --git a/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md b/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md new file mode 100644 index 00000000..1bbc0f72 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md @@ -0,0 +1,39 @@ +# Syslog Hooks for Logrus :walrus: + +## Usage + +```go +import ( + "log/syslog" + "github.com/sirupsen/logrus" + lSyslog "github.com/sirupsen/logrus/hooks/syslog" +) + +func main() { + log := logrus.New() + hook, err := lSyslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") + + if err == nil { + log.Hooks.Add(hook) + } +} +``` + +If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). Just assign empty string to the first two parameters of `NewSyslogHook`. It should look like the following. + +```go +import ( + "log/syslog" + "github.com/sirupsen/logrus" + lSyslog "github.com/sirupsen/logrus/hooks/syslog" +) + +func main() { + log := logrus.New() + hook, err := lSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "") + + if err == nil { + log.Hooks.Add(hook) + } +} +``` diff --git a/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go b/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go new file mode 100644 index 00000000..02b8df38 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go @@ -0,0 +1,55 @@ +// +build !windows,!nacl,!plan9 + +package syslog + +import ( + "fmt" + "log/syslog" + "os" + + "github.com/sirupsen/logrus" +) + +// SyslogHook to send logs via syslog. +type SyslogHook struct { + Writer *syslog.Writer + SyslogNetwork string + SyslogRaddr string +} + +// Creates a hook to be added to an instance of logger. This is called with +// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")` +// `if err == nil { log.Hooks.Add(hook) }` +func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) { + w, err := syslog.Dial(network, raddr, priority, tag) + return &SyslogHook{w, network, raddr}, err +} + +func (hook *SyslogHook) Fire(entry *logrus.Entry) error { + line, err := entry.String() + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err) + return err + } + + switch entry.Level { + case logrus.PanicLevel: + return hook.Writer.Crit(line) + case logrus.FatalLevel: + return hook.Writer.Crit(line) + case logrus.ErrorLevel: + return hook.Writer.Err(line) + case logrus.WarnLevel: + return hook.Writer.Warning(line) + case logrus.InfoLevel: + return hook.Writer.Info(line) + case logrus.DebugLevel, logrus.TraceLevel: + return hook.Writer.Debug(line) + default: + return nil + } +} + +func (hook *SyslogHook) Levels() []logrus.Level { + return logrus.AllLevels +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/.travis.yml b/vendor/gopkg.in/mcuadros/go-syslog.v2/.travis.yml new file mode 100644 index 00000000..4340e707 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/.travis.yml @@ -0,0 +1,21 @@ +language: go +go: + - 1.4 + - 1.5 + - 1.6 + - 1.7 + - 1.8 + - 1.9 + - "1.10" + - "1.11" + - "1.12" + - tip + +matrix: + allow_failures: + - go: tip + +go_import_path: gopkg.in/mcuadros/go-syslog.v2 + +install: + - go get -v -t ./... diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/LICENSE b/vendor/gopkg.in/mcuadros/go-syslog.v2/LICENSE new file mode 100644 index 00000000..b3154896 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013 Máximo Cuadros + +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/gopkg.in/mcuadros/go-syslog.v2/README.md b/vendor/gopkg.in/mcuadros/go-syslog.v2/README.md new file mode 100644 index 00000000..e17a0fae --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/README.md @@ -0,0 +1,48 @@ +go-syslog [![Build Status](https://travis-ci.org/mcuadros/go-syslog.svg?branch=master)](https://travis-ci.org/mcuadros/go-syslog) [![GoDoc](http://godoc.org/github.com/mcuadros/go-syslog?status.svg)](hhttps://godoc.org/gopkg.in/mcuadros/go-syslog.v2) [![GitHub release](https://img.shields.io/github/release/mcuadros/go-syslog.svg)](https://github.com/mcuadros/go-syslog/releases) +============================== + +Syslog server library for go, build easy your custom syslog server over UDP, TCP or Unix sockets using RFC3164, RFC6587 or RFC5424 + +Installation +------------ + +The recommended way to install go-syslog + +``` +go get gopkg.in/mcuadros/go-syslog.v2 +``` + +Examples +-------- + +How import the package + +```go +import "gopkg.in/mcuadros/go-syslog.v2" +``` + +Example of a basic syslog [UDP server](example/basic_udp.go): + +```go +channel := make(syslog.LogPartsChannel) +handler := syslog.NewChannelHandler(channel) + +server := syslog.NewServer() +server.SetFormat(syslog.RFC5424) +server.SetHandler(handler) +server.ListenUDP("0.0.0.0:514") +server.Boot() + +go func(channel syslog.LogPartsChannel) { + for logParts := range channel { + fmt.Println(logParts) + } +}(channel) + +server.Wait() +``` + +License +------- + +MIT, see [LICENSE](LICENSE) diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/doc.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/doc.go new file mode 100644 index 00000000..9ab6466a --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/doc.go @@ -0,0 +1,5 @@ +/* +Syslog server library for go, build easy your custom syslog server +over UDP, TCP or Unix sockets using RFC3164, RFC5424 and RFC6587 +*/ +package syslog // import "gopkg.in/mcuadros/go-syslog.v2" diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/format/automatic.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/automatic.go new file mode 100644 index 00000000..2400db86 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/automatic.go @@ -0,0 +1,104 @@ +package format + +import ( + "bufio" + "bytes" + "strconv" + + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc3164" + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424" +) + +/* Selecting an 'Automatic' format detects incoming format (i.e. RFC3164 vs RFC5424) and Framing + * (i.e. RFC6587 s3.4.1 octet counting as described here as RFC6587, and either no framing or + * RFC6587 s3.4.2 octet stuffing / non-transparent framing, described here as either RFC3164 + * or RFC6587). + * + * In essence if you don't know which format to select, or have multiple incoming formats, this + * is the one to go for. There is a theoretical performance penalty (it has to look at a few bytes + * at the start of the frame), and a risk that you may parse things you don't want to parse + * (rogue syslog clients using other formats), so if you can be absolutely sure of your syslog + * format, it would be best to select it explicitly. + */ + +type Automatic struct{} + +const ( + detectedUnknown = iota + detectedRFC3164 = iota + detectedRFC5424 = iota + detectedRFC6587 = iota +) + +/* + * Will always fallback to rfc3164 (see section 4.3.3) + */ +func detect(data []byte) int { + // all formats have a sapce somewhere + if i := bytes.IndexByte(data, ' '); i > 0 { + pLength := data[0:i] + if _, err := strconv.Atoi(string(pLength)); err == nil { + return detectedRFC6587 + } + // are we starting with < + if data[0] != '<' { + return detectedRFC3164 + } + // is there a close angle bracket before the ' '? there should be + angle := bytes.IndexByte(data, '>') + if (angle < 0) || (angle >= i) { + return detectedRFC3164 + } + + // if a single digit immediately follows the angle bracket, then a space + // it is RFC5424, as RFC3164 must begin with a letter (month name) + if (angle+2 == i) && (data[angle+1] >= '0') && (data[angle+1] <= '9') { + return detectedRFC5424 + } else { + return detectedRFC3164 + } + } + // fallback to rfc 3164 section 4.3.3 + return detectedRFC3164 +} + +func (f *Automatic) GetParser(line []byte) LogParser { + switch format := detect(line); format { + case detectedRFC3164: + return &parserWrapper{rfc3164.NewParser(line)} + case detectedRFC5424: + return &parserWrapper{rfc5424.NewParser(line)} + default: + // If the line was an RFC6587 line, the splitter should already have removed the length, + // so one of the above two will be chosen if the line is correctly formed. However, it + // may have a second length illegally placed at the start, in which case the detector + // will return detectedRFC6587. The line may also simply be malformed after the length in + // which case we will have detectedUnknown. In this case we return the simplest parser so + // the illegally formatted line is properly handled + return &parserWrapper{rfc3164.NewParser(line)} + } +} + +func (f *Automatic) GetSplitFunc() bufio.SplitFunc { + return f.automaticScannerSplit +} + +func (f *Automatic) automaticScannerSplit(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + + switch format := detect(data); format { + case detectedRFC6587: + return rfc6587ScannerSplit(data, atEOF) + case detectedRFC3164, detectedRFC5424: + // the default + return bufio.ScanLines(data, atEOF) + default: + if err != nil { + return 0, nil, err + } + // Request more data + return 0, nil, nil + } +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/format/format.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/format.go new file mode 100644 index 00000000..f77eb107 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/format.go @@ -0,0 +1,29 @@ +package format + +import ( + "bufio" + "time" + + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser" +) + +type LogParts map[string]interface{} + +type LogParser interface { + Parse() error + Dump() LogParts + Location(*time.Location) +} + +type Format interface { + GetParser([]byte) LogParser + GetSplitFunc() bufio.SplitFunc +} + +type parserWrapper struct { + syslogparser.LogParser +} + +func (w *parserWrapper) Dump() LogParts { + return LogParts(w.LogParser.Dump()) +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc3164.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc3164.go new file mode 100644 index 00000000..9ca80f7a --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc3164.go @@ -0,0 +1,17 @@ +package format + +import ( + "bufio" + + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc3164" +) + +type RFC3164 struct{} + +func (f *RFC3164) GetParser(line []byte) LogParser { + return &parserWrapper{rfc3164.NewParser(line)} +} + +func (f *RFC3164) GetSplitFunc() bufio.SplitFunc { + return nil +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc5424.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc5424.go new file mode 100644 index 00000000..bb37c7ce --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc5424.go @@ -0,0 +1,17 @@ +package format + +import ( + "bufio" + + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424" +) + +type RFC5424 struct{} + +func (f *RFC5424) GetParser(line []byte) LogParser { + return &parserWrapper{rfc5424.NewParser(line)} +} + +func (f *RFC5424) GetSplitFunc() bufio.SplitFunc { + return nil +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc6587.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc6587.go new file mode 100644 index 00000000..e0eef917 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc6587.go @@ -0,0 +1,45 @@ +package format + +import ( + "bufio" + "bytes" + "strconv" + + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424" +) + +type RFC6587 struct{} + +func (f *RFC6587) GetParser(line []byte) LogParser { + return &parserWrapper{rfc5424.NewParser(line)} +} + +func (f *RFC6587) GetSplitFunc() bufio.SplitFunc { + return rfc6587ScannerSplit +} + +func rfc6587ScannerSplit(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + + if i := bytes.IndexByte(data, ' '); i > 0 { + pLength := data[0:i] + length, err := strconv.Atoi(string(pLength)) + if err != nil { + if string(data[0:1]) == "<" { + // Assume this frame uses non-transparent-framing + return len(data), data, nil + } + return 0, nil, err + } + end := length + i + 1 + if len(data) >= end { + // Return the frame with the length removed + return end, data[i+1 : end], nil + } + } + + // Request more data + return 0, nil, nil +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/handler.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/handler.go new file mode 100644 index 00000000..9914b3ea --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/handler.go @@ -0,0 +1,35 @@ +package syslog + +import ( + "gopkg.in/mcuadros/go-syslog.v2/format" +) + +//The handler receive every syslog entry at Handle method +type Handler interface { + Handle(format.LogParts, int64, error) +} + +type LogPartsChannel chan format.LogParts + +//The ChannelHandler will send all the syslog entries into the given channel +type ChannelHandler struct { + channel LogPartsChannel +} + +//NewChannelHandler returns a new ChannelHandler +func NewChannelHandler(channel LogPartsChannel) *ChannelHandler { + handler := new(ChannelHandler) + handler.SetChannel(channel) + + return handler +} + +//The channel to be used +func (h *ChannelHandler) SetChannel(channel LogPartsChannel) { + h.channel = channel +} + +//Syslog entry receiver +func (h *ChannelHandler) Handle(logParts format.LogParts, messageLength int64, err error) { + h.channel <- logParts +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/LICENSE b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/LICENSE new file mode 100644 index 00000000..3e2dbb6f --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2013, Jérôme Renard +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/README.md b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/README.md new file mode 100644 index 00000000..ce59d376 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/README.md @@ -0,0 +1,4 @@ +Syslogparser +============ + +This is a fork for [github.com/jeromer/syslogparser](https://github.com/jeromer/syslogparser), since this library is intensively used by `go-syslog`, now is integrated as a `internal` package. diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc3164/rfc3164.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc3164/rfc3164.go new file mode 100644 index 00000000..486a0cb5 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc3164/rfc3164.go @@ -0,0 +1,292 @@ +package rfc3164 + +import ( + "bytes" + "os" + "time" + + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser" +) + +type Parser struct { + buff []byte + cursor int + l int + priority syslogparser.Priority + version int + header header + message rfc3164message + location *time.Location + skipTag bool +} + +type header struct { + timestamp time.Time + hostname string +} + +type rfc3164message struct { + tag string + content string +} + +func NewParser(buff []byte) *Parser { + return &Parser{ + buff: buff, + cursor: 0, + l: len(buff), + location: time.UTC, + } +} + +func (p *Parser) Location(location *time.Location) { + p.location = location +} + +func (p *Parser) Parse() error { + tcursor := p.cursor + pri, err := p.parsePriority() + if err != nil { + // RFC3164 sec 4.3.3 + p.priority = syslogparser.Priority{13, syslogparser.Facility{Value: 1}, syslogparser.Severity{Value: 5}} + p.cursor = tcursor + content, err := p.parseContent() + p.header.timestamp = time.Now().Round(time.Second) + if err != syslogparser.ErrEOL { + return err + } + p.message = rfc3164message{content: content} + return nil + } + + tcursor = p.cursor + hdr, err := p.parseHeader() + if err == syslogparser.ErrTimestampUnknownFormat { + // RFC3164 sec 4.3.2. + hdr.timestamp = time.Now().Round(time.Second) + // No tag processing should be done + p.skipTag = true + // Reset cursor for content read + p.cursor = tcursor + } else if err != nil { + return err + } else { + p.cursor++ + } + + msg, err := p.parsemessage() + if err != syslogparser.ErrEOL { + return err + } + + p.priority = pri + p.version = syslogparser.NO_VERSION + p.header = hdr + p.message = msg + + return nil +} + +func (p *Parser) Dump() syslogparser.LogParts { + return syslogparser.LogParts{ + "timestamp": p.header.timestamp, + "hostname": p.header.hostname, + "tag": p.message.tag, + "content": p.message.content, + "priority": p.priority.P, + "facility": p.priority.F.Value, + "severity": p.priority.S.Value, + } +} + +func (p *Parser) parsePriority() (syslogparser.Priority, error) { + return syslogparser.ParsePriority(p.buff, &p.cursor, p.l) +} + +func (p *Parser) parseHeader() (header, error) { + hdr := header{} + var err error + + ts, err := p.parseTimestamp() + if err != nil { + return hdr, err + } + + hostname, err := p.parseHostname() + if err != nil { + return hdr, err + } + + hdr.timestamp = ts + hdr.hostname = hostname + + return hdr, nil +} + +func (p *Parser) parsemessage() (rfc3164message, error) { + msg := rfc3164message{} + var err error + + if !p.skipTag { + tag, err := p.parseTag() + if err != nil { + return msg, err + } + msg.tag = tag + } + + content, err := p.parseContent() + if err != syslogparser.ErrEOL { + return msg, err + } + + msg.content = content + + return msg, err +} + +// https://tools.ietf.org/html/rfc3164#section-4.1.2 +func (p *Parser) parseTimestamp() (time.Time, error) { + var ts time.Time + var err error + var tsFmtLen int + var sub []byte + + tsFmts := []string{ + time.Stamp, + time.RFC3339, + } + // if timestamps starts with numeric try formats with different order + // it is more likely that timestamp is in RFC3339 format then + if c := p.buff[p.cursor]; c > '0' && c < '9' { + tsFmts = []string{ + time.RFC3339, + time.Stamp, + } + } + + found := false + for _, tsFmt := range tsFmts { + tsFmtLen = len(tsFmt) + + if p.cursor+tsFmtLen > p.l { + continue + } + + sub = p.buff[p.cursor : tsFmtLen+p.cursor] + ts, err = time.ParseInLocation(tsFmt, string(sub), p.location) + if err == nil { + found = true + break + } + } + + if !found { + p.cursor = len(time.Stamp) + + // XXX : If the timestamp is invalid we try to push the cursor one byte + // XXX : further, in case it is a space + if (p.cursor < p.l) && (p.buff[p.cursor] == ' ') { + p.cursor++ + } + + return ts, syslogparser.ErrTimestampUnknownFormat + } + + fixTimestampIfNeeded(&ts) + + p.cursor += tsFmtLen + + if (p.cursor < p.l) && (p.buff[p.cursor] == ' ') { + p.cursor++ + } + + return ts, nil +} + +func (p *Parser) parseHostname() (string, error) { + oldcursor := p.cursor + hostname, err := syslogparser.ParseHostname(p.buff, &p.cursor, p.l) + if err == nil && len(hostname) > 0 && string(hostname[len(hostname)-1]) == ":" { // not an hostname! we found a GNU implementation of syslog() + p.cursor = oldcursor - 1 + myhostname, err := os.Hostname() + if err == nil { + return myhostname, nil + } + return "", nil + } + return hostname, err +} + +// http://tools.ietf.org/html/rfc3164#section-4.1.3 +func (p *Parser) parseTag() (string, error) { + var b byte + var endOfTag bool + var bracketOpen bool + var tag []byte + var err error + var found bool + + from := p.cursor + + for { + if p.cursor == p.l { + // no tag found, reset cursor for content + p.cursor = from + return "", nil + } + + b = p.buff[p.cursor] + bracketOpen = (b == '[') + endOfTag = (b == ':' || b == ' ') + + // XXX : parse PID ? + if bracketOpen { + tag = p.buff[from:p.cursor] + found = true + } + + if endOfTag { + if !found { + tag = p.buff[from:p.cursor] + found = true + } + + p.cursor++ + break + } + + p.cursor++ + } + + if (p.cursor < p.l) && (p.buff[p.cursor] == ' ') { + p.cursor++ + } + + return string(tag), err +} + +func (p *Parser) parseContent() (string, error) { + if p.cursor > p.l { + return "", syslogparser.ErrEOL + } + + content := bytes.Trim(p.buff[p.cursor:p.l], " ") + p.cursor += len(content) + + return string(content), syslogparser.ErrEOL +} + +func fixTimestampIfNeeded(ts *time.Time) { + now := time.Now() + y := ts.Year() + + if ts.Year() == 0 { + y = now.Year() + } + + newTs := time.Date(y, ts.Month(), ts.Day(), ts.Hour(), ts.Minute(), + ts.Second(), ts.Nanosecond(), ts.Location()) + + *ts = newTs +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424/rfc5424.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424/rfc5424.go new file mode 100644 index 00000000..bea8e587 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424/rfc5424.go @@ -0,0 +1,606 @@ +// Note to self : never try to code while looking after your kids +// The result might look like this : https://pbs.twimg.com/media/BXqSuYXIEAAscVA.png + +package rfc5424 + +import ( + "fmt" + "math" + "strconv" + "time" + + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser" +) + +const ( + NILVALUE = '-' +) + +var ( + ErrYearInvalid = &syslogparser.ParserError{"Invalid year in timestamp"} + ErrMonthInvalid = &syslogparser.ParserError{"Invalid month in timestamp"} + ErrDayInvalid = &syslogparser.ParserError{"Invalid day in timestamp"} + ErrHourInvalid = &syslogparser.ParserError{"Invalid hour in timestamp"} + ErrMinuteInvalid = &syslogparser.ParserError{"Invalid minute in timestamp"} + ErrSecondInvalid = &syslogparser.ParserError{"Invalid second in timestamp"} + ErrSecFracInvalid = &syslogparser.ParserError{"Invalid fraction of second in timestamp"} + ErrTimeZoneInvalid = &syslogparser.ParserError{"Invalid time zone in timestamp"} + ErrInvalidTimeFormat = &syslogparser.ParserError{"Invalid time format"} + ErrInvalidAppName = &syslogparser.ParserError{"Invalid app name"} + ErrInvalidProcId = &syslogparser.ParserError{"Invalid proc ID"} + ErrInvalidMsgId = &syslogparser.ParserError{"Invalid msg ID"} + ErrNoStructuredData = &syslogparser.ParserError{"No structured data"} +) + +type Parser struct { + buff []byte + cursor int + l int + header header + structuredData string + message string +} + +type header struct { + priority syslogparser.Priority + version int + timestamp time.Time + hostname string + appName string + procId string + msgId string +} + +type partialTime struct { + hour int + minute int + seconds int + secFrac float64 +} + +type fullTime struct { + pt partialTime + loc *time.Location +} + +type fullDate struct { + year int + month int + day int +} + +func NewParser(buff []byte) *Parser { + return &Parser{ + buff: buff, + cursor: 0, + l: len(buff), + } +} + +func (p *Parser) Location(location *time.Location) { + // Ignore as RFC5424 syslog always has a timezone +} + +func (p *Parser) Parse() error { + hdr, err := p.parseHeader() + if err != nil { + return err + } + + p.header = hdr + + sd, err := p.parseStructuredData() + if err != nil { + return err + } + + p.structuredData = sd + p.cursor++ + + if p.cursor < p.l { + p.message = string(p.buff[p.cursor:]) + } + + return nil +} + +func (p *Parser) Dump() syslogparser.LogParts { + return syslogparser.LogParts{ + "priority": p.header.priority.P, + "facility": p.header.priority.F.Value, + "severity": p.header.priority.S.Value, + "version": p.header.version, + "timestamp": p.header.timestamp, + "hostname": p.header.hostname, + "app_name": p.header.appName, + "proc_id": p.header.procId, + "msg_id": p.header.msgId, + "structured_data": p.structuredData, + "message": p.message, + } +} + +// HEADER = PRI VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID +func (p *Parser) parseHeader() (header, error) { + hdr := header{} + + pri, err := p.parsePriority() + if err != nil { + return hdr, err + } + + hdr.priority = pri + + ver, err := p.parseVersion() + if err != nil { + return hdr, err + } + hdr.version = ver + p.cursor++ + + ts, err := p.parseTimestamp() + if err != nil { + return hdr, err + } + + hdr.timestamp = ts + p.cursor++ + + host, err := p.parseHostname() + if err != nil { + return hdr, err + } + + hdr.hostname = host + p.cursor++ + + appName, err := p.parseAppName() + if err != nil { + return hdr, err + } + + hdr.appName = appName + p.cursor++ + + procId, err := p.parseProcId() + if err != nil { + return hdr, nil + } + + hdr.procId = procId + p.cursor++ + + msgId, err := p.parseMsgId() + if err != nil { + return hdr, nil + } + + hdr.msgId = msgId + p.cursor++ + + return hdr, nil +} + +func (p *Parser) parsePriority() (syslogparser.Priority, error) { + return syslogparser.ParsePriority(p.buff, &p.cursor, p.l) +} + +func (p *Parser) parseVersion() (int, error) { + return syslogparser.ParseVersion(p.buff, &p.cursor, p.l) +} + +// https://tools.ietf.org/html/rfc5424#section-6.2.3 +func (p *Parser) parseTimestamp() (time.Time, error) { + var ts time.Time + + if p.cursor >= p.l { + return ts, ErrInvalidTimeFormat + } + + if p.buff[p.cursor] == NILVALUE { + p.cursor++ + return ts, nil + } + + fd, err := parseFullDate(p.buff, &p.cursor, p.l) + if err != nil { + return ts, err + } + + if p.cursor >= p.l || p.buff[p.cursor] != 'T' { + return ts, ErrInvalidTimeFormat + } + + p.cursor++ + + ft, err := parseFullTime(p.buff, &p.cursor, p.l) + if err != nil { + return ts, syslogparser.ErrTimestampUnknownFormat + } + + nSec, err := toNSec(ft.pt.secFrac) + if err != nil { + return ts, err + } + + ts = time.Date( + fd.year, + time.Month(fd.month), + fd.day, + ft.pt.hour, + ft.pt.minute, + ft.pt.seconds, + nSec, + ft.loc, + ) + + return ts, nil +} + +// HOSTNAME = NILVALUE / 1*255PRINTUSASCII +func (p *Parser) parseHostname() (string, error) { + return syslogparser.ParseHostname(p.buff, &p.cursor, p.l) +} + +// APP-NAME = NILVALUE / 1*48PRINTUSASCII +func (p *Parser) parseAppName() (string, error) { + return parseUpToLen(p.buff, &p.cursor, p.l, 48, ErrInvalidAppName) +} + +// PROCID = NILVALUE / 1*128PRINTUSASCII +func (p *Parser) parseProcId() (string, error) { + return parseUpToLen(p.buff, &p.cursor, p.l, 128, ErrInvalidProcId) +} + +// MSGID = NILVALUE / 1*32PRINTUSASCII +func (p *Parser) parseMsgId() (string, error) { + return parseUpToLen(p.buff, &p.cursor, p.l, 32, ErrInvalidMsgId) +} + +func (p *Parser) parseStructuredData() (string, error) { + return parseStructuredData(p.buff, &p.cursor, p.l) +} + +// ---------------------------------------------- +// https://tools.ietf.org/html/rfc5424#section-6 +// ---------------------------------------------- + +// XXX : bind them to Parser ? + +// FULL-DATE : DATE-FULLYEAR "-" DATE-MONTH "-" DATE-MDAY +func parseFullDate(buff []byte, cursor *int, l int) (fullDate, error) { + var fd fullDate + + year, err := parseYear(buff, cursor, l) + if err != nil { + return fd, err + } + + if *cursor >= l || buff[*cursor] != '-' { + return fd, syslogparser.ErrTimestampUnknownFormat + } + + *cursor++ + + month, err := parseMonth(buff, cursor, l) + if err != nil { + return fd, err + } + + if *cursor >= l || buff[*cursor] != '-' { + return fd, syslogparser.ErrTimestampUnknownFormat + } + + *cursor++ + + day, err := parseDay(buff, cursor, l) + if err != nil { + return fd, err + } + + fd = fullDate{ + year: year, + month: month, + day: day, + } + + return fd, nil +} + +// DATE-FULLYEAR = 4DIGIT +func parseYear(buff []byte, cursor *int, l int) (int, error) { + yearLen := 4 + + if *cursor+yearLen > l { + return 0, syslogparser.ErrEOL + } + + // XXX : we do not check for a valid year (ie. 1999, 2013 etc) + // XXX : we only checks the format is correct + sub := string(buff[*cursor : *cursor+yearLen]) + + *cursor += yearLen + + year, err := strconv.Atoi(sub) + if err != nil { + return 0, ErrYearInvalid + } + + return year, nil +} + +// DATE-MONTH = 2DIGIT ; 01-12 +func parseMonth(buff []byte, cursor *int, l int) (int, error) { + return syslogparser.Parse2Digits(buff, cursor, l, 1, 12, ErrMonthInvalid) +} + +// DATE-MDAY = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year +func parseDay(buff []byte, cursor *int, l int) (int, error) { + // XXX : this is a relaxed constraint + // XXX : we do not check if valid regarding February or leap years + // XXX : we only checks that day is in range [01 -> 31] + // XXX : in other words this function will not rant if you provide Feb 31th + return syslogparser.Parse2Digits(buff, cursor, l, 1, 31, ErrDayInvalid) +} + +// FULL-TIME = PARTIAL-TIME TIME-OFFSET +func parseFullTime(buff []byte, cursor *int, l int) (fullTime, error) { + var loc = new(time.Location) + var ft fullTime + + pt, err := parsePartialTime(buff, cursor, l) + if err != nil { + return ft, err + } + + loc, err = parseTimeOffset(buff, cursor, l) + if err != nil { + return ft, err + } + + ft = fullTime{ + pt: pt, + loc: loc, + } + + return ft, nil +} + +// PARTIAL-TIME = TIME-HOUR ":" TIME-MINUTE ":" TIME-SECOND[TIME-SECFRAC] +func parsePartialTime(buff []byte, cursor *int, l int) (partialTime, error) { + var pt partialTime + + hour, minute, err := getHourMinute(buff, cursor, l) + if err != nil { + return pt, err + } + + if *cursor >= l || buff[*cursor] != ':' { + return pt, ErrInvalidTimeFormat + } + + *cursor++ + + // ---- + + seconds, err := parseSecond(buff, cursor, l) + if err != nil { + return pt, err + } + + pt = partialTime{ + hour: hour, + minute: minute, + seconds: seconds, + } + + // ---- + + if *cursor >= l || buff[*cursor] != '.' { + return pt, nil + } + + *cursor++ + + secFrac, err := parseSecFrac(buff, cursor, l) + if err != nil { + return pt, nil + } + pt.secFrac = secFrac + + return pt, nil +} + +// TIME-HOUR = 2DIGIT ; 00-23 +func parseHour(buff []byte, cursor *int, l int) (int, error) { + return syslogparser.Parse2Digits(buff, cursor, l, 0, 23, ErrHourInvalid) +} + +// TIME-MINUTE = 2DIGIT ; 00-59 +func parseMinute(buff []byte, cursor *int, l int) (int, error) { + return syslogparser.Parse2Digits(buff, cursor, l, 0, 59, ErrMinuteInvalid) +} + +// TIME-SECOND = 2DIGIT ; 00-59 +func parseSecond(buff []byte, cursor *int, l int) (int, error) { + return syslogparser.Parse2Digits(buff, cursor, l, 0, 59, ErrSecondInvalid) +} + +// TIME-SECFRAC = "." 1*6DIGIT +func parseSecFrac(buff []byte, cursor *int, l int) (float64, error) { + maxDigitLen := 6 + + max := *cursor + maxDigitLen + from := *cursor + to := from + + for to = from; to < max; to++ { + if to >= l { + break + } + + c := buff[to] + if !syslogparser.IsDigit(c) { + break + } + } + + sub := string(buff[from:to]) + if len(sub) == 0 { + return 0, ErrSecFracInvalid + } + + secFrac, err := strconv.ParseFloat("0."+sub, 64) + *cursor = to + if err != nil { + return 0, ErrSecFracInvalid + } + + return secFrac, nil +} + +// TIME-OFFSET = "Z" / TIME-NUMOFFSET +func parseTimeOffset(buff []byte, cursor *int, l int) (*time.Location, error) { + + if *cursor >= l || buff[*cursor] == 'Z' { + *cursor++ + return time.UTC, nil + } + + return parseNumericalTimeOffset(buff, cursor, l) +} + +// TIME-NUMOFFSET = ("+" / "-") TIME-HOUR ":" TIME-MINUTE +func parseNumericalTimeOffset(buff []byte, cursor *int, l int) (*time.Location, error) { + var loc = new(time.Location) + + sign := buff[*cursor] + + if (sign != '+') && (sign != '-') { + return loc, ErrTimeZoneInvalid + } + + *cursor++ + + hour, minute, err := getHourMinute(buff, cursor, l) + if err != nil { + return loc, err + } + + tzStr := fmt.Sprintf("%s%02d:%02d", string(sign), hour, minute) + tmpTs, err := time.Parse("-07:00", tzStr) + if err != nil { + return loc, err + } + + return tmpTs.Location(), nil +} + +func getHourMinute(buff []byte, cursor *int, l int) (int, int, error) { + hour, err := parseHour(buff, cursor, l) + if err != nil { + return 0, 0, err + } + + if *cursor >= l || buff[*cursor] != ':' { + return 0, 0, ErrInvalidTimeFormat + } + + *cursor++ + + minute, err := parseMinute(buff, cursor, l) + if err != nil { + return 0, 0, err + } + + return hour, minute, nil +} + +func toNSec(sec float64) (int, error) { + _, frac := math.Modf(sec) + fracStr := strconv.FormatFloat(frac, 'f', 9, 64) + fracInt, err := strconv.Atoi(fracStr[2:]) + if err != nil { + return 0, err + } + + return fracInt, nil +} + +// ------------------------------------------------ +// https://tools.ietf.org/html/rfc5424#section-6.3 +// ------------------------------------------------ + +func parseStructuredData(buff []byte, cursor *int, l int) (string, error) { + var sdData string + var found bool + + if *cursor >= l { + return "-", nil + } + + if buff[*cursor] == NILVALUE { + *cursor++ + return "-", nil + } + + if buff[*cursor] != '[' { + return sdData, ErrNoStructuredData + } + + from := *cursor + to := from + + for to = from; to < l; to++ { + if found { + break + } + + b := buff[to] + + if b == ']' { + switch t := to + 1; { + case t == l: + found = true + case t <= l && buff[t] == ' ': + found = true + } + } + } + + if found { + *cursor = to + return string(buff[from:to]), nil + } + + return sdData, ErrNoStructuredData +} + +func parseUpToLen(buff []byte, cursor *int, l int, maxLen int, e error) (string, error) { + var to int + var found bool + var result string + + max := *cursor + maxLen + + for to = *cursor; (to <= max) && (to < l); to++ { + if buff[to] == ' ' { + found = true + break + } + } + + if found { + result = string(buff[*cursor:to]) + } else if to > max { + to = max // don't go past max + } + + *cursor = to + + if found { + return result, nil + } + + return "", e +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/syslogparser.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/syslogparser.go new file mode 100644 index 00000000..7a10820c --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/syslogparser.go @@ -0,0 +1,213 @@ +package syslogparser + +import ( + "fmt" + "strconv" + "strings" + "time" +) + +const ( + PRI_PART_START = '<' + PRI_PART_END = '>' + + NO_VERSION = -1 +) + +var ( + ErrEOL = &ParserError{"End of log line"} + ErrNoSpace = &ParserError{"No space found"} + + ErrPriorityNoStart = &ParserError{"No start char found for priority"} + ErrPriorityEmpty = &ParserError{"Priority field empty"} + ErrPriorityNoEnd = &ParserError{"No end char found for priority"} + ErrPriorityTooShort = &ParserError{"Priority field too short"} + ErrPriorityTooLong = &ParserError{"Priority field too long"} + ErrPriorityNonDigit = &ParserError{"Non digit found in priority"} + + ErrVersionNotFound = &ParserError{"Can not find version"} + + ErrTimestampUnknownFormat = &ParserError{"Timestamp format unknown"} + + ErrHostnameTooShort = &ParserError{"Hostname field too short"} +) + +type LogParser interface { + Parse() error + Dump() LogParts + Location(*time.Location) +} + +type ParserError struct { + ErrorString string +} + +type Priority struct { + P int + F Facility + S Severity +} + +type Facility struct { + Value int +} + +type Severity struct { + Value int +} + +type LogParts map[string]interface{} + +// https://tools.ietf.org/html/rfc3164#section-4.1 +func ParsePriority(buff []byte, cursor *int, l int) (Priority, error) { + pri := newPriority(0) + + if l <= 0 { + return pri, ErrPriorityEmpty + } + + if buff[*cursor] != PRI_PART_START { + return pri, ErrPriorityNoStart + } + + i := 1 + priDigit := 0 + + for i < l { + if i >= 5 { + return pri, ErrPriorityTooLong + } + + c := buff[i] + + if c == PRI_PART_END { + if i == 1 { + return pri, ErrPriorityTooShort + } + + *cursor = i + 1 + return newPriority(priDigit), nil + } + + if IsDigit(c) { + v, e := strconv.Atoi(string(c)) + if e != nil { + return pri, e + } + + priDigit = (priDigit * 10) + v + } else { + return pri, ErrPriorityNonDigit + } + + i++ + } + + return pri, ErrPriorityNoEnd +} + +// https://tools.ietf.org/html/rfc5424#section-6.2.2 +func ParseVersion(buff []byte, cursor *int, l int) (int, error) { + if *cursor >= l { + return NO_VERSION, ErrVersionNotFound + } + + c := buff[*cursor] + *cursor++ + + // XXX : not a version, not an error though as RFC 3164 does not support it + if !IsDigit(c) { + return NO_VERSION, nil + } + + v, e := strconv.Atoi(string(c)) + if e != nil { + *cursor-- + return NO_VERSION, e + } + + return v, nil +} + +func IsDigit(c byte) bool { + return c >= '0' && c <= '9' +} + +func newPriority(p int) Priority { + // The Priority value is calculated by first multiplying the Facility + // number by 8 and then adding the numerical value of the Severity. + + return Priority{ + P: p, + F: Facility{Value: p / 8}, + S: Severity{Value: p % 8}, + } +} + +func FindNextSpace(buff []byte, from int, l int) (int, error) { + var to int + + for to = from; to < l; to++ { + if buff[to] == ' ' { + to++ + return to, nil + } + } + + return 0, ErrNoSpace +} + +func Parse2Digits(buff []byte, cursor *int, l int, min int, max int, e error) (int, error) { + digitLen := 2 + + if *cursor+digitLen > l { + return 0, ErrEOL + } + + sub := string(buff[*cursor : *cursor+digitLen]) + + *cursor += digitLen + + i, err := strconv.Atoi(sub) + if err != nil { + return 0, e + } + + if i >= min && i <= max { + return i, nil + } + + return 0, e +} + +func ParseHostname(buff []byte, cursor *int, l int) (string, error) { + from := *cursor + + if from >= l { + return "", ErrHostnameTooShort + } + + var to int + + for to = from; to < l; to++ { + if buff[to] == ' ' { + break + } + } + + hostname := buff[from:to] + + *cursor = to + + return string(hostname), nil +} + +func ShowCursorPos(buff []byte, cursor int) { + fmt.Println(string(buff)) + padding := strings.Repeat("-", cursor) + fmt.Println(padding + "↑\n") +} + +func (err *ParserError) Error() string { + return err.ErrorString +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/server.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/server.go new file mode 100644 index 00000000..352597b8 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/server.go @@ -0,0 +1,378 @@ +package syslog + +import ( + "bufio" + "crypto/tls" + "errors" + "net" + "strings" + "sync" + "time" + + "gopkg.in/mcuadros/go-syslog.v2/format" +) + +var ( + RFC3164 = &format.RFC3164{} // RFC3164: http://www.ietf.org/rfc/rfc3164.txt + RFC5424 = &format.RFC5424{} // RFC5424: http://www.ietf.org/rfc/rfc5424.txt + RFC6587 = &format.RFC6587{} // RFC6587: http://www.ietf.org/rfc/rfc6587.txt - octet counting variant + Automatic = &format.Automatic{} // Automatically identify the format +) + +const ( + datagramChannelBufferSize = 10 + datagramReadBufferSize = 64 * 1024 +) + +// A function type which gets the TLS peer name from the connection. Can return +// ok=false to terminate the connection +type TlsPeerNameFunc func(tlsConn *tls.Conn) (tlsPeer string, ok bool) + +type Server struct { + listeners []net.Listener + connections []net.PacketConn + wait sync.WaitGroup + doneTcp chan bool + datagramChannel chan DatagramMessage + format format.Format + handler Handler + lastError error + readTimeoutMilliseconds int64 + tlsPeerNameFunc TlsPeerNameFunc + datagramPool sync.Pool +} + +//NewServer returns a new Server +func NewServer() *Server { + return &Server{tlsPeerNameFunc: defaultTlsPeerName, datagramPool: sync.Pool{ + New: func() interface{} { + return make([]byte, 65536) + }, + }} +} + +//Sets the syslog format (RFC3164 or RFC5424 or RFC6587) +func (s *Server) SetFormat(f format.Format) { + s.format = f +} + +//Sets the handler, this handler with receive every syslog entry +func (s *Server) SetHandler(handler Handler) { + s.handler = handler +} + +//Sets the connection timeout for TCP connections, in milliseconds +func (s *Server) SetTimeout(millseconds int64) { + s.readTimeoutMilliseconds = millseconds +} + +// Set the function that extracts a TLS peer name from the TLS connection +func (s *Server) SetTlsPeerNameFunc(tlsPeerNameFunc TlsPeerNameFunc) { + s.tlsPeerNameFunc = tlsPeerNameFunc +} + +// Default TLS peer name function - returns the CN of the certificate +func defaultTlsPeerName(tlsConn *tls.Conn) (tlsPeer string, ok bool) { + state := tlsConn.ConnectionState() + if len(state.PeerCertificates) <= 0 { + return "", false + } + cn := state.PeerCertificates[0].Subject.CommonName + return cn, true +} + +//Configure the server for listen on an UDP addr +func (s *Server) ListenUDP(addr string) error { + udpAddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return err + } + + connection, err := net.ListenUDP("udp", udpAddr) + if err != nil { + return err + } + connection.SetReadBuffer(datagramReadBufferSize) + + s.connections = append(s.connections, connection) + return nil +} + +//Configure the server for listen on an unix socket +func (s *Server) ListenUnixgram(addr string) error { + unixAddr, err := net.ResolveUnixAddr("unixgram", addr) + if err != nil { + return err + } + + connection, err := net.ListenUnixgram("unixgram", unixAddr) + if err != nil { + return err + } + connection.SetReadBuffer(datagramReadBufferSize) + + s.connections = append(s.connections, connection) + return nil +} + +//Configure the server for listen on a TCP addr +func (s *Server) ListenTCP(addr string) error { + tcpAddr, err := net.ResolveTCPAddr("tcp", addr) + if err != nil { + return err + } + + listener, err := net.ListenTCP("tcp", tcpAddr) + if err != nil { + return err + } + + s.doneTcp = make(chan bool) + s.listeners = append(s.listeners, listener) + return nil +} + +//Configure the server for listen on a TCP addr for TLS +func (s *Server) ListenTCPTLS(addr string, config *tls.Config) error { + listener, err := tls.Listen("tcp", addr, config) + if err != nil { + return err + } + + s.doneTcp = make(chan bool) + s.listeners = append(s.listeners, listener) + return nil +} + +//Starts the server, all the go routines goes to live +func (s *Server) Boot() error { + if s.format == nil { + return errors.New("please set a valid format") + } + + if s.handler == nil { + return errors.New("please set a valid handler") + } + + for _, listener := range s.listeners { + s.goAcceptConnection(listener) + } + + if len(s.connections) > 0 { + s.goParseDatagrams() + } + + for _, connection := range s.connections { + s.goReceiveDatagrams(connection) + } + + return nil +} + +func (s *Server) goAcceptConnection(listener net.Listener) { + s.wait.Add(1) + go func(listener net.Listener) { + loop: + for { + select { + case <-s.doneTcp: + break loop + default: + } + connection, err := listener.Accept() + if err != nil { + continue + } + + s.goScanConnection(connection) + } + + s.wait.Done() + }(listener) +} + +func (s *Server) goScanConnection(connection net.Conn) { + scanner := bufio.NewScanner(connection) + if sf := s.format.GetSplitFunc(); sf != nil { + scanner.Split(sf) + } + + remoteAddr := connection.RemoteAddr() + var client string + if remoteAddr != nil { + client = remoteAddr.String() + } + + tlsPeer := "" + if tlsConn, ok := connection.(*tls.Conn); ok { + // Handshake now so we get the TLS peer information + if err := tlsConn.Handshake(); err != nil { + connection.Close() + return + } + if s.tlsPeerNameFunc != nil { + var ok bool + tlsPeer, ok = s.tlsPeerNameFunc(tlsConn) + if !ok { + connection.Close() + return + } + } + } + + var scanCloser *ScanCloser + scanCloser = &ScanCloser{scanner, connection} + + s.wait.Add(1) + go s.scan(scanCloser, client, tlsPeer) +} + +func (s *Server) scan(scanCloser *ScanCloser, client string, tlsPeer string) { +loop: + for { + select { + case <-s.doneTcp: + break loop + default: + } + if s.readTimeoutMilliseconds > 0 { + scanCloser.closer.SetReadDeadline(time.Now().Add(time.Duration(s.readTimeoutMilliseconds) * time.Millisecond)) + } + if scanCloser.Scan() { + s.parser([]byte(scanCloser.Text()), client, tlsPeer) + } else { + break loop + } + } + scanCloser.closer.Close() + + s.wait.Done() +} + +func (s *Server) parser(line []byte, client string, tlsPeer string) { + parser := s.format.GetParser(line) + err := parser.Parse() + if err != nil { + s.lastError = err + } + + logParts := parser.Dump() + logParts["client"] = client + if logParts["hostname"] == "" && (s.format == RFC3164 || s.format == Automatic) { + if i := strings.Index(client, ":"); i > 1 { + logParts["hostname"] = client[:i] + } else { + logParts["hostname"] = client + } + } + logParts["tls_peer"] = tlsPeer + + s.handler.Handle(logParts, int64(len(line)), err) +} + +//Returns the last error +func (s *Server) GetLastError() error { + return s.lastError +} + +//Kill the server +func (s *Server) Kill() error { + for _, connection := range s.connections { + err := connection.Close() + if err != nil { + return err + } + } + + for _, listener := range s.listeners { + err := listener.Close() + if err != nil { + return err + } + } + // Only need to close channel once to broadcast to all waiting + if s.doneTcp != nil { + close(s.doneTcp) + } + if s.datagramChannel != nil { + close(s.datagramChannel) + } + return nil +} + +//Waits until the server stops +func (s *Server) Wait() { + s.wait.Wait() +} + +type TimeoutCloser interface { + Close() error + SetReadDeadline(t time.Time) error +} + +type ScanCloser struct { + *bufio.Scanner + closer TimeoutCloser +} + +type DatagramMessage struct { + message []byte + client string +} + +func (s *Server) goReceiveDatagrams(packetconn net.PacketConn) { + s.wait.Add(1) + go func() { + defer s.wait.Done() + for { + buf := s.datagramPool.Get().([]byte) + n, addr, err := packetconn.ReadFrom(buf) + if err == nil { + // Ignore trailing control characters and NULs + for ; (n > 0) && (buf[n-1] < 32); n-- { + } + if n > 0 { + var address string + if addr != nil { + address = addr.String() + } + s.datagramChannel <- DatagramMessage{buf[:n], address} + } + } else { + // there has been an error. Either the server has been killed + // or may be getting a transitory error due to (e.g.) the + // interface being shutdown in which case sleep() to avoid busy wait. + opError, ok := err.(*net.OpError) + if (ok) && !opError.Temporary() && !opError.Timeout() { + return + } + time.Sleep(10 * time.Millisecond) + } + } + }() +} + +func (s *Server) goParseDatagrams() { + s.datagramChannel = make(chan DatagramMessage, datagramChannelBufferSize) + + s.wait.Add(1) + go func() { + defer s.wait.Done() + for { + select { + case msg, ok := (<-s.datagramChannel): + if !ok { + return + } + if sf := s.format.GetSplitFunc(); sf != nil { + if _, token, err := sf(msg.message, true); err == nil { + s.parser(token, msg.client, "") + } + } else { + s.parser(msg.message, msg.client, "") + } + s.datagramPool.Put(msg.message[:cap(msg.message)]) + } + } + }() +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 58605387..9c1ce34a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -249,6 +249,7 @@ github.com/russross/blackfriday/v2 # github.com/sirupsen/logrus v1.8.1 ## explicit; go 1.13 github.com/sirupsen/logrus +github.com/sirupsen/logrus/hooks/syslog # github.com/spf13/afero v1.6.0 ## explicit; go 1.13 github.com/spf13/afero @@ -635,6 +636,13 @@ google.golang.org/protobuf/types/descriptorpb # gopkg.in/ini.v1 v1.66.2 ## explicit gopkg.in/ini.v1 +# gopkg.in/mcuadros/go-syslog.v2 v2.3.0 +## explicit +gopkg.in/mcuadros/go-syslog.v2 +gopkg.in/mcuadros/go-syslog.v2/format +gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser +gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc3164 +gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424 # gopkg.in/square/go-jose.v2 v2.6.0 ## explicit gopkg.in/square/go-jose.v2