use of com.microsoft.appcenter.ingestion.models.Log in project mobile-center-sdk-android by Microsoft.
the class DefaultChannel method handleSendingSuccess.
/**
* The actual implementation to react to sending a batch to the server successfully.
*
* @param groupState The group state.
* @param currentState The current state.
* @param batchId The batch ID.
*/
private synchronized void handleSendingSuccess(@NonNull final GroupState groupState, int currentState, @NonNull final String batchId) {
if (checkStateDidNotChange(groupState, currentState)) {
String groupName = groupState.mName;
mPersistence.deleteLogs(groupName, batchId);
List<Log> removedLogsForBatchId = groupState.mSendingBatches.remove(batchId);
GroupListener groupListener = groupState.mListener;
if (groupListener != null) {
for (Log log : removedLogsForBatchId) {
groupListener.onSuccess(log);
}
}
checkPendingLogs(groupName);
}
}
use of com.microsoft.appcenter.ingestion.models.Log in project mobile-center-sdk-android by Microsoft.
the class DefaultChannel method triggerIngestion.
/**
* This will, if we're not using the limit for pending batches, trigger sending of a new request.
* It will also reset the counters for sending out items for both the number of items enqueued and
* the handlers. It will do this even if we don't have reached the limit
* of pending batches or the time interval.
*
* @param groupName the group name
*/
private synchronized void triggerIngestion(@NonNull final String groupName) {
if (!mEnabled) {
return;
}
final GroupState groupState = mGroupStates.get(groupName);
int pendingLogCount = groupState.mPendingLogCount;
int maxFetch = Math.min(pendingLogCount, groupState.mMaxLogsPerBatch);
AppCenterLog.debug(LOG_TAG, "triggerIngestion(" + groupName + ") pendingLogCount=" + pendingLogCount);
cancelTimer(groupState);
/* Check if we have reached the maximum number of pending batches, log to LogCat and don't trigger another sending. */
if (groupState.mSendingBatches.size() == groupState.mMaxParallelBatches) {
AppCenterLog.debug(LOG_TAG, "Already sending " + groupState.mMaxParallelBatches + " batches of analytics data to the server.");
return;
}
/* Get a batch from Persistence. */
final List<Log> batch = new ArrayList<>(maxFetch);
final int stateSnapshot = mCurrentState;
final String batchId = mPersistence.getLogs(groupName, maxFetch, batch);
/* Decrement counter. */
groupState.mPendingLogCount -= maxFetch;
/* Nothing more to do if no logs. */
if (batchId == null) {
return;
}
AppCenterLog.debug(LOG_TAG, "ingestLogs(" + groupState.mName + "," + batchId + ") pendingLogCount=" + groupState.mPendingLogCount);
/* Call group listener before sending logs to ingestion service. */
if (groupState.mListener != null) {
for (Log log : batch) {
groupState.mListener.onBeforeSending(log);
}
}
/* Remember this batch. */
groupState.mSendingBatches.put(batchId, batch);
/*
* Due to bug on old Android versions (verified on 4.0.4),
* if we start an async task from here, i.e. the async persistence handler thread,
* we end up with AsyncTask configured with the wrong Handler to use for onPostExecute
* instead of using main thread as advertised in Javadoc (and its a static field there).
*
* Our SDK guards against an application that would make a first async task in non UI
* thread before SDK is initialized, but we should also avoid corrupting AsyncTask
* with our wrong handler to avoid creating bugs in the application code since we are
* a library.
*
* So make sure we execute the async task from UI thread to avoid any issue.
*/
HandlerUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
sendLogs(groupState, stateSnapshot, batch, batchId);
}
});
}
use of com.microsoft.appcenter.ingestion.models.Log in project mobile-center-sdk-android by Microsoft.
the class DatabasePersistence method getLogs.
@Override
@Nullable
public String getLogs(@NonNull String group, @IntRange(from = 0) int limit, @NonNull List<Log> outLogs) {
/* Log. */
AppCenterLog.debug(LOG_TAG, "Trying to get " + limit + " logs from the Persistence database for " + group);
/* Query database and get scanner. */
DatabaseStorage.DatabaseScanner scanner = mDatabaseStorage.getScanner(COLUMN_GROUP, group);
/* Add logs to output parameter after deserialization if logs are not already sent. */
int count = 0;
Map<Long, Log> candidates = new TreeMap<>();
List<Long> failedDbIdentifiers = new ArrayList<>();
File largePayloadGroupDirectory = getLargePayloadGroupDirectory(group);
for (Iterator<ContentValues> iterator = scanner.iterator(); iterator.hasNext() && count < limit; ) {
ContentValues values = iterator.next();
Long dbIdentifier = values.getAsLong(DatabaseManager.PRIMARY_KEY);
/*
* When we can't even read the identifier (in this case ContentValues is most likely empty).
* That probably means it contained a record larger than 2MB (from a previous SDK version)
* and we hit the cursor limit.
* Get rid of first non pending log.
*/
if (dbIdentifier == null) {
AppCenterLog.error(LOG_TAG, "Empty database record, probably content was larger than 2MB, need to delete as it's now corrupted.");
DatabaseStorage.DatabaseScanner idScanner = mDatabaseStorage.getScanner(COLUMN_GROUP, group, true);
for (ContentValues idValues : idScanner) {
Long invalidId = idValues.getAsLong(DatabaseManager.PRIMARY_KEY);
if (!mPendingDbIdentifiers.contains(invalidId) && !candidates.containsKey(invalidId)) {
/* Found the record to delete that we could not read when selecting all fields. */
deleteLog(largePayloadGroupDirectory, invalidId);
AppCenterLog.error(LOG_TAG, "Empty database corrupted empty record deleted, id=" + invalidId);
break;
}
}
idScanner.close();
continue;
}
/* If the log is already in pending state, then skip. Otherwise put the log to candidate container. */
if (!mPendingDbIdentifiers.contains(dbIdentifier)) {
try {
/* Deserialize JSON to Log. */
String logPayload;
String databasePayload = values.getAsString(COLUMN_LOG);
if (databasePayload == null) {
File file = getLargePayloadFile(largePayloadGroupDirectory, dbIdentifier);
AppCenterLog.debug(LOG_TAG, "Read payload file " + file);
logPayload = StorageHelper.InternalStorage.read(file);
if (logPayload == null) {
throw new JSONException("Log payload is null and not stored as a file.");
}
} else {
logPayload = databasePayload;
}
candidates.put(dbIdentifier, getLogSerializer().deserializeLog(logPayload));
count++;
} catch (JSONException e) {
/* If it is not able to deserialize, delete and get another log. */
AppCenterLog.error(LOG_TAG, "Cannot deserialize a log in the database", e);
/* Put the failed identifier to delete. */
failedDbIdentifiers.add(dbIdentifier);
}
}
}
scanner.close();
/* Delete any logs that cannot be de-serialized. */
if (failedDbIdentifiers.size() > 0) {
for (long id : failedDbIdentifiers) {
deleteLog(largePayloadGroupDirectory, id);
}
AppCenterLog.warn(LOG_TAG, "Deleted logs that cannot be deserialized");
}
/* No logs found. */
if (candidates.size() <= 0) {
AppCenterLog.debug(LOG_TAG, "No logs found in the Persistence database at the moment");
return null;
}
/* Generate an ID. */
String id = UUIDUtils.randomUUID().toString();
/* Log. */
AppCenterLog.debug(LOG_TAG, "Returning " + candidates.size() + " log(s) with an ID, " + id);
AppCenterLog.debug(LOG_TAG, "The SID/ID pairs for returning log(s) is/are:");
List<Long> pendingDbIdentifiersGroup = new ArrayList<>();
for (Map.Entry<Long, Log> entry : candidates.entrySet()) {
Long dbIdentifier = entry.getKey();
/* Change a database identifier to pending state. */
mPendingDbIdentifiers.add(dbIdentifier);
/* Store a database identifier to a group of the ID. */
pendingDbIdentifiersGroup.add(dbIdentifier);
/* Add to output parameter. */
outLogs.add(entry.getValue());
/* Log. */
AppCenterLog.debug(LOG_TAG, "\t" + entry.getValue().getSid() + " / " + dbIdentifier);
}
/* Update pending IDs. */
mPendingDbIdentifiersGroups.put(group + id, pendingDbIdentifiersGroup);
return id;
}
use of com.microsoft.appcenter.ingestion.models.Log in project mobile-center-sdk-android by Microsoft.
the class DefaultChannelTest method listener.
@Test
public void listener() throws Persistence.PersistenceException {
@SuppressWarnings("ConstantConditions") DefaultChannel channel = new DefaultChannel(mock(Context.class), null, mock(Persistence.class), mock(IngestionHttp.class), mCoreHandler);
channel.addGroup(TEST_GROUP, 50, BATCH_TIME_INTERVAL, MAX_PARALLEL_BATCHES, null);
Channel.Listener listener = spy(new AbstractChannelListener());
channel.addListener(listener);
Log log = mock(Log.class);
channel.enqueue(log, TEST_GROUP);
verify(listener).onEnqueuingLog(log, TEST_GROUP);
verify(listener).shouldFilter(log);
/* Check no more calls after removing listener. */
log = mock(Log.class);
channel.removeListener(listener);
channel.enqueue(log, TEST_GROUP);
verifyNoMoreInteractions(listener);
}
use of com.microsoft.appcenter.ingestion.models.Log in project mobile-center-sdk-android by Microsoft.
the class DefaultChannelTest method shutdown.
@Test
public void shutdown() throws Exception {
Persistence mockPersistence = mock(Persistence.class);
IngestionHttp mockIngestion = mock(IngestionHttp.class);
Channel.GroupListener mockListener = mock(Channel.GroupListener.class);
when(mockPersistence.getLogs(any(String.class), anyInt(), Matchers.<List<Log>>any())).then(getGetLogsAnswer(1));
DefaultChannel channel = new DefaultChannel(mock(Context.class), UUIDUtils.randomUUID().toString(), mockPersistence, mockIngestion, mCoreHandler);
channel.addGroup(TEST_GROUP, 1, BATCH_TIME_INTERVAL, MAX_PARALLEL_BATCHES, mockListener);
/* Enqueuing 1 event. */
channel.enqueue(mock(Log.class), TEST_GROUP);
verify(mockListener).onBeforeSending(notNull(Log.class));
channel.shutdown();
verify(mockListener, never()).onFailure(any(Log.class), any(Exception.class));
verify(mockPersistence).clearPendingLogState();
}
Aggregations