[MM-54593] HA aware Support Packet (#27598)

Этот коммит содержится в:
Ben Schumacher
2024-08-03 16:11:13 +02:00
коммит произвёл GitHub
родитель 3f4b8e8137
Коммит 1158e6358c
24 изменённых файлов: 377 добавлений и 300 удалений

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

@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/einterfaces"
)
@@ -144,6 +145,9 @@ func (c *ClusterMock) GetLogs(page, perPage int) ([]string, *model.AppError)
func (c *ClusterMock) QueryLogs(page, perPage int) (map[string][]string, *model.AppError) {
return nil, nil
}
func (c *ClusterMock) GenerateSupportPacket(rctx request.CTX, options *model.SupportPacketOptions) (map[string][]model.FileData, error) {
return nil, nil
}
func (c *ClusterMock) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { return nil, nil }
func (c *ClusterMock) ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError {
return nil

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

@@ -6,14 +6,16 @@ package platform
import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"os"
"time"
"github.com/pkg/errors"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/config"
)
@@ -206,6 +208,40 @@ func (ps *PlatformService) GetLogsSkipSend(page, perPage int, logFilter *model.L
return lines, nil
}
func (ps *PlatformService) GetLogFile(_ request.CTX) (*model.FileData, error) {
if !*ps.Config().LogSettings.EnableFile {
return nil, errors.New("Unable to retrieve mattermost logs because LogSettings.EnableFile is set to false")
}
mattermostLog := config.GetLogFileLocation(*ps.Config().LogSettings.FileLocation)
mattermostLogFileData, err := os.ReadFile(mattermostLog)
if err != nil {
return nil, errors.Wrapf(err, "failed read mattermost log file at path %s", mattermostLog)
}
return &model.FileData{
Filename: config.LogFilename,
Body: mattermostLogFileData,
}, nil
}
func (ps *PlatformService) GetNotificationLogFile(_ request.CTX) (*model.FileData, error) {
if !*ps.Config().NotificationLogSettings.EnableFile {
return nil, errors.New("Unable to retrieve notifications logs because NotificationLogSettings.EnableFile is set to false")
}
notificationsLog := config.GetNotificationsLogFileLocation(*ps.Config().LogSettings.FileLocation)
notificationsLogFileData, err := os.ReadFile(notificationsLog)
if err != nil {
return nil, errors.Wrapf(err, "failed read notifcation log file at path %s", notificationsLog)
}
return &model.FileData{
Filename: config.LogNotificationFilename,
Body: notificationsLogFileData,
}, nil
}
func isLogFilteredByLevel(logFilter *model.LogFilter, entry *model.LogEntry) bool {
logLevels := logFilter.LogLevels
if len(logLevels) == 0 {

104
server/channels/app/platform/log_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,104 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package platform
import (
"os"
"testing"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetMattermostLog(t *testing.T) {
th := Setup(t)
defer th.TearDown()
// disable mattermost log file setting in config so we should get an warning
th.Service.UpdateConfig(func(cfg *model.Config) {
*cfg.LogSettings.EnableFile = false
})
fileData, err := th.Service.GetLogFile(th.Context)
assert.Nil(t, fileData)
assert.ErrorContains(t, err, "Unable to retrieve mattermost logs because LogSettings.EnableFile is set to false")
dir, err := os.MkdirTemp("", "")
require.NoError(t, err)
t.Cleanup(func() {
err = os.RemoveAll(dir)
assert.NoError(t, err)
})
// Enable log file but point to an empty directory to get an error trying to read the file
th.Service.UpdateConfig(func(cfg *model.Config) {
*cfg.LogSettings.EnableFile = true
*cfg.LogSettings.FileLocation = dir
})
logLocation := config.GetLogFileLocation(dir)
// There is no mattermost.log file yet, so this fails
fileData, err = th.Service.GetLogFile(th.Context)
assert.Nil(t, fileData)
assert.ErrorContains(t, err, "failed read mattermost log file at path "+logLocation)
// Happy path where we get a log file and no warning
d1 := []byte("hello\ngo\n")
err = os.WriteFile(logLocation, d1, 0777)
require.NoError(t, err)
fileData, err = th.Service.GetLogFile(th.Context)
require.NoError(t, err)
require.NotNil(t, fileData)
assert.Equal(t, "mattermost.log", fileData.Filename)
assert.Positive(t, len(fileData.Body))
}
func TestGetNotificationLogFile(t *testing.T) {
th := Setup(t)
defer th.TearDown()
// Disable notifications file setting in config so we should get an warning
th.Service.UpdateConfig(func(cfg *model.Config) {
*cfg.NotificationLogSettings.EnableFile = false
})
fileData, err := th.Service.GetNotificationLogFile(th.Context)
assert.Nil(t, fileData)
assert.ErrorContains(t, err, "Unable to retrieve notifications logs because NotificationLogSettings.EnableFile is set to false")
dir, err := os.MkdirTemp("", "")
require.NoError(t, err)
t.Cleanup(func() {
err = os.RemoveAll(dir)
assert.NoError(t, err)
})
// Enable notifications file but point to an empty directory to get an error trying to read the file
th.Service.UpdateConfig(func(cfg *model.Config) {
*cfg.NotificationLogSettings.EnableFile = true
*cfg.LogSettings.FileLocation = dir
})
logLocation := config.GetNotificationsLogFileLocation(dir)
// There is no notifications.log file yet, so this fails
fileData, err = th.Service.GetNotificationLogFile(th.Context)
assert.Nil(t, fileData)
assert.ErrorContains(t, err, "failed read notifcation log file at path "+logLocation)
// Happy path where we have file and no error
d1 := []byte("hello\ngo\n")
err = os.WriteFile(logLocation, d1, 0777)
require.NoError(t, err)
fileData, err = th.Service.GetNotificationLogFile(th.Context)
assert.NoError(t, err)
require.NotNil(t, fileData)
assert.Equal(t, "notifications.log", fileData.Filename)
assert.Positive(t, len(fileData.Body))
}

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

@@ -4,6 +4,7 @@
package platform
import (
"bytes"
"context"
"fmt"
"net"
@@ -11,6 +12,7 @@ import (
"net/http/pprof"
"path"
"runtime"
rpprof "runtime/pprof"
"strings"
"sync"
"text/template"
@@ -23,11 +25,15 @@ import (
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/utils"
"github.com/mattermost/mattermost/server/v8/einterfaces"
)
const TimeToWaitForConnectionsToCloseOnServerShutdown = time.Second
const (
TimeToWaitForConnectionsToCloseOnServerShutdown = time.Second
cpuProfileDuration = 5 * time.Second
)
type platformMetrics struct {
server *http.Server
@@ -247,3 +253,52 @@ func (ps *PlatformService) Metrics() einterfaces.MetricsInterface {
return ps.metricsIFace
}
func (ps *PlatformService) CreateCPUProfile(_ request.CTX) (*model.FileData, error) {
var b bytes.Buffer
err := rpprof.StartCPUProfile(&b)
if err != nil {
return nil, errors.Wrap(err, "failed to start CPU profile")
}
time.Sleep(cpuProfileDuration)
rpprof.StopCPUProfile()
fileData := &model.FileData{
Filename: "cpu.prof",
Body: b.Bytes(),
}
return fileData, nil
}
func (ps *PlatformService) CreateHeapProfile(_ request.CTX) (*model.FileData, error) {
var b bytes.Buffer
err := rpprof.Lookup("heap").WriteTo(&b, 0)
if err != nil {
return nil, errors.Wrap(err, "failed to lookup heap profile")
}
fileData := &model.FileData{
Filename: "heap.prof",
Body: b.Bytes(),
}
return fileData, nil
}
func (ps *PlatformService) CreateGoroutineProfile(_ request.CTX) (*model.FileData, error) {
var b bytes.Buffer
err := rpprof.Lookup("goroutine").WriteTo(&b, 2)
if err != nil {
return nil, errors.Wrap(err, "failed to lookup goroutine profile")
}
fileData := &model.FileData{
Filename: "goroutines",
Body: b.Bytes(),
}
return fileData, nil
}