AdminLogManager.Cache.cs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Linq;
  3. using Content.Server.Database;
  4. using Content.Shared.Administration.Logs;
  5. using Content.Shared.Database;
  6. using Prometheus;
  7. namespace Content.Server.Administration.Logs;
  8. public sealed partial class AdminLogManager
  9. {
  10. private const int MaxRoundsCached = 3;
  11. private const int LogListInitialSize = 30_000;
  12. private readonly int _logTypes = Enum.GetValues<LogType>().Length;
  13. // TODO ADMIN LOGS make this thread safe or remove thread safety from the main partial class
  14. private readonly Dictionary<int, List<SharedAdminLog>> _roundsLogCache = new(MaxRoundsCached);
  15. private readonly Queue<int> _roundsLogCacheQueue = new();
  16. private static readonly Gauge CacheRoundCount = Metrics.CreateGauge(
  17. "admin_logs_cache_round_count",
  18. "How many rounds are in cache.");
  19. private static readonly Gauge CacheLogCount = Metrics.CreateGauge(
  20. "admin_logs_cache_log_count",
  21. "How many logs are in cache.");
  22. // TODO ADMIN LOGS cache previous {MaxRoundsCached} rounds on startup
  23. public void CacheNewRound()
  24. {
  25. List<SharedAdminLog>? list = null;
  26. _roundsLogCacheQueue.Enqueue(_currentRoundId);
  27. if (_roundsLogCacheQueue.Count > MaxRoundsCached)
  28. {
  29. var oldestRound = _roundsLogCacheQueue.Dequeue();
  30. if (_roundsLogCache.Remove(oldestRound, out var oldestList))
  31. {
  32. list = oldestList;
  33. list.Clear();
  34. }
  35. }
  36. list ??= new List<SharedAdminLog>(LogListInitialSize);
  37. _roundsLogCache.Add(_currentRoundId, list);
  38. CacheRoundCount.Set(_roundsLogCache.Count);
  39. }
  40. private void CacheLog(AdminLog log)
  41. {
  42. var players = log.Players.Select(player => player.PlayerUserId).ToArray();
  43. var record = new SharedAdminLog(log.Id, log.Type, log.Impact, log.Date, log.Message, players);
  44. CacheLog(record);
  45. }
  46. private void CacheLog(SharedAdminLog log)
  47. {
  48. // TODO ADMIN LOGS remove redundant data and don't do a dictionary lookup per log
  49. var cache = _roundsLogCache[_currentRoundId];
  50. cache.Add(log);
  51. CacheLogCount.Set(cache.Count);
  52. }
  53. private void CacheLogs(IEnumerable<SharedAdminLog> logs)
  54. {
  55. var cache = _roundsLogCache[_currentRoundId];
  56. cache.AddRange(logs);
  57. CacheLogCount.Set(cache.Count);
  58. }
  59. private bool TryGetCache(int roundId, [NotNullWhen(true)] out List<SharedAdminLog>? cache)
  60. {
  61. return _roundsLogCache.TryGetValue(roundId, out cache);
  62. }
  63. private bool TrySearchCache(LogFilter? filter, [NotNullWhen(true)] out List<SharedAdminLog>? results)
  64. {
  65. if (filter?.Round == null || !TryGetCache(filter.Round.Value, out var cache))
  66. {
  67. results = null;
  68. return false;
  69. }
  70. // TODO ADMIN LOGS a better heuristic than linq spaghetti
  71. var query = cache.AsEnumerable();
  72. query = filter.DateOrder switch
  73. {
  74. DateOrder.Ascending => query,
  75. DateOrder.Descending => query.Reverse(),
  76. _ => throw new ArgumentOutOfRangeException(nameof(filter),
  77. $"Unknown {nameof(DateOrder)} value {filter.DateOrder}")
  78. };
  79. if (filter.Search != null)
  80. {
  81. query = query.Where(log => log.Message.Contains(filter.Search, StringComparison.OrdinalIgnoreCase));
  82. }
  83. if (filter.Types != null && filter.Types.Count != _logTypes)
  84. {
  85. query = query.Where(log => filter.Types.Contains(log.Type));
  86. }
  87. if (filter.Impacts != null)
  88. {
  89. query = query.Where(log => filter.Impacts.Contains(log.Impact));
  90. }
  91. if (filter.Before != null)
  92. {
  93. query = query.Where(log => log.Date < filter.Before);
  94. }
  95. if (filter.After != null)
  96. {
  97. query = query.Where(log => log.Date > filter.After);
  98. }
  99. if (filter.IncludePlayers)
  100. {
  101. if (filter.AnyPlayers != null)
  102. {
  103. query = query.Where(log =>
  104. filter.AnyPlayers.Any(filterPlayer => log.Players.Contains(filterPlayer)) ||
  105. log.Players.Length == 0 && filter.IncludeNonPlayers);
  106. }
  107. if (filter.AllPlayers != null)
  108. {
  109. query = query.Where(log =>
  110. filter.AllPlayers.All(filterPlayer => log.Players.Contains(filterPlayer)) ||
  111. log.Players.Length == 0 && filter.IncludeNonPlayers);
  112. }
  113. }
  114. else
  115. {
  116. query = query.Where(log => log.Players.Length == 0);
  117. }
  118. if (filter.LogsSent != 0)
  119. {
  120. query = query.Skip(filter.LogsSent);
  121. }
  122. if (filter.Limit != null)
  123. {
  124. query = query.Take(filter.Limit.Value);
  125. }
  126. // TODO ADMIN LOGS array pool
  127. results = query.ToList();
  128. return true;
  129. }
  130. }