[MM-54319] Add AdvancedLoggingJSON setting to system console (#24733)

* Add AdvancedLoggingJSON setting to system console

* Apply suggestions from code review

Co-authored-by: Doug Lauder <wiggin77@warpmail.net>

* Add validation for AdvancedLoggingJSON

---------

Co-authored-by: Doug Lauder <wiggin77@warpmail.net>
Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Ben Schumacher
2023-10-25 16:40:07 +02:00
коммит произвёл GitHub
родитель 0844968d81
Коммит 671cf6cc3d
7 изменённых файлов: 194 добавлений и 1 удалений

Просмотреть файл

@@ -8730,6 +8730,14 @@
"id": "model.config.is_valid.localization.available_locales.app_error",
"translation": "Available Languages must contain Default Client Language."
},
{
"id": "model.config.is_valid.log.advanced_logging.json",
"translation": "Failed to parse JSON: {{.Error}}"
},
{
"id": "model.config.is_valid.log.advanced_logging.parse",
"translation": "Invalid format: {{.Error}}"
},
{
"id": "model.config.is_valid.login_attempts.app_error",
"translation": "Invalid maximum login attempts for service settings. Must be a positive number."

Просмотреть файл

@@ -1285,6 +1285,21 @@ func NewLogSettings() *LogSettings {
return settings
}
func (s *LogSettings) isValid() *AppError {
cfg := make(mlog.LoggerConfiguration)
err := json.Unmarshal(s.GetAdvancedLoggingConfig(), &cfg)
if err != nil {
return NewAppError("LogSettings.isValid", "model.config.is_valid.log.advanced_logging.json", map[string]any{"Error": err}, "", http.StatusBadRequest).Wrap(err)
}
err = cfg.IsValid()
if err != nil {
return NewAppError("LogSettings.isValid", "model.config.is_valid.log.advanced_logging.parse", map[string]any{"Error": err}, "", http.StatusBadRequest).Wrap(err)
}
return nil
}
func (s *LogSettings) SetDefaults() {
if s.EnableConsole == nil {
s.EnableConsole = NewBool(true)
@@ -3551,6 +3566,10 @@ func (o *Config) IsValid() *AppError {
return appErr
}
if appErr := o.LogSettings.isValid(); appErr != nil {
return appErr
}
if appErr := o.LocalizationSettings.isValid(); appErr != nil {
return appErr
}

Просмотреть файл

@@ -1208,6 +1208,109 @@ func TestLdapSettingsIsValid(t *testing.T) {
}
}
func TestLogSettingsIsValid(t *testing.T) {
for name, test := range map[string]struct {
LogSettings LogSettings
ExpectError bool
}{
"empty": {
LogSettings: LogSettings{},
ExpectError: false,
},
"AdvancedLoggingJSON contains empty string": {
LogSettings: LogSettings{
AdvancedLoggingJSON: json.RawMessage(``),
},
ExpectError: false,
},
"AdvancedLoggingJSON contains empty JSON": {
LogSettings: LogSettings{
AdvancedLoggingJSON: json.RawMessage(`{}`),
},
ExpectError: false,
},
"AdvancedLoggingJSON has JSON error ": {
LogSettings: LogSettings{
AdvancedLoggingJSON: json.RawMessage(`
{
"foo": "bar",
`),
},
ExpectError: true,
},
"AdvancedLoggingJSON has missing target": {
LogSettings: LogSettings{
AdvancedLoggingJSON: json.RawMessage(`
{
"foo": "bar",
}
`),
},
ExpectError: true,
},
"AdvancedLoggingJSON has an unknown Type": {
LogSettings: LogSettings{
AdvancedLoggingJSON: json.RawMessage(`
{
"console-log": {
"Type": "XYZ",
"Format": "json",
"Levels": [
{"ID": 10, "Name": "stdlog", "Stacktrace": false},
{"ID": 5, "Name": "debug", "Stacktrace": false},
{"ID": 4, "Name": "info", "Stacktrace": false, "color": 36},
{"ID": 3, "Name": "warn", "Stacktrace": false, "color": 33},
{"ID": 2, "Name": "error", "Stacktrace": true, "color": 31},
{"ID": 1, "Name": "fatal", "Stacktrace": true},
{"ID": 0, "Name": "panic", "Stacktrace": true}
],
"Options": {
"Out": "stdout"
},
"MaxQueueSize": 1000
}
}
`),
},
ExpectError: true,
},
"AdvancedLoggingJSON is valid": {
LogSettings: LogSettings{
AdvancedLoggingJSON: json.RawMessage(`
{
"console-log": {
"Type": "console",
"Format": "json",
"Levels": [
{"ID": 5, "Name": "debug", "Stacktrace": false},
{"ID": 4, "Name": "info", "Stacktrace": false, "color": 36},
{"ID": 3, "Name": "warn", "Stacktrace": false, "color": 33},
{"ID": 2, "Name": "error", "Stacktrace": true, "color": 31}
],
"Options": {
"Out": "stdout"
},
"MaxQueueSize": 1000
}
}
`),
},
ExpectError: false,
},
} {
t.Run(name, func(t *testing.T) {
test.LogSettings.SetDefaults()
appErr := test.LogSettings.isValid()
if test.ExpectError {
assert.NotNil(t, appErr)
} else {
assert.Nil(t, appErr)
}
})
}
}
func TestConfigSanitize(t *testing.T) {
c := Config{}
c.SetDefaults()

Просмотреть файл

@@ -7,7 +7,6 @@ package mlog
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log"
@@ -16,6 +15,8 @@ import (
"sync/atomic"
"time"
"github.com/pkg/errors"
"github.com/mattermost/logr/v2"
logrcfg "github.com/mattermost/logr/v2/config"
)
@@ -68,6 +69,21 @@ func (lc LoggerConfiguration) Append(cfg LoggerConfiguration) {
}
}
func (lc LoggerConfiguration) IsValid() error {
logger, err := logr.New()
if err != nil {
return errors.Wrap(err, "failed to create logger")
}
defer logger.Shutdown()
err = logrcfg.ConfigureTargets(logger, lc, nil)
if err != nil {
return errors.Wrap(err, "logger configuration is invalid")
}
return nil
}
func (lc LoggerConfiguration) toTargetCfg() map[string]logrcfg.TargetCfg {
tcfg := make(map[string]logrcfg.TargetCfg)
for k, v := range lc {

Просмотреть файл

@@ -1863,6 +1863,49 @@ const AdminDefinition = {
},
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.LOGGING)),
},
{
type: Constants.SettingsTypes.TYPE_LONG_TEXT,
key: 'LogSettings.AdvancedLoggingJSON',
label: t('admin.log.AdvancedLoggingJSONTitle'),
label_default: 'Advanced Logging:',
help_text: t('admin.log.AdvancedLoggingJSONDescription'),
help_text_default: 'The JSON configuration for Advanced Logging. Please see <link>documentation</link> to learn more about Advanced Logging and the JSON format it uses.',
help_text_markdown: false,
help_text_values: {
link: (msg) => (
<ExternalLink
location='admin_console'
href={DocLinks.ADVANCED_LOGGING}
>
{msg}
</ExternalLink>
),
},
placeholder: t('admin.log.AdvancedLoggingJSONPlaceholder'),
placeholder_default: 'Enter your JSON configuration',
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.LOGGING)),
validate: (value) => {
const valid = new ValidationResult(true, '', '');
if (!value) {
return valid;
}
try {
JSON.parse(value);
return valid;
} catch (error) {
return new ValidationResult(false, '', error.message);
}
},
onConfigLoad: (configVal) => JSON.stringify(configVal, null, ' '),
onConfigSave: (displayVal) => {
// Handle case where field is empty
if (!displayVal) {
return {undefined};
}
return JSON.parse(displayVal);
},
},
],
},
},

Просмотреть файл

@@ -1373,6 +1373,9 @@
"admin.license.warn.renew": "Renew",
"admin.lockTeammateNameDisplay": "Lock Teammate Name Display for all users: ",
"admin.lockTeammateNameDisplayHelpText": "When true, disables users' ability to change settings under <strong>Settings > Display > Teammate Name Display</strong>.",
"admin.log.AdvancedLoggingJSONDescription": "The JSON configuration for Advanced Logging. Please see <link>documentation</link> to learn more about Advanced Logging and the JSON format it uses.",
"admin.log.AdvancedLoggingJSONPlaceholder": "Enter your JSON configuration",
"admin.log.AdvancedLoggingJSONTitle": "Advanced Logging:",
"admin.log.consoleDescription": "Typically set to false in production. Developers may set this field to true to output log messages to console based on the console level option. If true, server writes messages to the standard output stream (stdout). Changing this setting requires a server restart before taking effect.",
"admin.log.consoleJsonTitle": "Output console logs as JSON:",
"admin.log.consoleTitle": "Output logs to console: ",

Просмотреть файл

@@ -1088,6 +1088,7 @@ export const HostedCustomerLinks = {
export const DocLinks = {
ABOUT_TEAMS: 'https://docs.mattermost.com/welcome/about-teams.html#team-url',
ADVANCED_LOGGING: 'https://mattermost.com/pl/advanced-logging',
CONFIGURE_DOCUMENT_CONTENT_SEARCH: 'https://mattermost.com/pl/configure-document-content-search',
CONFIGURE_AD_LDAP_QUERY_TIMEOUT: 'https://mattermost.com/pl/configure-ad-ldap-query-timeout',
CONFIGURE_OVERRIDE_SAML_BIND_DATA_WITH_LDAP: 'https://mattermost.com/pl/configure-override-saml-bind-data-with-ldap',