Add optional syslog logrus hook (#343)

* add optional syslog logrus hook

* document syslog
This commit is contained in:
tobi 2021-12-12 18:00:20 +01:00 committed by GitHub
parent 909f801742
commit c111b239f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 2242 additions and 37 deletions

View file

@ -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).

View file

@ -23,7 +23,6 @@
"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)

View file

@ -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)
}

View file

@ -73,6 +73,9 @@
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",

View file

@ -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"
```

View file

@ -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"

1
go.mod
View file

@ -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
)

2
go.sum
View file

@ -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=

View file

@ -84,4 +84,8 @@
SMTPUsername: "",
SMTPPassword: "",
SMTPFrom: "GoToSocial",
SyslogEnabled: false,
SyslogProtocol: "udp",
SyslogAddress: "localhost:514",
}

View file

@ -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 @@ type KeyNames struct {
SMTPPassword: "smtp-password",
SMTPFrom: "smtp-from",
SyslogEnabled: "syslog-enabled",
SyslogProtocol: "syslog-protocol",
SyslogAddress: "syslog-address",
AdminAccountUsername: "username",
AdminAccountEmail: "email",
AdminAccountPassword: "password",

View file

@ -83,6 +83,10 @@ type Values struct {
SMTPPassword string
SMTPFrom string
SyslogEnabled bool
SyslogProtocol string
SyslogAddress string
AdminAccountUsername string
AdminAccountEmail string
AdminAccountPassword string

View file

@ -22,31 +22,60 @@
"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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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{})
}

View file

@ -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() {

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -117,4 +117,8 @@ func InitTestConfig() {
SMTPUsername: "",
SMTPPassword: "",
SMTPFrom: "GoToSocial",
SyslogEnabled: false,
SyslogProtocol: "udp",
SyslogAddress: "localhost:514",
}

View file

@ -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
}

View file

@ -0,0 +1,39 @@
# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":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)
}
}
```

View file

@ -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
}

21
vendor/gopkg.in/mcuadros/go-syslog.v2/.travis.yml generated vendored Normal file
View file

@ -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 ./...

19
vendor/gopkg.in/mcuadros/go-syslog.v2/LICENSE generated vendored Normal file
View file

@ -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.

48
vendor/gopkg.in/mcuadros/go-syslog.v2/README.md generated vendored Normal file
View file

@ -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)

5
vendor/gopkg.in/mcuadros/go-syslog.v2/doc.go generated vendored Normal file
View file

@ -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"

View file

@ -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
}
}

29
vendor/gopkg.in/mcuadros/go-syslog.v2/format/format.go generated vendored Normal file
View file

@ -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())
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

35
vendor/gopkg.in/mcuadros/go-syslog.v2/handler.go generated vendored Normal file
View file

@ -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
}

View file

@ -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.

View file

@ -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.

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

378
vendor/gopkg.in/mcuadros/go-syslog.v2/server.go generated vendored Normal file
View file

@ -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)])
}
}
}()
}

8
vendor/modules.txt vendored
View file

@ -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