Tracking Push Notifications in a structured logger (notifications.log) (#10823)

* Remove NotificationRegistry table and use structured logging

* Fix ackId for notification sent

* Notification logger at server level

* Remove unused i18n strings
This commit is contained in:
Elias Nahum
2019-05-13 10:53:46 -04:00
committed by Christopher Speller
parent 30061df036
commit 5b252e8736
23 changed files with 206 additions and 578 deletions

View File

@@ -334,13 +334,6 @@ func pushNotificationAck(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
mlog.Debug("Push notification ack received",
mlog.String("ackId", ack.Id),
mlog.String("platform", ack.ClientPlatform),
mlog.String("type", ack.NotificationType),
mlog.Int64("at", ack.ClientReceivedAt),
)
ReturnStatusOK(w) ReturnStatusOK(w)
return return
} }

View File

@@ -23,7 +23,8 @@ import (
type App struct { type App struct {
Srv *Server Srv *Server
Log *mlog.Logger Log *mlog.Logger
NotificationsLog *mlog.Logger
T goi18n.TranslateFunc T goi18n.TranslateFunc
Session model.Session Session model.Session

View File

@@ -308,16 +308,13 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
) )
} else { } else {
// register that a notification was not sent // register that a notification was not sent
notificationRegistry := model.NotificationRegistry{ a.NotificationsLog.Warn("Notification not sent",
UserId: id, mlog.String("ackId", ""),
PostId: post.Id, mlog.String("type", model.PUSH_TYPE_MESSAGE),
SendStatus: model.PUSH_NOT_SENT, mlog.String("userId", id),
Type: model.PUSH_TYPE_MESSAGE, mlog.String("postId", post.Id),
} mlog.String("status", model.PUSH_NOT_SENT),
_, appErr := a.Srv.Store.NotificationRegistry().Save(&notificationRegistry) )
if appErr != nil {
mlog.Debug(appErr.Error())
}
} }
} }
@@ -343,16 +340,13 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
) )
} else { } else {
// register that a notification was not sent // register that a notification was not sent
notificationRegistry := model.NotificationRegistry{ a.NotificationsLog.Warn("Notification not sent",
UserId: id, mlog.String("ackId", ""),
PostId: post.Id, mlog.String("type", model.PUSH_TYPE_MESSAGE),
SendStatus: model.PUSH_NOT_SENT, mlog.String("userId", id),
Type: model.PUSH_TYPE_MESSAGE, mlog.String("postId", post.Id),
} mlog.String("status", model.PUSH_NOT_SENT),
_, appErr := a.Srv.Store.NotificationRegistry().Save(&notificationRegistry) )
if appErr != nil {
mlog.Debug(appErr.Error())
}
} }
} }
} }

View File

@@ -112,34 +112,30 @@ func (a *App) sendPushNotificationSync(post *model.Post, user *model.User, chann
tmpMessage.SetDeviceIdAndPlatform(session.DeviceId) tmpMessage.SetDeviceIdAndPlatform(session.DeviceId)
tmpMessage.AckId = model.NewId() tmpMessage.AckId = model.NewId()
mlog.Debug(
"Sending push notification",
mlog.String("ackId", tmpMessage.AckId),
mlog.String("deviceId", tmpMessage.DeviceId),
mlog.String("userId", user.Id),
)
err := a.sendToPushProxy(*tmpMessage, session) err := a.sendToPushProxy(*tmpMessage, session)
if err != nil { if err != nil {
mlog.Error( a.NotificationsLog.Error("Notification error",
"Failed to send Push Notification:", mlog.String("ackId", tmpMessage.AckId),
mlog.String("error", err.Error()), mlog.String("type", tmpMessage.Type),
mlog.String("userId", session.UserId), mlog.String("userId", session.UserId),
mlog.String("sessionId", session.Id), mlog.String("postId", tmpMessage.PostId),
mlog.String("deviceId", msg.DeviceId), mlog.String("channelId", tmpMessage.ChannelId),
mlog.String("ackId", msg.AckId), mlog.String("deviceId", tmpMessage.DeviceId),
mlog.String("status", err.Error()),
) )
appErr := a.Srv.Store.NotificationRegistry().UpdateSendStatus(tmpMessage.AckId, model.PUSH_SEND_ERROR+": "+err.Error())
if appErr != nil {
mlog.Debug(appErr.Error())
}
continue continue
} }
appErr := a.Srv.Store.NotificationRegistry().UpdateSendStatus(tmpMessage.AckId, model.PUSH_SEND_SUCCESS) a.NotificationsLog.Info("Notification sent",
if appErr != nil { mlog.String("ackId", tmpMessage.AckId),
mlog.Debug(appErr.Error()) mlog.String("type", tmpMessage.Type),
} mlog.String("userId", session.UserId),
mlog.String("postId", tmpMessage.PostId),
mlog.String("channelId", tmpMessage.ChannelId),
mlog.String("deviceId", tmpMessage.DeviceId),
mlog.String("status", model.PUSH_SEND_SUCCESS),
)
if a.Metrics != nil { if a.Metrics != nil {
a.Metrics.IncrementPostSentPush() a.Metrics.IncrementPostSentPush()
@@ -248,35 +244,30 @@ func (a *App) ClearPushNotificationSync(currentSessionId, userId, channelId stri
tmpMessage.SetDeviceIdAndPlatform(session.DeviceId) tmpMessage.SetDeviceIdAndPlatform(session.DeviceId)
tmpMessage.AckId = model.NewId() tmpMessage.AckId = model.NewId()
mlog.Debug(
"Sending clear push notification",
mlog.String("ackId", tmpMessage.AckId),
mlog.String("deviceId", tmpMessage.DeviceId),
mlog.String("userId", session.UserId),
mlog.String("channelId", channelId), //should we remove the message from the logs?
)
err := a.sendToPushProxy(*tmpMessage, session) err := a.sendToPushProxy(*tmpMessage, session)
if err != nil { if err != nil {
mlog.Error( a.NotificationsLog.Error("Notification error",
"Failed to send Push Notification:", mlog.String("ackId", tmpMessage.AckId),
mlog.String("error", err.Error()), mlog.String("type", tmpMessage.Type),
mlog.String("userId", session.UserId), mlog.String("userId", session.UserId),
mlog.String("sessionId", session.Id), mlog.String("postId", tmpMessage.PostId),
mlog.String("deviceId", msg.DeviceId), mlog.String("channelId", tmpMessage.ChannelId),
mlog.String("ackId", msg.AckId), mlog.String("deviceId", tmpMessage.DeviceId),
mlog.String("status", err.Error()),
) )
appErr := a.Srv.Store.NotificationRegistry().UpdateSendStatus(tmpMessage.AckId, model.PUSH_SEND_ERROR+": "+err.Error())
if appErr != nil {
mlog.Debug(appErr.Error())
}
continue continue
} }
appErr := a.Srv.Store.NotificationRegistry().UpdateSendStatus(tmpMessage.AckId, model.PUSH_SEND_SUCCESS) a.NotificationsLog.Info("Notification sent",
if appErr != nil { mlog.String("ackId", tmpMessage.AckId),
mlog.Debug(appErr.Error()) mlog.String("type", tmpMessage.Type),
} mlog.String("userId", session.UserId),
mlog.String("postId", tmpMessage.PostId),
mlog.String("channelId", tmpMessage.ChannelId),
mlog.String("deviceId", tmpMessage.DeviceId),
mlog.String("status", model.PUSH_SEND_SUCCESS),
)
if a.Metrics != nil { if a.Metrics != nil {
a.Metrics.IncrementPostSentPush() a.Metrics.IncrementPostSentPush()
@@ -343,18 +334,13 @@ func (a *App) StopPushNotificationsHubWorkers() {
func (a *App) sendToPushProxy(msg model.PushNotification, session *model.Session) error { func (a *App) sendToPushProxy(msg model.PushNotification, session *model.Session) error {
msg.ServerId = a.DiagnosticId() msg.ServerId = a.DiagnosticId()
notificationRegistry := model.NotificationRegistry{ a.NotificationsLog.Info("Notification will be sent",
AckId: msg.AckId, mlog.String("ackId", msg.AckId),
DeviceId: msg.DeviceId, mlog.String("type", msg.Type),
UserId: session.UserId, mlog.String("userId", session.UserId),
PostId: msg.PostId, mlog.String("postId", msg.PostId),
Type: msg.Type, mlog.String("status", model.PUSH_SEND_PREPARE),
} )
_, appErr := a.Srv.Store.NotificationRegistry().Save(&notificationRegistry)
if appErr != nil {
return appErr
}
request, err := http.NewRequest("POST", strings.TrimRight(*a.Config().EmailSettings.PushNotificationServer, "/")+model.API_URL_SUFFIX_V1+"/send_push", strings.NewReader(msg.ToJson())) request, err := http.NewRequest("POST", strings.TrimRight(*a.Config().EmailSettings.PushNotificationServer, "/")+model.API_URL_SUFFIX_V1+"/send_push", strings.NewReader(msg.ToJson()))
if err != nil { if err != nil {
@@ -388,10 +374,13 @@ func (a *App) SendAckToPushProxy(ack *model.PushNotificationAck) error {
return nil return nil
} }
appErr := a.Srv.Store.NotificationRegistry().MarkAsReceived(ack.Id, ack.ClientReceivedAt) a.NotificationsLog.Info("Notification received",
if appErr != nil { mlog.String("ackId", ack.Id),
return appErr mlog.String("type", ack.NotificationType),
} mlog.String("deviceType", ack.ClientPlatform),
mlog.Int64("receivedAt", ack.ClientReceivedAt),
mlog.String("status", model.PUSH_RECEIVED),
)
request, err := http.NewRequest( request, err := http.NewRequest(
"POST", "POST",

View File

@@ -99,6 +99,7 @@ func ServerConnector(s *Server) AppOption {
a.Srv = s a.Srv = s
a.Log = s.Log a.Log = s.Log
a.NotificationsLog = s.NotificationsLog
a.AccountMigration = s.AccountMigration a.AccountMigration = s.AccountMigration
a.Cluster = s.Cluster a.Cluster = s.Cluster

View File

@@ -108,7 +108,8 @@ type Server struct {
ImageProxy *imageproxy.ImageProxy ImageProxy *imageproxy.ImageProxy
Log *mlog.Logger Log *mlog.Logger
NotificationsLog *mlog.Logger
joinCluster bool joinCluster bool
startMetrics bool startMetrics bool
@@ -152,7 +153,13 @@ func NewServer(options ...Option) (*Server, error) {
} }
if s.Log == nil { if s.Log == nil {
s.Log = mlog.NewLogger(utils.MloggerConfigFromLoggerConfig(&s.Config().LogSettings)) s.Log = mlog.NewLogger(utils.MloggerConfigFromLoggerConfig(&s.Config().LogSettings, utils.GetLogFileLocation))
}
if s.NotificationsLog == nil {
notificationLogSettings := utils.GetLogSettingsFromNotificationsLogSettings(&s.Config().NotificationLogSettings)
s.NotificationsLog = mlog.NewLogger(utils.MloggerConfigFromLoggerConfig(notificationLogSettings, utils.GetNotificationsLogFileLocation)).
WithCallerSkip(1).With(mlog.String("logSource", "notifications"))
} }
// Redirect default golang logger to this logger // Redirect default golang logger to this logger
@@ -162,7 +169,10 @@ func NewServer(options ...Option) (*Server, error) {
mlog.InitGlobalLogger(s.Log) mlog.InitGlobalLogger(s.Log)
s.logListenerId = s.AddConfigListener(func(_, after *model.Config) { s.logListenerId = s.AddConfigListener(func(_, after *model.Config) {
s.Log.ChangeLevels(utils.MloggerConfigFromLoggerConfig(&after.LogSettings)) s.Log.ChangeLevels(utils.MloggerConfigFromLoggerConfig(&after.LogSettings, utils.GetLogFileLocation))
notificationLogSettings := utils.GetLogSettingsFromNotificationsLogSettings(&after.NotificationLogSettings)
s.NotificationsLog.ChangeLevels(utils.MloggerConfigFromLoggerConfig(notificationLogSettings, utils.GetNotificationsLogFileLocation))
}) })
s.HTTPService = httpservice.MakeHTTPService(s.FakeApp()) s.HTTPService = httpservice.MakeHTTPService(s.FakeApp())

View File

@@ -147,6 +147,15 @@
"EnableWebhookDebugging": true, "EnableWebhookDebugging": true,
"EnableDiagnostics": true "EnableDiagnostics": true
}, },
"NotificationLogSettings": {
"EnableConsole": true,
"ConsoleLevel": "DEBUG",
"ConsoleJson": true,
"EnableFile": true,
"FileLevel": "INFO",
"FileJson": true,
"FileLocation": ""
},
"PasswordSettings": { "PasswordSettings": {
"MinimumLength": 5, "MinimumLength": 5,
"Lowercase": false, "Lowercase": false,

View File

@@ -4790,34 +4790,6 @@
"id": "model.link_metadata.is_valid.url.app_error", "id": "model.link_metadata.is_valid.url.app_error",
"translation": "Link metadata URL must be set" "translation": "Link metadata URL must be set"
}, },
{
"id": "model.notification_registry.ack_id.app_error",
"translation": "Invalid Acknowledge Id"
},
{
"id": "model.notification_registry.create_at.app_error",
"translation": "Create at must be a valid time"
},
{
"id": "model.notification_registry.device_id.app_error",
"translation": "Invalid Device Id"
},
{
"id": "model.notification_registry.post_id.app_error",
"translation": "Invalid Post Id"
},
{
"id": "model.notification_registry.status.app_error",
"translation": "Invalid Send Status"
},
{
"id": "model.notification_registry.type.app_error",
"translation": "Invalid push type, must be \"clear\" or \"message\""
},
{
"id": "model.notification_registry.user_id.app_error",
"translation": "Invalid User Id"
},
{ {
"id": "model.oauth.is_valid.app_id.app_error", "id": "model.oauth.is_valid.app_id.app_error",
"translation": "Invalid app id" "translation": "Invalid app id"
@@ -5890,18 +5862,6 @@
"id": "store.sql_link_metadata.save.app_error", "id": "store.sql_link_metadata.save.app_error",
"translation": "Unable to save the link metadata" "translation": "Unable to save the link metadata"
}, },
{
"id": "store.sql_notification.mark_as_received.app_error",
"translation": "An error occurred marking the notification as received"
},
{
"id": "store.sql_notification.save.app_error",
"translation": "An error occurred while saving the notification registry"
},
{
"id": "store.sql_notification.update_status.app_error",
"translation": "An error occurred while updating the notification status"
},
{ {
"id": "store.sql_oauth.delete.commit_transaction.app_error", "id": "store.sql_oauth.delete.commit_transaction.app_error",
"translation": "Unable to commit transaction" "translation": "Unable to commit transaction"

View File

@@ -912,6 +912,46 @@ func (s *LogSettings) SetDefaults() {
} }
} }
type NotificationLogSettings struct {
EnableConsole *bool `restricted:"true"`
ConsoleLevel *string `restricted:"true"`
ConsoleJson *bool `restricted:"true"`
EnableFile *bool `restricted:"true"`
FileLevel *string `restricted:"true"`
FileJson *bool `restricted:"true"`
FileLocation *string `restricted:"true"`
}
func (s *NotificationLogSettings) SetDefaults() {
if s.EnableConsole == nil {
s.EnableConsole = NewBool(true)
}
if s.ConsoleLevel == nil {
s.ConsoleLevel = NewString("DEBUG")
}
if s.EnableFile == nil {
s.EnableFile = NewBool(true)
}
if s.FileLevel == nil {
s.FileLevel = NewString("INFO")
}
if s.FileLocation == nil {
s.FileLocation = NewString("")
}
if s.ConsoleJson == nil {
s.ConsoleJson = NewBool(true)
}
if s.FileJson == nil {
s.FileJson = NewBool(true)
}
}
type PasswordSettings struct { type PasswordSettings struct {
MinimumLength *int MinimumLength *int
Lowercase *bool Lowercase *bool
@@ -2241,38 +2281,39 @@ func (ips *ImageProxySettings) SetDefaults(ss ServiceSettings) {
type ConfigFunc func() *Config type ConfigFunc func() *Config
type Config struct { type Config struct {
ServiceSettings ServiceSettings ServiceSettings ServiceSettings
TeamSettings TeamSettings TeamSettings TeamSettings
ClientRequirements ClientRequirements ClientRequirements ClientRequirements
SqlSettings SqlSettings SqlSettings SqlSettings
LogSettings LogSettings LogSettings LogSettings
PasswordSettings PasswordSettings NotificationLogSettings NotificationLogSettings
FileSettings FileSettings PasswordSettings PasswordSettings
EmailSettings EmailSettings FileSettings FileSettings
RateLimitSettings RateLimitSettings EmailSettings EmailSettings
PrivacySettings PrivacySettings RateLimitSettings RateLimitSettings
SupportSettings SupportSettings PrivacySettings PrivacySettings
AnnouncementSettings AnnouncementSettings SupportSettings SupportSettings
ThemeSettings ThemeSettings AnnouncementSettings AnnouncementSettings
GitLabSettings SSOSettings ThemeSettings ThemeSettings
GoogleSettings SSOSettings GitLabSettings SSOSettings
Office365Settings SSOSettings GoogleSettings SSOSettings
LdapSettings LdapSettings Office365Settings SSOSettings
ComplianceSettings ComplianceSettings LdapSettings LdapSettings
LocalizationSettings LocalizationSettings ComplianceSettings ComplianceSettings
SamlSettings SamlSettings LocalizationSettings LocalizationSettings
NativeAppSettings NativeAppSettings SamlSettings SamlSettings
ClusterSettings ClusterSettings NativeAppSettings NativeAppSettings
MetricsSettings MetricsSettings ClusterSettings ClusterSettings
ExperimentalSettings ExperimentalSettings MetricsSettings MetricsSettings
AnalyticsSettings AnalyticsSettings ExperimentalSettings ExperimentalSettings
ElasticsearchSettings ElasticsearchSettings AnalyticsSettings AnalyticsSettings
DataRetentionSettings DataRetentionSettings ElasticsearchSettings ElasticsearchSettings
MessageExportSettings MessageExportSettings DataRetentionSettings DataRetentionSettings
JobSettings JobSettings MessageExportSettings MessageExportSettings
PluginSettings PluginSettings JobSettings JobSettings
DisplaySettings DisplaySettings PluginSettings PluginSettings
ImageProxySettings ImageProxySettings DisplaySettings DisplaySettings
ImageProxySettings ImageProxySettings
} }
func (o *Config) Clone() *Config { func (o *Config) Clone() *Config {
@@ -2344,6 +2385,7 @@ func (o *Config) SetDefaults() {
o.DataRetentionSettings.SetDefaults() o.DataRetentionSettings.SetDefaults()
o.RateLimitSettings.SetDefaults() o.RateLimitSettings.SetDefaults()
o.LogSettings.SetDefaults() o.LogSettings.SetDefaults()
o.NotificationLogSettings.SetDefaults()
o.JobSettings.SetDefaults() o.JobSettings.SetDefaults()
o.MessageExportSettings.SetDefaults() o.MessageExportSettings.SetDefaults()
o.DisplaySettings.SetDefaults() o.DisplaySettings.SetDefaults()

View File

@@ -6,7 +6,6 @@ package model
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"net/http"
"strings" "strings"
) )
@@ -26,22 +25,12 @@ const (
MHPNS = "https://push.mattermost.com" MHPNS = "https://push.mattermost.com"
PUSH_SEND_PREPARE = "Prepared to send"
PUSH_SEND_SUCCESS = "Successful" PUSH_SEND_SUCCESS = "Successful"
PUSH_SEND_ERROR = "Error"
PUSH_NOT_SENT = "Not Sent due to preferences" PUSH_NOT_SENT = "Not Sent due to preferences"
PUSH_RECEIVED = "Received by device"
) )
type NotificationRegistry struct {
AckId string
CreateAt int64
UserId string
DeviceId string
PostId string
SendStatus string
Type string
ReceiveAt int64
}
type PushNotificationAck struct { type PushNotificationAck struct {
Id string `json:"id"` Id string `json:"id"`
ClientReceivedAt int64 `json:"received_at"` ClientReceivedAt int64 `json:"received_at"`
@@ -104,44 +93,3 @@ func (ack *PushNotificationAck) ToJson() string {
b, _ := json.Marshal(ack) b, _ := json.Marshal(ack)
return string(b) return string(b)
} }
func (o *NotificationRegistry) IsValid() *AppError {
if len(o.AckId) != 26 {
return NewAppError("NotificationRegistry.IsValid", "model.notification_registry.ack_id.app_error", nil, "", http.StatusBadRequest)
}
if o.CreateAt == 0 {
return NewAppError("NotificationRegistry.IsValid", "model.notification_registry.create_at.app_error", nil, "AckId="+o.AckId, http.StatusBadRequest)
}
if len(o.UserId) != 26 {
return NewAppError("NotificationRegistry.IsValid", "model.notification_registry.user_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.PostId) > 0 && len(o.PostId) != 26 {
return NewAppError("NotificationRegistry.IsValid", "model.notification_registry.post_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.DeviceId) > 512 {
return NewAppError("NotificationRegistry.IsValid", "model.notification_registry.device_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.SendStatus) > 4096 {
return NewAppError("NotificationRegistry.IsValid", "model.notification_registry.status.app_error", nil, "", http.StatusBadRequest)
}
if o.Type != PUSH_TYPE_CLEAR && o.Type != PUSH_TYPE_MESSAGE {
return NewAppError("NotificationRegistry.IsValid", "model.notification_registry.type.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
func (o *NotificationRegistry) PreSave() {
if o.AckId == "" {
o.AckId = NewId()
}
o.CreateAt = GetMillis()
}

View File

@@ -195,10 +195,6 @@ func (s *LayeredStore) LinkMetadata() LinkMetadataStore {
return s.DatabaseLayer.LinkMetadata() return s.DatabaseLayer.LinkMetadata()
} }
func (s *LayeredStore) NotificationRegistry() NotificationRegistryStore {
return s.DatabaseLayer.NotificationRegistry()
}
func (s *LayeredStore) MarkSystemRanUnitTests() { func (s *LayeredStore) MarkSystemRanUnitTests() {
s.DatabaseLayer.MarkSystemRanUnitTests() s.DatabaseLayer.MarkSystemRanUnitTests()
} }

View File

@@ -1,81 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package sqlstore
import (
"net/http"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
)
type SqlNotificationRegistryStore struct {
SqlStore
}
func NewSqlNotificationRegistryStore(sqlStore SqlStore) store.NotificationRegistryStore {
s := &SqlNotificationRegistryStore{sqlStore}
for _, db := range sqlStore.GetAllConns() {
table := db.AddTableWithName(model.NotificationRegistry{}, "NotificationRegistry").SetKeys(false, "AckId")
table.ColMap("AckId").SetMaxSize(26)
table.ColMap("UserId").SetMaxSize(26)
table.ColMap("PostId").SetMaxSize(26)
table.ColMap("DeviceId").SetMaxSize(512)
table.ColMap("Type").SetMaxSize(26)
table.ColMap("SendStatus").SetMaxSize(4096)
}
return s
}
func (s *SqlNotificationRegistryStore) CreateIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_notification_ack_id", "NotificationRegistry", "AckId")
s.CreateIndexIfNotExists("idx_notification_create_at", "NotificationRegistry", "CreateAt")
s.CreateIndexIfNotExists("idx_notification_receive_at", "NotificationRegistry", "ReceiveAt")
s.CreateIndexIfNotExists("idx_notification_post_id", "NotificationRegistry", "PostId")
s.CreateIndexIfNotExists("idx_notification_user_id", "NotificationRegistry", "UserId")
s.CreateIndexIfNotExists("idx_notification_type", "NotificationRegistry", "Type")
}
func (s *SqlNotificationRegistryStore) Save(notification *model.NotificationRegistry) (*model.NotificationRegistry, *model.AppError) {
notification.PreSave()
appErr := notification.IsValid()
if appErr != nil {
return nil, appErr
}
err := s.GetMaster().Insert(notification)
if err != nil {
appErr = model.NewAppError("SqlNotificationRegistryStore.Save", "store.sql_notification.save.app_error", nil, "id="+notification.AckId+", "+err.Error(), http.StatusInternalServerError)
return nil, appErr
}
return notification, nil
}
func (s *SqlNotificationRegistryStore) MarkAsReceived(ackId string, time int64) *model.AppError {
result, err := s.GetMaster().Exec("UPDATE NotificationRegistry SET ReceiveAt = :ReceiveAt WHERE AckId = :AckId AND ReceiveAt = 0", map[string]interface{}{"AckId": ackId, "ReceiveAt": time})
if err != nil {
return model.NewAppError("SqlNotificationRegistryStore.Save", "store.sql_notification.mark_as_received.app_error", nil, "id="+ackId+", "+err.Error(), http.StatusInternalServerError)
}
affected, err := result.RowsAffected()
if err != nil {
return model.NewAppError("SqlNotificationRegistryStore.Save", "store.sql_notification.mark_as_received.app_error", nil, "id="+ackId+", "+err.Error(), http.StatusInternalServerError)
} else if affected != 1 {
return model.NewAppError("SqlNotificationRegistryStore.Save", "store.sql_notification.mark_as_received.app_error", nil, "id="+ackId+", Message already received", http.StatusInternalServerError)
}
return nil
}
func (s *SqlNotificationRegistryStore) UpdateSendStatus(ackId, status string) *model.AppError {
_, err := s.GetMaster().Exec("UPDATE NotificationRegistry SET SendStatus = :Status WHERE AckId = :AckId", map[string]interface{}{"AckId": ackId, "Status": status})
if err != nil {
return model.NewAppError("SqlNotificationRegistryStore.Save", "store.sql_notification.update_status.app_error", nil, "id="+ackId+", "+err.Error(), http.StatusInternalServerError)
}
return nil
}

View File

@@ -1,14 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package sqlstore
import (
"testing"
"github.com/mattermost/mattermost-server/store/storetest"
)
func TestNotificationRegistryStore(t *testing.T) {
StoreTest(t, storetest.TestNotificationRegistryStore)
}

View File

@@ -99,5 +99,4 @@ type SqlStore interface {
UserTermsOfService() store.UserTermsOfServiceStore UserTermsOfService() store.UserTermsOfServiceStore
LinkMetadata() store.LinkMetadataStore LinkMetadata() store.LinkMetadataStore
getQueryBuilder() sq.StatementBuilderType getQueryBuilder() sq.StatementBuilderType
NotificationRegistry() store.NotificationRegistryStore
} }

View File

@@ -99,7 +99,6 @@ type SqlSupplierOldStores struct {
group store.GroupStore group store.GroupStore
UserTermsOfService store.UserTermsOfServiceStore UserTermsOfService store.UserTermsOfServiceStore
linkMetadata store.LinkMetadataStore linkMetadata store.LinkMetadataStore
notificationRegistry store.NotificationRegistryStore
} }
type SqlSupplier struct { type SqlSupplier struct {
@@ -152,7 +151,6 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter
supplier.oldStores.TermsOfService = NewSqlTermsOfServiceStore(supplier, metrics) supplier.oldStores.TermsOfService = NewSqlTermsOfServiceStore(supplier, metrics)
supplier.oldStores.UserTermsOfService = NewSqlUserTermsOfServiceStore(supplier) supplier.oldStores.UserTermsOfService = NewSqlUserTermsOfServiceStore(supplier)
supplier.oldStores.linkMetadata = NewSqlLinkMetadataStore(supplier) supplier.oldStores.linkMetadata = NewSqlLinkMetadataStore(supplier)
supplier.oldStores.notificationRegistry = NewSqlNotificationRegistryStore(supplier)
initSqlSupplierReactions(supplier) initSqlSupplierReactions(supplier)
initSqlSupplierRoles(supplier) initSqlSupplierRoles(supplier)
@@ -1055,10 +1053,6 @@ func (ss *SqlSupplier) LinkMetadata() store.LinkMetadataStore {
return ss.oldStores.linkMetadata return ss.oldStores.linkMetadata
} }
func (ss *SqlSupplier) NotificationRegistry() store.NotificationRegistryStore {
return ss.oldStores.notificationRegistry
}
func (ss *SqlSupplier) DropAllTables() { func (ss *SqlSupplier) DropAllTables() {
ss.master.TruncateTables() ss.master.TruncateTables()
} }

View File

@@ -78,7 +78,6 @@ type Store interface {
TotalMasterDbConnections() int TotalMasterDbConnections() int
TotalReadDbConnections() int TotalReadDbConnections() int
TotalSearchDbConnections() int TotalSearchDbConnections() int
NotificationRegistry() NotificationRegistryStore
} }
type TeamStore interface { type TeamStore interface {
@@ -605,9 +604,3 @@ type LinkMetadataStore interface {
Save(linkMetadata *model.LinkMetadata) StoreChannel Save(linkMetadata *model.LinkMetadata) StoreChannel
Get(url string, timestamp int64) StoreChannel Get(url string, timestamp int64) StoreChannel
} }
type NotificationRegistryStore interface {
Save(notification *model.NotificationRegistry) (*model.NotificationRegistry, *model.AppError)
MarkAsReceived(ackId string, time int64) *model.AppError
UpdateSendStatus(ackId, status string) *model.AppError
}

View File

@@ -780,22 +780,6 @@ func (_m *LayeredStoreDatabaseLayer) Next() store.LayeredStoreSupplier {
return r0 return r0
} }
// NotificationRegistry provides a mock function with given fields:
func (_m *LayeredStoreDatabaseLayer) NotificationRegistry() store.NotificationRegistryStore {
ret := _m.Called()
var r0 store.NotificationRegistryStore
if rf, ok := ret.Get(0).(func() store.NotificationRegistryStore); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.NotificationRegistryStore)
}
}
return r0
}
// OAuth provides a mock function with given fields: // OAuth provides a mock function with given fields:
func (_m *LayeredStoreDatabaseLayer) OAuth() store.OAuthStore { func (_m *LayeredStoreDatabaseLayer) OAuth() store.OAuthStore {
ret := _m.Called() ret := _m.Called()

View File

@@ -1,70 +0,0 @@
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`.
package mocks
import mock "github.com/stretchr/testify/mock"
import model "github.com/mattermost/mattermost-server/model"
// NotificationRegistryStore is an autogenerated mock type for the NotificationRegistryStore type
type NotificationRegistryStore struct {
mock.Mock
}
// MarkAsReceived provides a mock function with given fields: ackId, time
func (_m *NotificationRegistryStore) MarkAsReceived(ackId string, time int64) *model.AppError {
ret := _m.Called(ackId, time)
var r0 *model.AppError
if rf, ok := ret.Get(0).(func(string, int64) *model.AppError); ok {
r0 = rf(ackId, time)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.AppError)
}
}
return r0
}
// Save provides a mock function with given fields: notification
func (_m *NotificationRegistryStore) Save(notification *model.NotificationRegistry) (*model.NotificationRegistry, *model.AppError) {
ret := _m.Called(notification)
var r0 *model.NotificationRegistry
if rf, ok := ret.Get(0).(func(*model.NotificationRegistry) *model.NotificationRegistry); ok {
r0 = rf(notification)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.NotificationRegistry)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(*model.NotificationRegistry) *model.AppError); ok {
r1 = rf(notification)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// UpdateSendStatus provides a mock function with given fields: ackId, status
func (_m *NotificationRegistryStore) UpdateSendStatus(ackId string, status string) *model.AppError {
ret := _m.Called(ackId, status)
var r0 *model.AppError
if rf, ok := ret.Get(0).(func(string, string) *model.AppError); ok {
r0 = rf(ackId, status)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.AppError)
}
}
return r0
}

View File

@@ -482,22 +482,6 @@ func (_m *SqlStore) MarkSystemRanUnitTests() {
_m.Called() _m.Called()
} }
// NotificationRegistry provides a mock function with given fields:
func (_m *SqlStore) NotificationRegistry() store.NotificationRegistryStore {
ret := _m.Called()
var r0 store.NotificationRegistryStore
if rf, ok := ret.Get(0).(func() store.NotificationRegistryStore); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.NotificationRegistryStore)
}
}
return r0
}
// OAuth provides a mock function with given fields: // OAuth provides a mock function with given fields:
func (_m *SqlStore) OAuth() store.OAuthStore { func (_m *SqlStore) OAuth() store.OAuthStore {
ret := _m.Called() ret := _m.Called()

View File

@@ -256,22 +256,6 @@ func (_m *Store) MarkSystemRanUnitTests() {
_m.Called() _m.Called()
} }
// NotificationRegistry provides a mock function with given fields:
func (_m *Store) NotificationRegistry() store.NotificationRegistryStore {
ret := _m.Called()
var r0 store.NotificationRegistryStore
if rf, ok := ret.Get(0).(func() store.NotificationRegistryStore); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.NotificationRegistryStore)
}
}
return r0
}
// OAuth provides a mock function with given fields: // OAuth provides a mock function with given fields:
func (_m *Store) OAuth() store.OAuthStore { func (_m *Store) OAuth() store.OAuthStore {
ret := _m.Called() ret := _m.Called()

View File

@@ -1,106 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package storetest
import (
"testing"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNotificationRegistryStore(t *testing.T, ss store.Store) {
t.Run("Save", func(t *testing.T) { testNotificationRegistryStoreSave(t, ss) })
t.Run("MarkAsReceived", func(t *testing.T) { testNotificationRegistryStoreMarkAsReceived(t, ss) })
t.Run("UpdateSendStatus", func(t *testing.T) { testNotificationRegistryStoreUpdateSendStatus(t, ss) })
}
func testNotificationRegistryStoreSave(t *testing.T, ss store.Store) {
t.Run("should save notification registry", func(t *testing.T) {
notificationRegistry := &model.NotificationRegistry{
AckId: model.NewId(),
DeviceId: "apple_or_android_device_identifier",
PostId: model.NewId(),
UserId: model.NewId(),
Type: model.PUSH_TYPE_MESSAGE,
}
result, err := ss.NotificationRegistry().Save(notificationRegistry)
require.Nil(t, err)
require.IsType(t, notificationRegistry, result)
assert.NotZero(t, result.CreateAt)
})
t.Run("should save notification registry without AckId", func(t *testing.T) {
notificationRegistry := &model.NotificationRegistry{
DeviceId: "apple_or_android_device_identifier",
PostId: model.NewId(),
UserId: model.NewId(),
Type: model.PUSH_TYPE_MESSAGE,
}
result, err := ss.NotificationRegistry().Save(notificationRegistry)
require.Nil(t, err)
require.IsType(t, notificationRegistry, result)
assert.NotEmpty(t, result.AckId)
assert.NotZero(t, result.CreateAt)
})
t.Run("should fail to save invalid notification registry", func(t *testing.T) {
notificationRegistry := &model.NotificationRegistry{
DeviceId: "apple_or_android_device_identifier",
PostId: model.NewId(),
UserId: model.NewId(),
Type: "garbage",
}
_, err := ss.NotificationRegistry().Save(notificationRegistry)
assert.NotNil(t, err)
})
}
func testNotificationRegistryStoreMarkAsReceived(t *testing.T, ss store.Store) {
notificationRegistry := &model.NotificationRegistry{
DeviceId: "apple_or_android_device_identifier",
PostId: model.NewId(),
UserId: model.NewId(),
Type: model.PUSH_TYPE_MESSAGE,
}
t.Run("should update when the notification was received", func(t *testing.T) {
result, err := ss.NotificationRegistry().Save(notificationRegistry)
if err != nil {
t.Fatal("Notification should have been saved")
}
err = ss.NotificationRegistry().MarkAsReceived(result.AckId, model.GetMillis())
assert.Nil(t, err)
})
}
func testNotificationRegistryStoreUpdateSendStatus(t *testing.T, ss store.Store) {
notificationRegistry := &model.NotificationRegistry{
DeviceId: "apple_or_android_device_identifier",
UserId: model.NewId(),
Type: model.PUSH_TYPE_CLEAR,
}
t.Run("should update when the notification send status", func(t *testing.T) {
result, err := ss.NotificationRegistry().Save(notificationRegistry)
if err != nil {
t.Fatal("Notification should have been saved")
}
err = ss.NotificationRegistry().UpdateSendStatus(result.AckId, "Some Status")
assert.Nil(t, err)
})
}

View File

@@ -50,28 +50,24 @@ type Store struct {
GroupStore mocks.GroupStore GroupStore mocks.GroupStore
UserTermsOfServiceStore mocks.UserTermsOfServiceStore UserTermsOfServiceStore mocks.UserTermsOfServiceStore
LinkMetadataStore mocks.LinkMetadataStore LinkMetadataStore mocks.LinkMetadataStore
NotificationRegistryStore mocks.NotificationRegistryStore
} }
func (s *Store) Team() store.TeamStore { return &s.TeamStore } func (s *Store) Team() store.TeamStore { return &s.TeamStore }
func (s *Store) Channel() store.ChannelStore { return &s.ChannelStore } func (s *Store) Channel() store.ChannelStore { return &s.ChannelStore }
func (s *Store) Post() store.PostStore { return &s.PostStore } func (s *Store) Post() store.PostStore { return &s.PostStore }
func (s *Store) User() store.UserStore { return &s.UserStore } func (s *Store) User() store.UserStore { return &s.UserStore }
func (s *Store) Bot() store.BotStore { return &s.BotStore } func (s *Store) Bot() store.BotStore { return &s.BotStore }
func (s *Store) Audit() store.AuditStore { return &s.AuditStore } func (s *Store) Audit() store.AuditStore { return &s.AuditStore }
func (s *Store) ClusterDiscovery() store.ClusterDiscoveryStore { return &s.ClusterDiscoveryStore } func (s *Store) ClusterDiscovery() store.ClusterDiscoveryStore { return &s.ClusterDiscoveryStore }
func (s *Store) Compliance() store.ComplianceStore { return &s.ComplianceStore } func (s *Store) Compliance() store.ComplianceStore { return &s.ComplianceStore }
func (s *Store) Session() store.SessionStore { return &s.SessionStore } func (s *Store) Session() store.SessionStore { return &s.SessionStore }
func (s *Store) OAuth() store.OAuthStore { return &s.OAuthStore } func (s *Store) OAuth() store.OAuthStore { return &s.OAuthStore }
func (s *Store) System() store.SystemStore { return &s.SystemStore } func (s *Store) System() store.SystemStore { return &s.SystemStore }
func (s *Store) Webhook() store.WebhookStore { return &s.WebhookStore } func (s *Store) Webhook() store.WebhookStore { return &s.WebhookStore }
func (s *Store) Command() store.CommandStore { return &s.CommandStore } func (s *Store) Command() store.CommandStore { return &s.CommandStore }
func (s *Store) CommandWebhook() store.CommandWebhookStore { return &s.CommandWebhookStore } func (s *Store) CommandWebhook() store.CommandWebhookStore { return &s.CommandWebhookStore }
func (s *Store) Preference() store.PreferenceStore { return &s.PreferenceStore } func (s *Store) Preference() store.PreferenceStore { return &s.PreferenceStore }
func (s *Store) License() store.LicenseStore { return &s.LicenseStore } func (s *Store) License() store.LicenseStore { return &s.LicenseStore }
func (s *Store) NotificationRegistry() store.NotificationRegistryStore {
return &s.NotificationRegistryStore
}
func (s *Store) Token() store.TokenStore { return &s.TokenStore } func (s *Store) Token() store.TokenStore { return &s.TokenStore }
func (s *Store) Emoji() store.EmojiStore { return &s.EmojiStore } func (s *Store) Emoji() store.EmojiStore { return &s.EmojiStore }
func (s *Store) Status() store.StatusStore { return &s.StatusStore } func (s *Store) Status() store.StatusStore { return &s.StatusStore }
@@ -116,7 +112,6 @@ func (s *Store) AssertExpectations(t mock.TestingT) bool {
&s.CommandWebhookStore, &s.CommandWebhookStore,
&s.PreferenceStore, &s.PreferenceStore,
&s.LicenseStore, &s.LicenseStore,
&s.NotificationRegistryStore,
&s.TokenStore, &s.TokenStore,
&s.EmojiStore, &s.EmojiStore,
&s.StatusStore, &s.StatusStore,

View File

@@ -13,11 +13,14 @@ import (
) )
const ( const (
LOG_ROTATE_SIZE = 10000 LOG_ROTATE_SIZE = 10000
LOG_FILENAME = "mattermost.log" LOG_FILENAME = "mattermost.log"
LOG_NOTIFICATION_FILENAME = "notifications.log"
) )
func MloggerConfigFromLoggerConfig(s *model.LogSettings) *mlog.LoggerConfiguration { type fileLocationFunc func(string) string
func MloggerConfigFromLoggerConfig(s *model.LogSettings, getFileFunc fileLocationFunc) *mlog.LoggerConfiguration {
return &mlog.LoggerConfiguration{ return &mlog.LoggerConfiguration{
EnableConsole: *s.EnableConsole, EnableConsole: *s.EnableConsole,
ConsoleJson: *s.ConsoleJson, ConsoleJson: *s.ConsoleJson,
@@ -25,7 +28,7 @@ func MloggerConfigFromLoggerConfig(s *model.LogSettings) *mlog.LoggerConfigurati
EnableFile: *s.EnableFile, EnableFile: *s.EnableFile,
FileJson: *s.FileJson, FileJson: *s.FileJson,
FileLevel: strings.ToLower(*s.FileLevel), FileLevel: strings.ToLower(*s.FileLevel),
FileLocation: GetLogFileLocation(*s.FileLocation), FileLocation: getFileFunc(*s.FileLocation),
} }
} }
@@ -37,6 +40,26 @@ func GetLogFileLocation(fileLocation string) string {
return filepath.Join(fileLocation, LOG_FILENAME) return filepath.Join(fileLocation, LOG_FILENAME)
} }
func GetNotificationsLogFileLocation(fileLocation string) string {
if fileLocation == "" {
fileLocation, _ = fileutils.FindDir("logs")
}
return filepath.Join(fileLocation, LOG_NOTIFICATION_FILENAME)
}
func GetLogSettingsFromNotificationsLogSettings(notificationLogSettings *model.NotificationLogSettings) *model.LogSettings {
return &model.LogSettings{
ConsoleJson: notificationLogSettings.ConsoleJson,
ConsoleLevel: notificationLogSettings.ConsoleLevel,
EnableConsole: notificationLogSettings.EnableConsole,
EnableFile: notificationLogSettings.EnableFile,
FileJson: notificationLogSettings.FileJson,
FileLevel: notificationLogSettings.FileLevel,
FileLocation: notificationLogSettings.FileLocation,
}
}
// DON'T USE THIS Modify the level on the app logger // DON'T USE THIS Modify the level on the app logger
func DisableDebugLogForTest() { func DisableDebugLogForTest() {
mlog.GloballyDisableDebugLogForTest() mlog.GloballyDisableDebugLogForTest()