1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tradefed.retry; 17 18 import com.android.tradefed.invoker.IInvocationContext; 19 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 20 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 21 import com.android.tradefed.log.LogUtil.CLog; 22 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 23 import com.android.tradefed.result.CollectingTestListener; 24 import com.android.tradefed.result.EventsLoggerListener; 25 import com.android.tradefed.result.FailureDescription; 26 import com.android.tradefed.result.ILogSaver; 27 import com.android.tradefed.result.ILogSaverListener; 28 import com.android.tradefed.result.ITestInvocationListener; 29 import com.android.tradefed.result.InputStreamSource; 30 import com.android.tradefed.result.LogDataType; 31 import com.android.tradefed.result.LogFile; 32 import com.android.tradefed.result.MultiFailureDescription; 33 import com.android.tradefed.result.ResultAndLogForwarder; 34 import com.android.tradefed.result.TestDescription; 35 import com.android.tradefed.result.TestResult; 36 import com.android.tradefed.result.TestRunResult; 37 import com.android.tradefed.result.retry.ISupportGranularResults; 38 import com.android.tradefed.result.skipped.SkipReason; 39 import com.android.tradefed.util.FileUtil; 40 41 import com.google.common.annotations.VisibleForTesting; 42 43 import java.io.File; 44 import java.io.IOException; 45 import java.util.ArrayList; 46 import java.util.HashMap; 47 import java.util.HashSet; 48 import java.util.LinkedHashMap; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Map.Entry; 52 import java.util.Set; 53 import java.util.stream.Collectors; 54 55 /** 56 * Special forwarder that aggregates the results when needed, based on the retry strategy that was 57 * taken. 58 */ 59 public class ResultAggregator extends CollectingTestListener { 60 61 /* Forwarder to ALL result reporters */ 62 private ResultAndLogForwarder mAllForwarder; 63 /* Forwarder to result reporters that only support aggregated results */ 64 private ResultAndLogForwarder mAggregatedForwarder; 65 /* Forwarder to result reporters that support the attempt reporting */ 66 private ResultAndLogForwarder mDetailedForwarder; 67 private RetryStrategy mRetryStrategy; 68 // Track whether or not a module was started. 69 private boolean mModuleInProgress = false; 70 71 // Holders for results in progress 72 private TestRunResult mDetailedRunResults = null; 73 private boolean mShouldReportFailure = true; 74 private List<FailureDescription> mAllDetailedFailures = new ArrayList<>(); 75 // Since we store some of the module level events, ensure the logs order is maintained. 76 private Map<String, LogFile> mDetailedModuleLogs = new LinkedHashMap<>(); 77 78 private boolean mUpdatedDetailedReporting = false; 79 80 // In some configuration of non-module retry, all attempts of runs might not be adjacent. We 81 // track that a special handling needs to be applied for this case. 82 private boolean mUnorderedRetry = true; 83 // Track whether run start was called for a module. 84 private boolean mRunStartCalled = false; 85 // Stores the results from non-module test runs until they are ready to be replayed. 86 private final Map<String, List<TestRunResult>> mPureRunResultForAgg = new LinkedHashMap<>(); 87 88 private ILogSaver mLogSaver; 89 private EventsLoggerListener mAggregatedEventsLogger; 90 private EventsLoggerListener mDetailedEventsLogger; 91 ResultAggregator(List<ITestInvocationListener> listeners, RetryStrategy strategy)92 public ResultAggregator(List<ITestInvocationListener> listeners, RetryStrategy strategy) { 93 94 List<ITestInvocationListener> supportDetails = 95 listeners 96 .stream() 97 .filter( 98 i -> 99 ((i instanceof ISupportGranularResults) 100 && ((ISupportGranularResults) i) 101 .supportGranularResults())) 102 .collect(Collectors.toList()); 103 List<ITestInvocationListener> noSupportDetails = 104 listeners 105 .stream() 106 .filter( 107 i -> 108 !(i instanceof ISupportGranularResults) 109 || !((ISupportGranularResults) i) 110 .supportGranularResults()) 111 .collect(Collectors.toList()); 112 113 mDetailedEventsLogger = new EventsLoggerListener("detailed-events"); 114 supportDetails.add(mDetailedEventsLogger); 115 mAggregatedEventsLogger = new EventsLoggerListener("aggregated-events"); 116 noSupportDetails.add(mAggregatedEventsLogger); 117 118 mAggregatedForwarder = new ResultAndLogForwarder(noSupportDetails); 119 mDetailedForwarder = new ResultAndLogForwarder(supportDetails); 120 List<ITestInvocationListener> allListeners = new ArrayList<>(listeners); 121 allListeners.add(mDetailedEventsLogger); 122 allListeners.add(mAggregatedEventsLogger); 123 mAllForwarder = new ResultAndLogForwarder(allListeners); 124 125 mRetryStrategy = strategy; 126 MergeStrategy mergeStrategy = MergeStrategy.getMergeStrategy(mRetryStrategy); 127 setMergeStrategy(mergeStrategy); 128 } 129 130 /** Sets the new reporting. */ setUpdatedReporting(boolean updatedReporting)131 public void setUpdatedReporting(boolean updatedReporting) { 132 mUpdatedDetailedReporting = updatedReporting; 133 } 134 135 /** {@inheritDoc} */ 136 @Override invocationStarted(IInvocationContext context)137 public void invocationStarted(IInvocationContext context) { 138 super.invocationStarted(context); 139 mAllForwarder.invocationStarted(context); 140 } 141 142 /** {@inheritDoc} */ 143 @Override invocationFailed(Throwable cause)144 public void invocationFailed(Throwable cause) { 145 super.invocationFailed(cause); 146 mAllForwarder.invocationFailed(cause); 147 } 148 149 /** {@inheritDoc} */ 150 @Override invocationFailed(FailureDescription failure)151 public void invocationFailed(FailureDescription failure) { 152 super.invocationFailed(failure); 153 mAllForwarder.invocationFailed(failure); 154 } 155 156 @Override invocationSkipped(SkipReason reason)157 public void invocationSkipped(SkipReason reason) { 158 super.invocationSkipped(reason); 159 mAllForwarder.invocationSkipped(reason); 160 } 161 162 /** {@inheritDoc} */ 163 @Override invocationEnded(long elapsedTime)164 public void invocationEnded(long elapsedTime) { 165 if (!mPureRunResultForAgg.isEmpty()) { 166 for (String name : mPureRunResultForAgg.keySet()) { 167 forwardTestRunResults(mPureRunResultForAgg.get(name), mAggregatedForwarder); 168 } 169 mPureRunResultForAgg.clear(); 170 } 171 172 forwardDetailedFailure(); 173 for (Entry<String, LogFile> assos : mDetailedModuleLogs.entrySet()) { 174 mDetailedForwarder.logAssociation(assos.getKey(), assos.getValue()); 175 } 176 mDetailedModuleLogs.clear(); 177 super.invocationEnded(elapsedTime); 178 // Make sure to forward the logs for the invocation. 179 forwardAggregatedInvocationLogs(); 180 181 // Log the aggregated events for debugging 182 mAggregatedEventsLogger.invocationEnded(elapsedTime); 183 saveEventsLog(mAggregatedEventsLogger.getLoggedEvents(), "aggregated-events"); 184 // Log the detailed events for debugging 185 mDetailedEventsLogger.invocationEnded(elapsedTime); 186 saveEventsLog(mDetailedEventsLogger.getLoggedEvents(), "detailed-events"); 187 188 mAllForwarder.invocationEnded(elapsedTime); 189 } 190 191 /** 192 * Forward all the invocation level logs to the result reporters that don't support the granular 193 * results. 194 */ forwardAggregatedInvocationLogs()195 public final void forwardAggregatedInvocationLogs() { 196 for (String key : getNonAssociatedLogFiles().keySet()) { 197 for (LogFile log : getNonAssociatedLogFiles().get(key)) { 198 mAggregatedForwarder.logAssociation(key, log); 199 } 200 } 201 } 202 cleanEventsFiles()203 public void cleanEventsFiles() { 204 if (mAggregatedEventsLogger != null) { 205 FileUtil.deleteFile(mAggregatedEventsLogger.getLoggedEvents()); 206 } 207 if (mDetailedEventsLogger != null) { 208 FileUtil.deleteFile(mDetailedEventsLogger.getLoggedEvents()); 209 } 210 } 211 212 /** {@inheritDoc} */ 213 @Override testModuleStarted(IInvocationContext moduleContext)214 public void testModuleStarted(IInvocationContext moduleContext) { 215 mUnorderedRetry = false; 216 mRunStartCalled = false; 217 if (!mPureRunResultForAgg.isEmpty()) { 218 for (String name : mPureRunResultForAgg.keySet()) { 219 forwardTestRunResults(mPureRunResultForAgg.get(name), mAggregatedForwarder); 220 } 221 mPureRunResultForAgg.clear(); 222 } 223 224 // Reset the reporting since we start a new module 225 mShouldReportFailure = true; 226 if (mDetailedRunResults != null) { 227 forwardDetailedFailure(); 228 } 229 230 mModuleInProgress = true; 231 super.testModuleStarted(moduleContext); 232 mAllForwarder.testModuleStarted(moduleContext); 233 } 234 235 /** {@inheritDoc} */ 236 @Override setLogSaver(ILogSaver logSaver)237 public void setLogSaver(ILogSaver logSaver) { 238 mLogSaver = logSaver; 239 super.setLogSaver(logSaver); 240 mAllForwarder.setLogSaver(logSaver); 241 } 242 243 /** {@inheritDoc} */ 244 @Override testLog(String dataName, LogDataType dataType, InputStreamSource dataStream)245 public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) { 246 super.testLog(dataName, dataType, dataStream); 247 mAllForwarder.testLog(dataName, dataType, dataStream); 248 } 249 250 // ====== Forwarders to the detailed result reporters 251 252 @Override testRunStarted(String name, int testCount, int attemptNumber, long startTime)253 public void testRunStarted(String name, int testCount, int attemptNumber, long startTime) { 254 mRunStartCalled = true; 255 // Due to retries happening after several other testRunStart, we need to wait before making 256 // the forwarding. 257 if (!mUnorderedRetry) { 258 if (!mPureRunResultForAgg.isEmpty() && mPureRunResultForAgg.get(name) != null) { 259 forwardTestRunResults(mPureRunResultForAgg.get(name), mAggregatedForwarder); 260 mPureRunResultForAgg.remove(name); 261 } 262 } 263 264 if (!mUpdatedDetailedReporting) { 265 if (mDetailedRunResults != null) { 266 if (mDetailedRunResults.getName().equals(name)) { 267 if (!mDetailedRunResults.isRunFailure()) { 268 if (RetryStrategy.RETRY_ANY_FAILURE.equals(mRetryStrategy)) { 269 mShouldReportFailure = false; 270 } 271 } 272 mDetailedForwarder.testRunEnded( 273 mDetailedRunResults.getElapsedTime(), 274 mDetailedRunResults.getRunProtoMetrics()); 275 mDetailedRunResults = null; 276 } else { 277 mShouldReportFailure = true; 278 forwardDetailedFailure(); 279 } 280 } 281 } 282 super.testRunStarted(name, testCount, attemptNumber, startTime); 283 mDetailedForwarder.testRunStarted(name, testCount, attemptNumber, startTime); 284 } 285 286 @Override testRunFailed(String errorMessage)287 public void testRunFailed(String errorMessage) { 288 super.testRunFailed(errorMessage); 289 // Don't forward here to the detailed forwarder in case we need to clear it. 290 if (mUpdatedDetailedReporting) { 291 mDetailedForwarder.testRunFailed(errorMessage); 292 } 293 } 294 295 @Override testRunFailed(FailureDescription failure)296 public void testRunFailed(FailureDescription failure) { 297 super.testRunFailed(failure); 298 // Don't forward here to the detailed forwarder in case we need to clear it. 299 if (mUpdatedDetailedReporting) { 300 mDetailedForwarder.testRunFailed(failure); 301 } 302 } 303 304 @Override testStarted(TestDescription test, long startTime)305 public void testStarted(TestDescription test, long startTime) { 306 super.testStarted(test, startTime); 307 mDetailedForwarder.testStarted(test, startTime); 308 } 309 310 @Override testIgnored(TestDescription test)311 public void testIgnored(TestDescription test) { 312 super.testIgnored(test); 313 mDetailedForwarder.testIgnored(test); 314 } 315 316 @Override testSkipped(TestDescription test, SkipReason reason)317 public void testSkipped(TestDescription test, SkipReason reason) { 318 super.testSkipped(test, reason); 319 mDetailedForwarder.testSkipped(test, reason); 320 } 321 322 @Override testAssumptionFailure(TestDescription test, String trace)323 public void testAssumptionFailure(TestDescription test, String trace) { 324 super.testAssumptionFailure(test, trace); 325 mDetailedForwarder.testAssumptionFailure(test, trace); 326 } 327 328 @Override testAssumptionFailure(TestDescription test, FailureDescription failure)329 public void testAssumptionFailure(TestDescription test, FailureDescription failure) { 330 super.testAssumptionFailure(test, failure); 331 mDetailedForwarder.testAssumptionFailure(test, failure); 332 } 333 334 @Override testFailed(TestDescription test, String trace)335 public void testFailed(TestDescription test, String trace) { 336 super.testFailed(test, trace); 337 mDetailedForwarder.testFailed(test, trace); 338 } 339 340 @Override testFailed(TestDescription test, FailureDescription failure)341 public void testFailed(TestDescription test, FailureDescription failure) { 342 super.testFailed(test, failure); 343 mDetailedForwarder.testFailed(test, failure); 344 } 345 346 @Override testEnded(TestDescription test, long endTime, HashMap<String, Metric> testMetrics)347 public void testEnded(TestDescription test, long endTime, HashMap<String, Metric> testMetrics) { 348 super.testEnded(test, endTime, testMetrics); 349 mDetailedForwarder.testEnded(test, endTime, testMetrics); 350 } 351 352 @Override logAssociation(String dataName, LogFile logFile)353 public void logAssociation(String dataName, LogFile logFile) { 354 super.logAssociation(dataName, logFile); 355 if (mDetailedRunResults != null) { 356 mDetailedModuleLogs.put(dataName, logFile); 357 } else { 358 mDetailedForwarder.logAssociation(dataName, logFile); 359 } 360 } 361 362 @Override testLogSaved( String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile)363 public void testLogSaved( 364 String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile) { 365 super.testLogSaved(dataName, dataType, dataStream, logFile); 366 mDetailedForwarder.testLogSaved(dataName, dataType, dataStream, logFile); 367 } 368 369 // ===== Forwarders to the aggregated reporters. 370 371 @Override testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics)372 public void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) { 373 super.testRunEnded(elapsedTime, runMetrics); 374 if (mUpdatedDetailedReporting) { 375 mDetailedForwarder.testRunEnded(elapsedTime, runMetrics); 376 } else { 377 mDetailedRunResults = getCurrentRunResults(); 378 if (mDetailedRunResults.isRunFailure()) { 379 FailureDescription currentFailure = mDetailedRunResults.getRunFailureDescription(); 380 if (currentFailure instanceof MultiFailureDescription) { 381 mAllDetailedFailures.addAll( 382 ((MultiFailureDescription) currentFailure).getFailures()); 383 } else { 384 mAllDetailedFailures.add(currentFailure); 385 } 386 } 387 } 388 389 // If we are not a module and we reach here. This allows to support non-suite scenarios 390 if (!mModuleInProgress) { 391 List<TestRunResult> results = 392 mPureRunResultForAgg.getOrDefault( 393 getCurrentRunResults().getName(), new ArrayList<>()); 394 results.add(getCurrentRunResults()); 395 mPureRunResultForAgg.put(getCurrentRunResults().getName(), results); 396 } 397 } 398 399 @Override testModuleEnded()400 public void testModuleEnded() { 401 if (!mUpdatedDetailedReporting) { 402 forwardDetailedFailure(); 403 for (Entry<String, LogFile> assos : mDetailedModuleLogs.entrySet()) { 404 mDetailedForwarder.logAssociation(assos.getKey(), assos.getValue()); 405 } 406 mDetailedModuleLogs.clear(); 407 } 408 mModuleInProgress = false; 409 super.testModuleEnded(); 410 // We still forward the testModuleEnd to the detailed reporters 411 mDetailedForwarder.testModuleEnded(); 412 413 // Only show run results if there was a run. 414 if (mRunStartCalled) { 415 List<TestRunResult> mergedResults = getMergedTestRunResults(); 416 Set<String> resultNames = new HashSet<>(); 417 int expectedTestCount = 0; 418 for (TestRunResult result : mergedResults) { 419 expectedTestCount += result.getExpectedTestCount(); 420 resultNames.add(result.getName()); 421 } 422 // Forward all the results aggregated 423 mAggregatedForwarder.testRunStarted( 424 getCurrentRunResults().getName(), 425 expectedTestCount, 426 /* Attempt*/ 0, 427 /* Start Time */ getCurrentRunResults().getStartTime()); 428 for (TestRunResult runResult : mergedResults) { 429 forwardTestResults(runResult.getTestResults(), mAggregatedForwarder); 430 if (runResult.isRunFailure()) { 431 mAggregatedForwarder.testRunFailed(runResult.getRunFailureDescription()); 432 } 433 // Provide a strong association of the run to its logs. 434 for (String key : runResult.getRunLoggedFiles().keySet()) { 435 for (LogFile logFile : runResult.getRunLoggedFiles().get(key)) { 436 mAggregatedForwarder.logAssociation(key, logFile); 437 } 438 } 439 } 440 441 mAggregatedForwarder.testRunEnded( 442 getCurrentRunResults().getElapsedTime(), 443 getCurrentRunResults().getRunProtoMetrics()); 444 // Ensure we don't carry results from one module to another. 445 for (String name : resultNames) { 446 clearResultsForName(name); 447 } 448 } 449 // Log all the module only logs 450 for (String key : getModuleLogFiles().keySet()) { 451 for (LogFile log : getModuleLogFiles().get(key)) { 452 mAggregatedForwarder.logAssociation(key, log); 453 } 454 } 455 clearModuleLogFiles(); 456 mAggregatedForwarder.testModuleEnded(); 457 mUnorderedRetry = true; 458 } 459 460 @VisibleForTesting getInvocationMetricRunError()461 String getInvocationMetricRunError() { 462 return InvocationMetricLogger.getInvocationMetrics() 463 .get(InvocationMetricKey.CLEARED_RUN_ERROR.toString()); 464 } 465 466 @VisibleForTesting addInvocationMetricRunError(String errors)467 void addInvocationMetricRunError(String errors) { 468 InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.CLEARED_RUN_ERROR, errors); 469 } 470 forwardTestResults( Map<TestDescription, TestResult> testResults, ITestInvocationListener listener)471 private void forwardTestResults( 472 Map<TestDescription, TestResult> testResults, ITestInvocationListener listener) { 473 for (Map.Entry<TestDescription, TestResult> testEntry : testResults.entrySet()) { 474 listener.testStarted(testEntry.getKey(), testEntry.getValue().getStartTime()); 475 switch (testEntry.getValue().getResultStatus()) { 476 case FAILURE: 477 listener.testFailed(testEntry.getKey(), testEntry.getValue().getFailure()); 478 break; 479 case ASSUMPTION_FAILURE: 480 listener.testAssumptionFailure( 481 testEntry.getKey(), testEntry.getValue().getFailure()); 482 break; 483 case IGNORED: 484 listener.testIgnored(testEntry.getKey()); 485 break; 486 case INCOMPLETE: 487 listener.testFailed( 488 testEntry.getKey(), 489 FailureDescription.create("Test did not complete due to exception.")); 490 break; 491 case SKIPPED: 492 listener.testSkipped(testEntry.getKey(), testEntry.getValue().getSkipReason()); 493 break; 494 default: 495 break; 496 } 497 // Provide a strong association of the test to its logs. 498 for (Entry<String, LogFile> logFile : 499 testEntry.getValue().getLoggedFiles().entrySet()) { 500 if (listener instanceof ILogSaverListener) { 501 ((ILogSaverListener) listener) 502 .logAssociation(logFile.getKey(), logFile.getValue()); 503 } 504 } 505 listener.testEnded( 506 testEntry.getKey(), 507 testEntry.getValue().getEndTime(), 508 testEntry.getValue().getProtoMetrics()); 509 } 510 } 511 512 /** 513 * Helper method to forward the results from multiple attempts of the same Test Run (same name). 514 */ forwardTestRunResults(List<TestRunResult> results, ILogSaverListener listener)515 private void forwardTestRunResults(List<TestRunResult> results, ILogSaverListener listener) { 516 TestRunResult result = 517 TestRunResult.merge(results, MergeStrategy.getMergeStrategy(mRetryStrategy)); 518 519 listener.testRunStarted( 520 result.getName(), result.getExpectedTestCount(), 0, result.getStartTime()); 521 forwardTestResults(result.getTestResults(), listener); 522 if (result.isRunFailure()) { 523 listener.testRunFailed(result.getRunFailureDescription()); 524 } 525 // Provide a strong association of the run to its logs. 526 for (String key : result.getRunLoggedFiles().keySet()) { 527 for (LogFile logFile : result.getRunLoggedFiles().get(key)) { 528 listener.logAssociation(key, logFile); 529 } 530 } 531 listener.testRunEnded(result.getElapsedTime(), result.getRunProtoMetrics()); 532 // Ensure we don't keep track of the results we just forwarded 533 clearResultsForName(result.getName()); 534 } 535 forwardDetailedFailure()536 private void forwardDetailedFailure() { 537 if (mDetailedRunResults != null) { 538 if (mDetailedRunResults.isRunFailure() && mShouldReportFailure) { 539 if (mAllDetailedFailures.size() == 1) { 540 mDetailedForwarder.testRunFailed(mAllDetailedFailures.get(0)); 541 } else { 542 mDetailedForwarder.testRunFailed( 543 new MultiFailureDescription(mAllDetailedFailures)); 544 } 545 } else { 546 // Log the run failure that was cleared 547 List<String> invocationFailures = new ArrayList<>(); 548 String value = getInvocationMetricRunError(); 549 if (value != null) { 550 invocationFailures.add(value); 551 } 552 // If there are failure, track them 553 if (!mAllDetailedFailures.isEmpty()) { 554 invocationFailures.add( 555 new MultiFailureDescription(mAllDetailedFailures).toString()); 556 addInvocationMetricRunError(String.join("\n\n", invocationFailures)); 557 } 558 } 559 mAllDetailedFailures.clear(); 560 mDetailedForwarder.testRunEnded( 561 mDetailedRunResults.getElapsedTime(), mDetailedRunResults.getRunProtoMetrics()); 562 mDetailedRunResults = null; 563 } 564 } 565 saveEventsLog(File eventsLog, String key)566 private void saveEventsLog(File eventsLog, String key) { 567 if (eventsLog != null && eventsLog.length() > 0 && mLogSaver != null) { 568 try { 569 LogFile logged = 570 mLogSaver.saveLogFile( 571 eventsLog.getName(), LogDataType.TF_EVENTS, eventsLog); 572 if (logged != null) { 573 mAggregatedForwarder.logAssociation(key, logged); 574 mDetailedForwarder.logAssociation(key, logged); 575 } 576 } catch (IOException e) { 577 CLog.e(e); 578 } 579 } 580 FileUtil.deleteFile(eventsLog); 581 } 582 583 @VisibleForTesting getEventsLogs()584 protected File[] getEventsLogs() { 585 File[] logs = new File[2]; 586 logs[0] = mAggregatedEventsLogger.getLoggedEvents(); 587 logs[1] = mDetailedEventsLogger.getLoggedEvents(); 588 return logs; 589 } 590 } 591