Beispiel #1
0
        protected override async Task <JobResult> ProcessQueueEntryAsync(QueueEntryContext <EventNotificationWorkItem> context)
        {
            var wi = context.QueueEntry.Value;
            var ev = await _eventRepository.GetByIdAsync(wi.EventId).AnyContext();

            if (ev == null || ev.IsDeleted)
            {
                return(JobResult.SuccessWithMessage($"Could not load event: {wi.EventId}"));
            }

            bool shouldLog = ev.ProjectId != Settings.Current.InternalProjectId;
            int  sent      = 0;

            _logger.Trace().Message(() => $"Process notification: project={ev.ProjectId} event={ev.Id} stack={ev.StackId}").WriteIf(shouldLog);

            var project = await _projectRepository.GetByIdAsync(ev.ProjectId, o => o.Cache()).AnyContext();

            if (project == null)
            {
                return(JobResult.SuccessWithMessage($"Could not load project: {ev.ProjectId}."));
            }
            _logger.Trace().Message(() => $"Loaded project: name={project.Name}").WriteIf(shouldLog);

            // after the first 2 occurrences, don't send a notification for the same stack more then once every 30 minutes
            var lastTimeSentUtc = await _cache.GetAsync <DateTime>(String.Concat("notify:stack-throttle:", ev.StackId), DateTime.MinValue).AnyContext();

            if (wi.TotalOccurrences > 2 &&
                !wi.IsRegression &&
                lastTimeSentUtc != DateTime.MinValue &&
                lastTimeSentUtc > SystemClock.UtcNow.AddMinutes(-30))
            {
                _logger.Info().Message("Skipping message because of stack throttling: last sent={0} occurrences={1}", lastTimeSentUtc, wi.TotalOccurrences).WriteIf(shouldLog);
                return(JobResult.Success);
            }

            if (context.CancellationToken.IsCancellationRequested)
            {
                return(JobResult.Cancelled);
            }

            // don't send more than 10 notifications for a given project every 30 minutes
            var    projectTimeWindow = TimeSpan.FromMinutes(30);
            string cacheKey          = String.Concat("notify:project-throttle:", ev.ProjectId, "-", SystemClock.UtcNow.Floor(projectTimeWindow).Ticks);
            double notificationCount = await _cache.IncrementAsync(cacheKey, 1, projectTimeWindow).AnyContext();

            if (notificationCount > 10 && !wi.IsRegression)
            {
                _logger.Info().Project(ev.ProjectId).Message("Skipping message because of project throttling: count={0}", notificationCount).WriteIf(shouldLog);
                return(JobResult.Success);
            }

            foreach (var kv in project.NotificationSettings)
            {
                var settings = kv.Value;
                _logger.Trace().Message(() => $"Processing notification: {kv.Key}").WriteIf(shouldLog);

                bool isCritical                = ev.IsCritical();
                bool shouldReportNewError      = settings.ReportNewErrors && wi.IsNew && ev.IsError();
                bool shouldReportCriticalError = settings.ReportCriticalErrors && isCritical && ev.IsError();
                bool shouldReportRegression    = settings.ReportEventRegressions && wi.IsRegression;
                bool shouldReportNewEvent      = settings.ReportNewEvents && wi.IsNew;
                bool shouldReportCriticalEvent = settings.ReportCriticalEvents && isCritical;
                bool shouldReport              = shouldReportNewError || shouldReportCriticalError || shouldReportRegression || shouldReportNewEvent || shouldReportCriticalEvent;

                _logger.Trace().Message(() => $"Settings: new error={settings.ReportNewErrors} critical error={settings.ReportCriticalErrors} regression={settings.ReportEventRegressions} new={settings.ReportNewEvents} critical={settings.ReportCriticalEvents}").WriteIf(shouldLog);
                _logger.Trace().Message(() => $"Should process: new error={shouldReportNewError} critical error={shouldReportCriticalError} regression={shouldReportRegression} new={shouldReportNewEvent} critical={shouldReportCriticalEvent}").WriteIf(shouldLog);

                var request = ev.GetRequestInfo();
                // check for known bots if the user has elected to not report them
                if (shouldReport && !String.IsNullOrEmpty(request?.UserAgent))
                {
                    var botPatterns = project.Configuration.Settings.ContainsKey(SettingsDictionary.KnownKeys.UserAgentBotPatterns)
                        ? project.Configuration.Settings.GetStringCollection(SettingsDictionary.KnownKeys.UserAgentBotPatterns).ToList()
                        : new List <string>();

                    var info = await _parser.ParseAsync(request.UserAgent, ev.ProjectId).AnyContext();

                    if (info != null && info.Device.IsSpider || request.UserAgent.AnyWildcardMatches(botPatterns))
                    {
                        shouldReport = false;
                        _logger.Info().Message("Skipping because event is from a bot \"{0}\".", request.UserAgent).WriteIf(shouldLog);
                    }
                }

                if (!shouldReport)
                {
                    continue;
                }

                bool processed;
                switch (kv.Key)
                {
                case Project.NotificationIntegrations.Slack:
                    processed = await _slackService.SendEventNoticeAsync(ev, project, wi.IsNew, wi.IsRegression, wi.TotalOccurrences).AnyContext();

                    break;

                default:
                    processed = await SendEmailNotificationAsync(kv.Key, project, ev, wi, shouldLog).AnyContext();

                    break;
                }

                _logger.Trace().Message(() => $"Finished processing notification: {kv.Key}").WriteIf(shouldLog);
                if (processed)
                {
                    sent++;
                }
            }

            // if we sent any notifications, mark the last time a notification for this stack was sent.
            if (sent > 0)
            {
                await _cache.SetAsync(String.Concat("notify:stack-throttle:", ev.StackId), SystemClock.UtcNow, SystemClock.UtcNow.AddMinutes(15)).AnyContext();

                _logger.Info().Message("Notifications sent: event={0} stack={1} count={2}", ev.Id, ev.StackId, sent).WriteIf(shouldLog);
            }

            return(JobResult.Success);
        }
Beispiel #2
0
 public Task <T> GetAsync <T>(object key)
 {
     return(_clientExt.GetAsync <T>(RedisUtils.KeyBuilder <T>(key)));
 }
Beispiel #3
0
        public static async Task <T> GetAsync <T>(this ICacheClient client, string key, T defaultValue)
        {
            var cacheValue = await client.GetAsync <T>(key).AnyContext();

            return(cacheValue.HasValue ? cacheValue.Value : defaultValue);
        }
 /** <inheritDoc /> */
 public TV Get(TK key)
 {
     return(_cache.GetAsync(key).GetResult());
 }
Beispiel #5
0
 public static new async Task <Player> GetAsync([NotNull] ICacheClient redis, DbRef playerRef, CancellationToken cancellationToken) => (await CacheManager.LookupOrRetrieveAsync(playerRef, redis, async(d, token) => await redis.GetAsync <Player>($"mudpie::player:{d}"), cancellationToken))?.DataObject;
Beispiel #6
0
        public async Task <ILock> AcquireAsync(string resource, TimeSpan?timeUntilExpires = null, bool releaseOnDispose = true, CancellationToken cancellationToken = default)
        {
            bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace);

            if (isTraceLogLevelEnabled)
            {
                _logger.LogTrace("AcquireLockAsync: {Resource}", resource);
            }

            bool allowLock = false;
            byte errors    = 0;

            string lockId = Guid.NewGuid().ToString("N");
            var    sw     = Stopwatch.StartNew();

            do
            {
                string cacheKey = GetCacheKey(resource, SystemClock.UtcNow);

                try {
                    if (isTraceLogLevelEnabled)
                    {
                        _logger.LogTrace("Current time: {CurrentTime} throttle: {ThrottlingPeriod} key: {Key}", SystemClock.UtcNow.ToString("mm:ss.fff"), SystemClock.UtcNow.Floor(_throttlingPeriod).ToString("mm:ss.fff"), cacheKey);
                    }
                    var hitCount = await _cacheClient.GetAsync <long?>(cacheKey, 0).AnyContext();

                    if (isTraceLogLevelEnabled)
                    {
                        _logger.LogTrace("Current hit count: {HitCount} max: {MaxHitsPerPeriod}", hitCount, _maxHitsPerPeriod);
                    }
                    if (hitCount <= _maxHitsPerPeriod - 1)
                    {
                        hitCount = await _cacheClient.IncrementAsync(cacheKey, 1, SystemClock.UtcNow.Ceiling(_throttlingPeriod)).AnyContext();

                        // make sure someone didn't beat us to it.
                        if (hitCount <= _maxHitsPerPeriod)
                        {
                            allowLock = true;
                            break;
                        }

                        if (isTraceLogLevelEnabled)
                        {
                            _logger.LogTrace("Max hits exceeded after increment for {Resource}.", resource);
                        }
                    }
                    else if (isTraceLogLevelEnabled)
                    {
                        _logger.LogTrace("Max hits exceeded for {Resource}.", resource);
                    }

                    if (cancellationToken.IsCancellationRequested)
                    {
                        break;
                    }

                    var sleepUntil = SystemClock.UtcNow.Ceiling(_throttlingPeriod).AddMilliseconds(1);
                    if (sleepUntil > SystemClock.UtcNow)
                    {
                        if (isTraceLogLevelEnabled)
                        {
                            _logger.LogTrace("Sleeping until key expires: {SleepUntil}", sleepUntil - SystemClock.UtcNow);
                        }
                        await SystemClock.SleepAsync(sleepUntil - SystemClock.UtcNow, cancellationToken).AnyContext();
                    }
                    else
                    {
                        if (isTraceLogLevelEnabled)
                        {
                            _logger.LogTrace("Default sleep");
                        }
                        await SystemClock.SleepAsync(50, cancellationToken).AnyContext();
                    }
                } catch (OperationCanceledException) {
                    return(null);
                } catch (Exception ex) {
                    _logger.LogError(ex, "Error acquiring throttled lock: name={Resource} message={Message}", resource, ex.Message);
                    errors++;
                    if (errors >= 3)
                    {
                        break;
                    }

                    await SystemClock.SleepSafeAsync(50, cancellationToken).AnyContext();
                }
            } while (!cancellationToken.IsCancellationRequested);

            if (cancellationToken.IsCancellationRequested && isTraceLogLevelEnabled)
            {
                _logger.LogTrace("Cancellation requested");
            }

            if (!allowLock)
            {
                return(null);
            }

            if (isTraceLogLevelEnabled)
            {
                _logger.LogTrace("Allowing lock: {Resource}", resource);
            }

            sw.Stop();
            return(new DisposableLock(resource, lockId, sw.Elapsed, this, _logger, releaseOnDispose));
        }
Beispiel #7
0
        public async Task <ILock> AcquireAsync(string name, TimeSpan?lockTimeout = null, CancellationToken cancellationToken = default(CancellationToken))
        {
            _logger.Trace("AcquireLockAsync: {name}", name);

            bool allowLock = false;
            byte errors    = 0;

            do
            {
                string cacheKey = GetCacheKey(name, SystemClock.UtcNow);

                try {
                    _logger.Trace("Current time: {0} throttle: {1} key: {2}", SystemClock.UtcNow.ToString("mm:ss.fff"), SystemClock.UtcNow.Floor(_throttlingPeriod).ToString("mm:ss.fff"), cacheKey);
                    var hitCount = await _cacheClient.GetAsync <long?>(cacheKey, 0).AnyContext();

                    _logger.Trace("Current hit count: {0} max: {1}", hitCount, _maxHitsPerPeriod);
                    if (hitCount <= _maxHitsPerPeriod - 1)
                    {
                        hitCount = await _cacheClient.IncrementAsync(cacheKey, 1, SystemClock.UtcNow.Ceiling(_throttlingPeriod)).AnyContext();

                        // make sure someone didn't beat us to it.
                        if (hitCount <= _maxHitsPerPeriod)
                        {
                            allowLock = true;
                            break;
                        }

                        _logger.Trace("Max hits exceeded after increment for {0}.", name);
                    }
                    else
                    {
                        _logger.Trace("Max hits exceeded for {0}.", name);
                    }

                    if (cancellationToken.IsCancellationRequested)
                    {
                        _logger.Trace("Cancellation Requested.");
                        break;
                    }

                    var sleepUntil = SystemClock.UtcNow.Ceiling(_throttlingPeriod).AddMilliseconds(1);
                    if (sleepUntil > SystemClock.UtcNow)
                    {
                        _logger.Trace("Sleeping until key expires: {0}", sleepUntil - SystemClock.UtcNow);
                        await SystemClock.SleepAsync(sleepUntil - SystemClock.UtcNow, cancellationToken).AnyContext();
                    }
                    else
                    {
                        _logger.Trace("Default sleep.");
                        await SystemClock.SleepAsync(50, cancellationToken).AnyContext();
                    }
                } catch (OperationCanceledException) {
                    return(null);
                } catch (Exception ex) {
                    _logger.Error(ex, "Error acquiring throttled lock: name={0} message={1}", name, ex.Message);
                    errors++;
                    if (errors >= 3)
                    {
                        break;
                    }

                    await SystemClock.SleepAsync(50, cancellationToken).AnyContext();
                }
            } while (!cancellationToken.IsCancellationRequested);

            if (cancellationToken.IsCancellationRequested)
            {
                _logger.Trace("Cancellation requested.");
            }

            if (!allowLock)
            {
                return(null);
            }

            _logger.Trace("Allowing lock: {0}", name);
            return(new DisposableLock(name, this, _logger));
        }
Beispiel #8
0
        public async Task CheckAsync(IDiscordMessage e)
        {
            if (e.Author.IsBot)
            {
                return;
            }

            if (experienceLock.CurrentCount == 0)
            {
                return;
            }

            try
            {
                using var scope = app.Services.CreateScope();
                var services = scope.ServiceProvider;

                if (e is IDiscordGuildMessage guildMessage)
                {
                    var key = GetContextKey(guildMessage.GuildId, e.Author.Id);
                    if (lastTimeExpGranted
                        .GetOrAdd(e.Author.Id, DateTime.Now)
                        .AddMinutes(1) < DateTime.Now)
                    {
                        var bonusExp  = MikiRandom.Next(1, 4);
                        var expObject = new ExperienceAdded
                        {
                            UserId     = (long)e.Author.Id,
                            GuildId    = (long)guildMessage.GuildId,
                            Experience = bonusExp,
                            Name       = e.Author.Username,
                        };

                        int currentLocalExp = await cache.GetAsync <int>(key);

                        if (currentLocalExp == 0)
                        {
                            var dbContext  = services.GetService <DbContext>();
                            var expProfile = await GetOrCreateExperienceProfileAsync(
                                dbContext, e.Author as IDiscordGuildUser);

                            await UpdateCacheExperienceAsync(expObject);

                            currentLocalExp = expProfile.Experience;
                        }

                        currentLocalExp += bonusExp;
                        experienceQueue.AddOrUpdate(e.Author.Id, expObject, (_, experience) =>
                        {
                            experience.Experience += expObject.Experience;
                            return(experience);
                        });

                        int level = User.CalculateLevel(currentLocalExp);
                        if (User.CalculateLevel(currentLocalExp - bonusExp) != level)
                        {
                            await LevelUpLocalAsync(e, level)
                            .ConfigureAwait(false);
                        }

                        lastTimeExpGranted.AddOrUpdate(
                            e.Author.Id, DateTime.Now, (x, d) => DateTime.Now);
                        await UpdateCacheExperienceAsync(expObject);
                    }
                }

                if (DateTime.Now >= this.lastDbSync + new TimeSpan(0, 1, 0))
                {
                    try
                    {
                        await experienceLock.WaitAsync();

                        Log.Message($"Applying Experience for {this.experienceQueue.Count} users");
                        this.lastDbSync = DateTime.Now;
                        var context = services.GetService <DbContext>();

                        await UpdateGlobalDatabaseAsync(context);
                        await UpdateLocalDatabaseAsync(context);
                        await UpdateGuildDatabaseAsync(context);

                        await context.SaveChangesAsync();
                    }
                    catch (Exception ex)
                    {
                        Log.Error(ex.Message + "\n" + ex.StackTrace);
                        sentryClient.CaptureException(ex);
                    }
                    finally
                    {
                        this.experienceQueue.Clear();
                        experienceLock.Release();
                    }
                }
            }
            catch (Exception ex)
            {
                Log.Error(ex);
                sentryClient.CaptureException(ex);
            }
        }
Beispiel #9
0
 public void GetAsync()
 {
     _cache.GetAsync(1).Wait();
 }
Beispiel #10
0
 protected T Get(long id) => _cache.GetAsync <T>(ToKey(id)).GetAwaiter().GetResult().Value;
Beispiel #11
0
 public Task <Client> GetAsync(string key)
 {
     return(cacheClient.GetAsync <Client>(key));
 }
Beispiel #12
0
        async Task Test <T>(ICacheClient client, T value, T value2)
        {
            string itemKey = "test";

            await client.UpsertAsync(itemKey, value);

            Assert.True(await client.ExistsAsync(itemKey));

            T i = await client.GetAsync <T>(itemKey);

            Assert.Equal(value, i);

            await client.RemoveAsync(itemKey);

            if (client is IExtendedCacheClient ex)
            {
                string hashKey = "test:hash";

                Assert.False(await client.ExistsAsync(hashKey));
                Assert.False(await ex.HashExistsAsync(hashKey, itemKey));

                Assert.DoesNotContain(await ex.HashKeysAsync(hashKey), x => x == itemKey);
                Assert.DoesNotContain(await ex.HashValuesAsync <T>(hashKey), x => x.Equals(value));
                Assert.DoesNotContain(await ex.HashGetAllAsync <T>(hashKey), x => x.Key == itemKey && x.Value.Equals(value));

                Assert.NotEqual(value, await ex.HashGetAsync <T>(hashKey, itemKey));
                Assert.NotEqual(1, await ex.HashLengthAsync(hashKey));

                await ex.HashUpsertAsync(hashKey, itemKey, value);

                Assert.True(await client.ExistsAsync(hashKey));
                Assert.True(await ex.HashExistsAsync(hashKey, itemKey));

                Assert.Contains(await ex.HashKeysAsync(hashKey), x => x == itemKey);
                Assert.Contains(await ex.HashValuesAsync <T>(hashKey), x => x.Equals(value));
                Assert.Contains(await ex.HashGetAllAsync <T>(hashKey), x => x.Key == itemKey && x.Value.Equals(value));

                Assert.Equal(value, await ex.HashGetAsync <T>(hashKey, itemKey));
                Assert.Equal(1, await ex.HashLengthAsync(hashKey));

                await ex.HashUpsertAsync(hashKey, itemKey, value2);

                Assert.True(await client.ExistsAsync(hashKey));
                Assert.True(await ex.HashExistsAsync(hashKey, itemKey));

                var keys = await ex.HashKeysAsync(hashKey);

                Assert.Contains(keys, x => x == itemKey);

                var values = await ex.HashValuesAsync <T>(hashKey);

                Assert.Contains(values, x => x.Equals(value2));

                Assert.Contains(await ex.HashGetAllAsync <T>(hashKey), x => x.Key == itemKey && x.Value.Equals(value2));

                Assert.Equal(value2, await ex.HashGetAsync <T>(hashKey, itemKey));
                Assert.Equal(1, await ex.HashLengthAsync(hashKey));

                await ex.HashDeleteAsync(hashKey, itemKey);

                Assert.Equal(default, await ex.HashGetAsync <T>(hashKey, itemKey));
Beispiel #13
0
        public async Task <IReadOnlyCollection <string> > GetAllowedUserPermissionAsync(ulong userId, ulong?guildId)
        {
            var cacheKey = CacheKey.GetUserPermissionCacheKey(guildId, userId);
            var cache    = await _cache.GetAsync <string[]>(cacheKey);

            if (cache.HasValue)
            {
                return(cache.Value);
            }

            var stopwatch = Stopwatch.StartNew();

            using var scope = _provider.CreateScope();
            var roleRepo = scope.ServiceProvider.GetService <IRolePermissionRepository>();
            var userRepo = scope.ServiceProvider.GetService <IUserPermissionRepository>();

            IDiscordUser user;
            IReadOnlyList <PermissionGroup> userPermissions;
            IReadOnlyList <IDiscordRole>    userRoles;

            if (guildId.HasValue)
            {
                var guildUser = await _client.GetGuildUserAsync(userId, guildId.Value);

                var guild = await _client.GetGuildAsync(guildId.Value);

                var roles = await guild.GetRolesAsync();

                userRoles = roles.Where(r => guildUser.RoleIds.Contains(r.Id)).ToList();

                user            = guildUser;
                userPermissions = await GetPermissionGroups(guildUser, guild);
            }
            else
            {
                userRoles = new IDiscordRole[0];
                user      = await _client.GetUserAsync(userId);

                userPermissions = GetDefaultPermissions(user);
            }

            var permissions = new Dictionary <string, bool>();

            void AddPermissions(IEnumerable <IPermission> newPermissions)
            {
                foreach (var permission in newPermissions)
                {
                    if (!permissions.ContainsKey(permission.Name))
                    {
                        permissions[permission.Name] = permission.Granted;
                    }
                }
            }

            // Add the default permissions.
            if (userPermissions.Contains(PermissionGroup.Developer) &&
                _defaultPermissions.TryGetValue(PermissionGroup.Developer, out var newPermissions))
            {
                AddPermissions(newPermissions);
            }

            if (userPermissions.Contains(PermissionGroup.Administrator) &&
                _defaultPermissions.TryGetValue(PermissionGroup.Administrator, out newPermissions))
            {
                AddPermissions(newPermissions);
            }

            // Add the user permissions from the database.
            if (userRepo != null && guildId.HasValue)
            {
                AddPermissions(await userRepo.GetAllAsync(guildId.Value, userId));
            }

            // Add the role permissions from the database.
            if (roleRepo != null && guildId.HasValue)
            {
                foreach (var role in userRoles.OrderBy(r => r.Id))
                {
                    AddPermissions(await roleRepo.GetAllAsync(guildId.Value, role.Id));
                }
            }

            // Add the default permissions.
            if (userPermissions.Contains(PermissionGroup.Moderator) &&
                _defaultPermissions.TryGetValue(PermissionGroup.Moderator, out newPermissions))
            {
                AddPermissions(newPermissions);
            }

            // Add the everyone role
            if (roleRepo != null && guildId.HasValue)
            {
                AddPermissions(await roleRepo.GetAllAsync(guildId.Value, guildId.Value));
            }

            if (_defaultPermissions.TryGetValue(PermissionGroup.User, out newPermissions))
            {
                AddPermissions(newPermissions);
            }

            // Store into the cache.
            var allowedPermissions = permissions.Where(kv => kv.Value).Select(kv => kv.Key).ToArray();

            _logger.LogDebug("Loaded the permissions for {Username} in {Duration:0.00} ms.", user.Username, stopwatch.Elapsed.TotalMilliseconds);

            await _cache.SetAsync(cacheKey, allowedPermissions, CacheTime);

            return(allowedPermissions);
        }
Beispiel #14
0
        protected override async Task <JobResult> ProcessQueueEntryAsync(QueueEntryContext <EventNotificationWorkItem> context)
        {
            var eventModel = await _eventRepository.GetByIdAsync(context.QueueEntry.Value.EventId).AnyContext();

            if (eventModel == null)
            {
                return(JobResult.FailedWithMessage($"Could not load event: {context.QueueEntry.Value.EventId}"));
            }

            var  eventNotification = new EventNotification(context.QueueEntry.Value, eventModel);
            bool shouldLog         = eventNotification.Event.ProjectId != Settings.Current.InternalProjectId;
            int  emailsSent        = 0;

            _logger.Trace().Message(() => $"Process notification: project={eventNotification.Event.ProjectId} event={eventNotification.Event.Id} stack={eventNotification.Event.StackId}").WriteIf(shouldLog);

            var project = await _projectRepository.GetByIdAsync(eventNotification.Event.ProjectId, true).AnyContext();

            if (project == null)
            {
                return(JobResult.FailedWithMessage($"Could not load project: {eventNotification.Event.ProjectId}."));
            }
            _logger.Trace().Message(() => $"Loaded project: name={project.Name}").WriteIf(shouldLog);

            var organization = await _organizationRepository.GetByIdAsync(project.OrganizationId, true).AnyContext();

            if (organization == null)
            {
                return(JobResult.FailedWithMessage($"Could not load organization: {project.OrganizationId}"));
            }

            _logger.Trace().Message(() => $"Loaded organization: {organization.Name}").WriteIf(shouldLog);

            var stack = await _stackRepository.GetByIdAsync(eventNotification.Event.StackId).AnyContext();

            if (stack == null)
            {
                return(JobResult.FailedWithMessage($"Could not load stack: {eventNotification.Event.StackId}"));
            }

            if (!organization.HasPremiumFeatures)
            {
                _logger.Info().Message("Skipping \"{0}\" because organization \"{1}\" does not have premium features.", eventNotification.Event.Id, eventNotification.Event.OrganizationId).WriteIf(shouldLog);
                return(JobResult.Success);
            }

            if (stack.DisableNotifications || stack.IsHidden)
            {
                _logger.Info().Message("Skipping \"{0}\" because stack \"{1}\" notifications are disabled or stack is hidden.", eventNotification.Event.Id, eventNotification.Event.StackId).WriteIf(shouldLog);
                return(JobResult.Success);
            }

            if (context.CancellationToken.IsCancellationRequested)
            {
                return(JobResult.Cancelled);
            }

            _logger.Trace().Message(() => $"Loaded stack: title={stack.Title}").WriteIf(shouldLog);
            int totalOccurrences = stack.TotalOccurrences;

            // after the first 2 occurrences, don't send a notification for the same stack more then once every 30 minutes
            var lastTimeSentUtc = await _cacheClient.GetAsync <DateTime>(String.Concat("notify:stack-throttle:", eventNotification.Event.StackId), DateTime.MinValue).AnyContext();

            if (totalOccurrences > 2 &&
                !eventNotification.IsRegression &&
                lastTimeSentUtc != DateTime.MinValue &&
                lastTimeSentUtc > DateTime.UtcNow.AddMinutes(-30))
            {
                _logger.Info().Message("Skipping message because of stack throttling: last sent={0} occurrences={1}", lastTimeSentUtc, totalOccurrences).WriteIf(shouldLog);
                return(JobResult.Success);
            }

            // don't send more than 10 notifications for a given project every 30 minutes
            var    projectTimeWindow = TimeSpan.FromMinutes(30);
            string cacheKey          = String.Concat("notify:project-throttle:", eventNotification.Event.ProjectId, "-", DateTime.UtcNow.Floor(projectTimeWindow).Ticks);
            double notificationCount = await _cacheClient.IncrementAsync(cacheKey, 1, projectTimeWindow).AnyContext();

            if (notificationCount > 10 && !eventNotification.IsRegression)
            {
                _logger.Info().Project(eventNotification.Event.ProjectId).Message("Skipping message because of project throttling: count={0}", notificationCount).WriteIf(shouldLog);
                return(JobResult.Success);
            }

            if (context.CancellationToken.IsCancellationRequested)
            {
                return(JobResult.Cancelled);
            }

            foreach (var kv in project.NotificationSettings)
            {
                var settings = kv.Value;
                _logger.Trace().Message(() => $"Processing notification: user={kv.Key}").WriteIf(shouldLog);

                var user = await _userRepository.GetByIdAsync(kv.Key).AnyContext();

                if (String.IsNullOrEmpty(user?.EmailAddress))
                {
                    _logger.Error("Could not load user {0} or blank email address {1}.", kv.Key, user?.EmailAddress ?? "");
                    continue;
                }

                if (!user.IsEmailAddressVerified)
                {
                    _logger.Info().Message("User {0} with email address {1} has not been verified.", user.Id, user.EmailAddress).WriteIf(shouldLog);
                    continue;
                }

                if (!user.EmailNotificationsEnabled)
                {
                    _logger.Info().Message("User {0} with email address {1} has email notifications disabled.", user.Id, user.EmailAddress).WriteIf(shouldLog);
                    continue;
                }

                if (!user.OrganizationIds.Contains(project.OrganizationId))
                {
                    _logger.Error().Message("Unauthorized user: project={0} user={1} organization={2} event={3}", project.Id, kv.Key, project.OrganizationId, eventNotification.Event.Id).Write();
                    continue;
                }

                _logger.Trace().Message(() => $"Loaded user: email={user.EmailAddress}").WriteIf(shouldLog);

                bool shouldReportNewError      = settings.ReportNewErrors && eventNotification.IsNew && eventNotification.Event.IsError();
                bool shouldReportCriticalError = settings.ReportCriticalErrors && eventNotification.IsCritical && eventNotification.Event.IsError();
                bool shouldReportRegression    = settings.ReportEventRegressions && eventNotification.IsRegression;
                bool shouldReportNewEvent      = settings.ReportNewEvents && eventNotification.IsNew;
                bool shouldReportCriticalEvent = settings.ReportCriticalEvents && eventNotification.IsCritical;
                bool shouldReport = shouldReportNewError || shouldReportCriticalError || shouldReportRegression || shouldReportNewEvent || shouldReportCriticalEvent;

                _logger.Trace().Message(() => $"Settings: newerror={settings.ReportNewErrors} criticalerror={settings.ReportCriticalErrors} regression={settings.ReportEventRegressions} new={settings.ReportNewEvents} critical={settings.ReportCriticalEvents}").WriteIf(shouldLog);
                _logger.Trace().Message(() => $"Should process: newerror={shouldReportNewError} criticalerror={shouldReportCriticalError} regression={shouldReportRegression} new={shouldReportNewEvent} critical={shouldReportCriticalEvent}").WriteIf(shouldLog);

                var request = eventNotification.Event.GetRequestInfo();
                // check for known bots if the user has elected to not report them
                if (shouldReport && !String.IsNullOrEmpty(request?.UserAgent))
                {
                    var botPatterns = project.Configuration.Settings.ContainsKey(SettingsDictionary.KnownKeys.UserAgentBotPatterns)
                        ? project.Configuration.Settings.GetStringCollection(SettingsDictionary.KnownKeys.UserAgentBotPatterns).ToList()
                        : new List <string>();

                    var info = await _parser.ParseAsync(request.UserAgent, eventNotification.Event.ProjectId).AnyContext();

                    if (info != null && info.Device.IsSpider || request.UserAgent.AnyWildcardMatches(botPatterns))
                    {
                        shouldReport = false;
                        _logger.Info().Message("Skipping because event is from a bot \"{0}\".", request.UserAgent).WriteIf(shouldLog);
                    }
                }

                if (!shouldReport)
                {
                    continue;
                }

                var model = new EventNotificationModel(eventNotification)
                {
                    ProjectName      = project.Name,
                    TotalOccurrences = totalOccurrences
                };

                // don't send notifications in non-production mode to email addresses that are not on the outbound email list.
                if (Settings.Current.WebsiteMode != WebsiteMode.Production &&
                    !Settings.Current.AllowedOutboundAddresses.Contains(v => user.EmailAddress.ToLowerInvariant().Contains(v)))
                {
                    _logger.Info().Message("Skipping because email is not on the outbound list and not in production mode.").WriteIf(shouldLog);
                    continue;
                }

                _logger.Trace("Sending email to {0}...", user.EmailAddress);
                await _mailer.SendEventNoticeAsync(user.EmailAddress, model).AnyContext();

                emailsSent++;
                _logger.Trace().Message(() => "Done sending email.").WriteIf(shouldLog);
            }

            // if we sent any emails, mark the last time a notification for this stack was sent.
            if (emailsSent > 0)
            {
                await _cacheClient.SetAsync(String.Concat("notify:stack-throttle:", eventNotification.Event.StackId), DateTime.UtcNow, DateTime.UtcNow.AddMinutes(15)).AnyContext();

                _logger.Info().Message("Notifications sent: event={0} stack={1} count={2}", eventNotification.Event.Id, eventNotification.Event.StackId, emailsSent).WriteIf(shouldLog);
            }

            return(JobResult.Success);
        }
Beispiel #15
0
    public override async Task EventBatchProcessingAsync(ICollection <EventContext> contexts)
    {
        if (_options.AppMode == AppMode.Development)
        {
            return;
        }

        var firstContext = contexts.First();

        if (!firstContext.Project.DeleteBotDataEnabled || !firstContext.IncludePrivateInformation)
        {
            return;
        }

        // Throttle errors by client ip address to no more than X every 5 minutes.
        var clientIpAddressGroups = contexts.GroupBy(c => c.Event.GetRequestInfo()?.ClientIpAddress);

        foreach (var clientIpAddressGroup in clientIpAddressGroups)
        {
            if (String.IsNullOrEmpty(clientIpAddressGroup.Key) || clientIpAddressGroup.Key.IsPrivateNetwork())
            {
                continue;
            }

            var    clientIpContexts = clientIpAddressGroup.ToList();
            string throttleCacheKey = String.Concat("bot:", clientIpAddressGroup.Key, ":", SystemClock.UtcNow.Floor(_throttlingPeriod).Ticks);
            int?   requestCount     = await _cache.GetAsync <int?>(throttleCacheKey, null).AnyContext();

            if (requestCount.HasValue)
            {
                await _cache.IncrementAsync(throttleCacheKey, clientIpContexts.Count).AnyContext();

                requestCount += clientIpContexts.Count;
            }
            else
            {
                await _cache.SetAsync(throttleCacheKey, clientIpContexts.Count, SystemClock.UtcNow.Ceiling(_throttlingPeriod)).AnyContext();

                requestCount = clientIpContexts.Count;
            }

            if (requestCount < _options.BotThrottleLimit)
            {
                continue;
            }

            _logger.LogInformation("Bot throttle triggered. IP: {IP} Time: {ThrottlingPeriod} Project: {project}", clientIpAddressGroup.Key, SystemClock.UtcNow.Floor(_throttlingPeriod), firstContext.Event.ProjectId);

            // The throttle was triggered, go and delete all the errors that triggered the throttle to reduce bot noise in the system
            await _workItemQueue.EnqueueAsync(new RemoveBotEventsWorkItem {
                OrganizationId  = firstContext.Event.OrganizationId,
                ProjectId       = firstContext.Event.ProjectId,
                ClientIpAddress = clientIpAddressGroup.Key,
                UtcStartDate    = SystemClock.UtcNow.Floor(_throttlingPeriod),
                UtcEndDate      = SystemClock.UtcNow.Ceiling(_throttlingPeriod)
            }).AnyContext();

            clientIpContexts.ForEach(c => {
                c.IsDiscarded = true;
                c.IsCancelled = true;
            });
        }
    }
Beispiel #16
0
 public Task <IEnumerable <Scope> > GetAsync(string key)
 {
     return(cacheClient.GetAsync <IEnumerable <Scope> >(key));
 }
Beispiel #17
0
 private async Task <bool> ShouldRefreshGlobalViewAsync()
 {
     return((await cache.GetAsync <int>(GetCacheKey())) == 1);
 }
        /// <summary>
        /// Cache get.
        /// </summary>
        private void GetAsync(BenchmarkState state)
        {
            var idx = BenchmarkUtils.GetRandomInt(Dataset);

            _cache.GetAsync(idx).Wait();
        }
        public async Task SaveStackUsagesAsync(bool sendNotifications = true, CancellationToken cancellationToken = default)
        {
            string occurrenceSetCacheKey = GetStackOccurrenceSetCacheKey();
            var    stackUsageSet         = await _cache.GetListAsync <(string OrganizationId, string ProjectId, string StackId)>(occurrenceSetCacheKey).AnyContext();

            if (!stackUsageSet.HasValue)
            {
                return;
            }

            foreach (var(organizationId, projectId, stackId) in stackUsageSet.Value)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    break;
                }

                var    removeFromSetTask = _cache.ListRemoveAsync(occurrenceSetCacheKey, (organizationId, projectId, stackId));
                string countCacheKey     = GetStackOccurrenceCountCacheKey(stackId);
                var    countTask         = _cache.GetAsync <long>(countCacheKey, 0);
                string minDateCacheKey   = GetStackOccurrenceMinDateCacheKey(stackId);
                var    minDateTask       = _cache.GetUnixTimeMillisecondsAsync(minDateCacheKey, SystemClock.UtcNow);
                string maxDateCacheKey   = GetStackOccurrenceMaxDateCacheKey(stackId);
                var    maxDateTask       = _cache.GetUnixTimeMillisecondsAsync(maxDateCacheKey, SystemClock.UtcNow);

                await Task.WhenAll(
                    removeFromSetTask,
                    countTask,
                    minDateTask,
                    maxDateTask
                    ).AnyContext();

                int occurrenceCount = (int)countTask.Result;
                if (occurrenceCount <= 0)
                {
                    await _cache.RemoveAllAsync(new[] { minDateCacheKey, maxDateCacheKey }).AnyContext();

                    continue;
                }

                await Task.WhenAll(
                    _cache.RemoveAllAsync(new [] { minDateCacheKey, maxDateCacheKey }),
                    _cache.DecrementAsync(countCacheKey, occurrenceCount, _expireTimeout)
                    ).AnyContext();

                var  occurrenceMinDate = minDateTask.Result;
                var  occurrenceMaxDate = maxDateTask.Result;
                bool shouldRetry       = false;
                try {
                    if (!await _stackRepository.IncrementEventCounterAsync(organizationId, projectId, stackId, occurrenceMinDate, occurrenceMaxDate, occurrenceCount, sendNotifications).AnyContext())
                    {
                        shouldRetry = true;
                        await IncrementStackUsageAsync(organizationId, projectId, stackId, occurrenceMinDate, occurrenceMaxDate, occurrenceCount).AnyContext();
                    }
                    else if (_logger.IsEnabled(LogLevel.Trace))
                    {
                        _logger.LogTrace("Increment event count {OccurrenceCount} for organization:{OrganizationId} project:{ProjectId} stack:{StackId} with Min Date:{OccurrenceMinDate} Max Date:{OccurrenceMaxDate}", occurrenceCount, organizationId, projectId, stackId, occurrenceMinDate, occurrenceMaxDate);
                    }
                } catch (Exception ex) {
                    _logger.LogError(ex, "Error incrementing event count for organization: {OrganizationId} project:{ProjectId} stack:{StackId}", organizationId, projectId, stackId);
                    if (!shouldRetry)
                    {
                        await IncrementStackUsageAsync(organizationId, projectId, stackId, occurrenceMinDate, occurrenceMaxDate, occurrenceCount).AnyContext();
                    }
                }
            }
        }
Beispiel #20
0
        public override async Task EventBatchProcessingAsync(ICollection <EventContext> contexts)
        {
            var identityGroups = contexts.Where(c => String.IsNullOrEmpty(c.Event.SessionId) && c.Event.GetUserIdentity()?.Identity != null)
                                 .OrderBy(c => c.Event.Date)
                                 .GroupBy(c => c.Event.GetUserIdentity().Identity);

            foreach (var identityGroup in identityGroups)
            {
                string       cacheKey            = $"{identityGroup.First().Project.Id}:identity:{identityGroup.Key.ToSHA1()}";
                string       sessionId           = null;
                EventContext sessionStartContext = null;

                var sessionsToUpdate = new Dictionary <string, EventContext>();
                foreach (var context in identityGroup)
                {
                    if (!context.Event.IsSessionStart() && String.IsNullOrEmpty(sessionId))
                    {
                        sessionId = await _cacheClient.GetAsync <string>(cacheKey, null).AnyContext();

                        if (!String.IsNullOrEmpty(sessionId))
                        {
                            await _cacheClient.SetExpirationAsync(cacheKey, _sessionTimeout).AnyContext();

                            await _cacheClient.SetExpirationAsync(GetSessionStartEventIdCacheKey(context.Project.Id, sessionId), _sessionTimeout).AnyContext();
                        }
                    }

                    if (context.Event.IsSessionStart() || String.IsNullOrEmpty(sessionId))
                    {
                        if (context.Event.IsSessionStart() && !String.IsNullOrEmpty(sessionId))
                        {
                            await CreateSessionEndEventAsync(context, sessionId).AnyContext();

                            if (sessionStartContext != null)
                            {
                                sessionStartContext.Event.UpdateSessionStart(context.Event.Date.UtcDateTime, isSessionEnd: true);
                            }
                            else
                            {
                                await UpdateSessionStartEventAsync(context, sessionId, isSessionEnd : true);
                            }
                        }

                        sessionId = context.Event.SessionId = ObjectId.GenerateNewId(context.Event.Date.DateTime).ToString();
                        await _cacheClient.SetAsync(cacheKey, sessionId, _sessionTimeout).AnyContext();

                        if (!context.Event.IsSessionStart())
                        {
                            var  lastContext  = GetLastActivity(identityGroup.Where(c => c.Event.Date.Ticks > context.Event.Date.Ticks).ToList());
                            bool isSessionEnd = lastContext != null && (lastContext.Event.IsSessionStart() || lastContext.Event.IsSessionEnd());

                            sessionStartContext = await CreateSessionStartEventAsync(context, lastContext?.Event.Date.UtcDateTime, isSessionEnd).AnyContext();

                            if (lastContext == null || !isSessionEnd)
                            {
                                await _cacheClient.SetAsync(GetSessionStartEventIdCacheKey(context.Project.Id, sessionId), sessionStartContext.Event.Id, _sessionTimeout).AnyContext();
                            }
                        }
                    }
                    else
                    {
                        context.Event.SessionId = sessionId;

                        if (sessionStartContext == null)
                        {
                            sessionsToUpdate[sessionId] = context;
                        }
                    }

                    if (context.Event.IsSessionStart())
                    {
                        sessionStartContext = context;
                    }
                    else if (context.Event.IsSessionEnd())
                    {
                        await _cacheClient.RemoveAllAsync(new [] {
                            cacheKey,
                            GetSessionStartEventIdCacheKey(context.Project.Id, sessionId)
                        }).AnyContext();

                        sessionId = null;
                    }
                }

                foreach (var pair in sessionsToUpdate)
                {
                    await UpdateSessionStartEventAsync(pair.Value, pair.Key, pair.Value.Event.IsSessionEnd());
                }
            }
        }
Beispiel #21
0
        public async Task <bool> OnHandle(MessageContext context, CLoginReqMessage message)
        {
            var session = context.GetSession <Session>();
            var logger  = _logger.ForContext(
                ("RemoteEndPoint", session.RemoteEndPoint.ToString()),
                ("Message", message.ToJson()));

            logger.Debug("Login");

            var allowedVersions = _appOptions.CurrentValue.ClientVersions;

            if (allowedVersions.All(x => message.Version != x))
            {
                logger.Information("Invalid client version={Version} supported versions are {SupportedVersions}",
                                   message.Version.ToString(), string.Join(",", allowedVersions.Select(x => x.ToString())));
                session.Send(new SLoginAckMessage(GameLoginResult.WrongVersion));
                await session.CloseAsync();

                return(true);
            }

            if (_sessionManager.Sessions.Count >= _networkOptions.MaxSessions)
            {
                session.Send(new SLoginAckMessage(GameLoginResult.ServerFull));
                return(true);
            }

            // Validate session
            var sessionId = await _cacheClient.GetAsync <string>(Constants.Cache.SessionKey(message.AccountId));

            if (!sessionId.HasValue || !sessionId.Value.Equals(message.SessionId))
            {
                logger.Information("Invalid session id");
                session.Send(new SLoginAckMessage(GameLoginResult.SessionTimeout));
                return(true);
            }

            AccountEntity accountEntity;

            using (var db = _databaseService.Open <AuthContext>())
            {
                var accountId = (long)message.AccountId;
                accountEntity = await db.Accounts
                                .Include(x => x.Bans)
                                .FirstOrDefaultAsync(x => x.Id == accountId);
            }

            if (accountEntity == null)
            {
                logger.Information("Wrong login");
                session.Send(new SLoginAckMessage(GameLoginResult.SessionTimeout));
                return(true);
            }

            // Check ban status
            var now = DateTimeOffset.Now.ToUnixTimeSeconds();
            var ban = accountEntity.Bans.FirstOrDefault(x => x.Duration == null || x.Date + x.Duration > now);

            if (ban != null)
            {
                var unbanDate = DateTimeOffset.MinValue;
                if (ban.Duration != null)
                {
                    unbanDate = DateTimeOffset.FromUnixTimeSeconds(ban.Date + (ban.Duration ?? 0));
                }

                logger.Information("Account is banned until {UnbanDate}", unbanDate);
                session.Send(new SLoginAckMessage(GameLoginResult.SessionTimeout));
                return(true);
            }

            var account = new Account((ulong)accountEntity.Id, accountEntity.Username, accountEntity.Nickname,
                                      (SecurityLevel)accountEntity.SecurityLevel);

            if (message.KickConnection)
            {
                var oldPlr = _playerManager[account.Id];
                if (oldPlr != null)
                {
                    logger.Information("Kicking old connection hostId={HostId}", oldPlr.Session.HostId);
                    await oldPlr.DisconnectAsync();
                }
            }

            if (_playerManager.Contains(account.Id))
            {
                // TODO Check if logged in on another server

                logger.Information("Account is already logged in");
                session.Send(new SLoginAckMessage(GameLoginResult.TerminateOtherConnection));
                return(true);
            }

            using (var db = _databaseService.Open <GameContext>())
            {
                var plr = await db.Players
                          .Include(x => x.Characters)
                          .Include(x => x.Items)
                          .Include(x => x.Licenses)
                          .FirstOrDefaultAsync(x => x.Id == accountEntity.Id);

                if (plr == null)
                {
                    var levelInfo = _gameDataService.Levels.GetValueOrDefault(_gameOptions.StartLevel);
                    if (levelInfo == null)
                    {
                        logger.Warning("Invalid StartLevel={StartLevel} in config", _gameOptions.StartLevel);
                    }

                    plr = new PlayerEntity
                    {
                        Id              = (int)account.Id,
                        AP              = _gameOptions.StartAP,
                        PEN             = _gameOptions.StartPEN,
                        Coins1          = _gameOptions.StartCoins1,
                        Coins2          = _gameOptions.StartCoins2,
                        TotalExperience = (int)(levelInfo?.TotalExperience ?? 0)
                    };

                    db.Players.Add(plr);
                    await db.SaveChangesAsync();
                }

                session.Player = _serviceProvider.GetRequiredService <Player>();
                session.Player.Initialize(session, account, plr);
                session.SessionId = message.SessionId;
            }

            _playerManager.Add(session.Player);
            logger.Information("Login success");

            var result = string.IsNullOrWhiteSpace(account.Nickname)
                ? GameLoginResult.ChooseNickname
                : GameLoginResult.OK;

            session.Send(new SLoginAckMessage(result, account.Id));

            if (!string.IsNullOrWhiteSpace(account.Nickname))
            {
                await session.Player.SendAccountInformation();
            }
            return(true);
        }
        public async Task <bool> IsLockedAsync(string name)
        {
            var result = await Run.WithRetriesAsync(() => _cacheClient.GetAsync <object>(name), logger : _logger).AnyContext();

            return(result.HasValue);
        }
Beispiel #23
0
        public override async Task EventProcessingAsync(EventContext context)
        {
            var user = context.Event.GetUserIdentity();

            if (String.IsNullOrEmpty(user?.Identity) || !String.IsNullOrEmpty(context.Event.SessionId))
            {
                return;
            }

            string cacheKey  = $"session:{context.Event.ProjectId}:{user.Identity}";
            var    sessionId = context.Event.Type != Event.KnownTypes.SessionStart ? await _cacheClient.GetAsync <string>(cacheKey, null).AnyContext() : null;

            if (sessionId == null)
            {
                sessionId = Guid.NewGuid().ToString("N");
                await _cacheClient.SetAsync(cacheKey, sessionId, _sessionTimeout).AnyContext();
            }
            else
            {
                await _cacheClient.SetExpirationAsync(cacheKey, _sessionTimeout).AnyContext();
            }

            context.Event.SessionId = sessionId;

            if (context.Event.Type == Event.KnownTypes.SessionEnd)
            {
                await _cacheClient.RemoveAsync(cacheKey).AnyContext();
            }
        }
Beispiel #24
0
 public static async Task <BlackjackManager> FromCacheClientAsync(ICacheClient client, ulong channelId, ulong userId)
 {
     return(FromContext(
                await client.GetAsync <BlackjackContext>($"miki:blackjack:{channelId}:{userId}")
                ));
 }
        public override async Task EventBatchProcessingAsync(ICollection <EventContext> contexts)
        {
            var sessionGroups = contexts.Where(c => !String.IsNullOrEmpty(c.Event.SessionId))
                                .OrderBy(c => c.Event.Date)
                                .GroupBy(c => c.Event.SessionId);

            foreach (var sessionGroup in sessionGroups)
            {
                var    oldestContext = sessionGroup.First();
                string cacheKey      = $"{oldestContext.Project.Id}:start:{oldestContext.Event.SessionId}";

                string sessionStartEventId = await _cacheClient.GetAsync <string>(cacheKey, null).AnyContext();

                if (!String.IsNullOrEmpty(sessionStartEventId))
                {
                    await _cacheClient.SetExpirationAsync(cacheKey, _sessionTimeout).AnyContext();
                }

                // Deduplicate session start and end events.
                sessionGroup.Where(c => c.Event.IsSessionStart()).Skip(String.IsNullOrEmpty(sessionStartEventId) ? 1 : 0).ForEach(c => c.IsCancelled = true);
                sessionGroup.OrderByDescending(c => c.Event.Date).Where(c => c.Event.IsSessionEnd()).Skip(1).ForEach(c => c.IsCancelled = true);

                var validContexts = sessionGroup.Where(c => !c.IsCancelled).ToList();
                if (validContexts.Count == 0)
                {
                    continue;
                }

                oldestContext = validContexts.First();
                var newestContext = validContexts.Last();

                var sessionStartEventContext = validContexts.FirstOrDefault(c => c.Event.IsSessionStart());
                UpdateEventContextDate(sessionStartEventContext, oldestContext.Event.Date);

                var sessionEndEventContext = validContexts.FirstOrDefault(c => c.Event.IsSessionEnd());
                UpdateEventContextDate(sessionEndEventContext, newestContext.Event.Date);

                // Update or create the session start event.
                var createSessionStartEvent = String.IsNullOrEmpty(sessionStartEventId) && sessionStartEventContext == null;
                if (createSessionStartEvent)
                {
                    string sessionStartId = await CreateSessionStartEventAsync(oldestContext, newestContext.Event.Date.UtcDateTime, sessionEndEventContext != null).AnyContext();

                    if (sessionEndEventContext == null)
                    {
                        await _cacheClient.SetAsync(cacheKey, sessionStartId, _sessionTimeout).AnyContext();
                    }
                }
                else if (sessionStartEventContext != null)
                {
                    if (validContexts.Count > 1)
                    {
                        sessionStartEventContext.Event.UpdateSessionStart(newestContext.Event.Date.UtcDateTime, sessionEndEventContext != null);
                    }

                    if (sessionEndEventContext == null)
                    {
                        sessionStartEventContext.SetProperty(CREATE_SESSION_START_CACHE_ENTRY, true);
                    }
                }
                else
                {
                    bool closeSession = sessionEndEventContext != null;

                    await _eventRepository.UpdateSessionStartLastActivityAsync(sessionStartEventId, newestContext.Event.Date.UtcDateTime, closeSession).AnyContext();

                    if (closeSession)
                    {
                        await _cacheClient.RemoveAsync(cacheKey).AnyContext();
                    }
                }
            }
        }
        public async Task CanIncrementUsageAsync()
        {
            var messageBus = GetService <IMessageBus>();

            var countdown = new AsyncCountdownEvent(2);
            await messageBus.SubscribeAsync <PlanOverage>(po => {
                _logger.Info($"Plan Overage for {po.OrganizationId} (Hourly: {po.IsHourly})");
                countdown.Signal();
            });

            var o = await _organizationRepository.AddAsync(new Organization { Name = "Test", MaxEventsPerMonth = 750, PlanId = BillingManager.SmallPlan.Id });

            var project = await _projectRepository.AddAsync(new Project { Name = "Test", OrganizationId = o.Id, NextSummaryEndOfDayTicks = SystemClock.UtcNow.Ticks }, opt => opt.Cache());

            await _configuration.Client.RefreshAsync(Indices.All);

            Assert.InRange(o.GetHourlyEventLimit(), 1, 750);

            int totalToIncrement = o.GetHourlyEventLimit() - 1;

            Assert.False(await _usageService.IncrementUsageAsync(o.Id, project.Id, false, totalToIncrement));
            await _configuration.Client.RefreshAsync(Indices.All);

            o = await _organizationRepository.GetByIdAsync(o.Id);

            await countdown.WaitAsync(TimeSpan.FromMilliseconds(150));

            Assert.Equal(2, countdown.CurrentCount);
            Assert.Equal(totalToIncrement, await _cache.GetAsync <long>(GetHourlyTotalCacheKey(o.Id), 0));
            Assert.Equal(totalToIncrement, await _cache.GetAsync <long>(GetHourlyTotalCacheKey(o.Id, project.Id), 0));
            Assert.Equal(totalToIncrement, await _cache.GetAsync <long>(GetMonthlyTotalCacheKey(o.Id), 0));
            Assert.Equal(totalToIncrement, await _cache.GetAsync <long>(GetMonthlyTotalCacheKey(o.Id, project.Id), 0));
            Assert.Equal(0, await _cache.GetAsync <long>(GetHourlyBlockedCacheKey(o.Id), 0));
            Assert.Equal(0, await _cache.GetAsync <long>(GetHourlyBlockedCacheKey(o.Id, project.Id), 0));
            Assert.Equal(0, await _cache.GetAsync <long>(GetMonthlyBlockedCacheKey(o.Id), 0));
            Assert.Equal(0, await _cache.GetAsync <long>(GetMonthlyBlockedCacheKey(o.Id, project.Id), 0));

            Assert.True(await _usageService.IncrementUsageAsync(o.Id, project.Id, false, 2));
            await _configuration.Client.RefreshAsync(Indices.All);

            o = await _organizationRepository.GetByIdAsync(o.Id);

            await countdown.WaitAsync(TimeSpan.FromMilliseconds(150));

            Assert.Equal(1, countdown.CurrentCount);
            Assert.Equal(totalToIncrement + 2, await _cache.GetAsync <long>(GetHourlyTotalCacheKey(o.Id), 0));
            Assert.Equal(totalToIncrement + 2, await _cache.GetAsync <long>(GetHourlyTotalCacheKey(o.Id, project.Id), 0));
            Assert.Equal(totalToIncrement + 2, await _cache.GetAsync <long>(GetMonthlyTotalCacheKey(o.Id), 0));
            Assert.Equal(totalToIncrement + 2, await _cache.GetAsync <long>(GetMonthlyTotalCacheKey(o.Id, project.Id), 0));
            Assert.Equal(1, await _cache.GetAsync <long>(GetHourlyBlockedCacheKey(o.Id), 0));
            Assert.Equal(1, await _cache.GetAsync <long>(GetHourlyBlockedCacheKey(o.Id, project.Id), 0));
            Assert.Equal(1, await _cache.GetAsync <long>(GetMonthlyBlockedCacheKey(o.Id), 0));
            Assert.Equal(1, await _cache.GetAsync <long>(GetMonthlyBlockedCacheKey(o.Id, project.Id), 0));

            o = await _organizationRepository.AddAsync(new Organization { Name = "Test", MaxEventsPerMonth = 750, PlanId = BillingManager.SmallPlan.Id });

            project = await _projectRepository.AddAsync(new Project { Name = "Test", OrganizationId = o.Id, NextSummaryEndOfDayTicks = SystemClock.UtcNow.Ticks }, opt => opt.Cache());

            await _configuration.Client.RefreshAsync(Indices.All);

            await _cache.RemoveAllAsync();

            totalToIncrement = o.GetHourlyEventLimit() + 20;
            Assert.True(await _usageService.IncrementUsageAsync(o.Id, project.Id, false, totalToIncrement));

            await countdown.WaitAsync(TimeSpan.FromMilliseconds(150));

            Assert.Equal(0, countdown.CurrentCount);
            Assert.Equal(totalToIncrement, await _cache.GetAsync <long>(GetHourlyTotalCacheKey(o.Id), 0));
            Assert.Equal(totalToIncrement, await _cache.GetAsync <long>(GetHourlyTotalCacheKey(o.Id, project.Id), 0));
            Assert.Equal(totalToIncrement, await _cache.GetAsync <long>(GetMonthlyTotalCacheKey(o.Id), 0));
            Assert.Equal(totalToIncrement, await _cache.GetAsync <long>(GetMonthlyTotalCacheKey(o.Id, project.Id), 0));
            Assert.Equal(20, await _cache.GetAsync <long>(GetHourlyBlockedCacheKey(o.Id), 0));
            Assert.Equal(20, await _cache.GetAsync <long>(GetHourlyBlockedCacheKey(o.Id, project.Id), 0));
            Assert.Equal(20, await _cache.GetAsync <long>(GetMonthlyBlockedCacheKey(o.Id), 0));
            Assert.Equal(20, await _cache.GetAsync <long>(GetMonthlyBlockedCacheKey(o.Id, project.Id), 0));
        }
Beispiel #27
0
 public override Task <object> GetOrDefaultAsync(string key)
 {
     return(_cacheClient.GetAsync <object>(GetLocalizedKey(key)));
 }
 public static async Task<bool> IsOverRequestLimitAsync(string organizationId, ICacheClient cacheClient, int apiThrottleLimit) {
     var cacheKey = String.Concat("api", ":", organizationId, ":", DateTime.UtcNow.Floor(TimeSpan.FromMinutes(15)).Ticks);
     var limit = await cacheClient.GetAsync<long>(cacheKey).AnyContext();
     return limit.HasValue && limit.Value >= apiThrottleLimit;
 }
Beispiel #29
0
        public async Task BuildAsync <T>(QueryBuilderContext <T> ctx) where T : class, new()
        {
            if (!ctx.Source.ShouldEnforceEventStackFilter())
            {
                return;
            }

            // TODO: Handle search expressions as well
            string filter             = ctx.Source.GetFilterExpression() ?? String.Empty;
            bool   altInvertRequested = false;

            if (filter.StartsWith("@!"))
            {
                altInvertRequested = true;
                filter             = filter.Substring(2);
                ctx.Source.FilterExpression(filter);
            }

            // when inverting to get excluded stack ids, add is_deleted as an alternate inverted criteria
            if (ctx.Options.GetSoftDeleteMode() == SoftDeleteQueryMode.ActiveOnly)
            {
                ctx.SetAlternateInvertedCriteria(new TermNode {
                    Field = "is_deleted", Term = "true"
                });
            }

            var stackFilter = await _eventStackFilter.GetStackFilterAsync(filter, ctx);

            const int stackIdLimit = 10000;

            string[] stackIds   = Array.Empty <string>();
            long     stackTotal = 0;

            string stackFilterValue  = stackFilter.Filter;
            bool   isStackIdsNegated = false; //= stackFilter.HasStatusOpen && !altInvertRequested;

            if (isStackIdsNegated)
            {
                stackFilterValue = stackFilter.InvertedFilter;
            }

            if (String.IsNullOrEmpty(stackFilterValue) && (!ctx.Source.ShouldEnforceEventStackFilter() || ctx.Options.GetSoftDeleteMode() != SoftDeleteQueryMode.ActiveOnly))
            {
                return;
            }

            _logger.LogTrace("Source: {Filter} Stack Filter: {StackFilter} Inverted Stack Filter: {InvertedStackFilter}", filter, stackFilter.Filter, stackFilter.InvertedFilter);

            var systemFilterQuery = GetSystemFilterQuery(ctx, isStackIdsNegated);

            systemFilterQuery.FilterExpression(stackFilterValue);
            var softDeleteMode = isStackIdsNegated ? SoftDeleteQueryMode.All : SoftDeleteQueryMode.ActiveOnly;

            systemFilterQuery.EventStackFilterInverted(isStackIdsNegated);

            FindResults <Stack> results = null;
            var tooManyStacksCheck      = await _cacheClient.GetAsync <long>(GetQueryHash(systemFilterQuery));

            if (tooManyStacksCheck.HasValue)
            {
                stackTotal = tooManyStacksCheck.Value;
            }
            else
            {
                results = await _stackRepository.GetIdsByQueryAsync(q => systemFilterQuery.As <Stack>(), o => o.PageLimit(stackIdLimit).SoftDeleteMode(softDeleteMode)).AnyContext();

                stackTotal = results.Total;
            }

            if (stackTotal > stackIdLimit)
            {
                if (!tooManyStacksCheck.HasValue)
                {
                    await _cacheClient.SetAsync(GetQueryHash(systemFilterQuery), stackTotal, TimeSpan.FromMinutes(15));
                }

                _logger.LogTrace("Query: {query} will be inverted due to id limit: {ResultCount}", stackFilterValue, stackTotal);
                isStackIdsNegated = !isStackIdsNegated;
                stackFilterValue  = isStackIdsNegated ? stackFilter.InvertedFilter : stackFilter.Filter;
                systemFilterQuery.FilterExpression(stackFilterValue);
                softDeleteMode = isStackIdsNegated ? SoftDeleteQueryMode.All : SoftDeleteQueryMode.ActiveOnly;
                systemFilterQuery.EventStackFilterInverted(isStackIdsNegated);

                tooManyStacksCheck = await _cacheClient.GetAsync <long>(GetQueryHash(systemFilterQuery));

                if (tooManyStacksCheck.HasValue)
                {
                    stackTotal = tooManyStacksCheck.Value;
                }
                else
                {
                    results = await _stackRepository.GetIdsByQueryAsync(q => systemFilterQuery.As <Stack>(), o => o.PageLimit(stackIdLimit).SoftDeleteMode(softDeleteMode)).AnyContext();

                    stackTotal = results.Total;
                }
            }

            if (stackTotal > stackIdLimit)
            {
                if (!tooManyStacksCheck.HasValue)
                {
                    await _cacheClient.SetAsync(GetQueryHash(systemFilterQuery), stackTotal, TimeSpan.FromMinutes(15));
                }
                throw new DocumentLimitExceededException("Please limit your search criteria.");
            }

            if (results?.Hits != null)
            {
                stackIds = results.Hits.Select(h => h.Id).ToArray();
            }

            _logger.LogTrace("Setting stack filter with {IdCount} ids", stackIds?.Length ?? 0);

            if (!isStackIdsNegated)
            {
                if (stackIds.Length > 0)
                {
                    ctx.Source.Stack(stackIds);
                }
                else
                {
                    ctx.Source.Stack("none");
                }
            }
            else
            {
                if (stackIds.Length > 0)
                {
                    ctx.Source.ExcludeStack(stackIds);
                }
            }

            // Strips stack only fields and stack only special fields
            string eventFilter = await _eventStackFilter.GetEventFilterAsync(filter, ctx);

            ctx.Source.FilterExpression(eventFilter);
        }