private static int dumpOneLine(int ix, Logger logger, long[] log) {
    int event = getFirstInt(log[ix]);
    switch (event) {
    case TREE_READER_NEXT_VECTOR: {
      logger.info(ix + ": TreeReader next vector " + getSecondInt(log[ix]));
      return ix + 1;
    }
    case READING_STRIPE: {
      logger.info(ix + ": Reading stripe " + getSecondInt(log[ix])
          + " at " + log[ix + 1] + " length " + log[ix + 2]);
      return ix + 3;
    }
    case SARG_RESULT: {
      logger.info(ix + ": Reading " + log[ix + 1] + " rgs for stripe " + getSecondInt(log[ix]));
      return ix + 2;
    }
    case SARG_RESULT2: {
      int rgsLength = (int) log[ix + 1];
      int elements = (rgsLength >> 6) + ((rgsLength & 63) == 0 ? 0 : 1);
      boolean[] rgs = new boolean[rgsLength];
      int rgsOffset = 0;
      for (int i = 0; i < elements; ++i) {
        long val = log[ix + i + 2];
        int bitsInByte = Math.min(rgsLength - rgsOffset, 64);
        for (int j = 0; j < rgsOffset; ++j, val >>>= 1) {
          rgs[rgsOffset + j] = (val & 1) == 1;
        }
        rgsOffset += bitsInByte;
      }
      logger.info(ix + ": Reading filtered rgs for stripe " + getSecondInt(log[ix])
          + ": " + DebugUtils.toString(rgs));
      return ix + (elements + 2);
    }
    case RANGES: {
      int val = getSecondInt(log[ix]);
      RangesSrc src = RangesSrc.values()[val >>> MAX_ELEMENT_BITS];
      int rangeCount = val & ((1 << MAX_ELEMENT_BITS) - 1);
      int currentOffset = ix + 3;
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < rangeCount; ++i, currentOffset += 3) {
        sb.append(printRange(currentOffset, log)).append(", ");
      }
      logger.info(ix + ": Ranges for file " + log[ix + 1] + " (base offset " + log[ix + 2]
          + ") after " + src + ": " + sb.toString());
      return ix + 3 + rangeCount * 3;
    }
    case COLUMN_READ: {
      logger.info(ix + ": Reading column " + getSecondInt(log[ix]) + " (included index "
          + getFirstInt(log[ix + 1]) + "; type "
          + ColumnEncoding.Kind.values()[getSecondInt(log[ix + 1])] + ")");
      return ix + 2;
    }
    case SKIP_STREAM: {
      long streamOffset = log[ix + 1];
      logger.info(ix + ": Skipping stream for col " + getSecondInt(log[ix]) + " [" + streamOffset
          + ", " + (streamOffset + getFirstInt(log[ix + 2])) + ") kind "
          + Kind.values()[getSecondInt(log[ix + 2])]);
      return ix + 3;
    }
    case ADD_STREAM: {
      long streamOffset = log[ix + 1];
      logger.info(ix + ": Adding stream for col " + getSecondInt(log[ix]) + " [" + streamOffset
          + ", " + (streamOffset + getFirstInt(log[ix + 2])) + ") kind "
          + Kind.values()[getSecondInt(log[ix + 2])] + ", index " + getFirstInt(log[ix + 3])
          + ", entire stream " + (getSecondInt(log[ix + 3]) == 1));
      return ix + 4;
    }
    case START_RG: {
      logger.info(ix + ": Starting rg " + getSecondInt(log[ix]));
      return ix + 1;
    }
    case START_COL: {
      logger.info(ix + ": Starting column " + getSecondInt(log[ix]));
      return ix + 1;
    }
    case START_STRIPE_STREAM: {
      logger.info(ix + ": Starting stripe-level stream " + Kind.values()[getSecondInt(log[ix])]);
      return ix + 1;
    }
    case START_STREAM: {
      long offset = log[ix + 1];
      int unlockLen = getFirstInt(log[ix + 2]);
      String unlockStr = (unlockLen == Integer.MAX_VALUE) ? "" : " unlock " + (offset + unlockLen);
      logger.info(ix + ": Starting on stream " + Kind.values()[getSecondInt(log[ix])] + "["
          + offset + ", "  + (offset + getSecondInt(log[ix + 2])) + ") " + unlockStr);
      return ix + 3;
    }
    case START_READ: {
      logger.info(ix + ": Starting read at 0x" + Integer.toHexString(getSecondInt(log[ix])));
      return ix + 1;
    }
    case UNCOMPRESSED_DATA: {
      long offset = log[ix + 1];
      logger.info(ix + ": Uncompressed data ["
          + offset + ", " + (offset + getSecondInt(log[ix])) + ")");
      return ix + 2;
    }
    case PARTIAL_UNCOMPRESSED_DATA: {
      long offset = log[ix + 1];
      logger.info(ix + ": Partial uncompressed data ["
          + offset + ", " + (offset + getSecondInt(log[ix])) + ")");
      return ix + 2;
    }
    case VALID_UNCOMPRESSED_CHUNK: {
      logger.info(ix + ": Combining uncompressed data for cache buffer of length "
          + getSecondInt(log[ix]) + " from 0x" + Integer.toHexString((int)log[ix + 1]));
      return ix + 2;
    }
    case CACHE_COLLISION: {
      logger.info(ix + ": Replacing " + printRange(ix + 1, log) + " with 0x"
          + Integer.toHexString(getSecondInt(log[ix])));
      return ix + 4;
    }
    case ORC_CB: {
      long offset = log[ix + 1];
      int val = getSecondInt(log[ix]);
      boolean isUncompressed = (val & 1) == 1;
      int cbLength = val >>> 1;
      logger.info(ix + ": Found " + (isUncompressed ? "un" : "") + "compressed ORC CB ["
          + offset + ", " + (offset + cbLength) + ")");
      return ix + 2;
    }
    case INVALID_ORC_CB: {
      long offset = log[ix + 1];
      logger.info(ix + ": Found incomplete ORC CB ["
          + offset + ", " + (offset + getSecondInt(log[ix])) + ")");
      return ix + 2;
    }
    case PARTIAL_CB: {
      logger.info(ix + ": Found buffer with a part of ORC CB " + printRange(ix + 1, log));
      return ix + 4;
    }
    case COMPOSITE_ORC_CB: {
      logger.info(ix + ": Combined ORC CB from multiple buffers " + printRange(ix + 2, log)
          + " last chunk taken " + getSecondInt(log[ix]) + ", remaining " + log[ix + 1]);
      return ix + 5;
    }
    default: throw new AssertionError("Unknown " + event);
    }
  }
Single threaded IO trace.
Note: this can be made MTT merely by using an AtomicInteger, and storing a thread ID.
ql/src/java/org/apache/hadoop/hive/ql/io/orc/encoded/IoTrace.java