private void HandleSendingFailure(State state, string batchId, Exception exception) { IList <Log> removedLogs; using (_mutex.GetLock(state)) { removedLogs = _sendingBatches[batchId]; _sendingBatches.Remove(batchId); } if (FailedToSendLog != null) { foreach (var log in removedLogs) { AppCenterLog.Debug(AppCenterLog.LogTag, $"Invoke FailedToSendLog event for channel '{Name}'"); FailedToSendLog?.Invoke(this, new FailedToSendLogEventArgs(log, exception)); } } try { _storage.DeleteLogs(Name, batchId); } catch (StorageException e) { AppCenterLog.Warn(AppCenterLog.LogTag, $"Could not delete logs for batch {batchId}", e); } }
bool ConfirmationHandler() { XamarinDevice.BeginInvokeOnMainThread(() => { Current.MainPage.DisplayActionSheet("Crash detected. Send anonymous crash report?", null, null, "Send", "Always Send", "Don't Send").ContinueWith((arg) => { var answer = arg.Result; UserConfirmation userConfirmationSelection; if (answer == "Send") { userConfirmationSelection = UserConfirmation.Send; } else if (answer == "Always Send") { userConfirmationSelection = UserConfirmation.AlwaysSend; } else { userConfirmationSelection = UserConfirmation.DontSend; } AppCenterLog.Debug(LogTag, "User selected confirmation option: \"" + answer + "\""); Crashes.NotifyUserConfirmation(userConfirmationSelection); }); }); return(true); }
private void SaveExceptionFile(Directory directory, string fileName, Exception exception) { try { using (var fileStream = NewFileStream(Path.Combine(directory.FullName, fileName), FileMode.Create)) { var formatter = new BinaryFormatter(); formatter.Serialize(fileStream, exception); } AppCenterLog.Debug(Crashes.LogTag, $"Saved exception in directory {ErrorStorageDirectoryName} with name {fileName}."); } catch (Exception e) { if (!exception.GetType().IsSerializable) { // Note that this still saves an empty file which acts as a marker for error report life cycle. Same as Android SDK. AppCenterLog.Warn(Crashes.LogTag, $"Cannot serialize {exception.GetType().FullName} exception for client side inspection. " + "If you want to have access to the exception in the callbacks, please add a Serializable attribute " + "and a deserialization constructor to the exception class."); } else { AppCenterLog.Warn(Crashes.LogTag, "Failed to serialize exception for client side inspection.", e); } } }
// Determines whether the application has started already and is not suspended, // but ApplicationLifecycleHelper has not yet fired an initial "resume" event. private static async Task <bool> HasStartedAndNeedsResume() { var needsResume = false; try { // Don't use CurrentSynchronizationContext as that seems to cause an error in Unity applications. var asyncAction = CoreApplication.MainView?.CoreWindow?.Dispatcher.RunAsync( CoreDispatcherPriority.Normal, () => { // If started already, a resume has already occurred. if (_started) { return; } if (CoreApplication.Views.Any(view => view.CoreWindow != null && view.CoreWindow.Visible)) { needsResume = true; } }); if (asyncAction != null) { await asyncAction; } } catch (Exception e) when(e is COMException || e is InvalidOperationException) { // If MainView can't be accessed, a COMException or InvalidOperationException is thrown. It means that the // MainView hasn't been created, and thus the UI hasn't appeared yet. AppCenterLog.Debug(AppCenterLog.LogTag, "Not invoking resume immediately because UI is not ready."); } return(needsResume); }
private void HandleSendingFailure(State state, string batchId, IngestionException e) { AppCenterLog.Error(AppCenterLog.LogTag, $"Sending logs for channel '{Name}', batch '{batchId}' failed: {e?.Message}"); try { var isRecoverable = e?.IsRecoverable ?? false; List <Log> removedLogs; using (_mutex.GetLock(state)) { removedLogs = _sendingBatches[batchId]; _sendingBatches.Remove(batchId); if (isRecoverable) { _pendingLogCount += removedLogs.Count; } } if (!isRecoverable && FailedToSendLog != null) { foreach (var log in removedLogs) { FailedToSendLog?.Invoke(this, new FailedToSendLogEventArgs(log, e)); } } Suspend(state, !isRecoverable, e); } catch (StatefulMutexException) { AppCenterLog.Debug(AppCenterLog.LogTag, "Handle sending failure operation has been canceled. Callbacks were invoked when channel suspended."); } }
private void CheckPendingLogs(State state) { if (!_enabled) { AppCenterLog.Info(AppCenterLog.LogTag, "The service has been disabled. Stop processing logs."); return; } AppCenterLog.Debug(AppCenterLog.LogTag, $"CheckPendingLogs({Name}) pending log count: {_pendingLogCount}"); using (_mutex.GetLock()) { if (_pendingLogCount >= _maxLogsPerBatch) { _batchScheduled = true; Task.Run(async() => { await TriggerIngestionAsync(state).ConfigureAwait(false); }); } else if (_pendingLogCount > 0 && !_batchScheduled) { _batchScheduled = true; // No need wait _batchTimeInterval here. Task.Run(async() => { await Task.Delay((int)_batchTimeInterval.TotalMilliseconds).ConfigureAwait(false); if (_batchScheduled) { await TriggerIngestionAsync(_mutex.State).ConfigureAwait(false); } }); } } }
/// <summary> /// Asynchronously deletes all logs for a particular channel /// </summary> /// <param name="channelName">Name of the channel to delete logs for</param> /// <exception cref="StorageException"/> public Task DeleteLogs(string channelName) { var task = new Task(() => { try { AppCenterLog.Debug(AppCenterLog.LogTag, $"Deleting all logs from storage for channel '{channelName}'"); ClearPendingLogStateWithoutEnqueue(channelName); _storageAdapter.DeleteAsync <LogEntry>(entry => entry.Channel == channelName) .Wait(); } catch (KeyNotFoundException e) { throw new StorageException(e); } }); try { _queue.Add(task); } catch (InvalidOperationException) { throw new StorageException("The operation has been cancelled"); } _flushSemaphore.Release(); return(task); }
/// <summary> /// Asynchronously deletes all logs in a particular batch /// </summary> /// <param name="channelName">The name of the channel associated with the batch</param> /// <param name="batchId">The batch identifier</param> /// <exception cref="StorageException"/> public Task DeleteLogs(string channelName, string batchId) { return(AddTaskToQueue(() => { try { AppCenterLog.Debug(AppCenterLog.LogTag, $"Deleting logs from storage for channel '{channelName}' with batch id '{batchId}'"); var identifiers = _pendingDbIdentifierGroups[GetFullIdentifier(channelName, batchId)]; _pendingDbIdentifierGroups.Remove(GetFullIdentifier(channelName, batchId)); var deletedIdsMessage = "The IDs for deleting log(s) is/ are:"; foreach (var id in identifiers) { deletedIdsMessage += "\n\t" + id; _pendingDbIdentifiers.Remove(id); } AppCenterLog.Debug(AppCenterLog.LogTag, deletedIdsMessage); foreach (var id in identifiers) { _storageAdapter .DeleteAsync <LogEntry>(entry => entry.Channel == channelName && entry.Id == id) .GetAwaiter().GetResult(); } } catch (KeyNotFoundException e) { throw new StorageException(e); } })); }
public async Task ShutdownAsync() { ThrowIfDisposed(); var tasks = new List <Task>(); lock (_channelGroupLock) { if (_isShutdown) { AppCenterLog.Warn(AppCenterLog.LogTag, "Attempted to shutdown channel multiple times."); return; } _isShutdown = true; _ingestion.Close(); foreach (var channel in _channels) { tasks.Add(channel.ShutdownAsync()); } } await Task.WhenAll(tasks).ConfigureAwait(false); AppCenterLog.Debug(AppCenterLog.LogTag, "Waiting for storage to finish operations."); if (!await _storage.ShutdownAsync(WaitStorageTimeout).ConfigureAwait(false)) { AppCenterLog.Warn(AppCenterLog.LogTag, "Storage taking too long to finish operations; shutting down channel without waiting any longer."); } }
/// <summary> /// Asynchronously deletes all logs in a particular batch /// </summary> /// <param name="channelName">The name of the channel associated with the batch</param> /// <param name="batchId">The batch identifier</param> /// <exception cref="StorageException"/> public Task DeleteLogs(string channelName, string batchId) { return(AddTaskToQueue(() => { try { AppCenterLog.Debug(AppCenterLog.LogTag, $"Deleting logs from storage for channel '{channelName}' with batch id '{batchId}'"); var identifiers = _pendingDbIdentifierGroups[GetFullIdentifier(channelName, batchId)]; _pendingDbIdentifierGroups.Remove(GetFullIdentifier(channelName, batchId)); var deletedIdsMessage = "The IDs for deleting log(s) is/are:"; foreach (var identifier in identifiers) { deletedIdsMessage += "\n\t" + identifier; _pendingDbIdentifiers.Remove(identifier); } AppCenterLog.Debug(AppCenterLog.LogTag, deletedIdsMessage); _storageAdapter.Delete(TableName, ColumnIdName, identifiers.Cast <object>().ToArray()); } catch (KeyNotFoundException e) { throw new StorageException(e); } })); }
internal ErrorReport(AndroidErrorReport androidReport) { Id = androidReport.Id; AppStartTime = DateTimeOffset.FromUnixTimeMilliseconds(androidReport.AppStartTime.Time); AppErrorTime = DateTimeOffset.FromUnixTimeMilliseconds(androidReport.AppErrorTime.Time); Device = androidReport.Device == null ? null : new Device(androidReport.Device); object androidThrowable; try { androidThrowable = androidReport.Throwable; } catch (Exception e) { AppCenterLog.Debug(Crashes.LogTag, "Cannot read throwable from java point of view, probably a .NET exception", e); androidThrowable = null; } AndroidDetails = new AndroidErrorDetails(androidThrowable, androidReport.ThreadName); iOSDetails = null; byte[] exceptionBytes = AndroidExceptionDataManager.LoadWrapperExceptionData(Java.Util.UUID.FromString(Id)); if (exceptionBytes != null) { StackTrace = CrashesUtils.DeserializeException(exceptionBytes); } }
private async Task SignalDeletingLogs(IList <string> sendingBatches) { var logs = new List <Log>(); try { do { var batchId = await _storage.GetLogsAsync(Name, ClearBatchSize, logs).ConfigureAwait(false); if (sendingBatches.Contains(batchId)) { continue; } foreach (var log in logs) { AppCenterLog.Debug(AppCenterLog.LogTag, $"Invoke SendingLog for channel '{Name}'"); SendingLog?.Invoke(this, new SendingLogEventArgs(log)); AppCenterLog.Debug(AppCenterLog.LogTag, $"Invoke FailedToSendLog event for channel '{Name}'"); FailedToSendLog?.Invoke(this, new FailedToSendLogEventArgs(log, new CancellationException())); } }while (logs.Count >= ClearBatchSize); } catch { AppCenterLog.Warn(AppCenterLog.LogTag, "Failed to invoke events for logs being deleted."); } }
private void OnPushNotificationReceivedHandler(PushNotificationChannel sender, WindowsPushNotificationReceivedEventArgs e) { XmlDocument content = null; if (e.NotificationType == PushNotificationType.Toast && (content = e.ToastNotification?.Content) != null) { AppCenterLog.Debug(LogTag, $"Received push notification payload: {content.GetXml()}"); if (ApplicationLifecycleHelper.Instance.IsSuspended) { AppCenterLog.Debug(LogTag, "Application in background. Push callback will be called when user clicks the toast notification."); } else { var pushNotification = ParseAppCenterPush(content); if (pushNotification != null) { e.Cancel = true; PushNotificationReceived?.Invoke(sender, pushNotification); AppCenterLog.Debug(LogTag, "Application in foreground. Intercept push notification and invoke push callback."); } else { AppCenterLog.Debug(LogTag, "Push ignored. It was not sent through App Center."); } } } else { AppCenterLog.Debug(LogTag, $"Push ignored. We only handle Toast notifications but PushNotificationType is '{e.NotificationType}'."); } }
/// <summary> /// Saves an error log on disk. /// </summary> /// <param name="exception">The exception that caused the crash.</param> /// <param name="errorLog">The error log.</param> public virtual void InstanceSaveErrorLogFiles(System.Exception exception, ManagedErrorLog errorLog) { try { // Serialize main log file. var errorLogString = LogSerializer.Serialize(errorLog); var errorLogFileName = errorLog.Id + ErrorLogFileExtension; AppCenterLog.Debug(Crashes.LogTag, "Saving uncaught exception."); var directory = InstanceGetErrorStorageDirectory(); directory.CreateFile(errorLogFileName, errorLogString); AppCenterLog.Debug(Crashes.LogTag, $"Saved error log in directory {ErrorStorageDirectoryName} with name {errorLogFileName}."); try { // Serialize exception as raw stack trace. var exceptionFileName = errorLog.Id + ExceptionFileExtension; directory.CreateFile(exceptionFileName, exception.ToString()); AppCenterLog.Debug(Crashes.LogTag, $"Saved exception in directory {ErrorStorageDirectoryName} with name {exceptionFileName}."); } catch (System.Exception ex) { AppCenterLog.Warn(Crashes.LogTag, "Failed to serialize exception for client side inspection.", ex); } } catch (System.Exception ex) { AppCenterLog.Error(Crashes.LogTag, "Failed to save error log.", ex); } }
private async Task TriggerIngestionAsync(State state) { using (await _mutex.GetLockAsync(state).ConfigureAwait(false)) { if (!_enabled || !_batchScheduled) { return; } AppCenterLog.Debug(AppCenterLog.LogTag, $"TriggerIngestion({Name}) pending log count: {_pendingLogCount}"); _batchScheduled = false; if (_sendingBatches.Count >= _maxParallelBatches) { AppCenterLog.Debug(AppCenterLog.LogTag, $"Already sending {_maxParallelBatches} batches of analytics data to the server"); return; } } // Get a batch from storage var logs = new List <Log>(); var batchId = await _storage.GetLogsAsync(Name, _maxLogsPerBatch, logs).ConfigureAwait(false); if (batchId != null) { using (await _mutex.GetLockAsync(state).ConfigureAwait(false)) { _sendingBatches.Add(batchId, logs); _pendingLogCount -= logs.Count; } try { // Before sending logs, trigger the sending event for this channel if (SendingLog != null) { foreach (var log in logs) { AppCenterLog.Debug(AppCenterLog.LogTag, $"Invoke SendingLog event for channel '{Name}'"); SendingLog?.Invoke(this, new SendingLogEventArgs(log)); } } // If the optional Install ID has no value, default to using empty GUID var installId = await AppCenter.GetInstallIdAsync().ConfigureAwait(false) ?? Guid.Empty; var ingestionCall = _ingestion.Call(_appSecret, installId, logs); using (await _mutex.GetLockAsync(state).ConfigureAwait(false)) { _calls.Add(ingestionCall); } ingestionCall.ContinueWith(call => HandleSendingResult(state, batchId, call)); CheckPendingLogs(state); } catch (StorageException) { AppCenterLog.Warn(AppCenterLog.LogTag, "Something went wrong sending logs to ingestion"); } } }
/// <summary> /// Asynchronously clears the stored state of logs that have been retrieved /// </summary> /// <param name="channelName"></param> public Task ClearPendingLogState(string channelName) { return(AddTaskToQueue(() => { ClearPendingLogStateWithoutEnqueue(channelName); AppCenterLog.Debug(AppCenterLog.LogTag, $"Clear pending log states for channel {channelName}"); })); }
/// <summary> /// Creates an instance of Storage given a connection object. /// </summary> internal Storage(IStorageAdapter adapter, string databasePath) { AppCenterLog.Debug(AppCenterLog.LogTag, $"Creating database at: {databasePath}"); _storageAdapter = adapter; _databasePath = databasePath; _queue.Add(new Task(InitializeDatabase)); _queueFlushTask = Task.Run(FlushQueueAsync); }
public Task DeleteLogs(string channelName) { lock (this) { AppCenterLog.Debug(AppCenterLog.LogTag, $"Storage.DeleteLogs for channelName={channelName}"); _storage.Remove(channelName); return(TaskExtension.GetCompletedTask()); } }
public Task PutLog(string channelName, Log log) { lock (this) { AppCenterLog.Debug(AppCenterLog.LogTag, $"Storage.PutLog for channelName={channelName}: {log}"); this[channelName].Add(log); return(TaskExtension.GetCompletedTask()); } }
internal static void EnableTls12() { // ReSharper disable once InvertIf if ((ServicePointManager.SecurityProtocol & SecurityProtocolType.Tls12) != SecurityProtocolType.Tls12) { ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; AppCenterLog.Debug(AppCenterLog.LogTag, "Enabled TLS 1.2 explicitly as it was disabled."); } }
public Task DeleteLogs(string channelName, string batchId) { lock (this) { AppCenterLog.Debug(AppCenterLog.LogTag, $"Storage.DeleteLogs for channelName={channelName} and batchId={batchId}"); var batch = _pending[batchId]; this[channelName].RemoveAll(log => batch.Contains(log)); return(TaskExtension.GetCompletedTask()); } }
/// <summary> /// If enabled, register push channel and send URI to backend. /// Also start intercepting pushes. /// If disabled and previously enabled, stop listening for pushes (they will still be received though). /// </summary> private void ApplyEnabledState(bool enabled) { if (enabled) { // We expect caller of this method to lock on _mutex, we can't do it here as that lock is not recursive AppCenterLog.Debug(LogTag, "Getting push token..."); var state = _mutex.State; CreatePushNotificationChannel(channel => { try { using (_mutex.GetLock(state)) { LatestPushToken = channel?.Uri; if (!string.IsNullOrEmpty(LatestPushToken)) { // Save channel member _channel = channel; // Subscribe to UserId Change. UserIdContext.UserIdUpdated -= OnUserIdUpdated; UserIdContext.UserIdUpdated += OnUserIdUpdated; // Subscribe to push. channel.PushNotificationReceived += OnPushNotificationReceivedHandler; // Send channel URI to backend AppCenterLog.Debug(LogTag, $"Push token '{LatestPushToken}'"); var pushInstallationLog = new PushInstallationLog(null, LatestPushToken, null, Guid.NewGuid(), UserIdContext.Instance.UserId); // Do not await the call to EnqueueAsync or the UI thread can be blocked! #pragma warning disable CS4014 Channel.EnqueueAsync(pushInstallationLog); #pragma warning restore } else { AppCenterLog.Error(LogTag, "Push service registering with App Center backend has failed."); } } } catch (StatefulMutexException) { AppCenterLog.Warn(LogTag, "Push Enabled state changed after creating channel."); } }); } else if (_channel != null) { LatestPushToken = null; UserIdContext.UserIdUpdated -= OnUserIdUpdated; _channel.PushNotificationReceived -= OnPushNotificationReceivedHandler; } }
private void CreatePushNotificationChannel(Action <PushNotificationChannel> created) { Task <PushNotificationChannel> CreatePushNotificationChannelForApplicationAsync() { try { return(new WindowsPushNotificationChannelManager() .CreatePushNotificationChannelForApplicationAsync() .AsTask()); } catch (Exception e) { return(Task.FromException <PushNotificationChannel>(e)); } } void OnNetworkStateChange(object sender, EventArgs e) { if (NetworkStateAdapter.IsConnected) { NetworkStateAdapter.NetworkStatusChanged -= OnNetworkStateChange; AppCenterLog.Debug(LogTag, "Second attempt to create notification channel..."); // Second attempt is the last one anyway. CreatePushNotificationChannelForApplicationAsync().ContinueWith(task => { if (task.IsFaulted) { AppCenterLog.Error(LogTag, "Unable to create notification channel.", task.Exception); return; } created?.Invoke(task.Result); }); } } // If this isn't the first time after installation, the notification channel is created successfully even without network. CreatePushNotificationChannelForApplicationAsync().ContinueWith(task => { if (task.IsFaulted && NetworkStateAdapter.IsConnected) { AppCenterLog.Error(LogTag, "Unable to create notification channel.", task.Exception); return; } if (!task.IsFaulted && task.Result != null) { created?.Invoke(task.Result); return; } AppCenterLog.Debug(LogTag, "The network isn't connected, another attempt to crate push notification channel " + "will be made after the network is available."); NetworkStateAdapter.NetworkStatusChanged += OnNetworkStateChange; }); }
/// <exception cref="AppCenterException">Attempted to add duplicate channel to group</exception> public IChannelUnit AddChannel(string name, int maxLogsPerBatch, TimeSpan batchTimeInterval, int maxParallelBatches) { ThrowIfDisposed(); lock (_channelGroupLock) { AppCenterLog.Debug(AppCenterLog.LogTag, $"AddChannel({name})"); var newChannel = new Channel(name, maxLogsPerBatch, batchTimeInterval, maxParallelBatches, AppSecret, _ingestion, _storage); AddChannel(newChannel); return(newChannel); } }
/// <summary> /// Suspend channel. /// </summary> /// <param name="state">Current state.</param> /// <param name="deleteLogs">Value indicating whether logs should be enabled or disabled.</param> /// <param name="exception">Possible error if unsuccessful.</param> /// <param name="needDisableChannel">Value indicating whether channel should be disabled. True by default.</param> private void Suspend(State state, bool deleteLogs, Exception exception, bool needDisableChannel = true) { AppCenterLog.Debug(AppCenterLog.LogTag, $"Suspend channel: '{Name}'"); try { IList <string> sendingBatches = null; IList <Log> unsentLogs = null; using (_mutex.GetLock(state)) { if (needDisableChannel) { _enabled = false; } _batchScheduled = false; _discardLogs = deleteLogs; if (deleteLogs) { sendingBatches = _sendingBatches.Keys.ToList(); unsentLogs = _sendingBatches.Values.SelectMany(batch => batch).ToList(); _sendingBatches.Clear(); } state = _mutex.InvalidateState(); } if (unsentLogs != null && FailedToSendLog != null) { foreach (var log in unsentLogs) { AppCenterLog.Debug(AppCenterLog.LogTag, $"Invoke FailedToSendLog event for channel '{Name}'"); FailedToSendLog?.Invoke(this, new FailedToSendLogEventArgs(log, exception)); } } if (deleteLogs) { IList <IServiceCall> calls; using (_mutex.GetLock(state)) { calls = _calls.ToList(); _calls.Clear(); _pendingLogCount = 0; TriggerDeleteLogsOnSuspending(sendingBatches); } foreach (var call in calls) { call.Cancel(); } } _storage.ClearPendingLogState(Name); } catch (StatefulMutexException) { AppCenterLog.Warn(AppCenterLog.LogTag, "The suspend operation has been canceled"); } }
/// <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; })); }
// Internal and static so that it can be tested more easily internal static bool HasSessionTimedOut(long now, long lastQueuedLogTime, long lastResumedTime, long lastPausedTime) { if (lastPausedTime == 0) { return(false); } var noLogSentForLong = lastQueuedLogTime == 0 || now - lastQueuedLogTime >= SessionTimeout; var wasBackgroundForLong = lastResumedTime - Math.Max(lastPausedTime, lastQueuedLogTime) >= SessionTimeout; AppCenterLog.Debug(Analytics.Instance.LogTag, $"noLogSentForLong={noLogSentForLong} " + $"wasBackgroundForLong={wasBackgroundForLong}"); return(noLogSentForLong && wasBackgroundForLong); }
/// <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 retrievedEntries = _storageAdapter.GetAsync <LogEntry>(entry => entry.Channel == channelName, limit) .GetAwaiter().GetResult(); foreach (var entry in retrievedEntries) { if (_pendingDbIdentifiers.Contains(entry.Id)) { continue; } 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.DeleteAsync <LogEntry>(row => row.Id == entry.Id) .GetAwaiter().GetResult(); } } 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; })); }
private async Task <PushNotificationChannel> CreatePushNotificationChannelAsync() { PushNotificationChannel channel = null; try { // If this isn't the first time after installation, the notification channel is created successfully even without network. channel = await new WindowsPushNotificationChannelManager().CreatePushNotificationChannelForApplicationAsync() .AsTask().ConfigureAwait(false); } catch (Exception exception) { if (NetworkStateAdapter.IsConnected) { AppCenterLog.Error(LogTag, "Unable to create notification channel.", exception); return(null); } } if (channel != null) { return(channel); } AppCenterLog.Debug(LogTag, "The network isn't connected, another attempt will be made after the network is available."); var networkSemaphore = new SemaphoreSlim(0); void NetworkStateChangeHandler(object sender, EventArgs e) { if (NetworkStateAdapter.IsConnected) { networkSemaphore.Release(); } } NetworkStateAdapter.NetworkStatusChanged += NetworkStateChangeHandler; await networkSemaphore.WaitAsync().ConfigureAwait(false); NetworkStateAdapter.NetworkStatusChanged -= NetworkStateChangeHandler; AppCenterLog.Debug(LogTag, "Second attempt to create notification channel..."); try { // Second attempt is the last one anyway. return(await new WindowsPushNotificationChannelManager().CreatePushNotificationChannelForApplicationAsync() .AsTask().ConfigureAwait(false)); } catch (Exception exception) { AppCenterLog.Error(LogTag, "Unable to create notification channel.", exception); return(null); } }
public void Pause() { lock (_lockObject) { if (_currentSessionState == SessionState.Inactive) { AppCenterLog.Warn(Analytics.Instance.LogTag, "Trying to pause already inactive session."); return; } AppCenterLog.Debug(Analytics.Instance.LogTag, "SessionTracker.Pause"); _lastPausedTime = TimeHelper.CurrentTimeInMilliseconds(); _currentSessionState = SessionState.Inactive; } }