private void dumpLog(File file, long startOffset, long endOffset, ArrayList<String> blobs,
      Map<String, LogBlobStatus> blobIdToLogRecord) throws IOException {
    logger.info("Dumping log file {}", file.getAbsolutePath());
    long currentOffset = 0;
    RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
    long fileSize = file.length();
    boolean lastBlobFailed = false;
    if (startOffset != -1) {
      currentOffset = startOffset;
    }
    if (endOffset == -1) {
      endOffset = fileSize;
    }
    logger.info("Starting dumping from offset {}", currentOffset);
    while (currentOffset < endOffset) {
      try {
        DumpDataHelper.LogBlobRecordInfo logBlobRecordInfo =
            DumpDataHelper.readSingleRecordFromLog(randomAccessFile, currentOffset, clusterMap, currentTimeInMs,
                metrics);
        if (throttler != null) {
          throttler.maybeThrottle(logBlobRecordInfo.totalRecordSize);
        }
        if (lastBlobFailed && !silent) {
          logger.info("Successful record found at {} after some failures ", currentOffset);
        }
        lastBlobFailed = false;
        if (!logBlobRecordInfo.isDeleted) {
          if (blobs != null) {
            if (blobs.contains(logBlobRecordInfo.blobId.getID())) {
              logger.info("{}\n{}\n{}\n{}\n{}\n{}", logBlobRecordInfo.messageHeader, logBlobRecordInfo.blobId,
                  logBlobRecordInfo.blobEncryptionKey, logBlobRecordInfo.blobProperty, logBlobRecordInfo.userMetadata,
                  logBlobRecordInfo.blobDataOutput);
              updateBlobIdToLogRecordMap(blobIdToLogRecord, logBlobRecordInfo.blobId.getID(), currentOffset,
                  !logBlobRecordInfo.isDeleted, logBlobRecordInfo.isExpired);
            }
          } else if (!silent) {
            logger.info("{}\n{}\n{}\n{}\n{}\n{} end offset {}", logBlobRecordInfo.messageHeader,
                logBlobRecordInfo.blobId, logBlobRecordInfo.blobEncryptionKey, logBlobRecordInfo.blobProperty,
                logBlobRecordInfo.userMetadata, logBlobRecordInfo.blobDataOutput,
                (currentOffset + logBlobRecordInfo.totalRecordSize));
            updateBlobIdToLogRecordMap(blobIdToLogRecord, logBlobRecordInfo.blobId.getID(), currentOffset,
                !logBlobRecordInfo.isDeleted, logBlobRecordInfo.isExpired);
          }
        } else {
          if (blobs != null) {
            if (blobs.contains(logBlobRecordInfo.blobId.getID())) {
              logger.info("{}\n{}\n{}", logBlobRecordInfo.messageHeader, logBlobRecordInfo.blobId,
                  logBlobRecordInfo.deleteMsg);
              updateBlobIdToLogRecordMap(blobIdToLogRecord, logBlobRecordInfo.blobId.getID(), currentOffset,
                  !logBlobRecordInfo.isDeleted, logBlobRecordInfo.isExpired);
            }
          } else if (!silent) {
            logger.info("{}\n{}\n{} end offset {}", logBlobRecordInfo.messageHeader, logBlobRecordInfo.blobId,
                logBlobRecordInfo.deleteMsg, (currentOffset + logBlobRecordInfo.totalRecordSize));
            updateBlobIdToLogRecordMap(blobIdToLogRecord, logBlobRecordInfo.blobId.getID(), currentOffset,
                !logBlobRecordInfo.isDeleted, logBlobRecordInfo.isExpired);
          }
        }
        currentOffset += (logBlobRecordInfo.totalRecordSize);
      } catch (IllegalArgumentException e) {
        if (!lastBlobFailed) {
          metrics.logDeserializationError.inc();
          logger.error("Illegal arg exception thrown at  {}, while reading blob starting at offset {}with exception: ",
              randomAccessFile.getChannel().position(), currentOffset, e);
        }
        currentOffset++;
        lastBlobFailed = true;
      } catch (MessageFormatException e) {
        if (!lastBlobFailed) {
          metrics.logDeserializationError.inc();
          logger.error(
              "MessageFormat exception thrown at  {} while reading blob starting at offset {} with exception: ",
              randomAccessFile.getChannel().position(), currentOffset, e);
        }
        currentOffset++;
        lastBlobFailed = true;
      } catch (EOFException e) {
        metrics.endOfFileOnDumpLogError.inc();
        logger.error("EOFException thrown at {}, Cause :{}, Msg :{}, stacktrace ",
            randomAccessFile.getChannel().position(), e.getCause(), e.getMessage(), e);
        throw (e);
      } catch (Exception e) {
        if (!lastBlobFailed) {
          metrics.unknownErrorOnDumpLog.inc();
          logger.error("Unknown exception thrown with Cause {}, Msg :{}, stacktrace ", e.getCause(), e.getMessage(), e);
          if (!silent) {
            logger.info("Trying out next offset {}", currentOffset + 1);
          }
        }
        currentOffset++;
        lastBlobFailed = true;
      }
    }
    logger.info("Dumped until offset {}", currentOffset);
  }
Dumps all blobs in a given log file
@param file the log file that needs to be parsed for
@param startOffset the starting offset from which records needs to be dumped from. Can be {@code null}
@param endOffset the end offset until which records need to be dumped to. Can be {@code null}
@param blobs List of blobIds to be filtered for. {@code null} if no filtering required
@param blobIdToLogRecord {@link HashMap} of blobId to {@link LogBlobStatus} to hold the information about blobs
                                        in the log after parsing
@throws IOException
ambry-tools/src/main/java/com/github/ambry/store/DumpLogTool.java