*** a/src/backend/access/rmgrdesc/xactdesc.c
--- b/src/backend/access/rmgrdesc/xactdesc.c
***************
*** 209,214 **** ParseAbortRecord(uint8 info, xl_xact_abort *xlrec, xl_xact_parsed_abort *parsed)
--- 209,249 ----
  	}
  }
  
+ /*
+  * ParsePrepareRecord
+  */
+ void
+ ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parsed_prepare *parsed)
+ {
+ 	char	   *bufptr;
+ 
+ 	bufptr = (char *) xlrec + MAXALIGN(sizeof(xl_xact_prepare));
+ 
+ 	parsed->origin_lsn = xlrec->origin_lsn;
+ 	parsed->origin_timestamp = xlrec->origin_timestamp;
+ 	parsed->twophase_xid = xlrec->xid;
+ 	parsed->dbId = xlrec->database;
+ 	parsed->nsubxacts = xlrec->nsubxacts;
+ 	parsed->nrels = xlrec->ncommitrels;
+ 	parsed->nabortrels = xlrec->nabortrels;
+ 	parsed->nmsgs = xlrec->ninvalmsgs;
+ 
+ 	strncpy(parsed->twophase_gid, bufptr, xlrec->gidlen);
+ 	bufptr += MAXALIGN(xlrec->gidlen);
+ 
+ 	parsed->subxacts = (TransactionId *) bufptr;
+ 	bufptr += MAXALIGN(xlrec->nsubxacts * sizeof(TransactionId));
+ 
+ 	parsed->xnodes = (RelFileNode *) bufptr;
+ 	bufptr += MAXALIGN(xlrec->ncommitrels * sizeof(RelFileNode));
+ 
+ 	parsed->abortnodes = (RelFileNode *) bufptr;
+ 	bufptr += MAXALIGN(xlrec->nabortrels * sizeof(RelFileNode));
+ 
+ 	parsed->msgs = (SharedInvalidationMessage *) bufptr;
+ 	bufptr += MAXALIGN(xlrec->ninvalmsgs * sizeof(SharedInvalidationMessage));
+ }
+ 
  static void
  xact_desc_commit(StringInfo buf, uint8 info, xl_xact_commit *xlrec, RepOriginId origin_id)
  {
***************
*** 293,298 **** xact_desc_abort(StringInfo buf, uint8 info, xl_xact_abort *xlrec)
--- 328,344 ----
  	}
  }
  
+ static void
+ xact_desc_prepare(StringInfo buf, uint8 info, xl_xact_prepare *xlrec)
+ {
+ 	xl_xact_parsed_prepare parsed;
+ 
+ 	ParsePrepareRecord(info, xlrec, &parsed);
+ 
+ 	appendStringInfo(buf, "gid %s: ", parsed.twophase_gid);
+ 	appendStringInfoString(buf, timestamptz_to_str(parsed.xact_time));
+ }
+ 
  static void
  xact_desc_assignment(StringInfo buf, xl_xact_assignment *xlrec)
  {
***************
*** 323,328 **** xact_desc(StringInfo buf, XLogReaderState *record)
--- 369,380 ----
  
  		xact_desc_abort(buf, XLogRecGetInfo(record), xlrec);
  	}
+ 	else if (info == XLOG_XACT_PREPARE)
+ 	{
+ 		xl_xact_prepare *xlrec = (xl_xact_prepare *) rec;
+ 
+ 		xact_desc_prepare(buf, XLogRecGetInfo(record), xlrec);
+ 	}
  	else if (info == XLOG_XACT_ASSIGNMENT)
  	{
  		xl_xact_assignment *xlrec = (xl_xact_assignment *) rec;
*** a/src/backend/access/transam/twophase.c
--- b/src/backend/access/transam/twophase.c
***************
*** 911,933 **** TwoPhaseGetDummyProc(TransactionId xid, bool lock_held)
   */
  #define TWOPHASE_MAGIC	0x57F94534	/* format identifier */
  
! typedef struct TwoPhaseFileHeader
! {
! 	uint32		magic;			/* format identifier */
! 	uint32		total_len;		/* actual file length */
! 	TransactionId xid;			/* original transaction XID */
! 	Oid			database;		/* OID of database it was in */
! 	TimestampTz prepared_at;	/* time of preparation */
! 	Oid			owner;			/* user running the transaction */
! 	int32		nsubxacts;		/* number of following subxact XIDs */
! 	int32		ncommitrels;	/* number of delete-on-commit rels */
! 	int32		nabortrels;		/* number of delete-on-abort rels */
! 	int32		ninvalmsgs;		/* number of cache invalidation messages */
! 	bool		initfileinval;	/* does relcache init file need invalidation? */
! 	uint16		gidlen;			/* length of the GID - GID follows the header */
! 	XLogRecPtr	origin_lsn;		/* lsn of this record at origin node */
! 	TimestampTz origin_timestamp;	/* time of prepare at origin node */
! } TwoPhaseFileHeader;
  
  /*
   * Header for each record in a state file
--- 911,917 ----
   */
  #define TWOPHASE_MAGIC	0x57F94534	/* format identifier */
  
! typedef xl_xact_prepare TwoPhaseFileHeader;
  
  /*
   * Header for each record in a state file
***************
*** 1332,1375 **** ReadTwoPhaseFile(TransactionId xid, bool missing_ok)
  	return buf;
  }
  
- /*
-  * ParsePrepareRecord
-  */
- void
- ParsePrepareRecord(uint8 info, char *xlrec, xl_xact_parsed_prepare *parsed)
- {
- 	TwoPhaseFileHeader *hdr;
- 	char	   *bufptr;
- 
- 	hdr = (TwoPhaseFileHeader *) xlrec;
- 	bufptr = xlrec + MAXALIGN(sizeof(TwoPhaseFileHeader));
- 
- 	parsed->origin_lsn = hdr->origin_lsn;
- 	parsed->origin_timestamp = hdr->origin_timestamp;
- 	parsed->twophase_xid = hdr->xid;
- 	parsed->dbId = hdr->database;
- 	parsed->nsubxacts = hdr->nsubxacts;
- 	parsed->nrels = hdr->ncommitrels;
- 	parsed->nabortrels = hdr->nabortrels;
- 	parsed->nmsgs = hdr->ninvalmsgs;
- 
- 	strncpy(parsed->twophase_gid, bufptr, hdr->gidlen);
- 	bufptr += MAXALIGN(hdr->gidlen);
- 
- 	parsed->subxacts = (TransactionId *) bufptr;
- 	bufptr += MAXALIGN(hdr->nsubxacts * sizeof(TransactionId));
- 
- 	parsed->xnodes = (RelFileNode *) bufptr;
- 	bufptr += MAXALIGN(hdr->ncommitrels * sizeof(RelFileNode));
- 
- 	parsed->abortnodes = (RelFileNode *) bufptr;
- 	bufptr += MAXALIGN(hdr->nabortrels * sizeof(RelFileNode));
- 
- 	parsed->msgs = (SharedInvalidationMessage *) bufptr;
- 	bufptr += MAXALIGN(hdr->ninvalmsgs * sizeof(SharedInvalidationMessage));
- }
- 
- 
  
  /*
   * Reads 2PC data from xlog. During checkpoint this data will be moved to
--- 1316,1321 ----
*** a/src/include/access/twophase.h
--- b/src/include/access/twophase.h
***************
*** 47,54 **** extern bool StandbyTransactionIdIsPrepared(TransactionId xid);
  
  extern TransactionId PrescanPreparedTransactions(TransactionId **xids_p,
  							int *nxids_p);
- extern void ParsePrepareRecord(uint8 info, char *xlrec,
- 				   xl_xact_parsed_prepare *parsed);
  extern void StandbyRecoverPreparedTransactions(void);
  extern void RecoverPreparedTransactions(void);
  
--- 47,52 ----
*** a/src/include/access/xact.h
--- b/src/include/access/xact.h
***************
*** 292,297 **** typedef struct xl_xact_abort
--- 292,315 ----
  } xl_xact_abort;
  #define MinSizeOfXactAbort sizeof(xl_xact_abort)
  
+ typedef struct xl_xact_prepare
+ {
+ 	uint32		magic;			/* format identifier */
+ 	uint32		total_len;		/* actual file length */
+ 	TransactionId xid;			/* original transaction XID */
+ 	Oid			database;		/* OID of database it was in */
+ 	TimestampTz prepared_at;	/* time of preparation */
+ 	Oid			owner;			/* user running the transaction */
+ 	int32		nsubxacts;		/* number of following subxact XIDs */
+ 	int32		ncommitrels;	/* number of delete-on-commit rels */
+ 	int32		nabortrels;		/* number of delete-on-abort rels */
+ 	int32		ninvalmsgs;		/* number of cache invalidation messages */
+ 	bool		initfileinval;	/* does relcache init file need invalidation? */
+ 	uint16		gidlen;			/* length of the GID - GID follows the header */
+ 	XLogRecPtr	origin_lsn;		/* lsn of this record at origin node */
+ 	TimestampTz origin_timestamp;	/* time of prepare at origin node */
+ } xl_xact_prepare;
+ 
  /*
   * Commit/Abort records in the above form are a bit verbose to parse, so
   * there's a deconstructed versions generated by ParseCommit/AbortRecord() for
***************
*** 435,440 **** extern const char *xact_identify(uint8 info);
--- 453,459 ----
  /* also in xactdesc.c, so they can be shared between front/backend code */
  extern void ParseCommitRecord(uint8 info, xl_xact_commit *xlrec, xl_xact_parsed_commit *parsed);
  extern void ParseAbortRecord(uint8 info, xl_xact_abort *xlrec, xl_xact_parsed_abort *parsed);
+ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parsed_prepare *parsed);
  
  extern void EnterParallelMode(void);
  extern void ExitParallelMode(void);
