feat(GODT-2666): feat(GODT-2667): introduce sessionID in bridge.

This commit is contained in:
Xavier Michelon
2023-06-06 15:44:33 +02:00
parent 1e9a77c7b2
commit ac00ef1b64
24 changed files with 550 additions and 138 deletions

View File

@ -40,12 +40,12 @@ func clearLogs(logDir string, maxLogs int, maxCrashes int) error {
// Remove old logs.
removeOldLogs(logDir, xslices.Filter(names, func(name string) bool {
return MatchLogName(name) && !MatchStackTraceName(name)
return MatchBridgeLogName(name) && !MatchStackTraceName(name)
}), maxLogs)
// Remove old stack traces.
removeOldLogs(logDir, xslices.Filter(names, func(name string) bool {
return MatchLogName(name) && MatchStackTraceName(name)
return MatchBridgeLogName(name) && MatchStackTraceName(name)
}), maxCrashes)
return nil
@ -58,7 +58,7 @@ func removeOldLogs(dir string, names []string, max int) {
// Sort by timestamp, oldest first.
slices.SortFunc(names, func(a, b string) bool {
return getLogTime(a) < getLogTime(b)
return getLogTime(a).Before(getLogTime(b))
})
for _, path := range xslices.Map(names[:len(names)-max], func(name string) string { return filepath.Join(dir, name) }) {

View File

@ -23,16 +23,15 @@ import (
"path/filepath"
"regexp"
"runtime/pprof"
"time"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/crash"
"github.com/sirupsen/logrus"
)
func DumpStackTrace(logsPath string) crash.RecoveryAction {
func DumpStackTrace(logsPath string, sessionID SessionID, appName string) crash.RecoveryAction {
return func(r interface{}) error {
file := filepath.Join(logsPath, getStackTraceName(constants.Version, constants.Revision))
file := filepath.Join(logsPath, getStackTraceName(sessionID, appName, constants.Version, constants.Tag))
f, err := os.OpenFile(filepath.Clean(file), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o600)
if err != nil {
@ -53,10 +52,10 @@ func DumpStackTrace(logsPath string) crash.RecoveryAction {
}
}
func getStackTraceName(version, revision string) string {
return fmt.Sprintf("v%v_%v_crash_%v.log", version, revision, time.Now().Unix())
func getStackTraceName(sessionID SessionID, appName, version, tag string) string {
return fmt.Sprintf("%v_000_%v_v%v_%v_crash.log", sessionID, appName, version, tag)
}
func MatchStackTraceName(name string) bool {
return regexp.MustCompile(`^v.*_crash_.*\.log$`).MatchString(name)
return regexp.MustCompile(`^\d{8}_\d{9}_000_.*_crash\.log$`).MatchString(name)
}

View File

@ -0,0 +1,32 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package logging
import (
"testing"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/stretchr/testify/require"
)
func TestMatchStackTraceName(t *testing.T) {
filename := getStackTraceName(NewSessionID(), constants.AppName, constants.Version, constants.Tag)
require.True(t, len(filename) > 0)
require.True(t, MatchStackTraceName(filename))
require.False(t, MatchStackTraceName("Invalid.log"))
}

View File

@ -19,26 +19,21 @@ package logging
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strconv"
"time"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/sirupsen/logrus"
)
const (
// MaxLogSize defines the maximum log size we should permit: 5 MB
//
// The Zendesk limit for an attachement is 50MB and this is what will
// The Zendesk limit for an attachment is 50MB and this is what will
// be allowed via the API. However, if that fails for some reason, the
// fallback is sending the report via email, which has a limit of 10mb
// total or 7MB per file. Since we can produce up to 6 logs, and we
// compress all the files (avarage compression - 80%), we need to have
// compress all the files (average compression - 80%), we need to have
// a limit of 30MB total before compression, hence 5MB per log file.
MaxLogSize = 5 * 1024 * 1024
@ -82,7 +77,7 @@ func (cs *coloredStdOutHook) Fire(entry *logrus.Entry) error {
return nil
}
func Init(logsPath, level string) error {
func Init(logsPath string, sessionID SessionID, appName, level string) error {
logrus.SetFormatter(&logrus.TextFormatter{
DisableColors: true,
FullTimestamp: true,
@ -91,13 +86,7 @@ func Init(logsPath, level string) error {
logrus.AddHook(newColoredStdOutHook())
rotator, err := NewRotator(MaxLogSize, func() (io.WriteCloser, error) {
if err := clearLogs(logsPath, MaxLogs, MaxLogs); err != nil {
return nil, err
}
return os.Create(filepath.Join(logsPath, getLogName(constants.Version, constants.Revision))) //nolint:gosec // G304
})
rotator, err := NewDefaultRotator(logsPath, sessionID, appName, MaxLogSize)
if err != nil {
return err
}
@ -137,34 +126,42 @@ func setLevel(level string) error {
return nil
}
func getLogName(version, revision string) string {
return fmt.Sprintf("v%v_%v_%v.log", version, revision, time.Now().Unix())
}
func getLogTime(filename string) time.Time {
re := regexp.MustCompile(`^(?P<sessionID>\d{8}_\d{9})_.*\.log$`)
func getLogTime(name string) int {
re := regexp.MustCompile(`^v.*_.*_(?P<timestamp>\d+).log$`)
match := re.FindStringSubmatch(name)
match := re.FindStringSubmatch(filename)
if len(match) == 0 {
logrus.Warn("Could not parse log name: ", name)
return 0
logrus.WithField("filename", filename).Warn("Could not parse log filename")
return time.Time{}
}
timestamp, err := strconv.Atoi(match[re.SubexpIndex("timestamp")])
if err != nil {
return 0
index := re.SubexpIndex("sessionID")
if index < 0 {
logrus.WithField("filename", filename).Warn("Could not parse log filename")
return time.Time{}
}
return timestamp
return SessionID(match[index]).toTime()
}
func MatchLogName(name string) bool {
return regexp.MustCompile(`^v.*\.log$`).MatchString(name)
// MatchBridgeLogName return true iff filename is a bridge log filename.
func MatchBridgeLogName(filename string) bool {
return matchLogName(filename, "bridge")
}
func MatchGUILogName(name string) bool {
return regexp.MustCompile(`^gui_v.*\.log$`).MatchString(name)
// MatchGUILogName return true iff filename is a bridge-gui log filename.
func MatchGUILogName(filename string) bool {
return matchLogName(filename, "gui")
}
// MatchLauncherLogName return true iff filename is a launcher log filename.
func MatchLauncherLogName(filename string) bool {
return matchLogName(filename, "launcher")
}
func matchLogName(logName, appName string) bool {
return regexp.MustCompile(`^\d{8}_\d{9}_\d{3}_` + appName + `.*\.log$`).MatchString(logName)
}
type logKey string
@ -180,12 +177,3 @@ func WithLogrusField(ctx context.Context, key string, value interface{}) context
fields[key] = value
return context.WithValue(ctx, logrusFields, fields)
}
func LogFromContext(ctx context.Context) *logrus.Entry {
fields, ok := ctx.Value(logrusFields).(logrus.Fields)
if !ok || fields == nil {
return logrus.WithField("ctx", "empty")
}
return logrus.WithFields(fields)
}

View File

@ -25,59 +25,95 @@ import (
"github.com/stretchr/testify/require"
)
// TestClearLogs tests that cearLogs removes only bridge old log files keeping last three of them.
func TestClearLogs(t *testing.T) {
dir := t.TempDir()
// Create some old log files.
require.NoError(t, os.WriteFile(filepath.Join(dir, "other.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.4.7_debe87f2f5_0000000001.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.4.8_debe87f2f5_0000000002.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.4.9_debe87f2f5_0000000003.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.0_debe87f2f5_0000000004.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.1_debe87f2f5_0000000005.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.2_debe87f2f5_0000000006.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.3_debe87f2f5_0000000007.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.4_debe87f2f5_0000000008.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.5_debe87f2f5_0000000009.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.6_debe87f2f5_0000000010.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.7_debe87f2f5_0000000011.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.8_debe87f2f5_0000000012.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.12_debe87f2f5_0000000013.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.9_debe87f2f5_0000000014.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.10_debe87f2f5_0000000015.log"), []byte("Hello"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.11_debe87f2f5_0000000016.log"), []byte("Hello"), 0o755))
// Clear the logs.
require.NoError(t, clearLogs(dir, 3, 0))
// We should only clear matching files, and keep the 3 most recent ones.
checkFileNames(t, dir, []string{
"other.log",
"v2.5.9_debe87f2f5_0000000014.log",
"v2.5.10_debe87f2f5_0000000015.log",
"v2.5.11_debe87f2f5_0000000016.log",
})
}
func checkFileNames(t *testing.T, dir string, expectedFileNames []string) {
require.ElementsMatch(t, expectedFileNames, getFileNames(t, dir))
}
func getFileNames(t *testing.T, dir string) []string {
files, err := os.ReadDir(dir)
func TestGetLogTime(t *testing.T) {
sessionID := NewSessionID()
fp := defaultFileProvider(os.TempDir(), sessionID, "bridge-test")
wc, err := fp(0)
require.NoError(t, err)
file, ok := wc.(*os.File)
require.True(t, ok)
fileNames := []string{}
for _, file := range files {
fileNames = append(fileNames, file.Name())
if file.IsDir() {
subDir := filepath.Join(dir, file.Name())
subFileNames := getFileNames(t, subDir)
for _, subFileName := range subFileNames {
fileNames = append(fileNames, file.Name()+"/"+subFileName)
}
}
}
return fileNames
sessionIDTime := sessionID.toTime()
require.False(t, sessionIDTime.IsZero())
logTime := getLogTime(filepath.Base(file.Name()))
require.False(t, logTime.IsZero())
require.Equal(t, sessionIDTime, logTime)
}
func TestMatchLogName(t *testing.T) {
bridgeLog := "20230602_094633102_000_bridge_v3.0.99+git_5b650b1be3.log"
crashLog := "20230602_094633102_000_bridge_v3.0.99+git_5b650b1be3_crash.log"
guiLog := "20230602_094633102_000_gui_v3.0.99+git_5b650b1be3.log"
launcherLog := "20230602_094633102_000_launcher_v3.0.99+git_5b650b1be3.log"
require.True(t, MatchBridgeLogName(bridgeLog))
require.False(t, MatchGUILogName(bridgeLog))
require.False(t, MatchLauncherLogName(bridgeLog))
require.True(t, MatchBridgeLogName(crashLog))
require.False(t, MatchGUILogName(crashLog))
require.False(t, MatchLauncherLogName(crashLog))
require.False(t, MatchBridgeLogName(guiLog))
require.True(t, MatchGUILogName(guiLog))
require.False(t, MatchLauncherLogName(guiLog))
require.False(t, MatchBridgeLogName(launcherLog))
require.False(t, MatchGUILogName(launcherLog))
require.True(t, MatchLauncherLogName(launcherLog))
}
// The test below is temporarily disabled, and will be restored when implementing new retention policy GODT-2668
// TestClearLogs tests that clearLogs removes only bridge old log files keeping last three of them.
// func TestClearLogs(t *testing.T) {
// dir := t.TempDir()
//
// // Create some old log files.
// require.NoError(t, os.WriteFile(filepath.Join(dir, "other.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.4.7_debe87f2f5_0000000001.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.4.8_debe87f2f5_0000000002.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.4.9_debe87f2f5_0000000003.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.0_debe87f2f5_0000000004.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.1_debe87f2f5_0000000005.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.2_debe87f2f5_0000000006.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.3_debe87f2f5_0000000007.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.4_debe87f2f5_0000000008.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.5_debe87f2f5_0000000009.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.6_debe87f2f5_0000000010.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.7_debe87f2f5_0000000011.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.8_debe87f2f5_0000000012.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.12_debe87f2f5_0000000013.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.9_debe87f2f5_0000000014.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.10_debe87f2f5_0000000015.log"), []byte("Hello"), 0o755))
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.11_debe87f2f5_0000000016.log"), []byte("Hello"), 0o755))
//
// // Clear the logs.
// require.NoError(t, clearLogs(dir, 3, 0))
//
// // We should only clear matching files, and keep the 3 most recent ones.
// checkFileNames(t, dir, []string{
// "other.log",
// "v2.5.9_debe87f2f5_0000000014.log",
// "v2.5.10_debe87f2f5_0000000015.log",
// "v2.5.11_debe87f2f5_0000000016.log",
// })
// }
//
// func checkFileNames(t *testing.T, dir string, expectedFileNames []string) {
// require.ElementsMatch(t, expectedFileNames, getFileNames(t, dir))
// }
//
// func getFileNames(t *testing.T, dir string) []string {
// files, err := os.ReadDir(dir)
// require.NoError(t, err)
//
// fileNames := []string{}
// for _, file := range files {
// fileNames = append(fileNames, file.Name())
// if file.IsDir() {
// subDir := filepath.Join(dir, file.Name())
// subFileNames := getFileNames(t, subDir)
// for _, subFileName := range subFileNames {
// fileNames = append(fileNames, file.Name()+"/"+subFileName)
// }
// }
// }
// return fileNames
// }

View File

@ -17,16 +17,36 @@
package logging
import "io"
import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
)
type Rotator struct {
getFile FileProvider
wc io.WriteCloser
size int
maxSize int
getFile FileProvider
wc io.WriteCloser
size int
maxSize int
nextIndex int
}
type FileProvider func() (io.WriteCloser, error)
type FileProvider func(index int) (io.WriteCloser, error)
func defaultFileProvider(logsPath string, sessionID SessionID, appName string) FileProvider {
return func(index int) (io.WriteCloser, error) {
if err := clearLogs(logsPath, MaxLogs, MaxLogs); err != nil {
return nil, err
}
return os.Create(filepath.Join(logsPath, //nolint:gosec // G304
fmt.Sprintf("%v_%03d_%v_v%v_%v.log", sessionID, index, appName, constants.Version, constants.Tag),
))
}
}
func NewRotator(maxSize int, getFile FileProvider) (*Rotator, error) {
r := &Rotator{
@ -41,6 +61,10 @@ func NewRotator(maxSize int, getFile FileProvider) (*Rotator, error) {
return r, nil
}
func NewDefaultRotator(logsPath string, sessionID SessionID, appName string, maxSize int) (*Rotator, error) {
return NewRotator(maxSize, defaultFileProvider(logsPath, sessionID, appName))
}
func (r *Rotator) Write(p []byte) (int, error) {
if r.size+len(p) > r.maxSize {
if err := r.rotate(); err != nil {
@ -63,11 +87,12 @@ func (r *Rotator) rotate() error {
_ = r.wc.Close()
}
wc, err := r.getFile()
wc, err := r.getFile(r.nextIndex)
if err != nil {
return err
}
r.nextIndex++
r.wc = wc
r.size = 0

View File

@ -19,8 +19,10 @@ package logging
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@ -38,7 +40,7 @@ func (c *WriteCloser) Close() error {
func TestRotator(t *testing.T) {
n := 0
getFile := func() (io.WriteCloser, error) {
getFile := func(_ int) (io.WriteCloser, error) {
n++
return &WriteCloser{}, nil
}
@ -75,11 +77,70 @@ func TestRotator(t *testing.T) {
assert.Equal(t, 4, n)
}
func countFilesMatching(pattern string) int {
files, err := filepath.Glob(pattern)
if err != nil {
return -1
}
return len(files)
}
func cleanupLogs(t *testing.T, sessionID SessionID) {
paths, err := filepath.Glob(filepath.Join(os.TempDir(), string(sessionID)+"*.log"))
require.NoError(t, err)
for _, path := range paths {
require.NoError(t, os.Remove(path))
}
}
func TestDefaultRotator(t *testing.T) {
fiveBytes := []byte("00000")
tmpDir := os.TempDir()
sessionID := NewSessionID()
basePath := filepath.Join(tmpDir, string(sessionID))
r, err := NewDefaultRotator(tmpDir, sessionID, "bridge", 10)
require.NoError(t, err)
require.Equal(t, 1, countFilesMatching(basePath+"_000_*.log"))
require.Equal(t, 1, countFilesMatching(basePath+"*.log"))
_, err = r.Write(fiveBytes)
require.NoError(t, err)
require.Equal(t, 1, countFilesMatching(basePath+"*.log"))
_, err = r.Write(fiveBytes)
require.NoError(t, err)
require.Equal(t, 1, countFilesMatching(basePath+"*.log"))
_, err = r.Write(fiveBytes)
require.NoError(t, err)
require.Equal(t, 2, countFilesMatching(basePath+"*.log"))
require.Equal(t, 1, countFilesMatching(basePath+"_001_*.log"))
for i := 0; i < 4; i++ {
_, err = r.Write(fiveBytes)
require.NoError(t, err)
}
require.NoError(t, r.wc.Close())
// total written: 35 bytes, i.e. 4 log files
logFileCount := countFilesMatching(basePath + "*.log")
require.Equal(t, 4, logFileCount)
for i := 0; i < logFileCount; i++ {
require.Equal(t, 1, countFilesMatching(basePath+fmt.Sprintf("_%03d_*.log", i)))
}
cleanupLogs(t, sessionID)
}
func BenchmarkRotate(b *testing.B) {
benchRotate(b, MaxLogSize, getTestFile(b, b.TempDir(), MaxLogSize-1))
}
func benchRotate(b *testing.B, logSize int, getFile func() (io.WriteCloser, error)) {
func benchRotate(b *testing.B, logSize int, getFile func(index int) (io.WriteCloser, error)) {
r, err := NewRotator(logSize, getFile)
require.NoError(b, err)
@ -92,8 +153,8 @@ func benchRotate(b *testing.B, logSize int, getFile func() (io.WriteCloser, erro
}
}
func getTestFile(b *testing.B, dir string, length int) func() (io.WriteCloser, error) {
return func() (io.WriteCloser, error) {
func getTestFile(b *testing.B, dir string, length int) func(int) (io.WriteCloser, error) {
return func(index int) (io.WriteCloser, error) {
b.StopTimer()
defer b.StartTimer()

View File

@ -0,0 +1,64 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
package logging
import (
"fmt"
"strconv"
"time"
)
type SessionID string
const (
timeFormat = "20060102_150405" // time format in Go does not support milliseconds without dot, so we'll process it manually.
)
// NewSessionID creates a sessionID based on the current time.
func NewSessionID() SessionID {
now := time.Now()
return SessionID(now.Format(timeFormat) + fmt.Sprintf("%03d", now.Nanosecond()/1000000))
}
// NewSessionIDFromString Return a new sessionID from string. If the str is empty a new time based sessionID is returned, otherwise the string
// is used as the sessionID.
func NewSessionIDFromString(str string) SessionID {
if (len(str)) > 0 {
return SessionID(str)
}
return NewSessionID()
}
// toTime converts a sessionID to a date/Time, considering the time zone is local.
func (s SessionID) toTime() time.Time {
if len(s) < 3 {
return time.Time{}
}
t, err := time.ParseInLocation(timeFormat, string(s)[:len(s)-3], time.Local)
if err != nil {
return time.Time{}
}
var ms int
if ms, err = strconv.Atoi(string(s)[len(s)-3:]); err != nil {
return time.Time{}
}
return t.Add(time.Duration(ms) * time.Millisecond)
}

View File

@ -0,0 +1,38 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
package logging
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestSessionID(t *testing.T) {
now := time.Now()
sessionID := NewSessionID()
sessionTime := sessionID.toTime()
require.False(t, sessionTime.IsZero())
require.WithinRange(t, sessionTime, now.Add(-1*time.Millisecond), now.Add(1*time.Millisecond))
fromString := NewSessionIDFromString("")
require.True(t, len(fromString) > 0)
fromString = NewSessionIDFromString(string(sessionID))
require.True(t, len(fromString) > 0)
require.Equal(t, sessionID, fromString)
}