Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(BRIDGE-218): observability support & metrics; reviewed remaining… #414

Merged
merged 1 commit into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/ProtonMail/gluon/internal/db_impl/sqlite3"
"github.com/ProtonMail/gluon/internal/session"
"github.com/ProtonMail/gluon/limits"
"github.com/ProtonMail/gluon/observability"
"github.com/ProtonMail/gluon/profiling"
"github.com/ProtonMail/gluon/reporter"
"github.com/ProtonMail/gluon/store"
Expand All @@ -39,6 +40,7 @@ type serverBuilder struct {
uidValidityGenerator imap.UIDValidityGenerator
panicHandler async.PanicHandler
dbCI db.ClientInterface
observabilitySender observability.Sender
}

func newBuilder() (*serverBuilder, error) {
Expand All @@ -52,6 +54,7 @@ func newBuilder() (*serverBuilder, error) {
uidValidityGenerator: imap.DefaultEpochUIDValidityGenerator(),
panicHandler: async.NoopPanicHandler{},
dbCI: sqlite3.NewBuilder(),
observabilitySender: nil,
}, nil
}

Expand Down Expand Up @@ -121,6 +124,7 @@ func (builder *serverBuilder) build() (*Server, error) {
disableParallelism: builder.disableParallelism,
uidValidityGenerator: builder.uidValidityGenerator,
panicHandler: builder.panicHandler,
observabilitySender: builder.observabilitySender,
}

return s, nil
Expand Down
8 changes: 5 additions & 3 deletions internal/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/gluon/internal/state"
"github.com/ProtonMail/gluon/limits"
"github.com/ProtonMail/gluon/observability"
"github.com/ProtonMail/gluon/observability/metrics"
"github.com/ProtonMail/gluon/reporter"
"github.com/ProtonMail/gluon/store"
"github.com/google/uuid"
Expand Down Expand Up @@ -121,9 +123,7 @@ func (b *Backend) AddUser(ctx context.Context, userID string, conn connector.Con
}

b.log.WithError(err).Errorf("First database migration failed")
reporter.ExceptionWithContext(ctx, "database migration failed", reporter.Context{
"error": err,
})
observability.AddOtherMetric(ctx, metrics.GenerateDatabaseMigrationFailed())

b.log.Debugf("First migration failed, recreating database")

Expand Down Expand Up @@ -180,6 +180,7 @@ func (b *Backend) RemoveUser(ctx context.Context, userID string, removeFiles boo
}

if err := user.close(ctx); err != nil {
// no events in Sentry so far.
reporter.MessageWithContext(ctx,
"Failed to close user from Backend.RemoveUser()",
reporter.Context{"error": err},
Expand Down Expand Up @@ -277,6 +278,7 @@ func (b *Backend) Close(ctx context.Context) error {

for userID, user := range b.users {
if err := user.close(ctx); err != nil {
// there's no events like this in sentry so far.
reporter.MessageWithContext(ctx,
"Failed to close user from Backend.Close()",
reporter.Context{"error": err},
Expand Down
4 changes: 4 additions & 0 deletions internal/backend/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/ProtonMail/gluon/internal/utils"
"github.com/ProtonMail/gluon/limits"
"github.com/ProtonMail/gluon/logging"
"github.com/ProtonMail/gluon/observability"
"github.com/ProtonMail/gluon/observability/metrics"
"github.com/ProtonMail/gluon/reporter"
"github.com/ProtonMail/gluon/store"
"github.com/bradenaw/juniper/xslices"
Expand Down Expand Up @@ -149,6 +151,7 @@ func newUser(

if err := user.deleteAllMessagesMarkedDeleted(ctx); err != nil {
log.WithError(err).Error("Failed to remove deleted messages")
observability.AddMessageRelatedMetric(ctx, metrics.GenerateFailedToRemoveDeletedMessagesMetric())
}

if err := user.cleanupStaleStoreData(ctx); err != nil {
Expand All @@ -171,6 +174,7 @@ func newUser(
}

if err := user.apply(ctx, update); err != nil {
// there's no events like this in sentry so far.
reporter.MessageWithContext(ctx,
"Failed to apply connector update",
reporter.Context{"error": err, "update": update.String()},
Expand Down
6 changes: 6 additions & 0 deletions internal/db_impl/sqlite3/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/ProtonMail/gluon/db"
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/gluon/internal/db_impl/sqlite3/utils"
"github.com/ProtonMail/gluon/observability"
"github.com/ProtonMail/gluon/observability/metrics"
"github.com/google/uuid"
_ "github.com/mattn/go-sqlite3"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -191,6 +193,10 @@ func (c *Client) wrapTx(ctx context.Context, op func(context.Context, *sql.Tx, *
}

if err := tx.Commit(); err != nil {
if !errors.Is(err, context.Canceled) {
observability.AddOtherMetric(ctx, metrics.GenerateFailedToCommitDatabaseTransactionMetric())
}

if c.debug {
entry.Debugf("Failed to commit Transaction")
}
Expand Down
3 changes: 3 additions & 0 deletions internal/session/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/ProtonMail/gluon/imap/command"
"github.com/ProtonMail/gluon/internal/response"
"github.com/ProtonMail/gluon/logging"
"github.com/ProtonMail/gluon/observability"
"github.com/ProtonMail/gluon/observability/metrics"
"github.com/ProtonMail/gluon/rfcparser"
)

Expand Down Expand Up @@ -62,6 +64,7 @@ func (s *Session) startCommandReader(ctx context.Context) <-chan commandResult {
}

s.log.WithError(err).WithField("type", parser.LastParsedCommand()).Error("Failed to parse IMAP command")
observability.AddImapMetric(ctx, metrics.GenerateFailedParseIMAPCommandMetric())
} else {
s.log.Debug(cmd.SanitizedString())
}
Expand Down
1 change: 1 addition & 0 deletions internal/session/handle_append.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func (s *Session) handleAppend(ctx context.Context, tag string, cmd *command.App

messageUID, err := mailbox.Append(ctx, cmd.Literal, flags, cmd.DateTime)
if err != nil {
// no events in sentry so far.
if shouldReportIMAPCommandError(err) {
reporter.MessageWithContext(ctx,
"Failed to append message to mailbox from state",
Expand Down
3 changes: 3 additions & 0 deletions internal/session/handle_copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/ProtonMail/gluon/internal/contexts"
"github.com/ProtonMail/gluon/internal/response"
"github.com/ProtonMail/gluon/internal/state"
"github.com/ProtonMail/gluon/observability"
"github.com/ProtonMail/gluon/observability/metrics"
"github.com/ProtonMail/gluon/profiling"
)

Expand Down Expand Up @@ -37,6 +39,7 @@ func (s *Session) handleCopy(ctx context.Context, tag string, cmd *command.Copy,
} else if errors.Is(err, state.ErrNoSuchMailbox) {
return response.No(tag).WithError(err).WithItems(response.ItemTryCreate()), nil
} else if err != nil {
observability.AddMessageRelatedMetric(ctx, metrics.GenerateFailedToCopyMessagesMetric())
return nil, err
}

Expand Down
3 changes: 3 additions & 0 deletions internal/session/handle_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/gluon/imap/command"
"github.com/ProtonMail/gluon/internal/response"
"github.com/ProtonMail/gluon/observability"
"github.com/ProtonMail/gluon/observability/metrics"
"github.com/ProtonMail/gluon/profiling"
)

Expand All @@ -24,6 +26,7 @@ func (s *Session) handleCreate(ctx context.Context, tag string, cmd *command.Cre
}

if err := s.state.Create(ctx, nameUTF8); err != nil {
observability.AddMessageRelatedMetric(ctx, metrics.GenerateFailedToCreateMailbox())
return err
}

Expand Down
3 changes: 3 additions & 0 deletions internal/session/handle_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/gluon/imap/command"
"github.com/ProtonMail/gluon/internal/response"
"github.com/ProtonMail/gluon/observability"
"github.com/ProtonMail/gluon/observability/metrics"
"github.com/ProtonMail/gluon/profiling"
)

Expand All @@ -25,6 +27,7 @@ func (s *Session) handleDelete(ctx context.Context, tag string, cmd *command.Del

selectedDeleted, err := s.state.Delete(ctx, nameUTF8)
if err != nil {
observability.AddOtherMetric(ctx, metrics.GenerateFailedToDeleteMailboxMetric())
return err
}

Expand Down
1 change: 1 addition & 0 deletions internal/session/handle_fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func (s *Session) handleFetch(ctx context.Context, tag string, cmd *command.Fetc
return response.Bad(tag).WithError(err), nil
} else if err != nil {
if shouldReportIMAPCommandError(err) {
// there's no events like this in sentry so far.
reporter.MessageWithContext(ctx,
"Failed to fetch messages",
reporter.Context{"error": err},
Expand Down
3 changes: 3 additions & 0 deletions internal/session/handle_move.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/ProtonMail/gluon/internal/contexts"
"github.com/ProtonMail/gluon/internal/response"
"github.com/ProtonMail/gluon/internal/state"
"github.com/ProtonMail/gluon/observability"
"github.com/ProtonMail/gluon/observability/metrics"
"github.com/ProtonMail/gluon/profiling"
)

Expand Down Expand Up @@ -35,6 +37,7 @@ func (s *Session) handleMove(ctx context.Context, tag string, cmd *command.Move,
} else if errors.Is(err, state.ErrNoSuchMailbox) {
return response.No(tag).WithError(err).WithItems(response.ItemTryCreate()), nil
} else if err != nil {
observability.AddMessageRelatedMetric(ctx, metrics.GenerateFailedToMoveMessagesFromMailboxMetric())
return nil, err
}

Expand Down
2 changes: 2 additions & 0 deletions internal/session/handle_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ func (s *Session) handleStore(ctx context.Context, tag string, cmd *command.Stor
return response.Bad(tag).WithError(err), nil
} else if err != nil {
if shouldReportIMAPCommandError(err) {
// A result of either a failed request, or the message does not exist on the remote.
// We've agreed to keep this in sentry.
reporter.MessageWithContext(ctx,
"Failed to store flags on messages",
reporter.Context{"error": err, "mailbox": mailbox.Name(), "action": cmd.Action.String()},
Expand Down
1 change: 1 addition & 0 deletions internal/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ func (s *Session) serve(ctx context.Context) error {
}

if s.errorCount += 1; s.errorCount >= maxSessionError {
// there's no events like this in sentry so far.
reporter.MessageWithContext(ctx,
"Too many errors in session, closing connection",
reporter.Context{"ID": s.imapID.String()},
Expand Down
17 changes: 3 additions & 14 deletions internal/state/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import (
"github.com/ProtonMail/gluon/db"
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/gluon/internal/ids"
"github.com/ProtonMail/gluon/reporter"
"github.com/ProtonMail/gluon/observability"
"github.com/ProtonMail/gluon/observability/metrics"
"github.com/ProtonMail/gluon/rfc822"
"github.com/bradenaw/juniper/xslices"
"golang.org/x/exp/slices"
Expand Down Expand Up @@ -111,20 +112,8 @@ func (state *State) actionCreateMessage(
return nil, 0, knownErr
}
if knownErr == nil {
// Try to collect the original message date.
var existingMessageDate time.Time
if existingMessage, msgErr := tx.GetMessageNoEdges(ctx, internalID); msgErr == nil {
existingMessageDate = existingMessage.Date
}

if cameFromDrafts {
reporter.ExceptionWithContext(ctx, "Append to drafts must not return an existing RemoteID", reporter.Context{
"remote-id": res.ID,
"new-date": res.Date,
"original-date": existingMessageDate,
"mailbox": mboxID.RemoteID,
})

observability.AddOtherMetric(ctx, metrics.GenerateAppendToDraftsMustNotReturnExistingRemoteID())
state.log.Errorf("Append to drafts must not return an existing RemoteID (Remote=%v, Internal=%v)", res.ID, knownInternalID)

return nil, 0, fmt.Errorf("append to drafts returned an existing remote ID")
Expand Down
1 change: 1 addition & 0 deletions internal/state/responders.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ func (u *targetedExists) handle(ctx context.Context, snap *snapshot, stateID Sta

if u.originStateSet && u.originStateID == stateID {
if err := snap.appendMessage(u.resp.messageID, u.resp.messageUID, flags); err != nil {
// No events reported so far.
reporter.ExceptionWithContext(ctx, "Failed to append message to snap via targetedExists", reporter.Context{"error": err})
return nil, nil, err
}
Expand Down
1 change: 1 addition & 0 deletions internal/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@ func (state *State) ApplyUpdate(ctx context.Context, update Update) error {
if err := state.user.GetDB().Write(ctx, func(ctx context.Context, tx db.Transaction) error {
return update.Apply(ctx, tx, state)
}); err != nil {
// Only one has been reported in Sentry so far...
reporter.MessageWithContext(ctx,
"Failed to apply state update",
reporter.Context{"error": err, "update": update.String()},
Expand Down
22 changes: 22 additions & 0 deletions observability/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package observability

import "context"

type observabilitySenderKeyType struct{}

var observabilitySenderKeyVal observabilitySenderKeyType

func NewContextWithObservabilitySender(ctx context.Context, sender Sender) context.Context {
return context.WithValue(ctx, observabilitySenderKeyVal, sender)
}

func getObservabilitySenderFromContext(ctx context.Context) (Sender, bool) {
v := ctx.Value(observabilitySenderKeyVal)
if v == nil {
return nil, false
}

sender, ok := v.(Sender)

return sender, ok
}
56 changes: 56 additions & 0 deletions observability/metrics/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package metrics

import "time"

const schemaName = "bridge_gluon_errors_total"
const schemaVersion = 1

func generateGluonErrorMetric(errorType string) map[string]interface{} {
return map[string]interface{}{
"Name": schemaName,
"Version": schemaVersion,
"Timestamp": time.Now().Unix(),
"Data": map[string]interface{}{
"Value": 1,
"Labels": map[string]string{
"errorType": errorType,
},
},
}
}

func GenerateFailedParseIMAPCommandMetric() map[string]interface{} {
return generateGluonErrorMetric("failedParseIMAPCommand")
}

func GenerateFailedToCreateMailbox() map[string]interface{} {
return generateGluonErrorMetric("failedCreateMailbox")
}

func GenerateFailedToDeleteMailboxMetric() map[string]interface{} {
return generateGluonErrorMetric("failedDeleteMailbox")
}

func GenerateFailedToCopyMessagesMetric() map[string]interface{} {
return generateGluonErrorMetric("failedCopyMessages")
}

func GenerateFailedToMoveMessagesFromMailboxMetric() map[string]interface{} {
return generateGluonErrorMetric("failedMoveMessagesFromMailbox")
}

func GenerateFailedToRemoveDeletedMessagesMetric() map[string]interface{} {
return generateGluonErrorMetric("failedRemoveDeletedMessages")
}

func GenerateFailedToCommitDatabaseTransactionMetric() map[string]interface{} {
return generateGluonErrorMetric("failedCommitDatabaseTransaction")
}

func GenerateAppendToDraftsMustNotReturnExistingRemoteID() map[string]interface{} {
return generateGluonErrorMetric("appendToDraftsReturnedExistingRemoteID")
}

func GenerateDatabaseMigrationFailed() map[string]interface{} {
return generateGluonErrorMetric("databaseMigrationFailed")
}
16 changes: 16 additions & 0 deletions observability/observability.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package observability

var imapErrorMetricType int
var messageErrorMetricType int
var otherErrorMetricType int

type Sender interface {
AddMetrics(metrics ...map[string]interface{})
AddDistinctMetrics(errType interface{}, metrics ...map[string]interface{})
}

func SetupMetricTypes(imapErrorType, messageErrorType, otherErrorType int) {
imapErrorMetricType = imapErrorType
messageErrorMetricType = messageErrorType
otherErrorMetricType = otherErrorType
}
Loading
Loading