/// <summary> /// Asynchronously adds a log to storage /// </summary> /// <param name="channelName">The name of the channel associated with the log</param> /// <param name="log">The log to add</param> /// <exception cref="StorageException"/> public Task PutLog(string channelName, Log log) { return(AddTaskToQueue(() => { var logJsonString = LogSerializer.Serialize(log); var maxSize = _storageAdapter.GetMaxStorageSize(); var logSize = Encoding.UTF8.GetBytes(logJsonString).Length; if (maxSize < 0) { throw new StorageException("Failed to store a log to the database."); } if (maxSize <= logSize) { throw new StorageException($"Log is too large ({logSize} bytes) to store in database. " + $"Current maximum database size is {maxSize} bytes."); } while (true) { try { _storageAdapter.Insert(TableName, new[] { ColumnChannelName, ColumnLogName }, new List <object[]> { new object[] { channelName, logJsonString } }); return; } catch (StorageFullException) { var oldestLog = _storageAdapter.Select(TableName, ColumnChannelName, channelName, string.Empty, null, 1, new string[] { ColumnIdName }); if (oldestLog != null && oldestLog.Count > 0 && oldestLog[0].Length > 0) { _storageAdapter.Delete(TableName, ColumnIdName, oldestLog[0][0]); } else { throw new StorageException("Failed to add a new log. Storage is full and old logs cannot be purged."); } } } })); }
/// <summary> /// Asynchronously retrieves logs from storage and flags them to avoid duplicate retrievals on subsequent calls /// </summary> /// <param name="channelName">Name of the channel to retrieve logs from</param> /// <param name="limit">The maximum number of logs to retrieve</param> /// <param name="logs">A list to which the retrieved logs will be added</param> /// <returns>A batch ID for the set of returned logs; null if no logs are found</returns> /// <exception cref="StorageException"/> public Task <string> GetLogsAsync(string channelName, int limit, List <Log> logs) { return(AddTaskToQueue(() => { logs?.Clear(); var retrievedLogs = new List <Log>(); AppCenterLog.Debug(AppCenterLog.LogTag, $"Trying to get up to {limit} logs from storage for {channelName}"); var idPairs = new List <Tuple <Guid?, long> >(); var failedToDeserializeALog = false; var objectEntries = _storageAdapter.Select(TableName, ColumnChannelName, channelName, ColumnIdName, _pendingDbIdentifiers.Cast <object>().ToArray(), limit); var retrievedEntries = objectEntries.Select(entries => new LogEntry() { Id = (long)entries[0], Channel = (string)entries[1], Log = (string)entries[2] } ).ToList(); foreach (var entry in retrievedEntries) { try { var log = LogSerializer.DeserializeLog(entry.Log); retrievedLogs.Add(log); idPairs.Add(Tuple.Create(log.Sid, Convert.ToInt64(entry.Id))); } catch (JsonException e) { AppCenterLog.Error(AppCenterLog.LogTag, "Cannot deserialize a log in storage", e); failedToDeserializeALog = true; _storageAdapter.Delete(TableName, ColumnIdName, entry.Id); } } if (failedToDeserializeALog) { AppCenterLog.Warn(AppCenterLog.LogTag, "Deleted logs that could not be deserialized"); } if (idPairs.Count == 0) { AppCenterLog.Debug(AppCenterLog.LogTag, $"No available logs in storage for channel '{channelName}'"); return null; } // Process the results var batchId = Guid.NewGuid().ToString(); ProcessLogIds(channelName, batchId, idPairs); logs?.AddRange(retrievedLogs); return batchId; })); }