I've attached the revised patch, split up differently:

1. Introduce rm_identify, change rm_desc, glue the two together in
   xlog.c
2. Introduce pg_xlogdump --stats[=record].

The requested changes (16, filter, z:, UNKNOWN) are included. The
grammar nitpicking and rebase cruft is^Ware^Wain't included.

I ran Postgres with WAL_DEBUG for a while, and I ran pg_xlogdump with
--stats (and --stats=rmgr) and --stats=record with and without a few
different variants of "-r heap". Everything looked OK to me.

I hope I didn't miss anything this time.

-- Abhijit
>From da6df2f24bb8ea768d6e7671474b0ad7d0b798d1 Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <a...@2ndquadrant.com>
Date: Fri, 19 Sep 2014 15:08:45 +0530
Subject: Introduce an RmgrDescData.rm_identify callback to name records
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

For example, rm_identify turns XLOG_BTREE_VACUUM into "VACUUM" based on
xl_info. rm_desc now omits the prefix that (unsystematically) duplicated
the rm_identity output, and only provides extra detail if available:

LOG:  INSERT @ 0/16C50F0: prev 0/16C5080; xid 690; len 31: Heap/INSERT: rel 1663/16384/16385; tid 0/3

In the above, "Heap" comes from rm_name, "INSERT" comes from
rm_identify, and "rel 1663/…" comes from rm_desc.

The three are glued together by a new xlog_outdesc function in xlog.c.
---
 contrib/pg_xlogdump/rmgrdesc.c            |   2 +-
 src/backend/access/rmgrdesc/clogdesc.c    |  27 ++++---
 src/backend/access/rmgrdesc/dbasedesc.c   |  24 +++++-
 src/backend/access/rmgrdesc/gindesc.c     |  54 ++++++++++---
 src/backend/access/rmgrdesc/gistdesc.c    |  25 +++++-
 src/backend/access/rmgrdesc/hashdesc.c    |   6 ++
 src/backend/access/rmgrdesc/heapdesc.c    | 123 +++++++++++++++++++++--------
 src/backend/access/rmgrdesc/mxactdesc.c   |  37 ++++++---
 src/backend/access/rmgrdesc/nbtdesc.c     | 124 +++++++++++++++---------------
 src/backend/access/rmgrdesc/relmapdesc.c  |  19 ++++-
 src/backend/access/rmgrdesc/seqdesc.c     |  21 +++--
 src/backend/access/rmgrdesc/smgrdesc.c    |  25 ++++--
 src/backend/access/rmgrdesc/spgdesc.c     |  59 +++++++++++---
 src/backend/access/rmgrdesc/standbydesc.c |  25 ++++--
 src/backend/access/rmgrdesc/tblspcdesc.c  |  25 ++++--
 src/backend/access/rmgrdesc/xactdesc.c    |  48 +++++++++---
 src/backend/access/rmgrdesc/xlogdesc.c    |  72 ++++++++++++-----
 src/backend/access/transam/rmgr.c         |   4 +-
 src/backend/access/transam/xlog.c         |  45 +++++++++--
 src/include/access/clog.h                 |   1 +
 src/include/access/gin.h                  |   1 +
 src/include/access/gist_private.h         |   1 +
 src/include/access/hash.h                 |   1 +
 src/include/access/heapam_xlog.h          |   2 +
 src/include/access/multixact.h            |   1 +
 src/include/access/nbtree.h               |   1 +
 src/include/access/rmgr.h                 |   2 +-
 src/include/access/rmgrlist.h             |  34 ++++----
 src/include/access/spgist.h               |   1 +
 src/include/access/xact.h                 |   1 +
 src/include/access/xlog.h                 |   1 +
 src/include/access/xlog_internal.h        |  11 +++
 src/include/catalog/storage_xlog.h        |   1 +
 src/include/commands/dbcommands.h         |   1 +
 src/include/commands/sequence.h           |   1 +
 src/include/commands/tablespace.h         |   1 +
 src/include/storage/standby.h             |   1 +
 src/include/utils/relmapper.h             |   1 +
 38 files changed, 597 insertions(+), 232 deletions(-)

diff --git a/contrib/pg_xlogdump/rmgrdesc.c b/contrib/pg_xlogdump/rmgrdesc.c
index cbcaaa6..1064d34 100644
--- a/contrib/pg_xlogdump/rmgrdesc.c
+++ b/contrib/pg_xlogdump/rmgrdesc.c
@@ -27,7 +27,7 @@
 #include "storage/standby.h"
 #include "utils/relmapper.h"
 
-#define PG_RMGR(symname,name,redo,desc,startup,cleanup) \
+#define PG_RMGR(symname,name,redo,desc,identify,startup,cleanup) \
 	{ name, desc, },
 
 const RmgrDescData RmgrDescTable[RM_MAX_ID + 1] = {
diff --git a/src/backend/access/rmgrdesc/clogdesc.c b/src/backend/access/rmgrdesc/clogdesc.c
index e82baa8..8beb6d0 100644
--- a/src/backend/access/rmgrdesc/clogdesc.c
+++ b/src/backend/access/rmgrdesc/clogdesc.c
@@ -23,20 +23,29 @@ clog_desc(StringInfo buf, XLogRecord *record)
 	char	   *rec = XLogRecGetData(record);
 	uint8		info = record->xl_info & ~XLR_INFO_MASK;
 
-	if (info == CLOG_ZEROPAGE)
+	if (info == CLOG_ZEROPAGE || info == CLOG_TRUNCATE)
 	{
 		int			pageno;
 
 		memcpy(&pageno, rec, sizeof(int));
-		appendStringInfo(buf, "zeropage: %d", pageno);
+		appendStringInfo(buf, "%d", pageno);
 	}
-	else if (info == CLOG_TRUNCATE)
-	{
-		int			pageno;
+}
 
-		memcpy(&pageno, rec, sizeof(int));
-		appendStringInfo(buf, "truncate before: %d", pageno);
+const char *
+clog_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info)
+	{
+		case CLOG_ZEROPAGE:
+			id = "ZEROPAGE";
+			break;
+		case CLOG_TRUNCATE:
+			id = "TRUNCATE";
+			break;
 	}
-	else
-		appendStringInfoString(buf, "UNKNOWN");
+
+	return id;
 }
diff --git a/src/backend/access/rmgrdesc/dbasedesc.c b/src/backend/access/rmgrdesc/dbasedesc.c
index 0230716..e36988a 100644
--- a/src/backend/access/rmgrdesc/dbasedesc.c
+++ b/src/backend/access/rmgrdesc/dbasedesc.c
@@ -28,7 +28,7 @@ dbase_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_dbase_create_rec *xlrec = (xl_dbase_create_rec *) rec;
 
-		appendStringInfo(buf, "create db: copy dir %u/%u to %u/%u",
+		appendStringInfo(buf, "copy dir %u/%u to %u/%u",
 						 xlrec->src_db_id, xlrec->src_tablespace_id,
 						 xlrec->db_id, xlrec->tablespace_id);
 	}
@@ -36,9 +36,25 @@ dbase_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_dbase_drop_rec *xlrec = (xl_dbase_drop_rec *) rec;
 
-		appendStringInfo(buf, "drop db: dir %u/%u",
+		appendStringInfo(buf, "dir %u/%u",
 						 xlrec->db_id, xlrec->tablespace_id);
 	}
-	else
-		appendStringInfoString(buf, "UNKNOWN");
+}
+
+const char *
+dbase_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info)
+	{
+		case XLOG_DBASE_CREATE:
+			id = "CREATE";
+			break;
+		case XLOG_DBASE_DROP:
+			id = "DROP";
+			break;
+	}
+
+	return id;
 }
diff --git a/src/backend/access/rmgrdesc/gindesc.c b/src/backend/access/rmgrdesc/gindesc.c
index 12d68d7..dd4c791 100644
--- a/src/backend/access/rmgrdesc/gindesc.c
+++ b/src/backend/access/rmgrdesc/gindesc.c
@@ -85,11 +85,9 @@ gin_desc(StringInfo buf, XLogRecord *record)
 	switch (info)
 	{
 		case XLOG_GIN_CREATE_INDEX:
-			appendStringInfoString(buf, "Create index, ");
 			desc_node(buf, *(RelFileNode *) rec, GIN_ROOT_BLKNO);
 			break;
 		case XLOG_GIN_CREATE_PTREE:
-			appendStringInfoString(buf, "Create posting tree, ");
 			desc_node(buf, ((ginxlogCreatePostingTree *) rec)->node, ((ginxlogCreatePostingTree *) rec)->blkno);
 			break;
 		case XLOG_GIN_INSERT:
@@ -97,7 +95,6 @@ gin_desc(StringInfo buf, XLogRecord *record)
 				ginxlogInsert *xlrec = (ginxlogInsert *) rec;
 				char	   *payload = rec + sizeof(ginxlogInsert);
 
-				appendStringInfoString(buf, "Insert item, ");
 				desc_node(buf, xlrec->node, xlrec->blkno);
 				appendStringInfo(buf, " isdata: %c isleaf: %c",
 							  (xlrec->flags & GIN_INSERT_ISDATA) ? 'T' : 'F',
@@ -142,7 +139,6 @@ gin_desc(StringInfo buf, XLogRecord *record)
 			{
 				ginxlogSplit *xlrec = (ginxlogSplit *) rec;
 
-				appendStringInfoString(buf, "Page split, ");
 				desc_node(buf, ((ginxlogSplit *) rec)->node, ((ginxlogSplit *) rec)->lblkno);
 				appendStringInfo(buf, " isrootsplit: %c", (((ginxlogSplit *) rec)->flags & GIN_SPLIT_ROOT) ? 'T' : 'F');
 				appendStringInfo(buf, " isdata: %c isleaf: %c",
@@ -151,14 +147,12 @@ gin_desc(StringInfo buf, XLogRecord *record)
 			}
 			break;
 		case XLOG_GIN_VACUUM_PAGE:
-			appendStringInfoString(buf, "Vacuum page, ");
 			desc_node(buf, ((ginxlogVacuumPage *) rec)->node, ((ginxlogVacuumPage *) rec)->blkno);
 			break;
 		case XLOG_GIN_VACUUM_DATA_LEAF_PAGE:
 			{
 				ginxlogVacuumDataLeafPage *xlrec = (ginxlogVacuumDataLeafPage *) rec;
 
-				appendStringInfoString(buf, "Vacuum data leaf page, ");
 				desc_node(buf, xlrec->node, xlrec->blkno);
 				if (record->xl_info & XLR_BKP_BLOCK(0))
 					appendStringInfo(buf, " (full page image)");
@@ -167,23 +161,59 @@ gin_desc(StringInfo buf, XLogRecord *record)
 			}
 			break;
 		case XLOG_GIN_DELETE_PAGE:
-			appendStringInfoString(buf, "Delete page, ");
 			desc_node(buf, ((ginxlogDeletePage *) rec)->node, ((ginxlogDeletePage *) rec)->blkno);
 			break;
 		case XLOG_GIN_UPDATE_META_PAGE:
-			appendStringInfoString(buf, "Update metapage, ");
 			desc_node(buf, ((ginxlogUpdateMeta *) rec)->node, GIN_METAPAGE_BLKNO);
 			break;
 		case XLOG_GIN_INSERT_LISTPAGE:
-			appendStringInfoString(buf, "Insert new list page, ");
 			desc_node(buf, ((ginxlogInsertListPage *) rec)->node, ((ginxlogInsertListPage *) rec)->blkno);
 			break;
 		case XLOG_GIN_DELETE_LISTPAGE:
-			appendStringInfo(buf, "Delete list pages (%d), ", ((ginxlogDeleteListPages *) rec)->ndeleted);
+			appendStringInfo(buf, "%d pages, ", ((ginxlogDeleteListPages *) rec)->ndeleted);
 			desc_node(buf, ((ginxlogDeleteListPages *) rec)->node, GIN_METAPAGE_BLKNO);
 			break;
-		default:
-			appendStringInfo(buf, "unknown gin op code %u", info);
+	}
+}
+
+const char *
+gin_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info)
+	{
+		case XLOG_GIN_CREATE_INDEX:
+			id = "CREATE_INDEX";
+			break;
+		case XLOG_GIN_CREATE_PTREE:
+			id = "CREATE_PTREE";
+			break;
+		case XLOG_GIN_INSERT:
+			id = "INSERT";
+			break;
+		case XLOG_GIN_SPLIT:
+			id = "SPLIT";
+			break;
+		case XLOG_GIN_VACUUM_PAGE:
+			id = "VACUUM_PAGE";
+			break;
+		case XLOG_GIN_VACUUM_DATA_LEAF_PAGE:
+			id = "VACUUM_DATA_LEAF_PAGE";
+			break;
+		case XLOG_GIN_DELETE_PAGE:
+			id = "DELETE_PAGE";
+			break;
+		case XLOG_GIN_UPDATE_META_PAGE:
+			id = "UPDATE_META_PAGE";
+			break;
+		case XLOG_GIN_INSERT_LISTPAGE:
+			id = "INSERT_LISTPAGE";
+			break;
+		case XLOG_GIN_DELETE_LISTPAGE:
+			id = "DELETE_LISTPAGE";
 			break;
 	}
+
+	return id;
 }
diff --git a/src/backend/access/rmgrdesc/gistdesc.c b/src/backend/access/rmgrdesc/gistdesc.c
index cdac496..f2ed1f6 100644
--- a/src/backend/access/rmgrdesc/gistdesc.c
+++ b/src/backend/access/rmgrdesc/gistdesc.c
@@ -50,20 +50,37 @@ gist_desc(StringInfo buf, XLogRecord *record)
 	switch (info)
 	{
 		case XLOG_GIST_PAGE_UPDATE:
-			appendStringInfoString(buf, "page_update: ");
 			out_gistxlogPageUpdate(buf, (gistxlogPageUpdate *) rec);
 			break;
 		case XLOG_GIST_PAGE_SPLIT:
 			out_gistxlogPageSplit(buf, (gistxlogPageSplit *) rec);
 			break;
 		case XLOG_GIST_CREATE_INDEX:
-			appendStringInfo(buf, "create_index: rel %u/%u/%u",
+			appendStringInfo(buf, "rel %u/%u/%u",
 							 ((RelFileNode *) rec)->spcNode,
 							 ((RelFileNode *) rec)->dbNode,
 							 ((RelFileNode *) rec)->relNode);
 			break;
-		default:
-			appendStringInfo(buf, "unknown gist op code %u", info);
+	}
+}
+
+const char *
+gist_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info)
+	{
+		case XLOG_GIST_PAGE_UPDATE:
+			id = "PAGE_UPDATE";
+			break;
+		case XLOG_GIST_PAGE_SPLIT:
+			id = "PAGE_SPLIT";
+			break;
+		case XLOG_GIST_CREATE_INDEX:
+			id = "CREATE_INDEX";
 			break;
 	}
+
+	return id;
 }
diff --git a/src/backend/access/rmgrdesc/hashdesc.c b/src/backend/access/rmgrdesc/hashdesc.c
index 86a0376..c58461c 100644
--- a/src/backend/access/rmgrdesc/hashdesc.c
+++ b/src/backend/access/rmgrdesc/hashdesc.c
@@ -20,3 +20,9 @@ void
 hash_desc(StringInfo buf, XLogRecord *record)
 {
 }
+
+const char *
+hash_identify(uint8 info)
+{
+	return NULL;
+}
diff --git a/src/backend/access/rmgrdesc/heapdesc.c b/src/backend/access/rmgrdesc/heapdesc.c
index 24b6f92..cd9d59d 100644
--- a/src/backend/access/rmgrdesc/heapdesc.c
+++ b/src/backend/access/rmgrdesc/heapdesc.c
@@ -51,17 +51,12 @@ heap_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_heap_insert *xlrec = (xl_heap_insert *) rec;
 
-		if (record->xl_info & XLOG_HEAP_INIT_PAGE)
-			appendStringInfoString(buf, "insert(init): ");
-		else
-			appendStringInfoString(buf, "insert: ");
 		out_target(buf, &(xlrec->target));
 	}
 	else if (info == XLOG_HEAP_DELETE)
 	{
 		xl_heap_delete *xlrec = (xl_heap_delete *) rec;
 
-		appendStringInfoString(buf, "delete: ");
 		out_target(buf, &(xlrec->target));
 		appendStringInfoChar(buf, ' ');
 		out_infobits(buf, xlrec->infobits_set);
@@ -70,10 +65,6 @@ heap_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_heap_update *xlrec = (xl_heap_update *) rec;
 
-		if (record->xl_info & XLOG_HEAP_INIT_PAGE)
-			appendStringInfoString(buf, "update(init): ");
-		else
-			appendStringInfoString(buf, "update: ");
 		out_target(buf, &(xlrec->target));
 		appendStringInfo(buf, " xmax %u ", xlrec->old_xmax);
 		out_infobits(buf, xlrec->old_infobits_set);
@@ -86,10 +77,6 @@ heap_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_heap_update *xlrec = (xl_heap_update *) rec;
 
-		if (record->xl_info & XLOG_HEAP_INIT_PAGE)	/* can this case happen? */
-			appendStringInfoString(buf, "hot_update(init): ");
-		else
-			appendStringInfoString(buf, "hot_update: ");
 		out_target(buf, &(xlrec->target));
 		appendStringInfo(buf, " xmax %u ", xlrec->old_xmax);
 		out_infobits(buf, xlrec->old_infobits_set);
@@ -102,7 +89,7 @@ heap_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_heap_lock *xlrec = (xl_heap_lock *) rec;
 
-		appendStringInfo(buf, "lock %u: ", xlrec->locking_xid);
+		appendStringInfo(buf, "xid %u: ", xlrec->locking_xid);
 		out_target(buf, &(xlrec->target));
 		appendStringInfoChar(buf, ' ');
 		out_infobits(buf, xlrec->infobits_set);
@@ -111,11 +98,8 @@ heap_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_heap_inplace *xlrec = (xl_heap_inplace *) rec;
 
-		appendStringInfoString(buf, "inplace: ");
 		out_target(buf, &(xlrec->target));
 	}
-	else
-		appendStringInfoString(buf, "UNKNOWN");
 }
 void
 heap2_desc(StringInfo buf, XLogRecord *record)
@@ -128,7 +112,7 @@ heap2_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_heap_clean *xlrec = (xl_heap_clean *) rec;
 
-		appendStringInfo(buf, "clean: rel %u/%u/%u; blk %u remxid %u",
+		appendStringInfo(buf, "rel %u/%u/%u; blk %u remxid %u",
 						 xlrec->node.spcNode, xlrec->node.dbNode,
 						 xlrec->node.relNode, xlrec->block,
 						 xlrec->latestRemovedXid);
@@ -137,27 +121,22 @@ heap2_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_heap_freeze_page *xlrec = (xl_heap_freeze_page *) rec;
 
-		appendStringInfo(buf, "freeze_page: rel %u/%u/%u; blk %u; cutoff xid %u ntuples %u",
+		appendStringInfo(buf, "rel %u/%u/%u; blk %u; cutoff xid %u ntuples %u",
 						 xlrec->node.spcNode, xlrec->node.dbNode,
 						 xlrec->node.relNode, xlrec->block,
 						 xlrec->cutoff_xid, xlrec->ntuples);
 	}
-	else if (info == XLOG_HEAP2_REWRITE)
-	{
-		appendStringInfoString(buf, "heap rewrite:");
-	}
 	else if (info == XLOG_HEAP2_CLEANUP_INFO)
 	{
 		xl_heap_cleanup_info *xlrec = (xl_heap_cleanup_info *) rec;
 
-		appendStringInfo(buf, "cleanup info: remxid %u",
-						 xlrec->latestRemovedXid);
+		appendStringInfo(buf, "remxid %u", xlrec->latestRemovedXid);
 	}
 	else if (info == XLOG_HEAP2_VISIBLE)
 	{
 		xl_heap_visible *xlrec = (xl_heap_visible *) rec;
 
-		appendStringInfo(buf, "visible: rel %u/%u/%u; blk %u",
+		appendStringInfo(buf, "rel %u/%u/%u; blk %u",
 						 xlrec->node.spcNode, xlrec->node.dbNode,
 						 xlrec->node.relNode, xlrec->block);
 	}
@@ -165,10 +144,6 @@ heap2_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_heap_multi_insert *xlrec = (xl_heap_multi_insert *) rec;
 
-		if (record->xl_info & XLOG_HEAP_INIT_PAGE)
-			appendStringInfoString(buf, "multi-insert (init): ");
-		else
-			appendStringInfoString(buf, "multi-insert: ");
 		appendStringInfo(buf, "rel %u/%u/%u; blk %u; %d tuples",
 				xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode,
 						 xlrec->blkno, xlrec->ntuples);
@@ -177,7 +152,7 @@ heap2_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_heap_lock_updated *xlrec = (xl_heap_lock_updated *) rec;
 
-		appendStringInfo(buf, "lock updated: xmax %u msk %04x; ", xlrec->xmax,
+		appendStringInfo(buf, "xmax %u msk %04x; ", xlrec->xmax,
 						 xlrec->infobits_set);
 		out_target(buf, &(xlrec->target));
 	}
@@ -185,11 +160,91 @@ heap2_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_heap_new_cid *xlrec = (xl_heap_new_cid *) rec;
 
-		appendStringInfo(buf, "new_cid: ");
 		out_target(buf, &(xlrec->target));
 		appendStringInfo(buf, "; cmin: %u, cmax: %u, combo: %u",
 						 xlrec->cmin, xlrec->cmax, xlrec->combocid);
 	}
-	else
-		appendStringInfoString(buf, "UNKNOWN");
+}
+
+static const char *
+append_init(const char *str)
+{
+	static char x[32];
+
+	strcpy(x, str);
+	strcat(x, "+INIT");
+
+	return x;
+}
+
+const char *
+heap_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info & XLOG_HEAP_OPMASK)
+	{
+		case XLOG_HEAP_INSERT:
+			id = "INSERT";
+			break;
+		case XLOG_HEAP_DELETE:
+			id = "DELETE";
+			break;
+		case XLOG_HEAP_UPDATE:
+			id = "UPDATE";
+			break;
+		case XLOG_HEAP_HOT_UPDATE:
+			id = "HOT_UPDATE";
+			break;
+		case XLOG_HEAP_LOCK:
+			id = "LOCK";
+			break;
+		case XLOG_HEAP_INPLACE:
+			id = "INPLACE";
+			break;
+	}
+
+	if (info & XLOG_HEAP_INIT_PAGE)
+		id = append_init(id);
+
+	return id;
+}
+
+const char *
+heap2_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info & XLOG_HEAP_OPMASK)
+	{
+		case XLOG_HEAP2_CLEAN:
+			id = "CLEAN";
+			break;
+		case XLOG_HEAP2_FREEZE_PAGE:
+			id = "FREEZE_PAGE";
+			break;
+		case XLOG_HEAP2_CLEANUP_INFO:
+			id = "CLEANUP_INFO";
+			break;
+		case XLOG_HEAP2_VISIBLE:
+			id = "VISIBLE";
+			break;
+		case XLOG_HEAP2_MULTI_INSERT:
+			id = "MULTI_INSERT";
+			break;
+		case XLOG_HEAP2_LOCK_UPDATED:
+			id = "LOCK_UPDATED";
+			break;
+		case XLOG_HEAP2_NEW_CID:
+			id = "NEW_CID";
+			break;
+		case XLOG_HEAP2_REWRITE:
+			id = "REWRITE";
+			break;
+	}
+
+	if (info & XLOG_HEAP_INIT_PAGE)
+		id = append_init(id);
+
+	return id;
 }
diff --git a/src/backend/access/rmgrdesc/mxactdesc.c b/src/backend/access/rmgrdesc/mxactdesc.c
index 50d9b55..177aebe 100644
--- a/src/backend/access/rmgrdesc/mxactdesc.c
+++ b/src/backend/access/rmgrdesc/mxactdesc.c
@@ -52,30 +52,43 @@ multixact_desc(StringInfo buf, XLogRecord *record)
 	char	   *rec = XLogRecGetData(record);
 	uint8		info = record->xl_info & ~XLR_INFO_MASK;
 
-	if (info == XLOG_MULTIXACT_ZERO_OFF_PAGE)
+	if (info == XLOG_MULTIXACT_ZERO_OFF_PAGE ||
+		info == XLOG_MULTIXACT_ZERO_MEM_PAGE)
 	{
 		int			pageno;
 
 		memcpy(&pageno, rec, sizeof(int));
-		appendStringInfo(buf, "zero offsets page: %d", pageno);
-	}
-	else if (info == XLOG_MULTIXACT_ZERO_MEM_PAGE)
-	{
-		int			pageno;
-
-		memcpy(&pageno, rec, sizeof(int));
-		appendStringInfo(buf, "zero members page: %d", pageno);
+		appendStringInfo(buf, "%d", pageno);
 	}
 	else if (info == XLOG_MULTIXACT_CREATE_ID)
 	{
 		xl_multixact_create *xlrec = (xl_multixact_create *) rec;
 		int			i;
 
-		appendStringInfo(buf, "create mxid %u offset %u nmembers %d: ", xlrec->mid,
+		appendStringInfo(buf, "%u offset %u nmembers %d: ", xlrec->mid,
 						 xlrec->moff, xlrec->nmembers);
 		for (i = 0; i < xlrec->nmembers; i++)
 			out_member(buf, &xlrec->members[i]);
 	}
-	else
-		appendStringInfoString(buf, "UNKNOWN");
+}
+
+const char *
+multixact_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info)
+	{
+		case XLOG_MULTIXACT_ZERO_OFF_PAGE:
+			id = "ZERO_OFF_PAGE";
+			break;
+		case XLOG_MULTIXACT_ZERO_MEM_PAGE:
+			id = "ZERO_MEM_PAGE";
+			break;
+		case XLOG_MULTIXACT_CREATE_ID:
+			id = "CREATE_ID";
+			break;
+	}
+
+	return id;
 }
diff --git a/src/backend/access/rmgrdesc/nbtdesc.c b/src/backend/access/rmgrdesc/nbtdesc.c
index bf3389c..7eb3bbd 100644
--- a/src/backend/access/rmgrdesc/nbtdesc.c
+++ b/src/backend/access/rmgrdesc/nbtdesc.c
@@ -34,74 +34,26 @@ btree_desc(StringInfo buf, XLogRecord *record)
 	switch (info)
 	{
 		case XLOG_BTREE_INSERT_LEAF:
-			{
-				xl_btree_insert *xlrec = (xl_btree_insert *) rec;
-
-				appendStringInfoString(buf, "insert: ");
-				out_target(buf, &(xlrec->target));
-				break;
-			}
 		case XLOG_BTREE_INSERT_UPPER:
-			{
-				xl_btree_insert *xlrec = (xl_btree_insert *) rec;
-
-				appendStringInfoString(buf, "insert_upper: ");
-				out_target(buf, &(xlrec->target));
-				break;
-			}
 		case XLOG_BTREE_INSERT_META:
 			{
 				xl_btree_insert *xlrec = (xl_btree_insert *) rec;
 
-				appendStringInfoString(buf, "insert_meta: ");
 				out_target(buf, &(xlrec->target));
 				break;
 			}
 		case XLOG_BTREE_SPLIT_L:
-			{
-				xl_btree_split *xlrec = (xl_btree_split *) rec;
-
-				appendStringInfo(buf, "split_l: rel %u/%u/%u ",
-								 xlrec->node.spcNode, xlrec->node.dbNode,
-								 xlrec->node.relNode);
-				appendStringInfo(buf, "left %u, right %u, next %u, level %u, firstright %d",
-							   xlrec->leftsib, xlrec->rightsib, xlrec->rnext,
-								 xlrec->level, xlrec->firstright);
-				break;
-			}
 		case XLOG_BTREE_SPLIT_R:
-			{
-				xl_btree_split *xlrec = (xl_btree_split *) rec;
-
-				appendStringInfo(buf, "split_r: rel %u/%u/%u ",
-								 xlrec->node.spcNode, xlrec->node.dbNode,
-								 xlrec->node.relNode);
-				appendStringInfo(buf, "left %u, right %u, next %u, level %u, firstright %d",
-							   xlrec->leftsib, xlrec->rightsib, xlrec->rnext,
-								 xlrec->level, xlrec->firstright);
-				break;
-			}
 		case XLOG_BTREE_SPLIT_L_ROOT:
-			{
-				xl_btree_split *xlrec = (xl_btree_split *) rec;
-
-				appendStringInfo(buf, "split_l_root: rel %u/%u/%u ",
-								 xlrec->node.spcNode, xlrec->node.dbNode,
-								 xlrec->node.relNode);
-				appendStringInfo(buf, "left %u, right %u, next %u, level %u, firstright %d",
-							   xlrec->leftsib, xlrec->rightsib, xlrec->rnext,
-								 xlrec->level, xlrec->firstright);
-				break;
-			}
 		case XLOG_BTREE_SPLIT_R_ROOT:
 			{
 				xl_btree_split *xlrec = (xl_btree_split *) rec;
 
-				appendStringInfo(buf, "split_r_root: rel %u/%u/%u ",
+				appendStringInfo(buf, "rel %u/%u/%u ",
 								 xlrec->node.spcNode, xlrec->node.dbNode,
 								 xlrec->node.relNode);
 				appendStringInfo(buf, "left %u, right %u, next %u, level %u, firstright %d",
-							   xlrec->leftsib, xlrec->rightsib, xlrec->rnext,
+								 xlrec->leftsib, xlrec->rightsib, xlrec->rnext,
 								 xlrec->level, xlrec->firstright);
 				break;
 			}
@@ -109,7 +61,7 @@ btree_desc(StringInfo buf, XLogRecord *record)
 			{
 				xl_btree_vacuum *xlrec = (xl_btree_vacuum *) rec;
 
-				appendStringInfo(buf, "vacuum: rel %u/%u/%u; blk %u, lastBlockVacuumed %u",
+				appendStringInfo(buf, "rel %u/%u/%u; blk %u, lastBlockVacuumed %u",
 								 xlrec->node.spcNode, xlrec->node.dbNode,
 								 xlrec->node.relNode, xlrec->block,
 								 xlrec->lastBlockVacuumed);
@@ -119,8 +71,8 @@ btree_desc(StringInfo buf, XLogRecord *record)
 			{
 				xl_btree_delete *xlrec = (xl_btree_delete *) rec;
 
-				appendStringInfo(buf, "delete: index %u/%u/%u; iblk %u, heap %u/%u/%u;",
-				xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode,
+				appendStringInfo(buf, "index %u/%u/%u; iblk %u, heap %u/%u/%u;",
+								 xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode,
 								 xlrec->block,
 								 xlrec->hnode.spcNode, xlrec->hnode.dbNode, xlrec->hnode.relNode);
 				break;
@@ -129,7 +81,6 @@ btree_desc(StringInfo buf, XLogRecord *record)
 			{
 				xl_btree_mark_page_halfdead *xlrec = (xl_btree_mark_page_halfdead *) rec;
 
-				appendStringInfoString(buf, "mark_page_halfdead: ");
 				out_target(buf, &(xlrec->target));
 				appendStringInfo(buf, "; topparent %u; leaf %u; left %u; right %u",
 								 xlrec->topparent, xlrec->leafblk, xlrec->leftblk, xlrec->rightblk);
@@ -140,8 +91,8 @@ btree_desc(StringInfo buf, XLogRecord *record)
 			{
 				xl_btree_unlink_page *xlrec = (xl_btree_unlink_page *) rec;
 
-				appendStringInfo(buf, "unlink_page: rel %u/%u/%u; ",
-				xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode);
+				appendStringInfo(buf, "rel %u/%u/%u; ",
+								 xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode);
 				appendStringInfo(buf, "dead %u; left %u; right %u; btpo_xact %u; ",
 								 xlrec->deadblk, xlrec->leftsib, xlrec->rightsib, xlrec->btpo_xact);
 				appendStringInfo(buf, "leaf %u; leafleft %u; leafright %u; topparent %u",
@@ -152,7 +103,7 @@ btree_desc(StringInfo buf, XLogRecord *record)
 			{
 				xl_btree_newroot *xlrec = (xl_btree_newroot *) rec;
 
-				appendStringInfo(buf, "newroot: rel %u/%u/%u; root %u lev %u",
+				appendStringInfo(buf, "rel %u/%u/%u; root %u lev %u",
 								 xlrec->node.spcNode, xlrec->node.dbNode,
 								 xlrec->node.relNode,
 								 xlrec->rootblk, xlrec->level);
@@ -162,13 +113,64 @@ btree_desc(StringInfo buf, XLogRecord *record)
 			{
 				xl_btree_reuse_page *xlrec = (xl_btree_reuse_page *) rec;
 
-				appendStringInfo(buf, "reuse_page: rel %u/%u/%u; latestRemovedXid %u",
+				appendStringInfo(buf, "rel %u/%u/%u; latestRemovedXid %u",
 								 xlrec->node.spcNode, xlrec->node.dbNode,
-							   xlrec->node.relNode, xlrec->latestRemovedXid);
+								 xlrec->node.relNode, xlrec->latestRemovedXid);
 				break;
 			}
-		default:
-			appendStringInfoString(buf, "UNKNOWN");
+	}
+}
+
+const char *
+btree_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info)
+	{
+		case XLOG_BTREE_INSERT_LEAF:
+			id = "INSERT_LEAF";
+			break;
+		case XLOG_BTREE_INSERT_UPPER:
+			id = "INSERT_UPPER";
+			break;
+		case XLOG_BTREE_INSERT_META:
+			id = "INSERT_META";
+			break;
+		case XLOG_BTREE_SPLIT_L:
+			id = "SPLIT_L";
+			break;
+		case XLOG_BTREE_SPLIT_R:
+			id = "SPLIT_R";
+			break;
+		case XLOG_BTREE_SPLIT_L_ROOT:
+			id = "SPLIT_L_ROOT";
+			break;
+		case XLOG_BTREE_SPLIT_R_ROOT:
+			id = "SPLIT_R_ROOT";
+			break;
+		case XLOG_BTREE_VACUUM:
+			id = "VACUUM";
+			break;
+		case XLOG_BTREE_DELETE:
+			id = "DELETE";
+			break;
+		case XLOG_BTREE_MARK_PAGE_HALFDEAD:
+			id = "MARK_PAGE_HALFDEAD";
+			break;
+		case XLOG_BTREE_UNLINK_PAGE:
+			id = "UNLINK_PAGE";
+			break;
+		case XLOG_BTREE_UNLINK_PAGE_META:
+			id = "UNLINK_PAGE_META";
+			break;
+		case XLOG_BTREE_NEWROOT:
+			id = "NEWROOT";
+			break;
+		case XLOG_BTREE_REUSE_PAGE:
+			id = "REUSE_PAGE";
 			break;
 	}
+
+	return id;
 }
diff --git a/src/backend/access/rmgrdesc/relmapdesc.c b/src/backend/access/rmgrdesc/relmapdesc.c
index 06fd4b3..39dcfb5 100644
--- a/src/backend/access/rmgrdesc/relmapdesc.c
+++ b/src/backend/access/rmgrdesc/relmapdesc.c
@@ -26,9 +26,22 @@ relmap_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_relmap_update *xlrec = (xl_relmap_update *) rec;
 
-		appendStringInfo(buf, "update relmap: database %u tablespace %u size %u",
+		appendStringInfo(buf, "database %u tablespace %u size %u",
 						 xlrec->dbid, xlrec->tsid, xlrec->nbytes);
 	}
-	else
-		appendStringInfoString(buf, "UNKNOWN");
+}
+
+const char *
+relmap_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info)
+	{
+		case XLOG_RELMAP_UPDATE:
+			id = "UPDATE";
+			break;
+	}
+
+	return id;
 }
diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/seqdesc.c
index 42eb9b9..d44fe62 100644
--- a/src/backend/access/rmgrdesc/seqdesc.c
+++ b/src/backend/access/rmgrdesc/seqdesc.c
@@ -25,13 +25,22 @@ seq_desc(StringInfo buf, XLogRecord *record)
 	xl_seq_rec *xlrec = (xl_seq_rec *) rec;
 
 	if (info == XLOG_SEQ_LOG)
-		appendStringInfoString(buf, "log: ");
-	else
+		appendStringInfo(buf, "rel %u/%u/%u",
+						 xlrec->node.spcNode, xlrec->node.dbNode,
+						 xlrec->node.relNode);
+}
+
+const char *
+seq_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info)
 	{
-		appendStringInfoString(buf, "UNKNOWN");
-		return;
+		case XLOG_SEQ_LOG:
+			id = "LOG";
+			break;
 	}
 
-	appendStringInfo(buf, "rel %u/%u/%u",
-			   xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode);
+	return id;
 }
diff --git a/src/backend/access/rmgrdesc/smgrdesc.c b/src/backend/access/rmgrdesc/smgrdesc.c
index c76c815..ee711bc 100644
--- a/src/backend/access/rmgrdesc/smgrdesc.c
+++ b/src/backend/access/rmgrdesc/smgrdesc.c
@@ -29,7 +29,7 @@ smgr_desc(StringInfo buf, XLogRecord *record)
 		xl_smgr_create *xlrec = (xl_smgr_create *) rec;
 		char	   *path = relpathperm(xlrec->rnode, xlrec->forkNum);
 
-		appendStringInfo(buf, "file create: %s", path);
+		appendStringInfo(buf, "%s", path);
 		pfree(path);
 	}
 	else if (info == XLOG_SMGR_TRUNCATE)
@@ -37,10 +37,25 @@ smgr_desc(StringInfo buf, XLogRecord *record)
 		xl_smgr_truncate *xlrec = (xl_smgr_truncate *) rec;
 		char	   *path = relpathperm(xlrec->rnode, MAIN_FORKNUM);
 
-		appendStringInfo(buf, "file truncate: %s to %u blocks", path,
-						 xlrec->blkno);
+		appendStringInfo(buf, "%s to %u blocks", path, xlrec->blkno);
 		pfree(path);
 	}
-	else
-		appendStringInfoString(buf, "UNKNOWN");
+}
+
+const char *
+smgr_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info)
+	{
+		case XLOG_SMGR_CREATE:
+			id = "CREATE";
+			break;
+		case XLOG_SMGR_TRUNCATE:
+			id = "TRUNCATE";
+			break;
+	}
+
+	return id;
 }
diff --git a/src/backend/access/rmgrdesc/spgdesc.c b/src/backend/access/rmgrdesc/spgdesc.c
index ed369b2..74b2177 100644
--- a/src/backend/access/rmgrdesc/spgdesc.c
+++ b/src/backend/access/rmgrdesc/spgdesc.c
@@ -32,32 +32,32 @@ spg_desc(StringInfo buf, XLogRecord *record)
 	switch (info)
 	{
 		case XLOG_SPGIST_CREATE_INDEX:
-			appendStringInfo(buf, "create_index: rel %u/%u/%u",
+			appendStringInfo(buf, "rel %u/%u/%u",
 							 ((RelFileNode *) rec)->spcNode,
 							 ((RelFileNode *) rec)->dbNode,
 							 ((RelFileNode *) rec)->relNode);
 			break;
 		case XLOG_SPGIST_ADD_LEAF:
 			out_target(buf, ((spgxlogAddLeaf *) rec)->node);
-			appendStringInfo(buf, "add leaf to page: %u",
+			appendStringInfo(buf, "%u",
 							 ((spgxlogAddLeaf *) rec)->blknoLeaf);
 			break;
 		case XLOG_SPGIST_MOVE_LEAFS:
 			out_target(buf, ((spgxlogMoveLeafs *) rec)->node);
-			appendStringInfo(buf, "move %u leafs from page %u to page %u",
+			appendStringInfo(buf, "%u leafs from page %u to page %u",
 							 ((spgxlogMoveLeafs *) rec)->nMoves,
 							 ((spgxlogMoveLeafs *) rec)->blknoSrc,
 							 ((spgxlogMoveLeafs *) rec)->blknoDst);
 			break;
 		case XLOG_SPGIST_ADD_NODE:
 			out_target(buf, ((spgxlogAddNode *) rec)->node);
-			appendStringInfo(buf, "add node to %u:%u",
+			appendStringInfo(buf, "%u:%u",
 							 ((spgxlogAddNode *) rec)->blkno,
 							 ((spgxlogAddNode *) rec)->offnum);
 			break;
 		case XLOG_SPGIST_SPLIT_TUPLE:
 			out_target(buf, ((spgxlogSplitTuple *) rec)->node);
-			appendStringInfo(buf, "split node %u:%u to %u:%u",
+			appendStringInfo(buf, "%u:%u to %u:%u",
 							 ((spgxlogSplitTuple *) rec)->blknoPrefix,
 							 ((spgxlogSplitTuple *) rec)->offnumPrefix,
 							 ((spgxlogSplitTuple *) rec)->blknoPostfix,
@@ -65,26 +65,61 @@ spg_desc(StringInfo buf, XLogRecord *record)
 			break;
 		case XLOG_SPGIST_PICKSPLIT:
 			out_target(buf, ((spgxlogPickSplit *) rec)->node);
-			appendStringInfoString(buf, "split leaf page");
 			break;
 		case XLOG_SPGIST_VACUUM_LEAF:
 			out_target(buf, ((spgxlogVacuumLeaf *) rec)->node);
-			appendStringInfo(buf, "vacuum leaf tuples on page %u",
+			appendStringInfo(buf, "page %u",
 							 ((spgxlogVacuumLeaf *) rec)->blkno);
 			break;
 		case XLOG_SPGIST_VACUUM_ROOT:
 			out_target(buf, ((spgxlogVacuumRoot *) rec)->node);
-			appendStringInfo(buf, "vacuum leaf tuples on root page %u",
+			appendStringInfo(buf, "page %u",
 							 ((spgxlogVacuumRoot *) rec)->blkno);
 			break;
 		case XLOG_SPGIST_VACUUM_REDIRECT:
 			out_target(buf, ((spgxlogVacuumRedirect *) rec)->node);
-			appendStringInfo(buf, "vacuum redirect tuples on page %u, newest XID %u",
+			appendStringInfo(buf, "page %u, newest XID %u",
 							 ((spgxlogVacuumRedirect *) rec)->blkno,
-						 ((spgxlogVacuumRedirect *) rec)->newestRedirectXid);
+							 ((spgxlogVacuumRedirect *) rec)->newestRedirectXid);
 			break;
-		default:
-			appendStringInfo(buf, "unknown spgist op code %u", info);
+	}
+}
+
+const char *
+spg_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info)
+	{
+		case XLOG_SPGIST_CREATE_INDEX:
+			id = "CREATE_INDEX";
+			break;
+		case XLOG_SPGIST_ADD_LEAF:
+			id = "ADD_LEAF";
+			break;
+		case XLOG_SPGIST_MOVE_LEAFS:
+			id = "MOVE_LEAFS";
+			break;
+		case XLOG_SPGIST_ADD_NODE:
+			id = "ADD_NODE";
+			break;
+		case XLOG_SPGIST_SPLIT_TUPLE:
+			id = "SPLIT_TUPLE";
+			break;
+		case XLOG_SPGIST_PICKSPLIT:
+			id = "PICKSPLIT";
+			break;
+		case XLOG_SPGIST_VACUUM_LEAF:
+			id = "VACUUM_LEAF";
+			break;
+		case XLOG_SPGIST_VACUUM_ROOT:
+			id = "VACUUM_ROOT";
+			break;
+		case XLOG_SPGIST_VACUUM_REDIRECT:
+			id = "VACUUM_REDIRECT";
 			break;
 	}
+
+	return id;
 }
diff --git a/src/backend/access/rmgrdesc/standbydesc.c b/src/backend/access/rmgrdesc/standbydesc.c
index a127d38..436b27c 100644
--- a/src/backend/access/rmgrdesc/standbydesc.c
+++ b/src/backend/access/rmgrdesc/standbydesc.c
@@ -47,10 +47,8 @@ standby_desc(StringInfo buf, XLogRecord *record)
 		xl_standby_locks *xlrec = (xl_standby_locks *) rec;
 		int			i;
 
-		appendStringInfoString(buf, "AccessExclusive locks:");
-
 		for (i = 0; i < xlrec->nlocks; i++)
-			appendStringInfo(buf, " xid %u db %u rel %u",
+			appendStringInfo(buf, "xid %u db %u rel %u ",
 							 xlrec->locks[i].xid, xlrec->locks[i].dbOid,
 							 xlrec->locks[i].relOid);
 	}
@@ -58,9 +56,24 @@ standby_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_running_xacts *xlrec = (xl_running_xacts *) rec;
 
-		appendStringInfoString(buf, "running xacts:");
 		standby_desc_running_xacts(buf, xlrec);
 	}
-	else
-		appendStringInfoString(buf, "UNKNOWN");
+}
+
+const char *
+standby_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info)
+	{
+		case XLOG_STANDBY_LOCK:
+			id = "LOCK";
+			break;
+		case XLOG_RUNNING_XACTS:
+			id = "RUNNING_XACTS";
+			break;
+	}
+
+	return id;
 }
diff --git a/src/backend/access/rmgrdesc/tblspcdesc.c b/src/backend/access/rmgrdesc/tblspcdesc.c
index 30b1f06..effeaf6 100644
--- a/src/backend/access/rmgrdesc/tblspcdesc.c
+++ b/src/backend/access/rmgrdesc/tblspcdesc.c
@@ -27,15 +27,30 @@ tblspc_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_tblspc_create_rec *xlrec = (xl_tblspc_create_rec *) rec;
 
-		appendStringInfo(buf, "create tablespace: %u \"%s\"",
-						 xlrec->ts_id, xlrec->ts_path);
+		appendStringInfo(buf, "%u \"%s\"", xlrec->ts_id, xlrec->ts_path);
 	}
 	else if (info == XLOG_TBLSPC_DROP)
 	{
 		xl_tblspc_drop_rec *xlrec = (xl_tblspc_drop_rec *) rec;
 
-		appendStringInfo(buf, "drop tablespace: %u", xlrec->ts_id);
+		appendStringInfo(buf, "%u", xlrec->ts_id);
 	}
-	else
-		appendStringInfoString(buf, "UNKNOWN");
+}
+
+const char *
+tblspc_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info)
+	{
+		case XLOG_TBLSPC_CREATE:
+			id = "CREATE";
+			break;
+		case XLOG_TBLSPC_DROP:
+			id = "DROP";
+			break;
+	}
+
+	return id;
 }
diff --git a/src/backend/access/rmgrdesc/xactdesc.c b/src/backend/access/rmgrdesc/xactdesc.c
index 994931e..11e64af 100644
--- a/src/backend/access/rmgrdesc/xactdesc.c
+++ b/src/backend/access/rmgrdesc/xactdesc.c
@@ -146,39 +146,32 @@ xact_desc(StringInfo buf, XLogRecord *record)
 	{
 		xl_xact_commit_compact *xlrec = (xl_xact_commit_compact *) rec;
 
-		appendStringInfoString(buf, "commit: ");
 		xact_desc_commit_compact(buf, xlrec);
 	}
 	else if (info == XLOG_XACT_COMMIT)
 	{
 		xl_xact_commit *xlrec = (xl_xact_commit *) rec;
 
-		appendStringInfoString(buf, "commit: ");
 		xact_desc_commit(buf, xlrec);
 	}
 	else if (info == XLOG_XACT_ABORT)
 	{
 		xl_xact_abort *xlrec = (xl_xact_abort *) rec;
 
-		appendStringInfoString(buf, "abort: ");
 		xact_desc_abort(buf, xlrec);
 	}
-	else if (info == XLOG_XACT_PREPARE)
-	{
-		appendStringInfoString(buf, "prepare");
-	}
 	else if (info == XLOG_XACT_COMMIT_PREPARED)
 	{
 		xl_xact_commit_prepared *xlrec = (xl_xact_commit_prepared *) rec;
 
-		appendStringInfo(buf, "commit prepared %u: ", xlrec->xid);
+		appendStringInfo(buf, "%u: ", xlrec->xid);
 		xact_desc_commit(buf, &xlrec->crec);
 	}
 	else if (info == XLOG_XACT_ABORT_PREPARED)
 	{
 		xl_xact_abort_prepared *xlrec = (xl_xact_abort_prepared *) rec;
 
-		appendStringInfo(buf, "abort prepared %u: ", xlrec->xid);
+		appendStringInfo(buf, "%u: ", xlrec->xid);
 		xact_desc_abort(buf, &xlrec->arec);
 	}
 	else if (info == XLOG_XACT_ASSIGNMENT)
@@ -190,9 +183,40 @@ xact_desc(StringInfo buf, XLogRecord *record)
 		 * interested in the top-level xid that issued the record and which
 		 * xids are being reported here.
 		 */
-		appendStringInfo(buf, "xid assignment xtop %u: ", xlrec->xtop);
+		appendStringInfo(buf, "xtop %u: ", xlrec->xtop);
 		xact_desc_assignment(buf, xlrec);
 	}
-	else
-		appendStringInfoString(buf, "UNKNOWN");
+}
+
+const char *
+xact_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info)
+	{
+		case XLOG_XACT_COMMIT:
+			id = "COMMIT";
+			break;
+		case XLOG_XACT_PREPARE:
+			id = "PREPARE";
+			break;
+		case XLOG_XACT_ABORT:
+			id = "ABORT";
+			break;
+		case XLOG_XACT_COMMIT_PREPARED:
+			id = "COMMIT_PREPARED";
+			break;
+		case XLOG_XACT_ABORT_PREPARED:
+			id = "ABORT_PREPARED";
+			break;
+		case XLOG_XACT_ASSIGNMENT:
+			id = "ASSIGNMENT";
+			break;
+		case XLOG_XACT_COMMIT_COMPACT:
+			id = "COMMIT_COMPACT";
+			break;
+	}
+
+	return id;
 }
diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c
index 2224da1..cdefaf5 100644
--- a/src/backend/access/rmgrdesc/xlogdesc.c
+++ b/src/backend/access/rmgrdesc/xlogdesc.c
@@ -42,7 +42,7 @@ xlog_desc(StringInfo buf, XLogRecord *record)
 	{
 		CheckPoint *checkpoint = (CheckPoint *) rec;
 
-		appendStringInfo(buf, "checkpoint: redo %X/%X; "
+		appendStringInfo(buf, "redo %X/%X; "
 						 "tli %u; prev tli %u; fpw %s; xid %u/%u; oid %u; multi %u; offset %u; "
 						 "oldest xid %u in DB %u; oldest multi %u in DB %u; "
 						 "oldest running xid %u; %s",
@@ -61,33 +61,24 @@ xlog_desc(StringInfo buf, XLogRecord *record)
 						 checkpoint->oldestActiveXid,
 				 (info == XLOG_CHECKPOINT_SHUTDOWN) ? "shutdown" : "online");
 	}
-	else if (info == XLOG_NOOP)
-	{
-		appendStringInfoString(buf, "xlog no-op");
-	}
 	else if (info == XLOG_NEXTOID)
 	{
 		Oid			nextOid;
 
 		memcpy(&nextOid, rec, sizeof(Oid));
-		appendStringInfo(buf, "nextOid: %u", nextOid);
-	}
-	else if (info == XLOG_SWITCH)
-	{
-		appendStringInfoString(buf, "xlog switch");
+		appendStringInfo(buf, "%u", nextOid);
 	}
 	else if (info == XLOG_RESTORE_POINT)
 	{
 		xl_restore_point *xlrec = (xl_restore_point *) rec;
 
-		appendStringInfo(buf, "restore point: %s", xlrec->rp_name);
-
+		appendStringInfo(buf, "%s", xlrec->rp_name);
 	}
 	else if (info == XLOG_FPI)
 	{
 		BkpBlock   *bkp = (BkpBlock *) rec;
 
-		appendStringInfo(buf, "full-page image: %s block %u",
+		appendStringInfo(buf, "%s block %u",
 						 relpathperm(bkp->node, bkp->fork),
 						 bkp->block);
 	}
@@ -96,7 +87,7 @@ xlog_desc(StringInfo buf, XLogRecord *record)
 		XLogRecPtr	startpoint;
 
 		memcpy(&startpoint, rec, sizeof(XLogRecPtr));
-		appendStringInfo(buf, "backup end: %X/%X",
+		appendStringInfo(buf, "%X/%X",
 						 (uint32) (startpoint >> 32), (uint32) startpoint);
 	}
 	else if (info == XLOG_PARAMETER_CHANGE)
@@ -118,7 +109,7 @@ xlog_desc(StringInfo buf, XLogRecord *record)
 			}
 		}
 
-		appendStringInfo(buf, "parameter change: max_connections=%d max_worker_processes=%d max_prepared_xacts=%d max_locks_per_xact=%d wal_level=%s",
+		appendStringInfo(buf, "max_connections=%d max_worker_processes=%d max_prepared_xacts=%d max_locks_per_xact=%d wal_level=%s",
 						 xlrec.MaxConnections,
 						 xlrec.max_worker_processes,
 						 xlrec.max_prepared_xacts,
@@ -130,17 +121,60 @@ xlog_desc(StringInfo buf, XLogRecord *record)
 		bool		fpw;
 
 		memcpy(&fpw, rec, sizeof(bool));
-		appendStringInfo(buf, "full_page_writes: %s", fpw ? "true" : "false");
+		appendStringInfo(buf, "%s", fpw ? "true" : "false");
 	}
 	else if (info == XLOG_END_OF_RECOVERY)
 	{
 		xl_end_of_recovery xlrec;
 
 		memcpy(&xlrec, rec, sizeof(xl_end_of_recovery));
-		appendStringInfo(buf, "end_of_recovery: tli %u; prev tli %u; time %s",
+		appendStringInfo(buf, "tli %u; prev tli %u; time %s",
 						 xlrec.ThisTimeLineID, xlrec.PrevTimeLineID,
 						 timestamptz_to_str(xlrec.end_time));
 	}
-	else
-		appendStringInfoString(buf, "UNKNOWN");
+}
+
+const char *
+xlog_identify(uint8 info)
+{
+	const char *id = NULL;
+
+	switch (info)
+	{
+		case XLOG_CHECKPOINT_SHUTDOWN:
+			id = "CHECKPOINT_SHUTDOWN";
+			break;
+		case XLOG_CHECKPOINT_ONLINE:
+			id = "CHECKPOINT_ONLINE";
+			break;
+		case XLOG_NOOP:
+			id = "NOOP";
+			break;
+		case XLOG_NEXTOID:
+			id = "NEXTOID";
+			break;
+		case XLOG_SWITCH:
+			id = "SWITCH";
+			break;
+		case XLOG_BACKUP_END:
+			id = "BACKUP_END";
+			break;
+		case XLOG_PARAMETER_CHANGE:
+			id = "PARAMETER_CHANGE";
+			break;
+		case XLOG_RESTORE_POINT:
+			id = "RESTORE_POINT";
+			break;
+		case XLOG_FPW_CHANGE:
+			id = "FPW_CHANGE";
+			break;
+		case XLOG_END_OF_RECOVERY:
+			id = "END_OF_RECOVERY";
+			break;
+		case XLOG_FPI:
+			id = "FPI";
+			break;
+	}
+
+	return id;
 }
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index c0a7a6f..2645a7a 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -25,8 +25,8 @@
 #include "utils/relmapper.h"
 
 /* must be kept in sync with RmgrData definition in xlog_internal.h */
-#define PG_RMGR(symname,name,redo,desc,startup,cleanup) \
-	{ name, redo, desc, startup, cleanup },
+#define PG_RMGR(symname,name,redo,desc,identify,startup,cleanup) \
+	{ name, redo, desc, identify, startup, cleanup },
 
 const RmgrData RmgrTable[RM_MAX_ID + 1] = {
 #include "access/rmgrlist.h"
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 34f2fc0..6335db5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -799,6 +799,7 @@ static bool CheckForStandbyTrigger(void);
 #ifdef WAL_DEBUG
 static void xlog_outrec(StringInfo buf, XLogRecord *record);
 #endif
+static void xlog_outdesc(StringInfo buf, RmgrId rmid, XLogRecord *record);
 static void pg_start_backup_callback(int code, Datum arg);
 static bool read_backup_label(XLogRecPtr *checkPointLoc,
 				  bool *backupEndRequired, bool *backupFromStandby);
@@ -1286,8 +1287,8 @@ begin:;
 			for (; rdata != NULL; rdata = rdata->next)
 				appendBinaryStringInfo(&recordbuf, rdata->data, rdata->len);
 
-			appendStringInfoString(&buf, " - ");
-			RmgrTable[rechdr->xl_rmid].rm_desc(&buf, (XLogRecord *) recordbuf.data);
+			appendStringInfoString(&buf, ": ");
+			xlog_outdesc(&buf, rechdr->xl_rmid, (XLogRecord *) recordbuf.data);
 		}
 		elog(LOG, "%s", buf.data);
 
@@ -6709,8 +6710,8 @@ StartupXLOG(void)
 							(uint32) (ReadRecPtr >> 32), (uint32) ReadRecPtr,
 							 (uint32) (EndRecPtr >> 32), (uint32) EndRecPtr);
 					xlog_outrec(&buf, record);
-					appendStringInfoString(&buf, " - ");
-					RmgrTable[record->xl_rmid].rm_desc(&buf, record);
+					appendStringInfoString(&buf, ": ");
+					xlog_outdesc(&buf, record->xl_rmid, record);
 					elog(LOG, "%s", buf.data);
 					pfree(buf.data);
 				}
@@ -9624,11 +9625,41 @@ xlog_outrec(StringInfo buf, XLogRecord *record)
 		if (record->xl_info & XLR_BKP_BLOCK(i))
 			appendStringInfo(buf, "; bkpb%d", i);
 	}
-
-	appendStringInfo(buf, ": %s", RmgrTable[record->xl_rmid].rm_name);
 }
 #endif   /* WAL_DEBUG */
 
+/*
+ * Returns a string describing an XLogRecord, consisting of its identity
+ * optionally followed by a colon, a space, and a further description.
+ */
+static void
+xlog_outdesc(StringInfo buf, RmgrId rmid, XLogRecord *record)
+{
+	const char *id;
+
+	appendStringInfoString(buf, RmgrTable[rmid].rm_name);
+	appendStringInfoChar(buf, '/');
+
+	id = RmgrTable[rmid].rm_identify(record->xl_info);
+	if (id == NULL)
+		appendStringInfo(buf, "UNKNOWN (%X)", record->xl_info);
+	else
+	{
+		StringInfoData desc;
+
+		initStringInfo(&desc);
+		RmgrTable[rmid].rm_desc(&desc, record);
+
+		appendStringInfoString(buf, id);
+		if (desc.len > 0)
+		{
+			appendStringInfoString(buf, ": ");
+			appendStringInfoString(buf, desc.data);
+			pfree(desc.data);
+		}
+	}
+}
+
 
 /*
  * Return the (possible) sync flag used for opening a file, depending on the
@@ -10664,7 +10695,7 @@ rm_redo_error_callback(void *arg)
 	StringInfoData buf;
 
 	initStringInfo(&buf);
-	RmgrTable[record->xl_rmid].rm_desc(&buf, record);
+	xlog_outdesc(&buf, record->xl_rmid, record);
 
 	/* don't bother emitting empty description */
 	if (buf.len > 0)
diff --git a/src/include/access/clog.h b/src/include/access/clog.h
index be9b867..8562631 100644
--- a/src/include/access/clog.h
+++ b/src/include/access/clog.h
@@ -49,5 +49,6 @@ extern void TruncateCLOG(TransactionId oldestXact);
 
 extern void clog_redo(XLogRecPtr lsn, XLogRecord *record);
 extern void clog_desc(StringInfo buf, XLogRecord *record);
+extern const char *clog_identify(uint8 info);
 
 #endif   /* CLOG_H */
diff --git a/src/include/access/gin.h b/src/include/access/gin.h
index a0b288d..0ebecb4 100644
--- a/src/include/access/gin.h
+++ b/src/include/access/gin.h
@@ -74,6 +74,7 @@ extern void ginUpdateStats(Relation index, const GinStatsData *stats);
 /* ginxlog.c */
 extern void gin_redo(XLogRecPtr lsn, XLogRecord *record);
 extern void gin_desc(StringInfo buf, XLogRecord *record);
+extern const char *gin_identify(uint8 info);
 extern void gin_xlog_startup(void);
 extern void gin_xlog_cleanup(void);
 
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 03e9903..879f113 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -452,6 +452,7 @@ extern SplitedPageLayout *gistSplit(Relation r, Page page, IndexTuple *itup,
 /* gistxlog.c */
 extern void gist_redo(XLogRecPtr lsn, XLogRecord *record);
 extern void gist_desc(StringInfo buf, XLogRecord *record);
+extern const char *gist_identify(uint8 info);
 extern void gist_xlog_startup(void);
 extern void gist_xlog_cleanup(void);
 
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index ff29fea..a81b9de 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -357,5 +357,6 @@ extern OffsetNumber _hash_binsearch_last(Page page, uint32 hash_value);
 /* hash.c */
 extern void hash_redo(XLogRecPtr lsn, XLogRecord *record);
 extern void hash_desc(StringInfo buf, XLogRecord *record);
+extern const char *hash_identify(uint8 info);
 
 #endif   /* HASH_H */
diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h
index 2544347..5ac98a5 100644
--- a/src/include/access/heapam_xlog.h
+++ b/src/include/access/heapam_xlog.h
@@ -357,8 +357,10 @@ extern void HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple,
 
 extern void heap_redo(XLogRecPtr lsn, XLogRecord *record);
 extern void heap_desc(StringInfo buf, XLogRecord *record);
+extern const char *heap_identify(uint8 info);
 extern void heap2_redo(XLogRecPtr lsn, XLogRecord *record);
 extern void heap2_desc(StringInfo buf, XLogRecord *record);
+extern const char *heap2_identify(uint8 info);
 extern void heap_xlog_logical_rewrite(XLogRecPtr lsn, XLogRecord *r);
 
 extern XLogRecPtr log_heap_cleanup_info(RelFileNode rnode,
diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
index 8db773b..b331447 100644
--- a/src/include/access/multixact.h
+++ b/src/include/access/multixact.h
@@ -136,6 +136,7 @@ extern void multixact_twophase_postabort(TransactionId xid, uint16 info,
 
 extern void multixact_redo(XLogRecPtr lsn, XLogRecord *record);
 extern void multixact_desc(StringInfo buf, XLogRecord *record);
+extern const char *multixact_identify(uint8 info);
 extern char *mxid_to_string(MultiXactId multi, int nmembers,
 			   MultiXactMember *members);
 
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 9fa943f..90fd6d0 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -726,5 +726,6 @@ extern void _bt_leafbuild(BTSpool *btspool, BTSpool *spool2);
  */
 extern void btree_redo(XLogRecPtr lsn, XLogRecord *record);
 extern void btree_desc(StringInfo buf, XLogRecord *record);
+extern const char *btree_identify(uint8 info);
 
 #endif   /* NBTREE_H */
diff --git a/src/include/access/rmgr.h b/src/include/access/rmgr.h
index 1b577a2..ff7fe62 100644
--- a/src/include/access/rmgr.h
+++ b/src/include/access/rmgr.h
@@ -19,7 +19,7 @@ typedef uint8 RmgrId;
  * Note: RM_MAX_ID must fit in RmgrId; widening that type will affect the XLOG
  * file format.
  */
-#define PG_RMGR(symname,name,redo,desc,startup,cleanup) \
+#define PG_RMGR(symname,name,redo,desc,identify,startup,cleanup) \
 	symname,
 
 typedef enum RmgrIds
diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 662fb77..77d4574 100644
--- a/src/include/access/rmgrlist.h
+++ b/src/include/access/rmgrlist.h
@@ -25,20 +25,20 @@
  */
 
 /* symbol name, textual name, redo, desc, startup, cleanup */
-PG_RMGR(RM_XLOG_ID, "XLOG", xlog_redo, xlog_desc, NULL, NULL)
-PG_RMGR(RM_XACT_ID, "Transaction", xact_redo, xact_desc, NULL, NULL)
-PG_RMGR(RM_SMGR_ID, "Storage", smgr_redo, smgr_desc, NULL, NULL)
-PG_RMGR(RM_CLOG_ID, "CLOG", clog_redo, clog_desc, NULL, NULL)
-PG_RMGR(RM_DBASE_ID, "Database", dbase_redo, dbase_desc, NULL, NULL)
-PG_RMGR(RM_TBLSPC_ID, "Tablespace", tblspc_redo, tblspc_desc, NULL, NULL)
-PG_RMGR(RM_MULTIXACT_ID, "MultiXact", multixact_redo, multixact_desc, NULL, NULL)
-PG_RMGR(RM_RELMAP_ID, "RelMap", relmap_redo, relmap_desc, NULL, NULL)
-PG_RMGR(RM_STANDBY_ID, "Standby", standby_redo, standby_desc, NULL, NULL)
-PG_RMGR(RM_HEAP2_ID, "Heap2", heap2_redo, heap2_desc, NULL, NULL)
-PG_RMGR(RM_HEAP_ID, "Heap", heap_redo, heap_desc, NULL, NULL)
-PG_RMGR(RM_BTREE_ID, "Btree", btree_redo, btree_desc, NULL, NULL)
-PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, NULL, NULL)
-PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_xlog_startup, gin_xlog_cleanup)
-PG_RMGR(RM_GIST_ID, "Gist", gist_redo, gist_desc, gist_xlog_startup, gist_xlog_cleanup)
-PG_RMGR(RM_SEQ_ID, "Sequence", seq_redo, seq_desc, NULL, NULL)
-PG_RMGR(RM_SPGIST_ID, "SPGist", spg_redo, spg_desc, spg_xlog_startup, spg_xlog_cleanup)
+PG_RMGR(RM_XLOG_ID, "XLOG", xlog_redo, xlog_desc, xlog_identify, NULL, NULL)
+PG_RMGR(RM_XACT_ID, "Transaction", xact_redo, xact_desc, xact_identify, NULL, NULL)
+PG_RMGR(RM_SMGR_ID, "Storage", smgr_redo, smgr_desc, smgr_identify, NULL, NULL)
+PG_RMGR(RM_CLOG_ID, "CLOG", clog_redo, clog_desc, clog_identify, NULL, NULL)
+PG_RMGR(RM_DBASE_ID, "Database", dbase_redo, dbase_desc, dbase_identify, NULL, NULL)
+PG_RMGR(RM_TBLSPC_ID, "Tablespace", tblspc_redo, tblspc_desc, tblspc_identify, NULL, NULL)
+PG_RMGR(RM_MULTIXACT_ID, "MultiXact", multixact_redo, multixact_desc, multixact_identify, NULL, NULL)
+PG_RMGR(RM_RELMAP_ID, "RelMap", relmap_redo, relmap_desc, relmap_identify, NULL, NULL)
+PG_RMGR(RM_STANDBY_ID, "Standby", standby_redo, standby_desc, standby_identify, NULL, NULL)
+PG_RMGR(RM_HEAP2_ID, "Heap2", heap2_redo, heap2_desc, heap2_identify, NULL, NULL)
+PG_RMGR(RM_HEAP_ID, "Heap", heap_redo, heap_desc, heap_identify, NULL, NULL)
+PG_RMGR(RM_BTREE_ID, "Btree", btree_redo, btree_desc, btree_identify, NULL, NULL)
+PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL)
+PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, gin_xlog_cleanup)
+PG_RMGR(RM_GIST_ID, "Gist", gist_redo, gist_desc, gist_identify, gist_xlog_startup, gist_xlog_cleanup)
+PG_RMGR(RM_SEQ_ID, "Sequence", seq_redo, seq_desc, seq_identify, NULL, NULL)
+PG_RMGR(RM_SPGIST_ID, "SPGist", spg_redo, spg_desc, spg_identify, spg_xlog_startup, spg_xlog_cleanup)
diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h
index 7f8655c..f218a83 100644
--- a/src/include/access/spgist.h
+++ b/src/include/access/spgist.h
@@ -198,6 +198,7 @@ extern Datum spgvacuumcleanup(PG_FUNCTION_ARGS);
 /* spgxlog.c */
 extern void spg_redo(XLogRecPtr lsn, XLogRecord *record);
 extern void spg_desc(StringInfo buf, XLogRecord *record);
+extern const char *spg_identify(uint8 info);
 extern void spg_xlog_startup(void);
 extern void spg_xlog_cleanup(void);
 
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 2168dc3..45376b4 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -256,5 +256,6 @@ extern int	xactGetCommittedChildren(TransactionId **ptr);
 
 extern void xact_redo(XLogRecPtr lsn, XLogRecord *record);
 extern void xact_desc(StringInfo buf, XLogRecord *record);
+extern const char *xact_identify(uint8 info);
 
 #endif   /* XACT_H */
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 7d6db49..0b7bfa5 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -304,6 +304,7 @@ extern Buffer RestoreBackupBlock(XLogRecPtr lsn, XLogRecord *record,
 
 extern void xlog_redo(XLogRecPtr lsn, XLogRecord *record);
 extern void xlog_desc(StringInfo buf, XLogRecord *record);
+extern const char *xlog_identify(uint8 info);
 
 extern void issue_xlog_fsync(int fd, XLogSegNo segno);
 
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 954114f..836cc7c 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -239,6 +239,16 @@ struct XLogRecord;
  * This struct must be kept in sync with the PG_RMGR definition in
  * rmgr.c.
  *
+ * rm_identify must return a name for the record based on xl_info
+ * (without reference to the rmid). For example, XLOG_BTREE_VACUUM
+ * would be named "VACUUM". rm_desc can then be called to obtain
+ * additional detail for the record, if available (e.g. the last
+ * block).
+ *
+ * The return value from rm_identify is a pointer to a statically
+ * allocated buffer, and is therefore valid only until the next
+ * invocation of the callback.
+ *
  * RmgrTable[] is indexed by RmgrId values (see rmgrlist.h).
  */
 typedef struct RmgrData
@@ -246,6 +256,7 @@ typedef struct RmgrData
 	const char *rm_name;
 	void		(*rm_redo) (XLogRecPtr lsn, struct XLogRecord *rptr);
 	void		(*rm_desc) (StringInfo buf, struct XLogRecord *rptr);
+	const char *(*rm_identify) (uint8 info);
 	void		(*rm_startup) (void);
 	void		(*rm_cleanup) (void);
 } RmgrData;
diff --git a/src/include/catalog/storage_xlog.h b/src/include/catalog/storage_xlog.h
index 7081c99..5fc7235 100644
--- a/src/include/catalog/storage_xlog.h
+++ b/src/include/catalog/storage_xlog.h
@@ -45,5 +45,6 @@ extern void log_smgrcreate(RelFileNode *rnode, ForkNumber forkNum);
 
 extern void smgr_redo(XLogRecPtr lsn, XLogRecord *record);
 extern void smgr_desc(StringInfo buf, XLogRecord *record);
+extern const char *smgr_identify(uint8 info);
 
 #endif   /* STORAGE_XLOG_H */
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index c2380dc..811713f 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -64,6 +64,7 @@ extern char *get_database_name(Oid dbid);
 
 extern void dbase_redo(XLogRecPtr lsn, XLogRecord *rptr);
 extern void dbase_desc(StringInfo buf, XLogRecord *rptr);
+extern const char *dbase_identify(uint8 info);
 
 extern void check_encoding_locale_matches(int encoding, const char *collate, const char *ctype);
 
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 8819c00..914d155 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -78,5 +78,6 @@ extern void ResetSequenceCaches(void);
 
 extern void seq_redo(XLogRecPtr lsn, XLogRecord *rptr);
 extern void seq_desc(StringInfo buf, XLogRecord *rptr);
+extern const char *seq_identify(uint8 info);
 
 #endif   /* SEQUENCE_H */
diff --git a/src/include/commands/tablespace.h b/src/include/commands/tablespace.h
index d01ae8b..0f16f40 100644
--- a/src/include/commands/tablespace.h
+++ b/src/include/commands/tablespace.h
@@ -57,5 +57,6 @@ extern bool directory_is_empty(const char *path);
 
 extern void tblspc_redo(XLogRecPtr lsn, XLogRecord *rptr);
 extern void tblspc_desc(StringInfo buf, XLogRecord *rptr);
+extern const char *tblspc_identify(uint8 info);
 
 #endif   /* TABLESPACE_H */
diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h
index da22fd3..1c63af5 100644
--- a/src/include/storage/standby.h
+++ b/src/include/storage/standby.h
@@ -83,6 +83,7 @@ typedef struct xl_running_xacts
 /* Recovery handlers for the Standby Rmgr (RM_STANDBY_ID) */
 extern void standby_redo(XLogRecPtr lsn, XLogRecord *record);
 extern void standby_desc(StringInfo buf, XLogRecord *record);
+extern const char *standby_identify(uint8 info);
 
 /*
  * Declarations for GetRunningTransactionData(). Similar to Snapshots, but
diff --git a/src/include/utils/relmapper.h b/src/include/utils/relmapper.h
index 76bcf18..37937dd 100644
--- a/src/include/utils/relmapper.h
+++ b/src/include/utils/relmapper.h
@@ -60,5 +60,6 @@ extern void RelationMapInitializePhase3(void);
 
 extern void relmap_redo(XLogRecPtr lsn, XLogRecord *record);
 extern void relmap_desc(StringInfo buf, XLogRecord *record);
+extern const char *relmap_identify(uint8 info);
 
 #endif   /* RELMAPPER_H */
-- 
1.9.1

>From c10148fac6a7eec517b024d8bd27b81552247817 Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <a...@2ndquadrant.com>
Date: Wed, 4 Jun 2014 14:22:33 +0530
Subject: Make 'pg_xlogdump --stats[=record]' display summary statistics

---
 contrib/pg_xlogdump/pg_xlogdump.c | 228 +++++++++++++++++++++++++++++++++++---
 contrib/pg_xlogdump/rmgrdesc.c    |   2 +-
 contrib/pg_xlogdump/rmgrdesc.h    |   1 +
 doc/src/sgml/pg_xlogdump.sgml     |  12 ++
 4 files changed, 229 insertions(+), 14 deletions(-)

diff --git a/contrib/pg_xlogdump/pg_xlogdump.c b/contrib/pg_xlogdump/pg_xlogdump.c
index c555786..0a176bb 100644
--- a/contrib/pg_xlogdump/pg_xlogdump.c
+++ b/contrib/pg_xlogdump/pg_xlogdump.c
@@ -15,9 +15,10 @@
 #include <dirent.h>
 #include <unistd.h>
 
-#include "access/xlog.h"
 #include "access/xlogreader.h"
 #include "access/transam.h"
+#include "catalog/pg_control.h"
+#include "catalog/storage_xlog.h"
 #include "common/fe_memutils.h"
 #include "getopt_long.h"
 #include "rmgrdesc.h"
@@ -41,6 +42,8 @@ typedef struct XLogDumpConfig
 	int			stop_after_records;
 	int			already_displayed_records;
 	bool		follow;
+	bool		stats;
+	bool		stats_per_record;
 
 	/* filter options */
 	int			filter_by_rmgr;
@@ -48,6 +51,22 @@ typedef struct XLogDumpConfig
 	bool		filter_by_xid_enabled;
 } XLogDumpConfig;
 
+typedef struct Stats
+{
+	uint64		count;
+	uint64		rec_len;
+	uint64		fpi_len;
+} Stats;
+
+#define MAX_XLINFO_TYPES 16
+
+typedef struct XLogDumpStats
+{
+	uint64		count;
+	Stats		rmgr_stats[RM_NEXT_ID];
+	Stats		record_stats[RM_NEXT_ID][MAX_XLINFO_TYPES];
+} XLogDumpStats;
+
 static void
 fatal_error(const char *fmt,...)
 __attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2)));
@@ -322,22 +341,48 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
 }
 
 /*
- * Print a record to stdout
+ * Store per-rmgr and per-record statistics for a given record.
  */
 static void
-XLogDumpDisplayRecord(XLogDumpConfig *config, XLogRecPtr ReadRecPtr, XLogRecord *record)
+XLogDumpCountRecord(XLogDumpConfig *config, XLogDumpStats *stats, XLogRecPtr ReadRecPtr, XLogRecord *record)
 {
-	const RmgrDescData *desc = &RmgrDescTable[record->xl_rmid];
+	RmgrId		rmid;
+	uint8		recid;
 
-	if (config->filter_by_rmgr != -1 &&
-		config->filter_by_rmgr != record->xl_rmid)
-		return;
+	stats->count++;
 
-	if (config->filter_by_xid_enabled &&
-		config->filter_by_xid != record->xl_xid)
-		return;
+	/* Update per-rmgr statistics */
 
-	config->already_displayed_records++;
+	rmid = record->xl_rmid;
+
+	stats->rmgr_stats[rmid].count++;
+	stats->rmgr_stats[rmid].rec_len +=
+		record->xl_len + SizeOfXLogRecord;
+	stats->rmgr_stats[rmid].fpi_len +=
+		record->xl_tot_len - (record->xl_len + SizeOfXLogRecord);
+
+	/*
+	 * Update per-record statistics, where the record is identified by a
+	 * combination of the RmgrId and the upper four bits of the xl_info
+	 * field (to give sixteen possible entries per RmgrId).
+	 */
+
+	recid = (record->xl_info & ~XLR_INFO_MASK) >> 4;
+
+	stats->record_stats[rmid][recid].count++;
+	stats->record_stats[rmid][recid].rec_len +=
+		record->xl_len + SizeOfXLogRecord;
+	stats->record_stats[rmid][recid].fpi_len +=
+		record->xl_tot_len - (record->xl_len + SizeOfXLogRecord);
+}
+
+/*
+ * Print a record to stdout
+ */
+static void
+XLogDumpDisplayRecord(XLogDumpConfig *config, XLogRecPtr ReadRecPtr, XLogRecord *record)
+{
+	const RmgrDescData *desc = &RmgrDescTable[record->xl_rmid];
 
 	printf("rmgr: %-11s len (rec/tot): %6u/%6u, tx: %10u, lsn: %X/%08X, prev %X/%08X, bkp: %u%u%u%u, desc: ",
 		   desc->rm_name,
@@ -380,6 +425,125 @@ XLogDumpDisplayRecord(XLogDumpConfig *config, XLogRecPtr ReadRecPtr, XLogRecord
 	}
 }
 
+/*
+ * Display a single row of record counts and sizes for an rmgr or record.
+ */
+static void
+XLogDumpStatsRow(const char *name,
+				 uint64 n, double n_pct,
+				 uint64 rec_len, double rec_len_pct,
+				 uint64 fpi_len, double fpi_len_pct,
+				 uint64 total_len, double total_len_pct)
+{
+	printf("%-27s %20zu (%6.02f) %20zu (%6.02f) %20zu (%6.02f) %20zu (%6.02f)\n",
+		   name, n, n_pct, rec_len, rec_len_pct, fpi_len, fpi_len_pct,
+		   total_len, total_len_pct);
+}
+
+
+/*
+ * Display summary statistics about the records seen so far.
+ */
+static void
+XLogDumpDisplayStats(XLogDumpConfig *config, XLogDumpStats *stats)
+{
+	int			ri, rj;
+	uint64		total_count = 0;
+	uint64		total_rec_len = 0;
+	uint64		total_fpi_len = 0;
+	uint64		total_len = 0;
+
+	/*
+	 * Make a first pass to calculate column totals:
+	 * count(*),
+	 * sum(xl_len+SizeOfXLogRecord),
+	 * sum(xl_tot_len-xl_len-SizeOfXLogRecord), and
+	 * sum(xl_tot_len).
+	 * These are used to calculate percentages for each record type.
+	 */
+
+	for (ri = 0; ri < RM_NEXT_ID; ri++)
+	{
+		total_count += stats->rmgr_stats[ri].count;
+		total_rec_len += stats->rmgr_stats[ri].rec_len;
+		total_fpi_len += stats->rmgr_stats[ri].fpi_len;
+	}
+	total_len = total_rec_len+total_fpi_len;
+
+	/*
+	 * 27 is strlen("Transaction/COMMIT_PREPARED"),
+	 * 20 is strlen(2^64), 8 is strlen("(100.00%)")
+	 */
+
+	printf("%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n"
+		   "%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n",
+		   "Type", "N", "(%)", "Record size", "(%)", "FPI size", "(%)", "Combined size", "(%)",
+		   "----", "-", "---", "-----------", "---", "--------", "---", "-------------", "---");
+
+	for (ri = 0; ri < RM_NEXT_ID; ri++)
+	{
+		uint64		count, rec_len, fpi_len, tot_len;
+		const RmgrDescData *desc = &RmgrDescTable[ri];
+
+		if (!config->stats_per_record)
+		{
+			count = stats->rmgr_stats[ri].count;
+			rec_len = stats->rmgr_stats[ri].rec_len;
+			fpi_len = stats->rmgr_stats[ri].fpi_len;
+			tot_len = rec_len + fpi_len;
+
+			XLogDumpStatsRow(desc->rm_name,
+							 count, 100 * (double)count / total_count,
+							 rec_len, 100 * (double)rec_len / total_rec_len,
+							 fpi_len, 100 * (double)fpi_len / total_fpi_len,
+							 tot_len, 100 * (double)tot_len / total_len);
+		}
+		else
+		{
+			for (rj = 0; rj < MAX_XLINFO_TYPES; rj++)
+			{
+				const char *id;
+
+				count = stats->record_stats[ri][rj].count;
+				rec_len = stats->record_stats[ri][rj].rec_len;
+				fpi_len = stats->record_stats[ri][rj].fpi_len;
+				tot_len = rec_len + fpi_len;
+
+				/* Skip undefined combinations and ones that didn't occur */
+				if (count == 0)
+					continue;
+
+				id = desc->rm_identify(rj << 4);
+				if (id == NULL)
+					id = psprintf("0x%x", rj << 4);
+
+				XLogDumpStatsRow(psprintf("%s/%s", desc->rm_name, id),
+								 count, 100 * (double)count / total_count,
+								 rec_len, 100 * (double)rec_len / total_rec_len,
+								 fpi_len, 100 * (double)fpi_len / total_fpi_len,
+								 tot_len, 100 * (double)tot_len / total_len);
+			}
+		}
+	}
+
+	printf("%-27s %20s %8s %20s %8s %20s %8s %20s\n",
+		   "", "--------", "", "--------", "", "--------", "", "--------");
+
+	/*
+	 * The percentages in earlier rows were calculated against the
+	 * column total, but the ones that follow are against the row total.
+	 * Note that these are displayed with a % symbol to differentiate
+	 * them from the earlier ones, and are thus up to 9 characters long.
+	 */
+
+	printf("%-27s %20zu %-9s%20zu %-9s%20zu %-9s%20zu %-6s\n",
+		   "Total",
+		   stats->count, "",
+		   total_rec_len, psprintf("[%.02f%%]", 100 * (double)total_rec_len / total_len),
+		   total_fpi_len, psprintf("[%.02f%%]", 100 * (double)total_fpi_len / total_len),
+		   total_len, "[100%]");
+}
+
 static void
 usage(void)
 {
@@ -401,6 +565,8 @@ usage(void)
 	printf("                         (default: 1 or the value used in STARTSEG)\n");
 	printf("  -V, --version          output version information, then exit\n");
 	printf("  -x, --xid=XID          only show records with TransactionId XID\n");
+	printf("  -z, --stats[=record]   show statistics instead of records\n");
+	printf("                         (optionally, show per-record statistics)\n");
 	printf("  -?, --help             show this help, then exit\n");
 }
 
@@ -412,6 +578,7 @@ main(int argc, char **argv)
 	XLogReaderState *xlogreader_state;
 	XLogDumpPrivate private;
 	XLogDumpConfig config;
+	XLogDumpStats stats;
 	XLogRecord *record;
 	XLogRecPtr	first_record;
 	char	   *errormsg;
@@ -428,6 +595,7 @@ main(int argc, char **argv)
 		{"timeline", required_argument, NULL, 't'},
 		{"xid", required_argument, NULL, 'x'},
 		{"version", no_argument, NULL, 'V'},
+		{"stats", optional_argument, NULL, 'z'},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -438,6 +606,7 @@ main(int argc, char **argv)
 
 	memset(&private, 0, sizeof(XLogDumpPrivate));
 	memset(&config, 0, sizeof(XLogDumpConfig));
+	memset(&stats, 0, sizeof(XLogDumpStats));
 
 	private.timeline = 1;
 	private.startptr = InvalidXLogRecPtr;
@@ -451,6 +620,8 @@ main(int argc, char **argv)
 	config.filter_by_rmgr = -1;
 	config.filter_by_xid = InvalidTransactionId;
 	config.filter_by_xid_enabled = false;
+	config.stats = false;
+	config.stats_per_record = false;
 
 	if (argc <= 1)
 	{
@@ -458,7 +629,7 @@ main(int argc, char **argv)
 		goto bad_argument;
 	}
 
-	while ((option = getopt_long(argc, argv, "be:?fn:p:r:s:t:Vx:",
+	while ((option = getopt_long(argc, argv, "be:?fn:p:r:s:t:Vx:z::",
 								 long_options, &optindex)) != -1)
 	{
 		switch (option)
@@ -551,6 +722,21 @@ main(int argc, char **argv)
 				}
 				config.filter_by_xid_enabled = true;
 				break;
+			case 'z':
+				config.stats = true;
+				config.stats_per_record = false;
+				if (optarg)
+				{
+					if (strcmp(optarg, "record") == 0)
+						config.stats_per_record = true;
+					else if (strcmp(optarg, "rmgr") != 0)
+					{
+						fprintf(stderr, "%s: unrecognised argument to --stats: %s\n",
+								progname, optarg);
+						goto bad_argument;
+					}
+				}
+				break;
 			default:
 				goto bad_argument;
 		}
@@ -711,14 +897,30 @@ main(int argc, char **argv)
 
 		/* after reading the first record, continue at next one */
 		first_record = InvalidXLogRecPtr;
-		XLogDumpDisplayRecord(&config, xlogreader_state->ReadRecPtr, record);
+
+		if (config.filter_by_rmgr != -1 &&
+			config.filter_by_rmgr != record->xl_rmid)
+			continue;
+
+		if (config.filter_by_xid_enabled &&
+			config.filter_by_xid != record->xl_xid)
+			continue;
+
+		if (config.stats == true)
+			XLogDumpCountRecord(&config, &stats, xlogreader_state->ReadRecPtr, record);
+		else
+			XLogDumpDisplayRecord(&config, xlogreader_state->ReadRecPtr, record);
 
 		/* check whether we printed enough */
+		config.already_displayed_records++;
 		if (config.stop_after_records > 0 &&
 			config.already_displayed_records >= config.stop_after_records)
 			break;
 	}
 
+	if (config.stats == true)
+		XLogDumpDisplayStats(&config, &stats);
+
 	if (errormsg)
 		fatal_error("error in WAL record at %X/%X: %s\n",
 					(uint32) (xlogreader_state->ReadRecPtr >> 32),
diff --git a/contrib/pg_xlogdump/rmgrdesc.c b/contrib/pg_xlogdump/rmgrdesc.c
index 1064d34..dc27fd1 100644
--- a/contrib/pg_xlogdump/rmgrdesc.c
+++ b/contrib/pg_xlogdump/rmgrdesc.c
@@ -28,7 +28,7 @@
 #include "utils/relmapper.h"
 
 #define PG_RMGR(symname,name,redo,desc,identify,startup,cleanup) \
-	{ name, desc, },
+	{ name, desc, identify, },
 
 const RmgrDescData RmgrDescTable[RM_MAX_ID + 1] = {
 #include "access/rmgrlist.h"
diff --git a/contrib/pg_xlogdump/rmgrdesc.h b/contrib/pg_xlogdump/rmgrdesc.h
index d964118..da805c5 100644
--- a/contrib/pg_xlogdump/rmgrdesc.h
+++ b/contrib/pg_xlogdump/rmgrdesc.h
@@ -14,6 +14,7 @@ typedef struct RmgrDescData
 {
 	const char *rm_name;
 	void		(*rm_desc) (StringInfo buf, XLogRecord *record);
+	const char *(*rm_identify) (uint8 info);
 } RmgrDescData;
 
 extern const RmgrDescData RmgrDescTable[];
diff --git a/doc/src/sgml/pg_xlogdump.sgml b/doc/src/sgml/pg_xlogdump.sgml
index 1d1a2ce..d9f4a6a 100644
--- a/doc/src/sgml/pg_xlogdump.sgml
+++ b/doc/src/sgml/pg_xlogdump.sgml
@@ -180,6 +180,18 @@ PostgreSQL documentation
      </varlistentry>
 
      <varlistentry>
+      <term><option>-z</option></term>
+      <term><option>--stats[=record]</option></term>
+      <listitem>
+       <para>
+        Display summary statistics (number and size of records and
+        full-page images) instead of individual records. Optionally
+        generate statistics per-record instead of per-rmgr.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
       <term><option>-?</></term>
       <term><option>--help</></term>
        <listitem>
-- 
1.9.1

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to