Printing LSN made easy

Started by Ashutosh Bapatabout 5 years ago25 messages
#1Ashutosh Bapat
ashutosh.bapat.oss@gmail.com
1 attachment(s)

Hi All,
An LSN is printed using format "%X/%X" and passing (uint32) (lsn >>
32), (uint32) lsn as arguments to that format specifier. This pattern
is repeated at 180 places according to Cscope. I find it hard to
remember this pattern every time I have to print LSN. Usually I end up
copying from some other place. Finding such a place takes a few
minutes and might be error prone esp when the lsn to be printed is an
expression. If we ever have to change this pattern in future, we will
need to change all those 180 places.

The solution seems to be simple though. In the attached patch, I have
added two macros
#define LSN_FORMAT "%X/%X"
#define LSN_FORMAT_ARG(lsn) (uint32) ((lsn) >> 32), (uint32) (lsn)

which can be used instead.

E.g. the following change in the patch
@@ -261,8 +261,7 @@ page_header(PG_FUNCTION_ARGS)
{
char lsnchar[64];

-               snprintf(lsnchar, sizeof(lsnchar), "%X/%X",
-                                (uint32) (lsn >> 32), (uint32) lsn);
+               snprintf(lsnchar, sizeof(lsnchar), LSN_FORMAT,
LSN_FORMAT_ARG(lsn));
                values[0] = CStringGetTextDatum(lsnchar);

LSN_FORMAT_ARG expands to two comma separated arguments and is kinda
open at both ends but it's handy that way.

Off list Peter Eisentraut pointed out that we can not use these macros
in elog/ereport since it creates problems for translations. He
suggested adding functions which return strings and use %s when doing
so.

The patch has two functions pg_lsn_out_internal() which takes an LSN
as input and returns a palloc'ed string containing the string
representation of LSN. This may not be suitable in performance
critical paths and also may leak memory if not freed. So there's
another function pg_lsn_out_buffer() which takes LSN and a char array
as input, fills the char array with the string representation and
returns the pointer to the char array. This allows the function to be
used as an argument in printf/elog etc. Macro MAXPG_LSNLEN has been
extern'elized for this purpose.

Off list Craig Ringer suggested introducing a new format specifier
similar to %m for LSN but I did not get time to take a look at the
relevant code. AFAIU it's available only to elog/ereport, so may not
be useful generally. But teaching printf variants about the new format
would be the best solution. However, I didn't find any way to do that.

In that patch I have changed some random code to use one of the above
methods appropriately, demonstrating their usage. Given that we have
grown 180 places already, I think that something like would have been
welcome earlier. But given that replication code is being actively
worked upon, it may not be too late. As a precedence lfirst_node() and
its variants arrived when the corresponding pattern had been repeated
at so many places.

I think we should move pg_lsn_out_internal() and pg_lsn_out_buffer()
somewhere else. Ideally xlogdefs.c would have been the best place but
there's no such file. May be we add those functions in pg_lsn.c and
add their declarations i xlogdefs.h.

--
Best Wishes,
Ashutosh Bapat

Attachments:

0001-Make-it-easy-to-print-LSN.patchtext/x-patch; charset=US-ASCII; name=0001-Make-it-easy-to-print-LSN.patchDownload
From 698e481f5f55b967b5c60dba4bc577f8baa20ff4 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@2ndquadrant.com>
Date: Fri, 16 Oct 2020 17:09:29 +0530
Subject: [PATCH] Make it easy to print LSN

The commit introduces following macros and functions to make it easy to
use LSNs in printf variants, elog, ereport and appendStringInfo
variants.

LSN_FORMAT - macro representing the format in which LSN is printed

LSN_FORMAT_ARG - macro to pass LSN as an argument to the above format

pg_lsn_out_internal - a function which returns palloc'ed char array
containing string representation of given LSN.

pg_lsn_out_buffer - similar to above but accepts and returns a char
array of size (MAXPG_LSNLEN + 1)

The commit also has some example usages of these.

Ashutosh Bapat
---
 contrib/pageinspect/rawpage.c                |  3 +-
 src/backend/access/rmgrdesc/replorigindesc.c |  5 +-
 src/backend/access/rmgrdesc/xlogdesc.c       |  3 +-
 src/backend/access/transam/xlog.c            |  8 ++--
 src/backend/utils/adt/pg_lsn.c               | 49 ++++++++++++++------
 src/include/access/xlogdefs.h                |  7 +++
 src/include/utils/pg_lsn.h                   |  3 ++
 7 files changed, 55 insertions(+), 23 deletions(-)

diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index c0181506a5..2cd055a5f0 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -261,8 +261,7 @@ page_header(PG_FUNCTION_ARGS)
 	{
 		char		lsnchar[64];
 
-		snprintf(lsnchar, sizeof(lsnchar), "%X/%X",
-				 (uint32) (lsn >> 32), (uint32) lsn);
+		snprintf(lsnchar, sizeof(lsnchar), LSN_FORMAT, LSN_FORMAT_ARG(lsn));
 		values[0] = CStringGetTextDatum(lsnchar);
 	}
 	else
diff --git a/src/backend/access/rmgrdesc/replorigindesc.c b/src/backend/access/rmgrdesc/replorigindesc.c
index 19e14f910b..a3f49b5750 100644
--- a/src/backend/access/rmgrdesc/replorigindesc.c
+++ b/src/backend/access/rmgrdesc/replorigindesc.c
@@ -29,10 +29,9 @@ replorigin_desc(StringInfo buf, XLogReaderState *record)
 
 				xlrec = (xl_replorigin_set *) rec;
 
-				appendStringInfo(buf, "set %u; lsn %X/%X; force: %d",
+				appendStringInfo(buf, "set %u; lsn " LSN_FORMAT "; force: %d",
 								 xlrec->node_id,
-								 (uint32) (xlrec->remote_lsn >> 32),
-								 (uint32) xlrec->remote_lsn,
+								 LSN_FORMAT_ARG(xlrec->remote_lsn),
 								 xlrec->force);
 				break;
 			}
diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c
index 3200f777f5..22bdae5d9a 100644
--- a/src/backend/access/rmgrdesc/xlogdesc.c
+++ b/src/backend/access/rmgrdesc/xlogdesc.c
@@ -89,8 +89,7 @@ xlog_desc(StringInfo buf, XLogReaderState *record)
 		XLogRecPtr	startpoint;
 
 		memcpy(&startpoint, rec, sizeof(XLogRecPtr));
-		appendStringInfo(buf, "%X/%X",
-						 (uint32) (startpoint >> 32), (uint32) startpoint);
+		appendStringInfo(buf, LSN_FORMAT, LSN_FORMAT_ARG(startpoint));
 	}
 	else if (info == XLOG_PARAMETER_CHANGE)
 	{
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 13f1d8c3dc..2436af5244 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -79,6 +79,7 @@
 #include "utils/pg_rusage.h"
 #include "utils/snapmgr.h"
 #include "utils/timestamp.h"
+#include "utils/pg_lsn.h"
 
 extern uint32 bootstrap_data_checksum_version;
 
@@ -5886,15 +5887,16 @@ recoveryStopsAfter(XLogReaderState *record)
 		recoveryTargetInclusive &&
 		record->ReadRecPtr >= recoveryTargetLSN)
 	{
+		char		buf[MAXPG_LSNLEN + 1];
+
 		recoveryStopAfter = true;
 		recoveryStopXid = InvalidTransactionId;
 		recoveryStopLSN = record->ReadRecPtr;
 		recoveryStopTime = 0;
 		recoveryStopName[0] = '\0';
 		ereport(LOG,
-				(errmsg("recovery stopping after WAL location (LSN) \"%X/%X\"",
-						(uint32) (recoveryStopLSN >> 32),
-						(uint32) recoveryStopLSN)));
+				(errmsg("recovery stopping after WAL location (LSN) \"%s\"",
+						pg_lsn_out_buffer(recoveryStopLSN, buf))));
 		return true;
 	}
 
diff --git a/src/backend/utils/adt/pg_lsn.c b/src/backend/utils/adt/pg_lsn.c
index ad0a7bd869..403a20c568 100644
--- a/src/backend/utils/adt/pg_lsn.c
+++ b/src/backend/utils/adt/pg_lsn.c
@@ -19,7 +19,6 @@
 #include "utils/numeric.h"
 #include "utils/pg_lsn.h"
 
-#define MAXPG_LSNLEN			17
 #define MAXPG_LSNCOMPONENT	8
 
 /*----------------------------------------------------------
@@ -81,18 +80,7 @@ Datum
 pg_lsn_out(PG_FUNCTION_ARGS)
 {
 	XLogRecPtr	lsn = PG_GETARG_LSN(0);
-	char		buf[MAXPG_LSNLEN + 1];
-	char	   *result;
-	uint32		id,
-				off;
-
-	/* Decode ID and offset */
-	id = (uint32) (lsn >> 32);
-	off = (uint32) lsn;
-
-	snprintf(buf, sizeof buf, "%X/%X", id, off);
-	result = pstrdup(buf);
-	PG_RETURN_CSTRING(result);
+	PG_RETURN_CSTRING(pg_lsn_out_internal(lsn));
 }
 
 Datum
@@ -317,3 +305,38 @@ pg_lsn_mii(PG_FUNCTION_ARGS)
 	/* Convert to pg_lsn */
 	return DirectFunctionCall1(numeric_pg_lsn, res);
 }
+
+/*
+ * pg_lsn_out_internal
+ *
+ * Returns palloc'ed string representation of an LSN. Used by pg_lsn_out() but
+ * is useful to print LSN in non-performance critical paths. The caller may
+ * pfree the returned memory chunk.
+ */
+char *
+pg_lsn_out_internal(XLogRecPtr lsn)
+{
+	char		buf[MAXPG_LSNLEN + 1];
+
+	snprintf(buf, sizeof(buf), LSN_FORMAT, LSN_FORMAT_ARG(lsn));
+
+	return pstrdup(buf);
+}
+
+/*
+ * pg_lsn_out_buffer
+ *
+ * Similar to the above function but saves the string representation of LSN in
+ * the given buffer which is expected to be at least MAXPG_LSNLEN long. It can
+ * be used in performance critical paths to avoid expensive memory allocation.
+ *
+ * The function returns pointer to the input buffer so that it can be used in
+ * printf variants.
+ */
+char *
+pg_lsn_out_buffer(XLogRecPtr lsn, char *buf)
+{
+	snprintf(buf, sizeof(buf), LSN_FORMAT, LSN_FORMAT_ARG(lsn));
+
+	return buf;
+}
diff --git a/src/include/access/xlogdefs.h b/src/include/access/xlogdefs.h
index e1f5812213..c704ca2454 100644
--- a/src/include/access/xlogdefs.h
+++ b/src/include/access/xlogdefs.h
@@ -35,6 +35,13 @@ typedef uint64 XLogRecPtr;
  */
 #define FirstNormalUnloggedLSN	((XLogRecPtr) 1000)
 
+/* Handy macros to print LSN */
+#define LSN_FORMAT "%X/%X"
+#define LSN_FORMAT_ARG(lsn) (uint32) ((lsn) >> 32), (uint32) (lsn)
+
+/* Maximum length of string representation of an LSN */
+#define MAXPG_LSNLEN			17
+
 /*
  * XLogSegNo - physical log file sequence number.
  */
diff --git a/src/include/utils/pg_lsn.h b/src/include/utils/pg_lsn.h
index 25d6c5b38e..7a8637d2bd 100644
--- a/src/include/utils/pg_lsn.h
+++ b/src/include/utils/pg_lsn.h
@@ -26,4 +26,7 @@
 
 extern XLogRecPtr pg_lsn_in_internal(const char *str, bool *have_error);
 
+extern char *pg_lsn_out_internal(XLogRecPtr lsn);
+extern char *pg_lsn_out_buffer(XLogRecPtr lsn, char *buf);
+
 #endif							/* PG_LSN_H */
-- 
2.17.1

#2Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Ashutosh Bapat (#1)
1 attachment(s)
Re: Printing LSN made easy

Hi,

On 2020-11-27 13:40, Ashutosh Bapat wrote:

Off list Peter Eisentraut pointed out that we can not use these macros
in elog/ereport since it creates problems for translations. He
suggested adding functions which return strings and use %s when doing
so.

The patch has two functions pg_lsn_out_internal() which takes an LSN
as input and returns a palloc'ed string containing the string
representation of LSN. This may not be suitable in performance
critical paths and also may leak memory if not freed. So there's
another function pg_lsn_out_buffer() which takes LSN and a char array
as input, fills the char array with the string representation and
returns the pointer to the char array. This allows the function to be
used as an argument in printf/elog etc. Macro MAXPG_LSNLEN has been
extern'elized for this purpose.

If usage of macros in elog/ereport can cause problems for translation,
then even with this patch life is not get simpler significantly. For
example, instead of just doing like:

              elog(WARNING,
-                 "xlog min recovery request %X/%X is past current point 
%X/%X",
-                 (uint32) (lsn >> 32), (uint32) lsn,
-                 (uint32) (newMinRecoveryPoint >> 32),
-                 (uint32) newMinRecoveryPoint);
+                 "xlog min recovery request " LSN_FORMAT " is past 
current point " LSN_FORMAT,
+                 LSN_FORMAT_ARG(lsn),
+                 LSN_FORMAT_ARG(newMinRecoveryPoint));

we have to either declare two additional local buffers, which is
verbose; or use pg_lsn_out_internal() and rely on memory contexts (or do
pfree() manually, which is verbose again) to prevent memory leaks.

Off list Craig Ringer suggested introducing a new format specifier
similar to %m for LSN but I did not get time to take a look at the
relevant code. AFAIU it's available only to elog/ereport, so may not
be useful generally. But teaching printf variants about the new format
would be the best solution. However, I didn't find any way to do that.

It seems that this topic has been extensively discussed off-list, but
still strong +1 for the patch. I always wanted LSN printing to be more
concise.

I have just tried new printing utilities in a couple of new places and
it looks good to me.

+char *
+pg_lsn_out_internal(XLogRecPtr lsn)
+{
+	char		buf[MAXPG_LSNLEN + 1];
+
+	snprintf(buf, sizeof(buf), LSN_FORMAT, LSN_FORMAT_ARG(lsn));
+
+	return pstrdup(buf);
+}

Would it be a bit more straightforward if we palloc buf initially and
just return a pointer instead of doing pstrdup()?

Regards
--
Alexey Kondratov

Postgres Professional https://www.postgrespro.com
Russian Postgres Company

Attachments:

0001-Make-it-easy-to-print-LSN.patchtext/x-patch; charset=US-ASCII; name=0001-Make-it-easy-to-print-LSN.patchDownload
From 698e481f5f55b967b5c60dba4bc577f8baa20ff4 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@2ndquadrant.com>
Date: Fri, 16 Oct 2020 17:09:29 +0530
Subject: [PATCH] Make it easy to print LSN

The commit introduces following macros and functions to make it easy to
use LSNs in printf variants, elog, ereport and appendStringInfo
variants.

LSN_FORMAT - macro representing the format in which LSN is printed

LSN_FORMAT_ARG - macro to pass LSN as an argument to the above format

pg_lsn_out_internal - a function which returns palloc'ed char array
containing string representation of given LSN.

pg_lsn_out_buffer - similar to above but accepts and returns a char
array of size (MAXPG_LSNLEN + 1)

The commit also has some example usages of these.

Ashutosh Bapat
---
 contrib/pageinspect/rawpage.c                |  3 +-
 src/backend/access/rmgrdesc/replorigindesc.c |  5 +-
 src/backend/access/rmgrdesc/xlogdesc.c       |  3 +-
 src/backend/access/transam/xlog.c            |  8 ++--
 src/backend/utils/adt/pg_lsn.c               | 49 ++++++++++++++------
 src/include/access/xlogdefs.h                |  7 +++
 src/include/utils/pg_lsn.h                   |  3 ++
 7 files changed, 55 insertions(+), 23 deletions(-)

diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index c0181506a5..2cd055a5f0 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -261,8 +261,7 @@ page_header(PG_FUNCTION_ARGS)
 	{
 		char		lsnchar[64];
 
-		snprintf(lsnchar, sizeof(lsnchar), "%X/%X",
-				 (uint32) (lsn >> 32), (uint32) lsn);
+		snprintf(lsnchar, sizeof(lsnchar), LSN_FORMAT, LSN_FORMAT_ARG(lsn));
 		values[0] = CStringGetTextDatum(lsnchar);
 	}
 	else
diff --git a/src/backend/access/rmgrdesc/replorigindesc.c b/src/backend/access/rmgrdesc/replorigindesc.c
index 19e14f910b..a3f49b5750 100644
--- a/src/backend/access/rmgrdesc/replorigindesc.c
+++ b/src/backend/access/rmgrdesc/replorigindesc.c
@@ -29,10 +29,9 @@ replorigin_desc(StringInfo buf, XLogReaderState *record)
 
 				xlrec = (xl_replorigin_set *) rec;
 
-				appendStringInfo(buf, "set %u; lsn %X/%X; force: %d",
+				appendStringInfo(buf, "set %u; lsn " LSN_FORMAT "; force: %d",
 								 xlrec->node_id,
-								 (uint32) (xlrec->remote_lsn >> 32),
-								 (uint32) xlrec->remote_lsn,
+								 LSN_FORMAT_ARG(xlrec->remote_lsn),
 								 xlrec->force);
 				break;
 			}
diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c
index 3200f777f5..22bdae5d9a 100644
--- a/src/backend/access/rmgrdesc/xlogdesc.c
+++ b/src/backend/access/rmgrdesc/xlogdesc.c
@@ -89,8 +89,7 @@ xlog_desc(StringInfo buf, XLogReaderState *record)
 		XLogRecPtr	startpoint;
 
 		memcpy(&startpoint, rec, sizeof(XLogRecPtr));
-		appendStringInfo(buf, "%X/%X",
-						 (uint32) (startpoint >> 32), (uint32) startpoint);
+		appendStringInfo(buf, LSN_FORMAT, LSN_FORMAT_ARG(startpoint));
 	}
 	else if (info == XLOG_PARAMETER_CHANGE)
 	{
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 13f1d8c3dc..2436af5244 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -79,6 +79,7 @@
 #include "utils/pg_rusage.h"
 #include "utils/snapmgr.h"
 #include "utils/timestamp.h"
+#include "utils/pg_lsn.h"
 
 extern uint32 bootstrap_data_checksum_version;
 
@@ -5886,15 +5887,16 @@ recoveryStopsAfter(XLogReaderState *record)
 		recoveryTargetInclusive &&
 		record->ReadRecPtr >= recoveryTargetLSN)
 	{
+		char		buf[MAXPG_LSNLEN + 1];
+
 		recoveryStopAfter = true;
 		recoveryStopXid = InvalidTransactionId;
 		recoveryStopLSN = record->ReadRecPtr;
 		recoveryStopTime = 0;
 		recoveryStopName[0] = '\0';
 		ereport(LOG,
-				(errmsg("recovery stopping after WAL location (LSN) \"%X/%X\"",
-						(uint32) (recoveryStopLSN >> 32),
-						(uint32) recoveryStopLSN)));
+				(errmsg("recovery stopping after WAL location (LSN) \"%s\"",
+						pg_lsn_out_buffer(recoveryStopLSN, buf))));
 		return true;
 	}
 
diff --git a/src/backend/utils/adt/pg_lsn.c b/src/backend/utils/adt/pg_lsn.c
index ad0a7bd869..403a20c568 100644
--- a/src/backend/utils/adt/pg_lsn.c
+++ b/src/backend/utils/adt/pg_lsn.c
@@ -19,7 +19,6 @@
 #include "utils/numeric.h"
 #include "utils/pg_lsn.h"
 
-#define MAXPG_LSNLEN			17
 #define MAXPG_LSNCOMPONENT	8
 
 /*----------------------------------------------------------
@@ -81,18 +80,7 @@ Datum
 pg_lsn_out(PG_FUNCTION_ARGS)
 {
 	XLogRecPtr	lsn = PG_GETARG_LSN(0);
-	char		buf[MAXPG_LSNLEN + 1];
-	char	   *result;
-	uint32		id,
-				off;
-
-	/* Decode ID and offset */
-	id = (uint32) (lsn >> 32);
-	off = (uint32) lsn;
-
-	snprintf(buf, sizeof buf, "%X/%X", id, off);
-	result = pstrdup(buf);
-	PG_RETURN_CSTRING(result);
+	PG_RETURN_CSTRING(pg_lsn_out_internal(lsn));
 }
 
 Datum
@@ -317,3 +305,38 @@ pg_lsn_mii(PG_FUNCTION_ARGS)
 	/* Convert to pg_lsn */
 	return DirectFunctionCall1(numeric_pg_lsn, res);
 }
+
+/*
+ * pg_lsn_out_internal
+ *
+ * Returns palloc'ed string representation of an LSN. Used by pg_lsn_out() but
+ * is useful to print LSN in non-performance critical paths. The caller may
+ * pfree the returned memory chunk.
+ */
+char *
+pg_lsn_out_internal(XLogRecPtr lsn)
+{
+	char		buf[MAXPG_LSNLEN + 1];
+
+	snprintf(buf, sizeof(buf), LSN_FORMAT, LSN_FORMAT_ARG(lsn));
+
+	return pstrdup(buf);
+}
+
+/*
+ * pg_lsn_out_buffer
+ *
+ * Similar to the above function but saves the string representation of LSN in
+ * the given buffer which is expected to be at least MAXPG_LSNLEN long. It can
+ * be used in performance critical paths to avoid expensive memory allocation.
+ *
+ * The function returns pointer to the input buffer so that it can be used in
+ * printf variants.
+ */
+char *
+pg_lsn_out_buffer(XLogRecPtr lsn, char *buf)
+{
+	snprintf(buf, sizeof(buf), LSN_FORMAT, LSN_FORMAT_ARG(lsn));
+
+	return buf;
+}
diff --git a/src/include/access/xlogdefs.h b/src/include/access/xlogdefs.h
index e1f5812213..c704ca2454 100644
--- a/src/include/access/xlogdefs.h
+++ b/src/include/access/xlogdefs.h
@@ -35,6 +35,13 @@ typedef uint64 XLogRecPtr;
  */
 #define FirstNormalUnloggedLSN	((XLogRecPtr) 1000)
 
+/* Handy macros to print LSN */
+#define LSN_FORMAT "%X/%X"
+#define LSN_FORMAT_ARG(lsn) (uint32) ((lsn) >> 32), (uint32) (lsn)
+
+/* Maximum length of string representation of an LSN */
+#define MAXPG_LSNLEN			17
+
 /*
  * XLogSegNo - physical log file sequence number.
  */
diff --git a/src/include/utils/pg_lsn.h b/src/include/utils/pg_lsn.h
index 25d6c5b38e..7a8637d2bd 100644
--- a/src/include/utils/pg_lsn.h
+++ b/src/include/utils/pg_lsn.h
@@ -26,4 +26,7 @@
 
 extern XLogRecPtr pg_lsn_in_internal(const char *str, bool *have_error);
 
+extern char *pg_lsn_out_internal(XLogRecPtr lsn);
+extern char *pg_lsn_out_buffer(XLogRecPtr lsn, char *buf);
+
 #endif							/* PG_LSN_H */
-- 
2.17.1

#3Li Japin
japinli@hotmail.com
In reply to: Alexey Kondratov (#2)
Re: Printing LSN made easy

Hi,

Here, we cannot use sizeof(but) to get the buf size, because it is a pointer, so it always
8 bytes on 64-bit or 4 bytes on 32-bit machine.

+char *
+pg_lsn_out_buffer(XLogRecPtr lsn, char *buf)
+{
+       snprintf(buf, sizeof(buf), LSN_FORMAT, LSN_FORMAT_ARG(lsn));
+
+       return buf;
+}

--
Best regards
Japin Li
ChengDu WenWu Information Technolog Co.,Ltd.

Show quoted text

On Nov 27, 2020, at 10:24 PM, Alexey Kondratov <a.kondratov@postgrespro.ru> wrote:

Hi,

On 2020-11-27 13:40, Ashutosh Bapat wrote:

Off list Peter Eisentraut pointed out that we can not use these macros
in elog/ereport since it creates problems for translations. He
suggested adding functions which return strings and use %s when doing
so.
The patch has two functions pg_lsn_out_internal() which takes an LSN
as input and returns a palloc'ed string containing the string
representation of LSN. This may not be suitable in performance
critical paths and also may leak memory if not freed. So there's
another function pg_lsn_out_buffer() which takes LSN and a char array
as input, fills the char array with the string representation and
returns the pointer to the char array. This allows the function to be
used as an argument in printf/elog etc. Macro MAXPG_LSNLEN has been
extern'elized for this purpose.

If usage of macros in elog/ereport can cause problems for translation, then even with this patch life is not get simpler significantly. For example, instead of just doing like:

elog(WARNING,
-                 "xlog min recovery request %X/%X is past current point %X/%X",
-                 (uint32) (lsn >> 32), (uint32) lsn,
-                 (uint32) (newMinRecoveryPoint >> 32),
-                 (uint32) newMinRecoveryPoint);
+                 "xlog min recovery request " LSN_FORMAT " is past current point " LSN_FORMAT,
+                 LSN_FORMAT_ARG(lsn),
+                 LSN_FORMAT_ARG(newMinRecoveryPoint));

we have to either declare two additional local buffers, which is verbose; or use pg_lsn_out_internal() and rely on memory contexts (or do pfree() manually, which is verbose again) to prevent memory leaks.

Off list Craig Ringer suggested introducing a new format specifier
similar to %m for LSN but I did not get time to take a look at the
relevant code. AFAIU it's available only to elog/ereport, so may not
be useful generally. But teaching printf variants about the new format
would be the best solution. However, I didn't find any way to do that.

It seems that this topic has been extensively discussed off-list, but still strong +1 for the patch. I always wanted LSN printing to be more concise.

I have just tried new printing utilities in a couple of new places and it looks good to me.

+char *
+pg_lsn_out_internal(XLogRecPtr lsn)
+{
+	char		buf[MAXPG_LSNLEN + 1];
+
+	snprintf(buf, sizeof(buf), LSN_FORMAT, LSN_FORMAT_ARG(lsn));
+
+	return pstrdup(buf);
+}

Would it be a bit more straightforward if we palloc buf initially and just return a pointer instead of doing pstrdup()?

Regards
--
Alexey Kondratov

Postgres Professional https://www.postgrespro.com
Russian Postgres Company<0001-Make-it-easy-to-print-LSN.patch>

#4Michael Paquier
michael@paquier.xyz
In reply to: Ashutosh Bapat (#1)
Re: Printing LSN made easy

On Fri, Nov 27, 2020 at 04:10:27PM +0530, Ashutosh Bapat wrote:

LSN_FORMAT_ARG expands to two comma separated arguments and is kinda
open at both ends but it's handy that way.

Agreed that's useful.

Off list Peter Eisentraut pointed out that we can not use these macros
in elog/ereport since it creates problems for translations. He
suggested adding functions which return strings and use %s when doing
so.

What's the problem with translations? We already have equivalent
stuff with INT64_FORMAT that may map to %ld or %lld. But here we have
a format that's set in stone.

The patch has two functions pg_lsn_out_internal() which takes an LSN
as input and returns a palloc'ed string containing the string
representation of LSN. This may not be suitable in performance
critical paths and also may leak memory if not freed.

I'd rather never use this flavor. An OOM could mask the actual error
behind.

Off list Craig Ringer suggested introducing a new format specifier
similar to %m for LSN but I did not get time to take a look at the
relevant code. AFAIU it's available only to elog/ereport, so may not
be useful generally. But teaching printf variants about the new format
would be the best solution. However, I didn't find any way to do that.

-1. %m maps to errno, that is much more generic. A set of macros
that maps to our internal format would be fine enough IMO.
--
Michael

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#4)
Re: Printing LSN made easy

Michael Paquier <michael@paquier.xyz> writes:

On Fri, Nov 27, 2020 at 04:10:27PM +0530, Ashutosh Bapat wrote:

Off list Craig Ringer suggested introducing a new format specifier
similar to %m for LSN but I did not get time to take a look at the
relevant code. AFAIU it's available only to elog/ereport, so may not
be useful generally. But teaching printf variants about the new format
would be the best solution. However, I didn't find any way to do that.

-1. %m maps to errno, that is much more generic. A set of macros
that maps to our internal format would be fine enough IMO.

Agreed. snprintf.c is meant to implement a recognized standard
(ok, %m is a GNU extension, but it's still pretty standard).
I'm not on board with putting PG-only extensions in there.

regards, tom lane

#6Craig Ringer
craig.ringer@enterprisedb.com
In reply to: Tom Lane (#5)
Re: Printing LSN made easy

On Mon, Nov 30, 2020 at 1:10 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Michael Paquier <michael@paquier.xyz> writes:

On Fri, Nov 27, 2020 at 04:10:27PM +0530, Ashutosh Bapat wrote:

Off list Craig Ringer suggested introducing a new format specifier
similar to %m for LSN but I did not get time to take a look at the
relevant code. AFAIU it's available only to elog/ereport, so may not
be useful generally. But teaching printf variants about the new format
would be the best solution. However, I didn't find any way to do that.

-1. %m maps to errno, that is much more generic. A set of macros
that maps to our internal format would be fine enough IMO.

Agreed. snprintf.c is meant to implement a recognized standard
(ok, %m is a GNU extension, but it's still pretty standard).
I'm not on board with putting PG-only extensions in there.

Fair enough. I did not realise that %m was a GNU extension (never looked
closely) so I thought we had precedent for Pg specific extensions there.

Agreed in that case, no argument from me.

#7Ashutosh Bapat
ashutosh.bapat.oss@gmail.com
In reply to: Alexey Kondratov (#2)
Re: Printing LSN made easy

On Fri, Nov 27, 2020 at 7:54 PM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:

Hi,

On 2020-11-27 13:40, Ashutosh Bapat wrote:

Off list Peter Eisentraut pointed out that we can not use these macros
in elog/ereport since it creates problems for translations. He
suggested adding functions which return strings and use %s when doing
so.

The patch has two functions pg_lsn_out_internal() which takes an LSN
as input and returns a palloc'ed string containing the string
representation of LSN. This may not be suitable in performance
critical paths and also may leak memory if not freed. So there's
another function pg_lsn_out_buffer() which takes LSN and a char array
as input, fills the char array with the string representation and
returns the pointer to the char array. This allows the function to be
used as an argument in printf/elog etc. Macro MAXPG_LSNLEN has been
extern'elized for this purpose.

If usage of macros in elog/ereport can cause problems for translation,
then even with this patch life is not get simpler significantly. For
example, instead of just doing like:

elog(WARNING,
-                 "xlog min recovery request %X/%X is past current point
%X/%X",
-                 (uint32) (lsn >> 32), (uint32) lsn,
-                 (uint32) (newMinRecoveryPoint >> 32),
-                 (uint32) newMinRecoveryPoint);
+                 "xlog min recovery request " LSN_FORMAT " is past
current point " LSN_FORMAT,
+                 LSN_FORMAT_ARG(lsn),
+                 LSN_FORMAT_ARG(newMinRecoveryPoint));

we have to either declare two additional local buffers, which is
verbose; or use pg_lsn_out_internal() and rely on memory contexts (or do
pfree() manually, which is verbose again) to prevent memory leaks.

I agree, that using LSN_FORMAT is best, but if that's not allowed, at
least the pg_lsn_out_internal() and variants encapsulate the
LSN_FORMAT so that the callers don't need to remember those and we
have to change only a couple of places when the LSN_FORMAT itself
changes.

Off list Craig Ringer suggested introducing a new format specifier
similar to %m for LSN but I did not get time to take a look at the
relevant code. AFAIU it's available only to elog/ereport, so may not
be useful generally. But teaching printf variants about the new format
would be the best solution. However, I didn't find any way to do that.

It seems that this topic has been extensively discussed off-list, but
still strong +1 for the patch. I always wanted LSN printing to be more
concise.

I have just tried new printing utilities in a couple of new places and
it looks good to me.

Thanks.

+char *
+pg_lsn_out_internal(XLogRecPtr lsn)
+{
+       char            buf[MAXPG_LSNLEN + 1];
+
+       snprintf(buf, sizeof(buf), LSN_FORMAT, LSN_FORMAT_ARG(lsn));
+
+       return pstrdup(buf);
+}

Would it be a bit more straightforward if we palloc buf initially and
just return a pointer instead of doing pstrdup()?

Possibly. I just followed the code in pg_lsn_out(), which snprintf()
in a char array and then does pstrdup(). I don't quite understand the
purpose of that.

--
Best Wishes,
Ashutosh Bapat

#8Ashutosh Bapat
ashutosh.bapat.oss@gmail.com
In reply to: Li Japin (#3)
Re: Printing LSN made easy

On Fri, Nov 27, 2020 at 9:51 PM Li Japin <japinli@hotmail.com> wrote:

Hi,

Here, we cannot use sizeof(but) to get the buf size, because it is a pointer, so it always
8 bytes on 64-bit or 4 bytes on 32-bit machine.

For an array, the sizeof() returns the size of memory consumed by the
array. See section "Application to arrays" at
https://en.wikipedia.org/wiki/Sizeof.
--
Best Wishes,
Ashutosh Bapat

#9Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Michael Paquier (#4)
Re: Printing LSN made easy

On Sun, Nov 29, 2020 at 1:23 PM Michael Paquier <michael@paquier.xyz> wrote:

On Fri, Nov 27, 2020 at 04:10:27PM +0530, Ashutosh Bapat wrote:

LSN_FORMAT_ARG expands to two comma separated arguments and is kinda
open at both ends but it's handy that way.

Agreed that's useful.

Off list Peter Eisentraut pointed out that we can not use these macros
in elog/ereport since it creates problems for translations. He
suggested adding functions which return strings and use %s when doing
so.

What's the problem with translations? We already have equivalent
stuff with INT64_FORMAT that may map to %ld or %lld. But here we have
a format that's set in stone.

Peter Eisentraut explained that the translation system can not handle
strings broken by macros, e.g. errmsg("foo" MACRO "bar"), since it doesn't
know statically what the macro resolves to. But I am not familiar with our
translation system myself. But if we allow INT64_FORMAT, LSN_FORMAT should
be allowed. That makes life much easier. We do not need other functions at
all.

Peter E or somebody familiar with translations can provide more
information.

The patch has two functions pg_lsn_out_internal() which takes an LSN
as input and returns a palloc'ed string containing the string
representation of LSN. This may not be suitable in performance
critical paths and also may leak memory if not freed.

I'd rather never use this flavor. An OOM could mask the actual error
behind.

Hmm that's a good point. It might be useful in non-ERROR cases, merely
because it avoids declaring a char array :).

No one seems to reject this idea so I will add it to the next commitfest to
get more reviews esp. clarification around whether macros are enough or not.

--
Best Wishes,
Ashutosh

#10Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Tom Lane (#5)
Re: Printing LSN made easy

On Sun, Nov 29, 2020 at 10:40 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Michael Paquier <michael@paquier.xyz> writes:

On Fri, Nov 27, 2020 at 04:10:27PM +0530, Ashutosh Bapat wrote:

Off list Craig Ringer suggested introducing a new format specifier
similar to %m for LSN but I did not get time to take a look at the
relevant code. AFAIU it's available only to elog/ereport, so may not
be useful generally. But teaching printf variants about the new format
would be the best solution. However, I didn't find any way to do that.

-1. %m maps to errno, that is much more generic. A set of macros
that maps to our internal format would be fine enough IMO.

Agreed. snprintf.c is meant to implement a recognized standard
(ok, %m is a GNU extension, but it's still pretty standard).
I'm not on board with putting PG-only extensions in there.

Thanks for the clarification.

--
Best Wishes,
Ashutosh

#11Li Japin
japinli@hotmail.com
In reply to: Ashutosh Bapat (#8)
Re: Printing LSN made easy

Hi,

On Nov 30, 2020, at 9:06 PM, Ashutosh Bapat <ashutosh.bapat.oss@gmail.com<mailto:ashutosh.bapat.oss@gmail.com>> wrote:

On Fri, Nov 27, 2020 at 9:51 PM Li Japin <japinli@hotmail.com<mailto:japinli@hotmail.com>> wrote:

Hi,

Here, we cannot use sizeof(but) to get the buf size, because it is a pointer, so it always
8 bytes on 64-bit or 4 bytes on 32-bit machine.

For an array, the sizeof() returns the size of memory consumed by the
array. See section "Application to arrays" at
https://en.wikipedia.org/wiki/Sizeof.

That’s true! However, in pg_lsn_out_buffer(), it converts to a pointer, not an array. See the following test:

```c
#include <stdio.h>
#include <stdint.h>

typedef uint64_t XLogRecPtr;
typedef uint32_t uint32;

#define MAXPG_LSNLEN 17
#define LSN_FORMAT "%X/%X"
#define LSN_FORMAT_ARG(lsn) (uint32) ((lsn) >> 32), (uint32) (lsn)

char *
pg_lsn_out_buffer(XLogRecPtr lsn, char *buf)
{
printf("pg_lsn_out_buffer: sizeof(buf) = %lu\n", sizeof(buf));
snprintf(buf, sizeof(buf), LSN_FORMAT, LSN_FORMAT_ARG(lsn));
return buf;
}

char *
pg_lsn_out_buffer1(XLogRecPtr lsn, char *buf, size_t len)
{
printf("pg_lsn_out_buffer1: sizeof(buf) = %lu, len = %lu\n", sizeof(buf), len);
snprintf(buf, len, LSN_FORMAT, LSN_FORMAT_ARG(lsn));
return buf;
}

int
main(void)
{
char buf[MAXPG_LSNLEN + 1];
XLogRecPtr lsn = 1234567UL;

printf("main: sizeof(buf) = %lu\n", sizeof(buf));
pg_lsn_out_buffer(lsn, buf);
printf("buffer's content from pg_lsn_out_buffer: %s\n", buf);

pg_lsn_out_buffer1(lsn, buf, sizeof(buf));
printf("buffer's content from pg_lsn_out_buffer1: %s\n", buf);
return 0;
}
```

The above output is:

```
main: sizeof(buf) = 18
pg_lsn_out_buffer: sizeof(buf) = 8
buffer's content from pg_lsn_out_buffer: 0/12D68
pg_lsn_out_buffer1: sizeof(buf) = 8, len = 18
buffer's content from pg_lsn_out_buffer1: 0/12D687
```

--
Best regards
Japin Li
ChengDu WenWu Information Technolog Co.,Ltd.

#12Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Ashutosh Bapat (#9)
Re: Printing LSN made easy

On 2020-Nov-30, Ashutosh Bapat wrote:

Peter Eisentraut explained that the translation system can not handle
strings broken by macros, e.g. errmsg("foo" MACRO "bar"), since it doesn't
know statically what the macro resolves to. But I am not familiar with our
translation system myself. But if we allow INT64_FORMAT, LSN_FORMAT should
be allowed. That makes life much easier. We do not need other functions at
all.

Peter E or somebody familiar with translations can provide more
information.

We don't allow INT64_FORMAT in translatable strings, period. (See
commit 6a1cd8b9236d which undid some hacks we had to use to work around
the lack of it, by allowing %llu to take its place.) For the same
reason, we cannot allow LSN_FORMAT or similar constructions in
translatable strings either.

If the solution to ugliness of LSN printing is going to require that we
add a "%s" which prints a previously formatted string with LSN_FORMAT,
it won't win us anything.

As annoyed as I am about our LSN printing, I don't think this patch
has the solutions we need.

#13Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#5)
Re: Printing LSN made easy

Hi,

On 2020-11-29 12:10:21 -0500, Tom Lane wrote:

Agreed. snprintf.c is meant to implement a recognized standard
(ok, %m is a GNU extension, but it's still pretty standard).
I'm not on board with putting PG-only extensions in there.

I wonder if there's something we could layer on the elog.c level
instead. But I also don't like the idea of increasing the use of wrapper
functions that need to allocate string buffers than then need to get
copied in turn.

We could do something like
errmsg("plain string arg: %s, conv string arg: %s", somestr, estr_lsn(lsn))

where estr_lsn() wouldn't do any conversion directly, instead setting up
a callback in ErrorData that knows how to do the type specific
conversion. Then during EVALUATE_MESSAGE() we'd evaluate the message
piecemeal and call the output callbacks for each arg, using the
StringInfo.

There's two main issues with something roughly like this:
1) We'd need to do format string parsing somewhere above snprintf.c,
which isn't free.
2) Without relying on C11 / _Generic() some ugly macro hackery would be
needed to have a argument-number indexed state. If we did rely on
_Generic() we probably could do better, even getting rid of the need
for something like estr_lsn().

Greetings,

Andres Freund

#14Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Li Japin (#11)
Re: Printing LSN made easy

On Mon, Nov 30, 2020 at 7:38 PM Li Japin <japinli@hotmail.com> wrote:

Hi,

On Nov 30, 2020, at 9:06 PM, Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
wrote:

On Fri, Nov 27, 2020 at 9:51 PM Li Japin <japinli@hotmail.com> wrote:

Hi,

Here, we cannot use sizeof(but) to get the buf size, because it is a
pointer, so it always
8 bytes on 64-bit or 4 bytes on 32-bit machine.

For an array, the sizeof() returns the size of memory consumed by the
array. See section "Application to arrays" at
https://en.wikipedia.org/wiki/Sizeof.

That’s true! However, in pg_lsn_out_buffer(), it converts to a pointer,
not an array. See the following test:

Ah! Thanks for pointing that out. I have fixed this in my repository.
However, from Alvaro's reply it looks like the approach is not acceptable,
so I am not posting the fixed version here.

--
Best Wishes,
Ashutosh

#15Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Alvaro Herrera (#12)
Re: Printing LSN made easy

On Mon, Nov 30, 2020 at 8:07 PM Alvaro Herrera <alvherre@alvh.no-ip.org>
wrote:

On 2020-Nov-30, Ashutosh Bapat wrote:

Peter Eisentraut explained that the translation system can not handle
strings broken by macros, e.g. errmsg("foo" MACRO "bar"), since it

doesn't

know statically what the macro resolves to. But I am not familiar with

our

translation system myself. But if we allow INT64_FORMAT, LSN_FORMAT

should

be allowed. That makes life much easier. We do not need other functions

at

all.

Peter E or somebody familiar with translations can provide more
information.

We don't allow INT64_FORMAT in translatable strings, period. (See
commit 6a1cd8b9236d which undid some hacks we had to use to work around
the lack of it, by allowing %llu to take its place.) For the same
reason, we cannot allow LSN_FORMAT or similar constructions in
translatable strings either.

If the solution to ugliness of LSN printing is going to require that we
add a "%s" which prints a previously formatted string with LSN_FORMAT,
it won't win us anything.

Thanks for the commit. The commit reverted code which uses a char array to
print int64. On the same lines, using a character array to print an LSN
won't be acceptable. I agree.

Maybe we should update the section "Message-Writing Guidelines" in
doc/src/sgml/nls.sgml. I think it's implied by "Do not construct sentences
at run-time, like ... " but using a macro is not really constructing
sentences run time. Hopefully, some day, we will fix the translation system
to handle strings with macros and make it easier to print PG specific
objects in strings easily.

As annoyed as I am about our LSN printing, I don't think this patch
has the solutions we need.

I think we could at least accept the macros so that they can be used in
non-translatable strings i.e. use it with sprints, appendStringInfo
variants etc. The macros will also serve to document the string format of
LSN. Developers can then copy from the macros rather than copying from
"some other" (erroneous) usage in the code.

--
Best Wishes,
Ashutosh

#16Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Ashutosh Bapat (#1)
Re: Printing LSN made easy

On 2020-11-27 11:40, Ashutosh Bapat wrote:

The solution seems to be simple though. In the attached patch, I have
added two macros
#define LSN_FORMAT "%X/%X"
#define LSN_FORMAT_ARG(lsn) (uint32) ((lsn) >> 32), (uint32) (lsn)

which can be used instead.

It looks like we are not getting any consensus on this approach. One
reduced version I would consider is just the second part, so you'd write
something like

snprintf(lsnchar, sizeof(lsnchar), "%X/%X",
LSN_FORMAT_ARGS(lsn));

This would still reduce notational complexity quite a bit but avoid any
funny business with the format strings.

--
Peter Eisentraut
2ndQuadrant, an EDB company
https://www.2ndquadrant.com/

#17Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#16)
Re: Printing LSN made easy

On Wed, Jan 20, 2021 at 07:25:37AM +0100, Peter Eisentraut wrote:

It looks like we are not getting any consensus on this approach. One
reduced version I would consider is just the second part, so you'd write
something like

snprintf(lsnchar, sizeof(lsnchar), "%X/%X",
LSN_FORMAT_ARGS(lsn));

This would still reduce notational complexity quite a bit but avoid any
funny business with the format strings.

That seems reasonable to me. So +1.
--
Michael

#18Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Peter Eisentraut (#16)
Re: Printing LSN made easy

On Wed, Jan 20, 2021 at 11:55 AM Peter Eisentraut <
peter.eisentraut@2ndquadrant.com> wrote:

On 2020-11-27 11:40, Ashutosh Bapat wrote:

The solution seems to be simple though. In the attached patch, I have
added two macros
#define LSN_FORMAT "%X/%X"
#define LSN_FORMAT_ARG(lsn) (uint32) ((lsn) >> 32), (uint32) (lsn)

which can be used instead.

It looks like we are not getting any consensus on this approach. One
reduced version I would consider is just the second part, so you'd write
something like

snprintf(lsnchar, sizeof(lsnchar), "%X/%X",
LSN_FORMAT_ARGS(lsn));

This would still reduce notational complexity quite a bit but avoid any
funny business with the format strings.

Thanks for looking into this. I would like to keep both the LSN_FORMAT and
LSN_FORMAT_ARGS but with a note that the first can not be used in elog() or
in messages which require localization. We have many other places doing
snprintf() and such stuff, which can use LSN_FORMAT. If we do so, the
functions to output string representation will not be needed so they can be
removed.

--
Best Wishes,
Ashutosh

#19Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Michael Paquier (#17)
Re: Printing LSN made easy

At Wed, 20 Jan 2021 16:40:59 +0900, Michael Paquier <michael@paquier.xyz> wrote in

On Wed, Jan 20, 2021 at 07:25:37AM +0100, Peter Eisentraut wrote:

It looks like we are not getting any consensus on this approach. One
reduced version I would consider is just the second part, so you'd write
something like

snprintf(lsnchar, sizeof(lsnchar), "%X/%X",
LSN_FORMAT_ARGS(lsn));

This would still reduce notational complexity quite a bit but avoid any
funny business with the format strings.

That seems reasonable to me. So +1.

That seems in the good balance. +1, too.

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#20Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Ashutosh Bapat (#18)
Re: Printing LSN made easy

On 2021-01-20 08:50, Ashutosh Bapat wrote:

Thanks for looking into this. I would like to keep both the LSN_FORMAT
and LSN_FORMAT_ARGS but with a note that the first can not be used in
elog() or in messages which require localization. We have many other
places doing snprintf() and such stuff, which can use LSN_FORMAT. If we
do so, the functions to output string representation will not be needed
so they can be removed.

Then you'd end up with half the code doing this and half the code doing
that. That doesn't sound very attractive. It's not like "%X/%X" is
hard to type.

--
Peter Eisentraut
2ndQuadrant, an EDB company
https://www.2ndquadrant.com/

#21Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Peter Eisentraut (#20)
Re: Printing LSN made easy

On Thu, Jan 21, 2021 at 3:53 PM Peter Eisentraut <
peter.eisentraut@2ndquadrant.com> wrote:

On 2021-01-20 08:50, Ashutosh Bapat wrote:

Thanks for looking into this. I would like to keep both the LSN_FORMAT
and LSN_FORMAT_ARGS but with a note that the first can not be used in
elog() or in messages which require localization. We have many other
places doing snprintf() and such stuff, which can use LSN_FORMAT. If we
do so, the functions to output string representation will not be needed
so they can be removed.

Then you'd end up with half the code doing this and half the code doing
that. That doesn't sound very attractive. It's not like "%X/%X" is
hard to type.

:). That's true. I thought LSN_FORMAT would document the string
representation of an LSN. But anyway I am fine with this as well.

--
Peter Eisentraut
2ndQuadrant, an EDB company
https://www.2ndquadrant.com/

--
--
Best Wishes,
Ashutosh

#22Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Ashutosh Bapat (#21)
1 attachment(s)
Re: Printing LSN made easy

Here is an updated patch that just introduces LSN_FORMAT_ARGS(). I
think the result is quite pleasant.

--
Peter Eisentraut
2ndQuadrant, an EDB company
https://www.2ndquadrant.com/

Attachments:

v2-0001-Simplify-printing-of-LSNs.patchtext/plain; charset=UTF-8; name=v2-0001-Simplify-printing-of-LSNs.patch; x-mac-creator=0; x-mac-type=0Download
From 91d831518c8a71b010bdc835caacaedb20ffb896 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 18 Feb 2021 13:02:08 +0100
Subject: [PATCH v2] Simplify printing of LSNs

Add a macro LSN_FORMAT_ARGS for use in printf-style printing of LSNs.

Discussion: https://www.postgresql.org/message-id/flat/CAExHW5ub5NaTELZ3hJUCE6amuvqAtsSxc7O+uK7y4t9Rrk23cw@mail.gmail.com
---
 contrib/amcheck/verify_nbtree.c               |  57 +++------
 contrib/pageinspect/rawpage.c                 |   3 +-
 src/backend/access/heap/rewriteheap.c         |   6 +-
 src/backend/access/rmgrdesc/replorigindesc.c  |   3 +-
 src/backend/access/rmgrdesc/xactdesc.c        |   3 +-
 src/backend/access/rmgrdesc/xlogdesc.c        |   5 +-
 src/backend/access/transam/timeline.c         |   2 +-
 src/backend/access/transam/twophase.c         |   6 +-
 src/backend/access/transam/xlog.c             | 110 +++++++-----------
 src/backend/access/transam/xlogreader.c       |  58 ++++-----
 src/backend/access/transam/xlogutils.c        |   3 +-
 src/backend/replication/backup_manifest.c     |   4 +-
 src/backend/replication/basebackup.c          |   2 +-
 .../libpqwalreceiver/libpqwalreceiver.c       |   4 +-
 src/backend/replication/logical/logical.c     |  34 +++---
 src/backend/replication/logical/origin.c      |   3 +-
 .../replication/logical/reorderbuffer.c       |   3 +-
 src/backend/replication/logical/snapbuild.c   |  20 ++--
 src/backend/replication/logical/tablesync.c   |   4 +-
 src/backend/replication/logical/worker.c      |   7 +-
 src/backend/replication/slot.c                |   3 +-
 src/backend/replication/slotfuncs.c           |   3 +-
 src/backend/replication/syncrep.c             |   8 +-
 src/backend/replication/walreceiver.c         |  20 ++--
 src/backend/replication/walsender.c           |  32 +++--
 src/backend/storage/ipc/standby.c             |   4 +-
 src/backend/utils/adt/pg_lsn.c                |   8 +-
 src/bin/pg_basebackup/pg_receivewal.c         |   8 +-
 src/bin/pg_basebackup/pg_recvlogical.c        |  13 +--
 src/bin/pg_basebackup/receivelog.c            |   6 +-
 src/bin/pg_controldata/pg_controldata.c       |  18 +--
 src/bin/pg_rewind/parsexlog.c                 |  14 +--
 src/bin/pg_rewind/pg_rewind.c                 |  13 +--
 src/bin/pg_verifybackup/pg_verifybackup.c     |   6 +-
 src/bin/pg_waldump/pg_waldump.c               |  20 ++--
 src/include/access/xlogdefs.h                 |   7 ++
 36 files changed, 212 insertions(+), 308 deletions(-)

diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 4db1a64d51..a5a7627839 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -1078,8 +1078,7 @@ bt_target_page_check(BtreeCheckState *state)
 										state->targetblock,
 										BTreeTupleGetNAtts(itup, state->rel),
 										P_ISLEAF(topaque) ? "heap" : "index",
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+										LSN_FORMAT_ARGS(state->targetlsn))));
 		}
 	}
 
@@ -1120,8 +1119,7 @@ bt_target_page_check(BtreeCheckState *state)
 					 errdetail_internal("Index tid=(%u,%u) tuple size=%zu lp_len=%u page lsn=%X/%X.",
 										state->targetblock, offset,
 										tupsize, ItemIdGetLength(itemid),
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn),
+										LSN_FORMAT_ARGS(state->targetlsn)),
 					 errhint("This could be a torn page problem.")));
 
 		/* Check the number of index tuple attributes */
@@ -1147,8 +1145,7 @@ bt_target_page_check(BtreeCheckState *state)
 										BTreeTupleGetNAtts(itup, state->rel),
 										P_ISLEAF(topaque) ? "heap" : "index",
 										htid,
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+										LSN_FORMAT_ARGS(state->targetlsn))));
 		}
 
 		/*
@@ -1195,8 +1192,7 @@ bt_target_page_check(BtreeCheckState *state)
 							RelationGetRelationName(state->rel)),
 					 errdetail_internal("Index tid=%s points to heap tid=%s page lsn=%X/%X.",
 										itid, htid,
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+										LSN_FORMAT_ARGS(state->targetlsn))));
 		}
 
 		/*
@@ -1225,8 +1221,7 @@ bt_target_page_check(BtreeCheckState *state)
 											 RelationGetRelationName(state->rel)),
 							 errdetail_internal("Index tid=%s posting list offset=%d page lsn=%X/%X.",
 												itid, i,
-												(uint32) (state->targetlsn >> 32),
-												(uint32) state->targetlsn)));
+												LSN_FORMAT_ARGS(state->targetlsn))));
 				}
 
 				ItemPointerCopy(current, &last);
@@ -1282,8 +1277,7 @@ bt_target_page_check(BtreeCheckState *state)
 										itid,
 										P_ISLEAF(topaque) ? "heap" : "index",
 										htid,
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+										LSN_FORMAT_ARGS(state->targetlsn))));
 		}
 
 		/* Fingerprint leaf page tuples (those that point to the heap) */
@@ -1390,8 +1384,7 @@ bt_target_page_check(BtreeCheckState *state)
 										itid,
 										P_ISLEAF(topaque) ? "heap" : "index",
 										htid,
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+										LSN_FORMAT_ARGS(state->targetlsn))));
 		}
 		/* Reset, in case scantid was set to (itup) posting tuple's max TID */
 		skey->scantid = scantid;
@@ -1442,8 +1435,7 @@ bt_target_page_check(BtreeCheckState *state)
 										nitid,
 										P_ISLEAF(topaque) ? "heap" : "index",
 										nhtid,
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+										LSN_FORMAT_ARGS(state->targetlsn))));
 		}
 
 		/*
@@ -1500,8 +1492,7 @@ bt_target_page_check(BtreeCheckState *state)
 								RelationGetRelationName(state->rel)),
 						 errdetail_internal("Last item on page tid=(%u,%u) page lsn=%X/%X.",
 											state->targetblock, offset,
-											(uint32) (state->targetlsn >> 32),
-											(uint32) state->targetlsn)));
+											LSN_FORMAT_ARGS(state->targetlsn))));
 			}
 		}
 
@@ -1907,8 +1898,7 @@ bt_child_highkey_check(BtreeCheckState *state,
 							RelationGetRelationName(state->rel)),
 					 errdetail_internal("Target block=%u child block=%u target page lsn=%X/%X.",
 										state->targetblock, blkno,
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+										LSN_FORMAT_ARGS(state->targetlsn))));
 
 		/* Check level for non-ignorable page */
 		if (!P_IGNORE(opaque) && opaque->btpo.level != target_level - 1)
@@ -1993,8 +1983,7 @@ bt_child_highkey_check(BtreeCheckState *state,
 										RelationGetRelationName(state->rel)),
 								 errdetail_internal("Target block=%u child block=%u target page lsn=%X/%X.",
 													state->targetblock, blkno,
-													(uint32) (state->targetlsn >> 32),
-													(uint32) state->targetlsn)));
+													LSN_FORMAT_ARGS(state->targetlsn))));
 					pivotkey_offset = P_HIKEY;
 				}
 				itemid = PageGetItemIdCareful(state, state->targetblock,
@@ -2024,8 +2013,7 @@ bt_child_highkey_check(BtreeCheckState *state,
 									RelationGetRelationName(state->rel)),
 							 errdetail_internal("Target block=%u child block=%u target page lsn=%X/%X.",
 												state->targetblock, blkno,
-												(uint32) (state->targetlsn >> 32),
-												(uint32) state->targetlsn)));
+												LSN_FORMAT_ARGS(state->targetlsn))));
 				itup = state->lowkey;
 			}
 
@@ -2037,8 +2025,7 @@ bt_child_highkey_check(BtreeCheckState *state,
 								RelationGetRelationName(state->rel)),
 						 errdetail_internal("Target block=%u child block=%u target page lsn=%X/%X.",
 											state->targetblock, blkno,
-											(uint32) (state->targetlsn >> 32),
-											(uint32) state->targetlsn)));
+											LSN_FORMAT_ARGS(state->targetlsn))));
 			}
 		}
 
@@ -2178,8 +2165,7 @@ bt_child_check(BtreeCheckState *state, BTScanInsert targetkey,
 						RelationGetRelationName(state->rel)),
 				 errdetail_internal("Parent block=%u child block=%u parent page lsn=%X/%X.",
 									state->targetblock, childblock,
-									(uint32) (state->targetlsn >> 32),
-									(uint32) state->targetlsn)));
+									LSN_FORMAT_ARGS(state->targetlsn))));
 
 	for (offset = P_FIRSTDATAKEY(copaque);
 		 offset <= maxoffset;
@@ -2220,8 +2206,7 @@ bt_child_check(BtreeCheckState *state, BTScanInsert targetkey,
 							RelationGetRelationName(state->rel)),
 					 errdetail_internal("Parent block=%u child index tid=(%u,%u) parent page lsn=%X/%X.",
 										state->targetblock, childblock, offset,
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+										LSN_FORMAT_ARGS(state->targetlsn))));
 	}
 
 	pfree(child);
@@ -2292,8 +2277,7 @@ bt_downlink_missing_check(BtreeCheckState *state, bool rightsplit,
 				 errdetail_internal("Block=%u level=%u left sibling=%u page lsn=%X/%X.",
 									blkno, opaque->btpo.level,
 									opaque->btpo_prev,
-									(uint32) (pagelsn >> 32),
-									(uint32) pagelsn)));
+									LSN_FORMAT_ARGS(pagelsn))));
 		return;
 	}
 
@@ -2314,8 +2298,7 @@ bt_downlink_missing_check(BtreeCheckState *state, bool rightsplit,
 						RelationGetRelationName(state->rel)),
 				 errdetail_internal("Block=%u page lsn=%X/%X.",
 									blkno,
-									(uint32) (pagelsn >> 32),
-									(uint32) pagelsn)));
+									LSN_FORMAT_ARGS(pagelsn))));
 
 	/* Descend from the given page, which is an internal page */
 	elog(DEBUG1, "checking for interrupted multi-level deletion due to missing downlink in index \"%s\"",
@@ -2381,8 +2364,7 @@ bt_downlink_missing_check(BtreeCheckState *state, bool rightsplit,
 								 RelationGetRelationName(state->rel)),
 				 errdetail_internal("Top parent/target block=%u leaf block=%u top parent/under check lsn=%X/%X.",
 									blkno, childblk,
-									(uint32) (pagelsn >> 32),
-									(uint32) pagelsn)));
+									LSN_FORMAT_ARGS(pagelsn))));
 
 	/*
 	 * Iff leaf page is half-dead, its high key top parent link should point
@@ -2408,8 +2390,7 @@ bt_downlink_missing_check(BtreeCheckState *state, bool rightsplit,
 					RelationGetRelationName(state->rel)),
 			 errdetail_internal("Block=%u level=%u page lsn=%X/%X.",
 								blkno, opaque->btpo.level,
-								(uint32) (pagelsn >> 32),
-								(uint32) pagelsn)));
+								LSN_FORMAT_ARGS(pagelsn))));
 }
 
 /*
diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index 9e9ee8a493..7272b21016 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -309,8 +309,7 @@ page_header(PG_FUNCTION_ARGS)
 	{
 		char		lsnchar[64];
 
-		snprintf(lsnchar, sizeof(lsnchar), "%X/%X",
-				 (uint32) (lsn >> 32), (uint32) lsn);
+		snprintf(lsnchar, sizeof(lsnchar), "%X/%X", LSN_FORMAT_ARGS(lsn));
 		values[0] = CStringGetTextDatum(lsnchar);
 	}
 	else
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index fcaad9ba0b..8241ba8f31 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -997,8 +997,7 @@ logical_rewrite_log_mapping(RewriteState state, TransactionId xid,
 		snprintf(path, MAXPGPATH,
 				 "pg_logical/mappings/" LOGICAL_REWRITE_FORMAT,
 				 dboid, relid,
-				 (uint32) (state->rs_begin_lsn >> 32),
-				 (uint32) state->rs_begin_lsn,
+				 LSN_FORMAT_ARGS(state->rs_begin_lsn),
 				 xid, GetCurrentTransactionId());
 
 		dlist_init(&src->mappings);
@@ -1120,8 +1119,7 @@ heap_xlog_logical_rewrite(XLogReaderState *r)
 	snprintf(path, MAXPGPATH,
 			 "pg_logical/mappings/" LOGICAL_REWRITE_FORMAT,
 			 xlrec->mapped_db, xlrec->mapped_rel,
-			 (uint32) (xlrec->start_lsn >> 32),
-			 (uint32) xlrec->start_lsn,
+			 LSN_FORMAT_ARGS(xlrec->start_lsn),
 			 xlrec->mapped_xid, XLogRecGetXid(r));
 
 	fd = OpenTransientFile(path,
diff --git a/src/backend/access/rmgrdesc/replorigindesc.c b/src/backend/access/rmgrdesc/replorigindesc.c
index 2e29ecc6d5..1f314c4771 100644
--- a/src/backend/access/rmgrdesc/replorigindesc.c
+++ b/src/backend/access/rmgrdesc/replorigindesc.c
@@ -31,8 +31,7 @@ replorigin_desc(StringInfo buf, XLogReaderState *record)
 
 				appendStringInfo(buf, "set %u; lsn %X/%X; force: %d",
 								 xlrec->node_id,
-								 (uint32) (xlrec->remote_lsn >> 32),
-								 (uint32) xlrec->remote_lsn,
+								 LSN_FORMAT_ARGS(xlrec->remote_lsn),
 								 xlrec->force);
 				break;
 			}
diff --git a/src/backend/access/rmgrdesc/xactdesc.c b/src/backend/access/rmgrdesc/xactdesc.c
index c6fb1ec572..4b0d10f073 100644
--- a/src/backend/access/rmgrdesc/xactdesc.c
+++ b/src/backend/access/rmgrdesc/xactdesc.c
@@ -306,8 +306,7 @@ xact_desc_commit(StringInfo buf, uint8 info, xl_xact_commit *xlrec, RepOriginId
 	{
 		appendStringInfo(buf, "; origin: node %u, lsn %X/%X, at %s",
 						 origin_id,
-						 (uint32) (parsed.origin_lsn >> 32),
-						 (uint32) parsed.origin_lsn,
+						 LSN_FORMAT_ARGS(parsed.origin_lsn),
 						 timestamptz_to_str(parsed.origin_timestamp));
 	}
 }
diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c
index 92cc7ea073..e6090a9dad 100644
--- a/src/backend/access/rmgrdesc/xlogdesc.c
+++ b/src/backend/access/rmgrdesc/xlogdesc.c
@@ -49,7 +49,7 @@ xlog_desc(StringInfo buf, XLogReaderState *record)
 						 "oldest xid %u in DB %u; oldest multi %u in DB %u; "
 						 "oldest/newest commit timestamp xid: %u/%u; "
 						 "oldest running xid %u; %s",
-						 (uint32) (checkpoint->redo >> 32), (uint32) checkpoint->redo,
+						 LSN_FORMAT_ARGS(checkpoint->redo),
 						 checkpoint->ThisTimeLineID,
 						 checkpoint->PrevTimeLineID,
 						 checkpoint->fullPageWrites ? "true" : "false",
@@ -89,8 +89,7 @@ xlog_desc(StringInfo buf, XLogReaderState *record)
 		XLogRecPtr	startpoint;
 
 		memcpy(&startpoint, rec, sizeof(XLogRecPtr));
-		appendStringInfo(buf, "%X/%X",
-						 (uint32) (startpoint >> 32), (uint32) startpoint);
+		appendStringInfo(buf, "%X/%X", LSN_FORMAT_ARGS(startpoint));
 	}
 	else if (info == XLOG_PARAMETER_CHANGE)
 	{
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 690471ac4e..8d0903c175 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -402,7 +402,7 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
 			 "%s%u\t%X/%X\t%s\n",
 			 (srcfd < 0) ? "" : "\n",
 			 parentTLI,
-			 (uint32) (switchpoint >> 32), (uint32) (switchpoint),
+			 LSN_FORMAT_ARGS(switchpoint),
 			 reason);
 
 	nbytes = strlen(buffer);
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 70d22577ce..0cae9486f6 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -1348,16 +1348,14 @@ XlogReadTwoPhaseData(XLogRecPtr lsn, char **buf, int *len)
 		ereport(ERROR,
 				(errcode_for_file_access(),
 				 errmsg("could not read two-phase state from WAL at %X/%X",
-						(uint32) (lsn >> 32),
-						(uint32) lsn)));
+						LSN_FORMAT_ARGS(lsn))));
 
 	if (XLogRecGetRmid(xlogreader) != RM_XACT_ID ||
 		(XLogRecGetInfo(xlogreader) & XLOG_XACT_OPMASK) != XLOG_XACT_PREPARE)
 		ereport(ERROR,
 				(errcode_for_file_access(),
 				 errmsg("expected two-phase state data is not present in WAL at %X/%X",
-						(uint32) (lsn >> 32),
-						(uint32) lsn)));
+						LSN_FORMAT_ARGS(lsn))));
 
 	if (len != NULL)
 		*len = XLogRecGetDataLen(xlogreader);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e0c37f73f3..377afb8732 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -1219,8 +1219,7 @@ XLogInsertRecord(XLogRecData *rdata,
 		oldCxt = MemoryContextSwitchTo(walDebugCxt);
 
 		initStringInfo(&buf);
-		appendStringInfo(&buf, "INSERT @ %X/%X: ",
-						 (uint32) (EndPos >> 32), (uint32) EndPos);
+		appendStringInfo(&buf, "INSERT @ %X/%X: ", LSN_FORMAT_ARGS(EndPos));
 
 		/*
 		 * We have to piece together the WAL record data from the XLogRecData
@@ -1821,8 +1820,7 @@ WaitXLogInsertionsToFinish(XLogRecPtr upto)
 	{
 		ereport(LOG,
 				(errmsg("request to flush past end of generated WAL; request %X/%X, current position %X/%X",
-						(uint32) (upto >> 32), (uint32) upto,
-						(uint32) (reservedUpto >> 32), (uint32) reservedUpto)));
+						LSN_FORMAT_ARGS(upto), LSN_FORMAT_ARGS(reservedUpto))));
 		upto = reservedUpto;
 	}
 
@@ -1973,7 +1971,7 @@ GetXLogBuffer(XLogRecPtr ptr)
 
 		if (expectedEndPtr != endptr)
 			elog(PANIC, "could not find WAL buffer for %X/%X",
-				 (uint32) (ptr >> 32), (uint32) ptr);
+				 LSN_FORMAT_ARGS(ptr));
 	}
 	else
 	{
@@ -2290,7 +2288,7 @@ AdvanceXLInsertBuffer(XLogRecPtr upto, bool opportunistic)
 	if (XLOG_DEBUG && npages > 0)
 	{
 		elog(DEBUG1, "initialized %d pages, up to %X/%X",
-			 npages, (uint32) (NewPageEndPtr >> 32), (uint32) NewPageEndPtr);
+			 npages, LSN_FORMAT_ARGS(NewPageEndPtr));
 	}
 #endif
 }
@@ -2471,9 +2469,8 @@ XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
 
 		if (LogwrtResult.Write >= EndPtr)
 			elog(PANIC, "xlog write request %X/%X is past end of log %X/%X",
-				 (uint32) (LogwrtResult.Write >> 32),
-				 (uint32) LogwrtResult.Write,
-				 (uint32) (EndPtr >> 32), (uint32) EndPtr);
+				 LSN_FORMAT_ARGS(LogwrtResult.Write),
+				 LSN_FORMAT_ARGS(EndPtr));
 
 		/* Advance LogwrtResult.Write to end of current buffer page */
 		LogwrtResult.Write = EndPtr;
@@ -2823,9 +2820,7 @@ UpdateMinRecoveryPoint(XLogRecPtr lsn, bool force)
 		if (!force && newMinRecoveryPoint < lsn)
 			elog(WARNING,
 				 "xlog min recovery request %X/%X is past current point %X/%X",
-				 (uint32) (lsn >> 32), (uint32) lsn,
-				 (uint32) (newMinRecoveryPoint >> 32),
-				 (uint32) newMinRecoveryPoint);
+				 LSN_FORMAT_ARGS(lsn), LSN_FORMAT_ARGS(newMinRecoveryPoint));
 
 		/* update control file */
 		if (ControlFile->minRecoveryPoint < newMinRecoveryPoint)
@@ -2838,8 +2833,7 @@ UpdateMinRecoveryPoint(XLogRecPtr lsn, bool force)
 
 			ereport(DEBUG2,
 					(errmsg_internal("updated min recovery point to %X/%X on timeline %u",
-							(uint32) (minRecoveryPoint >> 32),
-							(uint32) minRecoveryPoint,
+							LSN_FORMAT_ARGS(minRecoveryPoint),
 							newMinRecoveryPointTLI)));
 		}
 	}
@@ -2878,9 +2872,9 @@ XLogFlush(XLogRecPtr record)
 #ifdef WAL_DEBUG
 	if (XLOG_DEBUG)
 		elog(LOG, "xlog flush request %X/%X; write %X/%X; flush %X/%X",
-			 (uint32) (record >> 32), (uint32) record,
-			 (uint32) (LogwrtResult.Write >> 32), (uint32) LogwrtResult.Write,
-			 (uint32) (LogwrtResult.Flush >> 32), (uint32) LogwrtResult.Flush);
+			 LSN_FORMAT_ARGS(record),
+			 LSN_FORMAT_ARGS(LogwrtResult.Write),
+			 LSN_FORMAT_ARGS(LogwrtResult.Flush));
 #endif
 
 	START_CRIT_SECTION();
@@ -3013,8 +3007,8 @@ XLogFlush(XLogRecPtr record)
 	if (LogwrtResult.Flush < record)
 		elog(ERROR,
 			 "xlog flush request %X/%X is not satisfied --- flushed only to %X/%X",
-			 (uint32) (record >> 32), (uint32) record,
-			 (uint32) (LogwrtResult.Flush >> 32), (uint32) LogwrtResult.Flush);
+			 LSN_FORMAT_ARGS(record),
+			 LSN_FORMAT_ARGS(LogwrtResult.Flush));
 }
 
 /*
@@ -3129,10 +3123,10 @@ XLogBackgroundFlush(void)
 #ifdef WAL_DEBUG
 	if (XLOG_DEBUG)
 		elog(LOG, "xlog bg flush request write %X/%X; flush: %X/%X, current is write %X/%X; flush %X/%X",
-			 (uint32) (WriteRqst.Write >> 32), (uint32) WriteRqst.Write,
-			 (uint32) (WriteRqst.Flush >> 32), (uint32) WriteRqst.Flush,
-			 (uint32) (LogwrtResult.Write >> 32), (uint32) LogwrtResult.Write,
-			 (uint32) (LogwrtResult.Flush >> 32), (uint32) LogwrtResult.Flush);
+			 LSN_FORMAT_ARGS(WriteRqst.Write),
+			 LSN_FORMAT_ARGS(WriteRqst.Flush),
+			 LSN_FORMAT_ARGS(LogwrtResult.Write),
+			 LSN_FORMAT_ARGS(LogwrtResult.Flush));
 #endif
 
 	START_CRIT_SECTION();
@@ -4560,7 +4554,7 @@ rescanLatestTimeLine(void)
 				(errmsg("new timeline %u forked off current database system timeline %u before current recovery point %X/%X",
 						newtarget,
 						ThisTimeLineID,
-						(uint32) (EndRecPtr >> 32), (uint32) EndRecPtr)));
+						LSN_FORMAT_ARGS(EndRecPtr))));
 		return false;
 	}
 
@@ -5754,8 +5748,7 @@ recoveryStopsBefore(XLogReaderState *record)
 		recoveryStopName[0] = '\0';
 		ereport(LOG,
 				(errmsg("recovery stopping before WAL location (LSN) \"%X/%X\"",
-						(uint32) (recoveryStopLSN >> 32),
-						(uint32) recoveryStopLSN)));
+						LSN_FORMAT_ARGS(recoveryStopLSN))));
 		return true;
 	}
 
@@ -5918,8 +5911,7 @@ recoveryStopsAfter(XLogReaderState *record)
 		recoveryStopName[0] = '\0';
 		ereport(LOG,
 				(errmsg("recovery stopping after WAL location (LSN) \"%X/%X\"",
-						(uint32) (recoveryStopLSN >> 32),
-						(uint32) recoveryStopLSN)));
+						LSN_FORMAT_ARGS(recoveryStopLSN))));
 		return true;
 	}
 
@@ -6531,8 +6523,7 @@ StartupXLOG(void)
 		else if (recoveryTarget == RECOVERY_TARGET_LSN)
 			ereport(LOG,
 					(errmsg("starting point-in-time recovery to WAL location (LSN) \"%X/%X\"",
-							(uint32) (recoveryTargetLSN >> 32),
-							(uint32) recoveryTargetLSN)));
+							LSN_FORMAT_ARGS(recoveryTargetLSN))));
 		else if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE)
 			ereport(LOG,
 					(errmsg("starting point-in-time recovery to earliest consistent point")));
@@ -6598,7 +6589,7 @@ StartupXLOG(void)
 			wasShutdown = ((record->xl_info & ~XLR_INFO_MASK) == XLOG_CHECKPOINT_SHUTDOWN);
 			ereport(DEBUG1,
 					(errmsg_internal("checkpoint record is at %X/%X",
-							(uint32) (checkPointLoc >> 32), (uint32) checkPointLoc)));
+									 LSN_FORMAT_ARGS(checkPointLoc))));
 			InRecovery = true;	/* force recovery even if SHUTDOWNED */
 
 			/*
@@ -6731,7 +6722,7 @@ StartupXLOG(void)
 		{
 			ereport(DEBUG1,
 					(errmsg_internal("checkpoint record is at %X/%X",
-							(uint32) (checkPointLoc >> 32), (uint32) checkPointLoc)));
+									 LSN_FORMAT_ARGS(checkPointLoc))));
 		}
 		else
 		{
@@ -6783,11 +6774,9 @@ StartupXLOG(void)
 				(errmsg("requested timeline %u is not a child of this server's history",
 						recoveryTargetTLI),
 				 errdetail("Latest checkpoint is at %X/%X on timeline %u, but in the history of the requested timeline, the server forked off from that timeline at %X/%X.",
-						   (uint32) (ControlFile->checkPoint >> 32),
-						   (uint32) ControlFile->checkPoint,
+						   LSN_FORMAT_ARGS(ControlFile->checkPoint),
 						   ControlFile->checkPointCopy.ThisTimeLineID,
-						   (uint32) (switchpoint >> 32),
-						   (uint32) switchpoint)));
+						   LSN_FORMAT_ARGS(switchpoint))));
 	}
 
 	/*
@@ -6800,15 +6789,14 @@ StartupXLOG(void)
 		ereport(FATAL,
 				(errmsg("requested timeline %u does not contain minimum recovery point %X/%X on timeline %u",
 						recoveryTargetTLI,
-						(uint32) (ControlFile->minRecoveryPoint >> 32),
-						(uint32) ControlFile->minRecoveryPoint,
+						LSN_FORMAT_ARGS(ControlFile->minRecoveryPoint),
 						ControlFile->minRecoveryPointTLI)));
 
 	LastRec = RecPtr = checkPointLoc;
 
 	ereport(DEBUG1,
 			(errmsg_internal("redo record is at %X/%X; shutdown %s",
-							 (uint32) (checkPoint.redo >> 32), (uint32) checkPoint.redo,
+							 LSN_FORMAT_ARGS(checkPoint.redo),
 							 wasShutdown ? "true" : "false")));
 	ereport(DEBUG1,
 			(errmsg_internal("next transaction ID: " UINT64_FORMAT "; next OID: %u",
@@ -7254,7 +7242,7 @@ StartupXLOG(void)
 
 			ereport(LOG,
 					(errmsg("redo starts at %X/%X",
-							(uint32) (ReadRecPtr >> 32), (uint32) ReadRecPtr)));
+							LSN_FORMAT_ARGS(ReadRecPtr))));
 
 			/*
 			 * main redo apply loop
@@ -7272,8 +7260,8 @@ StartupXLOG(void)
 
 					initStringInfo(&buf);
 					appendStringInfo(&buf, "REDO @ %X/%X; LSN %X/%X: ",
-									 (uint32) (ReadRecPtr >> 32), (uint32) ReadRecPtr,
-									 (uint32) (EndRecPtr >> 32), (uint32) EndRecPtr);
+									 LSN_FORMAT_ARGS(ReadRecPtr),
+									 LSN_FORMAT_ARGS(EndRecPtr));
 					xlog_outrec(&buf, xlogreader);
 					appendStringInfoString(&buf, " - ");
 					xlog_outdesc(&buf, xlogreader);
@@ -7516,7 +7504,7 @@ StartupXLOG(void)
 
 			ereport(LOG,
 					(errmsg("redo done at %X/%X system usage: %s",
-							(uint32) (ReadRecPtr >> 32), (uint32) ReadRecPtr,
+							LSN_FORMAT_ARGS(ReadRecPtr),
 							pg_rusage_show(&ru0))));
 			xtime = GetLatestXTime();
 			if (xtime)
@@ -7684,8 +7672,7 @@ StartupXLOG(void)
 			snprintf(reason, sizeof(reason),
 					 "%s LSN %X/%X\n",
 					 recoveryStopAfter ? "after" : "before",
-					 (uint32) (recoveryStopLSN >> 32),
-					 (uint32) recoveryStopLSN);
+					 LSN_FORMAT_ARGS(recoveryStopLSN));
 		else if (recoveryTarget == RECOVERY_TARGET_NAME)
 			snprintf(reason, sizeof(reason),
 					 "at restore point \"%s\"",
@@ -8109,8 +8096,7 @@ CheckRecoveryConsistency(void)
 		reachedConsistency = true;
 		ereport(LOG,
 				(errmsg("consistent recovery state reached at %X/%X",
-						(uint32) (lastReplayedEndRecPtr >> 32),
-						(uint32) lastReplayedEndRecPtr)));
+						LSN_FORMAT_ARGS(lastReplayedEndRecPtr))));
 	}
 
 	/*
@@ -9344,8 +9330,7 @@ RecoveryRestartPoint(const CheckPoint *checkPoint)
 		elog(trace_recovery(DEBUG2),
 			 "could not record restart point at %X/%X because there "
 			 "are unresolved references to invalid pages",
-			 (uint32) (checkPoint->redo >> 32),
-			 (uint32) checkPoint->redo);
+			 LSN_FORMAT_ARGS(checkPoint->redo));
 		return;
 	}
 
@@ -9422,8 +9407,7 @@ CreateRestartPoint(int flags)
 	{
 		ereport(DEBUG2,
 				(errmsg_internal("skipping restartpoint, already performed at %X/%X",
-						(uint32) (lastCheckPoint.redo >> 32),
-						(uint32) lastCheckPoint.redo)));
+								 LSN_FORMAT_ARGS(lastCheckPoint.redo))));
 
 		UpdateMinRecoveryPoint(InvalidXLogRecPtr, true);
 		if (flags & CHECKPOINT_IS_SHUTDOWN)
@@ -9595,7 +9579,7 @@ CreateRestartPoint(int flags)
 	xtime = GetLatestXTime();
 	ereport((log_checkpoints ? LOG : DEBUG2),
 			(errmsg("recovery restart point at %X/%X",
-					(uint32) (lastCheckPoint.redo >> 32), (uint32) lastCheckPoint.redo),
+					LSN_FORMAT_ARGS(lastCheckPoint.redo)),
 			 xtime ? errdetail("Last completed transaction was at log time %s.",
 							   timestamptz_to_str(xtime)) : 0));
 
@@ -9837,7 +9821,7 @@ XLogRestorePoint(const char *rpName)
 
 	ereport(LOG,
 			(errmsg("restore point \"%s\" created at %X/%X",
-					rpName, (uint32) (RecPtr >> 32), (uint32) RecPtr)));
+					rpName, LSN_FORMAT_ARGS(RecPtr))));
 
 	return RecPtr;
 }
@@ -10008,8 +9992,7 @@ checkTimeLineSwitch(XLogRecPtr lsn, TimeLineID newTLI, TimeLineID prevTLI)
 		ereport(PANIC,
 				(errmsg("unexpected timeline ID %u in checkpoint record, before reaching minimum recovery point %X/%X on timeline %u",
 						newTLI,
-						(uint32) (minRecoveryPoint >> 32),
-						(uint32) minRecoveryPoint,
+						LSN_FORMAT_ARGS(minRecoveryPoint),
 						minRecoveryPointTLI)));
 
 	/* Looks good */
@@ -10365,8 +10348,7 @@ static void
 xlog_outrec(StringInfo buf, XLogReaderState *record)
 {
 	appendStringInfo(buf, "prev %X/%X; xid %u",
-					 (uint32) (XLogRecGetPrev(record) >> 32),
-					 (uint32) XLogRecGetPrev(record),
+					 LSN_FORMAT_ARGS(XLogRecGetPrev(record)),
 					 XLogRecGetXid(record));
 
 	appendStringInfo(buf, "; len %u",
@@ -10952,9 +10934,9 @@ do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p,
 					"%Y-%m-%d %H:%M:%S %Z",
 					pg_localtime(&stamp_time, log_timezone));
 		appendStringInfo(labelfile, "START WAL LOCATION: %X/%X (file %s)\n",
-						 (uint32) (startpoint >> 32), (uint32) startpoint, xlogfilename);
+						 LSN_FORMAT_ARGS(startpoint), xlogfilename);
 		appendStringInfo(labelfile, "CHECKPOINT LOCATION: %X/%X\n",
-						 (uint32) (checkpointloc >> 32), (uint32) checkpointloc);
+						 LSN_FORMAT_ARGS(checkpointloc));
 		appendStringInfo(labelfile, "BACKUP METHOD: %s\n",
 						 exclusive ? "pg_start_backup" : "streamed");
 		appendStringInfo(labelfile, "BACKUP FROM: %s\n",
@@ -11440,9 +11422,9 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive, TimeLineID *stoptli_p)
 					 errmsg("could not create file \"%s\": %m",
 							histfilepath)));
 		fprintf(fp, "START WAL LOCATION: %X/%X (file %s)\n",
-				(uint32) (startpoint >> 32), (uint32) startpoint, startxlogfilename);
+				LSN_FORMAT_ARGS(startpoint), startxlogfilename);
 		fprintf(fp, "STOP WAL LOCATION: %X/%X (file %s)\n",
-				(uint32) (stoppoint >> 32), (uint32) stoppoint, stopxlogfilename);
+				LSN_FORMAT_ARGS(stoppoint), stopxlogfilename);
 
 		/*
 		 * Transfer remaining lines including label and start timeline to
@@ -11895,8 +11877,7 @@ rm_redo_error_callback(void *arg)
 
 	/* translator: %s is a WAL record description */
 	errcontext("WAL redo at %X/%X for %s",
-			   (uint32) (record->ReadRecPtr >> 32),
-			   (uint32) record->ReadRecPtr,
+			   LSN_FORMAT_ARGS(record->ReadRecPtr),
 			   buf.data);
 
 	pfree(buf.data);
@@ -12494,8 +12475,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 
 							if (curFileTLI > 0 && tli < curFileTLI)
 								elog(ERROR, "according to history file, WAL location %X/%X belongs to timeline %u, but previous recovered WAL file came from timeline %u",
-									 (uint32) (tliRecPtr >> 32),
-									 (uint32) tliRecPtr,
+									 LSN_FORMAT_ARGS(tliRecPtr),
 									 tli, curFileTLI);
 						}
 						curFileTLI = tli;
diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c
index bb95e0e527..42738eb940 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -347,7 +347,7 @@ XLogReadRecord(XLogReaderState *state, char **errormsg)
 	else if (targetRecOff < pageHeaderSize)
 	{
 		report_invalid_record(state, "invalid record offset at %X/%X",
-							  (uint32) (RecPtr >> 32), (uint32) RecPtr);
+							  LSN_FORMAT_ARGS(RecPtr));
 		goto err;
 	}
 
@@ -355,7 +355,7 @@ XLogReadRecord(XLogReaderState *state, char **errormsg)
 		targetRecOff == pageHeaderSize)
 	{
 		report_invalid_record(state, "contrecord is requested by %X/%X",
-							  (uint32) (RecPtr >> 32), (uint32) RecPtr);
+							  LSN_FORMAT_ARGS(RecPtr));
 		goto err;
 	}
 
@@ -396,7 +396,7 @@ XLogReadRecord(XLogReaderState *state, char **errormsg)
 		{
 			report_invalid_record(state,
 								  "invalid record length at %X/%X: wanted %u, got %u",
-								  (uint32) (RecPtr >> 32), (uint32) RecPtr,
+								  LSN_FORMAT_ARGS(RecPtr),
 								  (uint32) SizeOfXLogRecord, total_len);
 			goto err;
 		}
@@ -420,8 +420,7 @@ XLogReadRecord(XLogReaderState *state, char **errormsg)
 		{
 			/* We treat this as a "bogus data" condition */
 			report_invalid_record(state, "record length %u at %X/%X too long",
-								  total_len,
-								  (uint32) (RecPtr >> 32), (uint32) RecPtr);
+								  total_len, LSN_FORMAT_ARGS(RecPtr));
 			goto err;
 		}
 
@@ -452,7 +451,7 @@ XLogReadRecord(XLogReaderState *state, char **errormsg)
 			{
 				report_invalid_record(state,
 									  "there is no contrecord flag at %X/%X",
-									  (uint32) (RecPtr >> 32), (uint32) RecPtr);
+									  LSN_FORMAT_ARGS(RecPtr));
 				goto err;
 			}
 
@@ -467,7 +466,7 @@ XLogReadRecord(XLogReaderState *state, char **errormsg)
 									  "invalid contrecord length %u (expected %lld) at %X/%X",
 									  pageHeader->xlp_rem_len,
 									  ((long long) total_len) - gotlen,
-									  (uint32) (RecPtr >> 32), (uint32) RecPtr);
+									  LSN_FORMAT_ARGS(RecPtr));
 				goto err;
 			}
 
@@ -694,7 +693,7 @@ ValidXLogRecordHeader(XLogReaderState *state, XLogRecPtr RecPtr,
 	{
 		report_invalid_record(state,
 							  "invalid record length at %X/%X: wanted %u, got %u",
-							  (uint32) (RecPtr >> 32), (uint32) RecPtr,
+							  LSN_FORMAT_ARGS(RecPtr),
 							  (uint32) SizeOfXLogRecord, record->xl_tot_len);
 		return false;
 	}
@@ -702,8 +701,7 @@ ValidXLogRecordHeader(XLogReaderState *state, XLogRecPtr RecPtr,
 	{
 		report_invalid_record(state,
 							  "invalid resource manager ID %u at %X/%X",
-							  record->xl_rmid, (uint32) (RecPtr >> 32),
-							  (uint32) RecPtr);
+							  record->xl_rmid, LSN_FORMAT_ARGS(RecPtr));
 		return false;
 	}
 	if (randAccess)
@@ -716,9 +714,8 @@ ValidXLogRecordHeader(XLogReaderState *state, XLogRecPtr RecPtr,
 		{
 			report_invalid_record(state,
 								  "record with incorrect prev-link %X/%X at %X/%X",
-								  (uint32) (record->xl_prev >> 32),
-								  (uint32) record->xl_prev,
-								  (uint32) (RecPtr >> 32), (uint32) RecPtr);
+								  LSN_FORMAT_ARGS(record->xl_prev),
+								  LSN_FORMAT_ARGS(RecPtr));
 			return false;
 		}
 	}
@@ -733,9 +730,8 @@ ValidXLogRecordHeader(XLogReaderState *state, XLogRecPtr RecPtr,
 		{
 			report_invalid_record(state,
 								  "record with incorrect prev-link %X/%X at %X/%X",
-								  (uint32) (record->xl_prev >> 32),
-								  (uint32) record->xl_prev,
-								  (uint32) (RecPtr >> 32), (uint32) RecPtr);
+								  LSN_FORMAT_ARGS(record->xl_prev),
+								  LSN_FORMAT_ARGS(RecPtr));
 			return false;
 		}
 	}
@@ -770,7 +766,7 @@ ValidXLogRecord(XLogReaderState *state, XLogRecord *record, XLogRecPtr recptr)
 	{
 		report_invalid_record(state,
 							  "incorrect resource manager data checksum in record at %X/%X",
-							  (uint32) (recptr >> 32), (uint32) recptr);
+							  LSN_FORMAT_ARGS(recptr));
 		return false;
 	}
 
@@ -881,7 +877,7 @@ XLogReaderValidatePageHeader(XLogReaderState *state, XLogRecPtr recptr,
 
 		report_invalid_record(state,
 							  "unexpected pageaddr %X/%X in log segment %s, offset %u",
-							  (uint32) (hdr->xlp_pageaddr >> 32), (uint32) hdr->xlp_pageaddr,
+							  LSN_FORMAT_ARGS(hdr->xlp_pageaddr),
 							  fname,
 							  offset);
 		return false;
@@ -1252,8 +1248,7 @@ DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, char **errormsg)
 				report_invalid_record(state,
 									  "out-of-order block_id %u at %X/%X",
 									  block_id,
-									  (uint32) (state->ReadRecPtr >> 32),
-									  (uint32) state->ReadRecPtr);
+									  LSN_FORMAT_ARGS(state->ReadRecPtr));
 				goto err;
 			}
 			state->max_block_id = block_id;
@@ -1274,7 +1269,7 @@ DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, char **errormsg)
 			{
 				report_invalid_record(state,
 									  "BKPBLOCK_HAS_DATA set, but no data included at %X/%X",
-									  (uint32) (state->ReadRecPtr >> 32), (uint32) state->ReadRecPtr);
+									  LSN_FORMAT_ARGS(state->ReadRecPtr));
 				goto err;
 			}
 			if (!blk->has_data && blk->data_len != 0)
@@ -1282,7 +1277,7 @@ DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, char **errormsg)
 				report_invalid_record(state,
 									  "BKPBLOCK_HAS_DATA not set, but data length is %u at %X/%X",
 									  (unsigned int) blk->data_len,
-									  (uint32) (state->ReadRecPtr >> 32), (uint32) state->ReadRecPtr);
+									  LSN_FORMAT_ARGS(state->ReadRecPtr));
 				goto err;
 			}
 			datatotal += blk->data_len;
@@ -1320,7 +1315,7 @@ DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, char **errormsg)
 										  (unsigned int) blk->hole_offset,
 										  (unsigned int) blk->hole_length,
 										  (unsigned int) blk->bimg_len,
-										  (uint32) (state->ReadRecPtr >> 32), (uint32) state->ReadRecPtr);
+										  LSN_FORMAT_ARGS(state->ReadRecPtr));
 					goto err;
 				}
 
@@ -1335,7 +1330,7 @@ DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, char **errormsg)
 										  "BKPIMAGE_HAS_HOLE not set, but hole offset %u length %u at %X/%X",
 										  (unsigned int) blk->hole_offset,
 										  (unsigned int) blk->hole_length,
-										  (uint32) (state->ReadRecPtr >> 32), (uint32) state->ReadRecPtr);
+										  LSN_FORMAT_ARGS(state->ReadRecPtr));
 					goto err;
 				}
 
@@ -1349,7 +1344,7 @@ DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, char **errormsg)
 					report_invalid_record(state,
 										  "BKPIMAGE_IS_COMPRESSED set, but block image length %u at %X/%X",
 										  (unsigned int) blk->bimg_len,
-										  (uint32) (state->ReadRecPtr >> 32), (uint32) state->ReadRecPtr);
+										  LSN_FORMAT_ARGS(state->ReadRecPtr));
 					goto err;
 				}
 
@@ -1364,7 +1359,7 @@ DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, char **errormsg)
 					report_invalid_record(state,
 										  "neither BKPIMAGE_HAS_HOLE nor BKPIMAGE_IS_COMPRESSED set, but block image length is %u at %X/%X",
 										  (unsigned int) blk->data_len,
-										  (uint32) (state->ReadRecPtr >> 32), (uint32) state->ReadRecPtr);
+										  LSN_FORMAT_ARGS(state->ReadRecPtr));
 					goto err;
 				}
 			}
@@ -1379,7 +1374,7 @@ DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, char **errormsg)
 				{
 					report_invalid_record(state,
 										  "BKPBLOCK_SAME_REL set but no previous rel at %X/%X",
-										  (uint32) (state->ReadRecPtr >> 32), (uint32) state->ReadRecPtr);
+										  LSN_FORMAT_ARGS(state->ReadRecPtr));
 					goto err;
 				}
 
@@ -1391,9 +1386,7 @@ DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, char **errormsg)
 		{
 			report_invalid_record(state,
 								  "invalid block_id %u at %X/%X",
-								  block_id,
-								  (uint32) (state->ReadRecPtr >> 32),
-								  (uint32) state->ReadRecPtr);
+								  block_id, LSN_FORMAT_ARGS(state->ReadRecPtr));
 			goto err;
 		}
 	}
@@ -1480,7 +1473,7 @@ DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, char **errormsg)
 shortdata_err:
 	report_invalid_record(state,
 						  "record with invalid length at %X/%X",
-						  (uint32) (state->ReadRecPtr >> 32), (uint32) state->ReadRecPtr);
+						  LSN_FORMAT_ARGS(state->ReadRecPtr));
 err:
 	*errormsg = state->errormsg_buf;
 
@@ -1569,8 +1562,7 @@ RestoreBlockImage(XLogReaderState *record, uint8 block_id, char *page)
 							BLCKSZ - bkpb->hole_length, true) < 0)
 		{
 			report_invalid_record(record, "invalid compressed image at %X/%X, block %d",
-								  (uint32) (record->ReadRecPtr >> 32),
-								  (uint32) record->ReadRecPtr,
+								  LSN_FORMAT_ARGS(record->ReadRecPtr),
 								  block_id);
 			return false;
 		}
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index e723253297..a7a473de4a 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -776,8 +776,7 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa
 
 		elog(DEBUG3, "switched to timeline %u valid until %X/%X",
 			 state->currTLI,
-			 (uint32) (state->currTLIValidUntil >> 32),
-			 (uint32) (state->currTLIValidUntil));
+			 LSN_FORMAT_ARGS(state->currTLIValidUntil));
 	}
 }
 
diff --git a/src/backend/replication/backup_manifest.c b/src/backend/replication/backup_manifest.c
index 32bb0efb3d..8882444025 100644
--- a/src/backend/replication/backup_manifest.c
+++ b/src/backend/replication/backup_manifest.c
@@ -277,8 +277,8 @@ AddWALInfoToBackupManifest(backup_manifest_info *manifest, XLogRecPtr startptr,
 						 "%s{ \"Timeline\": %u, \"Start-LSN\": \"%X/%X\", \"End-LSN\": \"%X/%X\" }",
 						 first_wal_range ? "" : ",\n",
 						 entry->tli,
-						 (uint32) (tl_beginptr >> 32), (uint32) tl_beginptr,
-						 (uint32) (endptr >> 32), (uint32) endptr);
+						 LSN_FORMAT_ARGS(tl_beginptr),
+						 LSN_FORMAT_ARGS(endptr));
 
 		if (starttli == entry->tli)
 		{
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index 0f54635550..240e902d28 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -1075,7 +1075,7 @@ SendXlogRecPtrResult(XLogRecPtr ptr, TimeLineID tli)
 	pq_sendint16(&buf, 2);		/* number of columns */
 
 	len = snprintf(str, sizeof(str),
-				   "%X/%X", (uint32) (ptr >> 32), (uint32) ptr);
+				   "%X/%X", LSN_FORMAT_ARGS(ptr));
 	pq_sendint32(&buf, len);
 	pq_sendbytes(&buf, str, len);
 
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 7714696140..5272eed9ab 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -406,9 +406,7 @@ libpqrcv_startstreaming(WalReceiverConn *conn,
 	if (options->logical)
 		appendStringInfoString(&cmd, " LOGICAL");
 
-	appendStringInfo(&cmd, " %X/%X",
-					 (uint32) (options->startpoint >> 32),
-					 (uint32) options->startpoint);
+	appendStringInfo(&cmd, " %X/%X", LSN_FORMAT_ARGS(options->startpoint));
 
 	/*
 	 * Additional options are different depending on if we are doing logical
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 0977aec711..baeb45ff43 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -514,9 +514,8 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 		 * replication.
 		 */
 		elog(DEBUG1, "cannot stream from %X/%X, minimum is %X/%X, forwarding",
-			 (uint32) (start_lsn >> 32), (uint32) start_lsn,
-			 (uint32) (slot->data.confirmed_flush >> 32),
-			 (uint32) slot->data.confirmed_flush);
+			 LSN_FORMAT_ARGS(start_lsn),
+			 LSN_FORMAT_ARGS(slot->data.confirmed_flush));
 
 		start_lsn = slot->data.confirmed_flush;
 	}
@@ -538,10 +537,8 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 			(errmsg("starting logical decoding for slot \"%s\"",
 					NameStr(slot->data.name)),
 			 errdetail("Streaming transactions committing after %X/%X, reading WAL from %X/%X.",
-					   (uint32) (slot->data.confirmed_flush >> 32),
-					   (uint32) slot->data.confirmed_flush,
-					   (uint32) (slot->data.restart_lsn >> 32),
-					   (uint32) slot->data.restart_lsn)));
+					   LSN_FORMAT_ARGS(slot->data.confirmed_flush),
+					   LSN_FORMAT_ARGS(slot->data.restart_lsn))));
 
 	return ctx;
 }
@@ -567,8 +564,7 @@ DecodingContextFindStartpoint(LogicalDecodingContext *ctx)
 	XLogBeginRead(ctx->reader, slot->data.restart_lsn);
 
 	elog(DEBUG1, "searching for logical decoding starting point, starting at %X/%X",
-		 (uint32) (slot->data.restart_lsn >> 32),
-		 (uint32) slot->data.restart_lsn);
+		 LSN_FORMAT_ARGS(slot->data.restart_lsn));
 
 	/* Wait for a consistent starting point */
 	for (;;)
@@ -688,8 +684,7 @@ output_plugin_error_callback(void *arg)
 				   NameStr(state->ctx->slot->data.name),
 				   NameStr(state->ctx->slot->data.plugin),
 				   state->callback_name,
-				   (uint32) (state->report_location >> 32),
-				   (uint32) state->report_location);
+				   LSN_FORMAT_ARGS(state->report_location));
 	else
 		errcontext("slot \"%s\", output plugin \"%s\", in the %s callback",
 				   NameStr(state->ctx->slot->data.name),
@@ -1623,8 +1618,8 @@ LogicalIncreaseRestartDecodingForSlot(XLogRecPtr current_lsn, XLogRecPtr restart
 		SpinLockRelease(&slot->mutex);
 
 		elog(DEBUG1, "got new restart lsn %X/%X at %X/%X",
-			 (uint32) (restart_lsn >> 32), (uint32) restart_lsn,
-			 (uint32) (current_lsn >> 32), (uint32) current_lsn);
+			 LSN_FORMAT_ARGS(restart_lsn),
+			 LSN_FORMAT_ARGS(current_lsn));
 	}
 	else
 	{
@@ -1638,14 +1633,11 @@ LogicalIncreaseRestartDecodingForSlot(XLogRecPtr current_lsn, XLogRecPtr restart
 		SpinLockRelease(&slot->mutex);
 
 		elog(DEBUG1, "failed to increase restart lsn: proposed %X/%X, after %X/%X, current candidate %X/%X, current after %X/%X, flushed up to %X/%X",
-			 (uint32) (restart_lsn >> 32), (uint32) restart_lsn,
-			 (uint32) (current_lsn >> 32), (uint32) current_lsn,
-			 (uint32) (candidate_restart_lsn >> 32),
-			 (uint32) candidate_restart_lsn,
-			 (uint32) (candidate_restart_valid >> 32),
-			 (uint32) candidate_restart_valid,
-			 (uint32) (confirmed_flush >> 32),
-			 (uint32) confirmed_flush);
+			 LSN_FORMAT_ARGS(restart_lsn),
+			 LSN_FORMAT_ARGS(current_lsn),
+			 LSN_FORMAT_ARGS(candidate_restart_lsn),
+			 LSN_FORMAT_ARGS(candidate_restart_valid),
+			 LSN_FORMAT_ARGS(confirmed_flush));
 	}
 
 	/* candidates are already valid with the current flush position, apply */
diff --git a/src/backend/replication/logical/origin.c b/src/backend/replication/logical/origin.c
index 685eaa6134..39471fddad 100644
--- a/src/backend/replication/logical/origin.c
+++ b/src/backend/replication/logical/origin.c
@@ -789,8 +789,7 @@ StartupReplicationOrigin(void)
 		ereport(LOG,
 				(errmsg("recovered replication state of node %u to %X/%X",
 						disk_state.roident,
-						(uint32) (disk_state.remote_lsn >> 32),
-						(uint32) disk_state.remote_lsn)));
+						LSN_FORMAT_ARGS(disk_state.remote_lsn))));
 	}
 
 	/* now check checksum */
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 5a62ab8bbc..c742e1d970 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -4366,8 +4366,7 @@ ReorderBufferSerializedPath(char *path, ReplicationSlot *slot, TransactionId xid
 
 	snprintf(path, MAXPGPATH, "pg_replslot/%s/xid-%u-lsn-%X-%X.spill",
 			 NameStr(MyReplicationSlot->data.name),
-			 xid,
-			 (uint32) (recptr >> 32), (uint32) recptr);
+			 xid, LSN_FORMAT_ARGS(recptr));
 }
 
 /*
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 752cf2d7db..e11788795f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -801,7 +801,7 @@ SnapBuildDistributeNewCatalogSnapshot(SnapBuild *builder, XLogRecPtr lsn)
 			continue;
 
 		elog(DEBUG2, "adding a new snapshot to %u at %X/%X",
-			 txn->xid, (uint32) (lsn >> 32), (uint32) lsn);
+			 txn->xid, LSN_FORMAT_ARGS(lsn));
 
 		/*
 		 * increase the snapshot's refcount for the transaction we are handing
@@ -1191,7 +1191,7 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 	{
 		ereport(DEBUG1,
 				(errmsg_internal("skipping snapshot at %X/%X while building logical decoding snapshot, xmin horizon too low",
-								 (uint32) (lsn >> 32), (uint32) lsn),
+								 LSN_FORMAT_ARGS(lsn)),
 				 errdetail_internal("initial xmin horizon of %u vs the snapshot's %u",
 									builder->initial_xmin_horizon, running->oldestRunningXid)));
 
@@ -1230,7 +1230,7 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 
 		ereport(LOG,
 				(errmsg("logical decoding found consistent point at %X/%X",
-						(uint32) (lsn >> 32), (uint32) lsn),
+						LSN_FORMAT_ARGS(lsn)),
 				 errdetail("There are no running transactions.")));
 
 		return false;
@@ -1274,7 +1274,7 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 
 		ereport(LOG,
 				(errmsg("logical decoding found initial starting point at %X/%X",
-						(uint32) (lsn >> 32), (uint32) lsn),
+						LSN_FORMAT_ARGS(lsn)),
 				 errdetail("Waiting for transactions (approximately %d) older than %u to end.",
 						   running->xcnt, running->nextXid)));
 
@@ -1298,7 +1298,7 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 
 		ereport(LOG,
 				(errmsg("logical decoding found initial consistent point at %X/%X",
-						(uint32) (lsn >> 32), (uint32) lsn),
+						LSN_FORMAT_ARGS(lsn)),
 				 errdetail("Waiting for transactions (approximately %d) older than %u to end.",
 						   running->xcnt, running->nextXid)));
 
@@ -1323,7 +1323,7 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 
 		ereport(LOG,
 				(errmsg("logical decoding found consistent point at %X/%X",
-						(uint32) (lsn >> 32), (uint32) lsn),
+						LSN_FORMAT_ARGS(lsn)),
 				 errdetail("There are no old transactions anymore.")));
 	}
 
@@ -1477,7 +1477,7 @@ SnapBuildSerialize(SnapBuild *builder, XLogRecPtr lsn)
 	 * no hope continuing to decode anyway.
 	 */
 	sprintf(path, "pg_logical/snapshots/%X-%X.snap",
-			(uint32) (lsn >> 32), (uint32) lsn);
+			LSN_FORMAT_ARGS(lsn));
 
 	/*
 	 * first check whether some other backend already has written the snapshot
@@ -1520,7 +1520,7 @@ SnapBuildSerialize(SnapBuild *builder, XLogRecPtr lsn)
 
 	/* to make sure only we will write to this tempfile, include pid */
 	sprintf(tmppath, "pg_logical/snapshots/%X-%X.snap.%u.tmp",
-			(uint32) (lsn >> 32), (uint32) lsn, MyProcPid);
+			LSN_FORMAT_ARGS(lsn), MyProcPid);
 
 	/*
 	 * Unlink temporary file if it already exists, needs to have been before a
@@ -1670,7 +1670,7 @@ SnapBuildRestore(SnapBuild *builder, XLogRecPtr lsn)
 		return false;
 
 	sprintf(path, "pg_logical/snapshots/%X-%X.snap",
-			(uint32) (lsn >> 32), (uint32) lsn);
+			LSN_FORMAT_ARGS(lsn));
 
 	fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
 
@@ -1854,7 +1854,7 @@ SnapBuildRestore(SnapBuild *builder, XLogRecPtr lsn)
 
 	ereport(LOG,
 			(errmsg("logical decoding found consistent point at %X/%X",
-					(uint32) (lsn >> 32), (uint32) lsn),
+					LSN_FORMAT_ARGS(lsn)),
 			 errdetail("Logical decoding will begin using saved snapshot.")));
 	return true;
 
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 24a6ce5d8e..feb634e7ac 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -1120,9 +1120,7 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 
 	elog(DEBUG1,
 		 "LogicalRepSyncTableStart: '%s' origin_startpos lsn %X/%X",
-		 originname,
-		 (uint32) (*origin_startpos >> 32),
-		 (uint32) *origin_startpos);
+		 originname, LSN_FORMAT_ARGS(*origin_startpos));
 
 	/*
 	 * We are done with the initial data synchronization, update the state.
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index cfc924cd89..18d05286b6 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -2359,10 +2359,9 @@ send_feedback(XLogRecPtr recvpos, bool force, bool requestReply)
 
 	elog(DEBUG2, "sending feedback (force %d) to recv %X/%X, write %X/%X, flush %X/%X",
 		 force,
-		 (uint32) (recvpos >> 32), (uint32) recvpos,
-		 (uint32) (writepos >> 32), (uint32) writepos,
-		 (uint32) (flushpos >> 32), (uint32) flushpos
-		);
+		 LSN_FORMAT_ARGS(recvpos),
+		 LSN_FORMAT_ARGS(writepos),
+		 LSN_FORMAT_ARGS(flushpos));
 
 	walrcv_send(wrconn, reply_message->data, reply_message->len);
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index e00c7ffc01..fb4af2ef52 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1242,8 +1242,7 @@ InvalidateObsoleteReplicationSlots(XLogSegNo oldestSegno)
 		ereport(LOG,
 				(errmsg("invalidating slot \"%s\" because its restart_lsn %X/%X exceeds max_slot_wal_keep_size",
 						NameStr(slotname),
-						(uint32) (restart_lsn >> 32),
-						(uint32) restart_lsn)));
+						LSN_FORMAT_ARGS(restart_lsn))));
 
 		SpinLockAcquire(&s->mutex);
 		s->data.invalidated_at = s->data.restart_lsn;
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 057f41046d..d24bb5b0b5 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -659,8 +659,7 @@ pg_replication_slot_advance(PG_FUNCTION_ARGS)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("cannot advance replication slot to %X/%X, minimum is %X/%X",
-						(uint32) (moveto >> 32), (uint32) moveto,
-						(uint32) (minlsn >> 32), (uint32) minlsn)));
+						LSN_FORMAT_ARGS(moveto), LSN_FORMAT_ARGS(minlsn))));
 
 	/* Do the actual slot update, depending on the slot type */
 	if (OidIsValid(MyReplicationSlot->data.database))
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index f765002e0d..7fa1a87cd8 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -222,7 +222,7 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 		new_status = (char *) palloc(len + 32 + 1);
 		memcpy(new_status, old_status, len);
 		sprintf(new_status + len, " waiting for %X/%X",
-				(uint32) (lsn >> 32), (uint32) lsn);
+				LSN_FORMAT_ARGS(lsn));
 		set_ps_display(new_status);
 		new_status[len] = '\0'; /* truncate off " waiting ..." */
 	}
@@ -534,9 +534,9 @@ SyncRepReleaseWaiters(void)
 	LWLockRelease(SyncRepLock);
 
 	elog(DEBUG3, "released %d procs up to write %X/%X, %d procs up to flush %X/%X, %d procs up to apply %X/%X",
-		 numwrite, (uint32) (writePtr >> 32), (uint32) writePtr,
-		 numflush, (uint32) (flushPtr >> 32), (uint32) flushPtr,
-		 numapply, (uint32) (applyPtr >> 32), (uint32) applyPtr);
+		 numwrite, LSN_FORMAT_ARGS(writePtr),
+		 numflush, LSN_FORMAT_ARGS(flushPtr),
+		 numapply, LSN_FORMAT_ARGS(applyPtr));
 }
 
 /*
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 723f513d8b..208611056a 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -392,13 +392,11 @@ WalReceiverMain(void)
 			if (first_stream)
 				ereport(LOG,
 						(errmsg("started streaming WAL from primary at %X/%X on timeline %u",
-								(uint32) (startpoint >> 32), (uint32) startpoint,
-								startpointTLI)));
+								LSN_FORMAT_ARGS(startpoint), startpointTLI)));
 			else
 				ereport(LOG,
 						(errmsg("restarted WAL streaming at %X/%X on timeline %u",
-								(uint32) (startpoint >> 32), (uint32) startpoint,
-								startpointTLI)));
+								LSN_FORMAT_ARGS(startpoint), startpointTLI)));
 			first_stream = false;
 
 			/* Initialize LogstreamResult and buffers for processing messages */
@@ -465,7 +463,7 @@ WalReceiverMain(void)
 									(errmsg("replication terminated by primary server"),
 									 errdetail("End of WAL reached on timeline %u at %X/%X.",
 											   startpointTLI,
-											   (uint32) (LogstreamResult.Write >> 32), (uint32) LogstreamResult.Write)));
+											   LSN_FORMAT_ARGS(LogstreamResult.Write))));
 							endofwal = true;
 							break;
 						}
@@ -699,8 +697,7 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 		char		activitymsg[50];
 
 		snprintf(activitymsg, sizeof(activitymsg), "restarting at %X/%X",
-				 (uint32) (*startpoint >> 32),
-				 (uint32) *startpoint);
+				 LSN_FORMAT_ARGS(*startpoint));
 		set_ps_display(activitymsg);
 	}
 }
@@ -1002,8 +999,7 @@ XLogWalRcvFlush(bool dying)
 			char		activitymsg[50];
 
 			snprintf(activitymsg, sizeof(activitymsg), "streaming %X/%X",
-					 (uint32) (LogstreamResult.Write >> 32),
-					 (uint32) LogstreamResult.Write);
+					 LSN_FORMAT_ARGS(LogstreamResult.Write));
 			set_ps_display(activitymsg);
 		}
 
@@ -1080,9 +1076,9 @@ XLogWalRcvSendReply(bool force, bool requestReply)
 
 	/* Send it */
 	elog(DEBUG2, "sending write %X/%X flush %X/%X apply %X/%X%s",
-		 (uint32) (writePtr >> 32), (uint32) writePtr,
-		 (uint32) (flushPtr >> 32), (uint32) flushPtr,
-		 (uint32) (applyPtr >> 32), (uint32) applyPtr,
+		 LSN_FORMAT_ARGS(writePtr),
+		 LSN_FORMAT_ARGS(flushPtr),
+		 LSN_FORMAT_ARGS(applyPtr),
 		 requestReply ? " (reply requested)" : "");
 
 	walrcv_send(wrconn, reply_message.data, reply_message.len);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 64167fe3a6..81244541e2 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -402,7 +402,7 @@ IdentifySystem(void)
 	else
 		logptr = GetFlushRecPtr();
 
-	snprintf(xloc, sizeof(xloc), "%X/%X", (uint32) (logptr >> 32), (uint32) logptr);
+	snprintf(xloc, sizeof(xloc), "%X/%X", LSN_FORMAT_ARGS(logptr));
 
 	if (MyDatabaseId != InvalidOid)
 	{
@@ -674,13 +674,11 @@ StartReplication(StartReplicationCmd *cmd)
 			{
 				ereport(ERROR,
 						(errmsg("requested starting point %X/%X on timeline %u is not in this server's history",
-								(uint32) (cmd->startpoint >> 32),
-								(uint32) (cmd->startpoint),
+								LSN_FORMAT_ARGS(cmd->startpoint),
 								cmd->timeline),
 						 errdetail("This server's history forked from timeline %u at %X/%X.",
 								   cmd->timeline,
-								   (uint32) (switchpoint >> 32),
-								   (uint32) (switchpoint))));
+								   LSN_FORMAT_ARGS(switchpoint))));
 			}
 			sendTimeLineValidUpto = switchpoint;
 		}
@@ -723,10 +721,8 @@ StartReplication(StartReplicationCmd *cmd)
 		{
 			ereport(ERROR,
 					(errmsg("requested starting point %X/%X is ahead of the WAL flush position of this server %X/%X",
-							(uint32) (cmd->startpoint >> 32),
-							(uint32) (cmd->startpoint),
-							(uint32) (FlushPtr >> 32),
-							(uint32) (FlushPtr))));
+							LSN_FORMAT_ARGS(cmd->startpoint),
+							LSN_FORMAT_ARGS(FlushPtr))));
 		}
 
 		/* Start streaming from the requested point */
@@ -769,8 +765,7 @@ StartReplication(StartReplicationCmd *cmd)
 		bool		nulls[2];
 
 		snprintf(startpos_str, sizeof(startpos_str), "%X/%X",
-				 (uint32) (sendTimeLineValidUpto >> 32),
-				 (uint32) sendTimeLineValidUpto);
+				 LSN_FORMAT_ARGS(sendTimeLineValidUpto));
 
 		dest = CreateDestReceiver(DestRemoteSimple);
 		MemSet(nulls, false, sizeof(nulls));
@@ -1063,8 +1058,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	}
 
 	snprintf(xloc, sizeof(xloc), "%X/%X",
-			 (uint32) (MyReplicationSlot->data.confirmed_flush >> 32),
-			 (uint32) MyReplicationSlot->data.confirmed_flush);
+			 LSN_FORMAT_ARGS(MyReplicationSlot->data.confirmed_flush));
 
 	dest = CreateDestReceiver(DestRemoteSimple);
 	MemSet(nulls, false, sizeof(nulls));
@@ -1900,9 +1894,9 @@ ProcessStandbyReplyMessage(void)
 		replyTimeStr = pstrdup(timestamptz_to_str(replyTime));
 
 		elog(DEBUG2, "write %X/%X flush %X/%X apply %X/%X%s reply_time %s",
-			 (uint32) (writePtr >> 32), (uint32) writePtr,
-			 (uint32) (flushPtr >> 32), (uint32) flushPtr,
-			 (uint32) (applyPtr >> 32), (uint32) applyPtr,
+			 LSN_FORMAT_ARGS(writePtr),
+			 LSN_FORMAT_ARGS(flushPtr),
+			 LSN_FORMAT_ARGS(applyPtr),
 			 replyRequested ? " (reply requested)" : "",
 			 replyTimeStr);
 
@@ -2694,8 +2688,8 @@ XLogSendPhysical(void)
 		WalSndCaughtUp = true;
 
 		elog(DEBUG1, "walsender reached end of timeline at %X/%X (sent up to %X/%X)",
-			 (uint32) (sendTimeLineValidUpto >> 32), (uint32) sendTimeLineValidUpto,
-			 (uint32) (sentPtr >> 32), (uint32) sentPtr);
+			 LSN_FORMAT_ARGS(sendTimeLineValidUpto),
+			 LSN_FORMAT_ARGS(sentPtr));
 		return;
 	}
 
@@ -2826,7 +2820,7 @@ XLogSendPhysical(void)
 		char		activitymsg[50];
 
 		snprintf(activitymsg, sizeof(activitymsg), "streaming %X/%X",
-				 (uint32) (sentPtr >> 32), (uint32) sentPtr);
+				 LSN_FORMAT_ARGS(sentPtr));
 		set_ps_display(activitymsg);
 	}
 }
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 5877a60715..a3ee652030 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -1262,7 +1262,7 @@ LogCurrentRunningXacts(RunningTransactions CurrRunningXacts)
 		elog(trace_recovery(DEBUG2),
 			 "snapshot of %u running transactions overflowed (lsn %X/%X oldest xid %u latest complete %u next xid %u)",
 			 CurrRunningXacts->xcnt,
-			 (uint32) (recptr >> 32), (uint32) recptr,
+			 LSN_FORMAT_ARGS(recptr),
 			 CurrRunningXacts->oldestRunningXid,
 			 CurrRunningXacts->latestCompletedXid,
 			 CurrRunningXacts->nextXid);
@@ -1270,7 +1270,7 @@ LogCurrentRunningXacts(RunningTransactions CurrRunningXacts)
 		elog(trace_recovery(DEBUG2),
 			 "snapshot of %u+%u running transaction ids (lsn %X/%X oldest xid %u latest complete %u next xid %u)",
 			 CurrRunningXacts->xcnt, CurrRunningXacts->subxcnt,
-			 (uint32) (recptr >> 32), (uint32) recptr,
+			 LSN_FORMAT_ARGS(recptr),
 			 CurrRunningXacts->oldestRunningXid,
 			 CurrRunningXacts->latestCompletedXid,
 			 CurrRunningXacts->nextXid);
diff --git a/src/backend/utils/adt/pg_lsn.c b/src/backend/utils/adt/pg_lsn.c
index 12ad0c4c31..b41dfe9eb0 100644
--- a/src/backend/utils/adt/pg_lsn.c
+++ b/src/backend/utils/adt/pg_lsn.c
@@ -83,14 +83,8 @@ pg_lsn_out(PG_FUNCTION_ARGS)
 	XLogRecPtr	lsn = PG_GETARG_LSN(0);
 	char		buf[MAXPG_LSNLEN + 1];
 	char	   *result;
-	uint32		id,
-				off;
-
-	/* Decode ID and offset */
-	id = (uint32) (lsn >> 32);
-	off = (uint32) lsn;
 
-	snprintf(buf, sizeof buf, "%X/%X", id, off);
+	snprintf(buf, sizeof buf, "%X/%X", LSN_FORMAT_ARGS(lsn));
 	result = pstrdup(buf);
 	PG_RETURN_CSTRING(result);
 }
diff --git a/src/bin/pg_basebackup/pg_receivewal.c b/src/bin/pg_basebackup/pg_receivewal.c
index 4122d84094..0d15012c29 100644
--- a/src/bin/pg_basebackup/pg_receivewal.c
+++ b/src/bin/pg_basebackup/pg_receivewal.c
@@ -115,14 +115,14 @@ stop_streaming(XLogRecPtr xlogpos, uint32 timeline, bool segment_finished)
 	/* we assume that we get called once at the end of each segment */
 	if (verbose && segment_finished)
 		pg_log_info("finished segment at %X/%X (timeline %u)",
-					(uint32) (xlogpos >> 32), (uint32) xlogpos,
+					LSN_FORMAT_ARGS(xlogpos),
 					timeline);
 
 	if (!XLogRecPtrIsInvalid(endpos) && endpos < xlogpos)
 	{
 		if (verbose)
 			pg_log_info("stopped log streaming at %X/%X (timeline %u)",
-						(uint32) (xlogpos >> 32), (uint32) xlogpos,
+						LSN_FORMAT_ARGS(xlogpos),
 						timeline);
 		time_to_stop = true;
 		return true;
@@ -139,7 +139,7 @@ stop_streaming(XLogRecPtr xlogpos, uint32 timeline, bool segment_finished)
 	if (verbose && prevtimeline != 0 && prevtimeline != timeline)
 		pg_log_info("switched to timeline %u at %X/%X",
 					timeline,
-					(uint32) (prevpos >> 32), (uint32) prevpos);
+					LSN_FORMAT_ARGS(prevpos));
 
 	prevtimeline = timeline;
 	prevpos = xlogpos;
@@ -420,7 +420,7 @@ StreamLog(void)
 	 */
 	if (verbose)
 		pg_log_info("starting log streaming at %X/%X (timeline %u)",
-					(uint32) (stream.startpos >> 32), (uint32) stream.startpos,
+					LSN_FORMAT_ARGS(stream.startpos),
 					stream.timeline);
 
 	stream.stream_stop = stop_streaming;
diff --git a/src/bin/pg_basebackup/pg_recvlogical.c b/src/bin/pg_basebackup/pg_recvlogical.c
index 553ba7b8f4..bf0246c426 100644
--- a/src/bin/pg_basebackup/pg_recvlogical.c
+++ b/src/bin/pg_basebackup/pg_recvlogical.c
@@ -131,8 +131,8 @@ sendFeedback(PGconn *conn, TimestampTz now, bool force, bool replyRequested)
 
 	if (verbose)
 		pg_log_info("confirming write up to %X/%X, flush to %X/%X (slot %s)",
-					(uint32) (output_written_lsn >> 32), (uint32) output_written_lsn,
-					(uint32) (output_fsync_lsn >> 32), (uint32) output_fsync_lsn,
+					LSN_FORMAT_ARGS(output_written_lsn),
+					LSN_FORMAT_ARGS(output_fsync_lsn),
 					replication_slot);
 
 	replybuf[len] = 'r';
@@ -228,12 +228,12 @@ StreamLogicalLog(void)
 	 */
 	if (verbose)
 		pg_log_info("starting log streaming at %X/%X (slot %s)",
-					(uint32) (startpos >> 32), (uint32) startpos,
+					LSN_FORMAT_ARGS(startpos),
 					replication_slot);
 
 	/* Initiate the replication stream at specified location */
 	appendPQExpBuffer(query, "START_REPLICATION SLOT \"%s\" LOGICAL %X/%X",
-					  replication_slot, (uint32) (startpos >> 32), (uint32) startpos);
+					  replication_slot, LSN_FORMAT_ARGS(startpos));
 
 	/* print options if there are any */
 	if (noptions)
@@ -1045,10 +1045,9 @@ prepareToTerminate(PGconn *conn, XLogRecPtr endpos, bool keepalive, XLogRecPtr l
 	{
 		if (keepalive)
 			pg_log_info("end position %X/%X reached by keepalive",
-						(uint32) (endpos >> 32), (uint32) endpos);
+						LSN_FORMAT_ARGS(endpos));
 		else
 			pg_log_info("end position %X/%X reached by WAL record at %X/%X",
-						(uint32) (endpos >> 32), (uint32) (endpos),
-						(uint32) (lsn >> 32), (uint32) lsn);
+						LSN_FORMAT_ARGS(endpos), LSN_FORMAT_ARGS(lsn));
 	}
 }
diff --git a/src/bin/pg_basebackup/receivelog.c b/src/bin/pg_basebackup/receivelog.c
index 4fc050f3a1..7a2148fd05 100644
--- a/src/bin/pg_basebackup/receivelog.c
+++ b/src/bin/pg_basebackup/receivelog.c
@@ -560,7 +560,7 @@ ReceiveXlogStream(PGconn *conn, StreamCtl *stream)
 		/* Initiate the replication stream at specified location */
 		snprintf(query, sizeof(query), "START_REPLICATION %s%X/%X TIMELINE %u",
 				 slotcmd,
-				 (uint32) (stream->startpos >> 32), (uint32) stream->startpos,
+				 LSN_FORMAT_ARGS(stream->startpos),
 				 stream->timeline);
 		res = PQexec(conn, query);
 		if (PQresultStatus(res) != PGRES_COPY_BOTH)
@@ -616,8 +616,8 @@ ReceiveXlogStream(PGconn *conn, StreamCtl *stream)
 			if (stream->startpos > stoppos)
 			{
 				pg_log_error("server stopped streaming timeline %u at %X/%X, but reported next timeline %u to begin at %X/%X",
-							 stream->timeline, (uint32) (stoppos >> 32), (uint32) stoppos,
-							 newtimeline, (uint32) (stream->startpos >> 32), (uint32) stream->startpos);
+							 stream->timeline, LSN_FORMAT_ARGS(stoppos),
+							 newtimeline, LSN_FORMAT_ARGS(stream->startpos));
 				goto error;
 			}
 
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index 3e00ac0f70..f911f98d94 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -236,11 +236,9 @@ main(int argc, char *argv[])
 	printf(_("pg_control last modified:             %s\n"),
 		   pgctime_str);
 	printf(_("Latest checkpoint location:           %X/%X\n"),
-		   (uint32) (ControlFile->checkPoint >> 32),
-		   (uint32) ControlFile->checkPoint);
+		   LSN_FORMAT_ARGS(ControlFile->checkPoint));
 	printf(_("Latest checkpoint's REDO location:    %X/%X\n"),
-		   (uint32) (ControlFile->checkPointCopy.redo >> 32),
-		   (uint32) ControlFile->checkPointCopy.redo);
+		   LSN_FORMAT_ARGS(ControlFile->checkPointCopy.redo));
 	printf(_("Latest checkpoint's REDO WAL file:    %s\n"),
 		   xlogfilename);
 	printf(_("Latest checkpoint's TimeLineID:       %u\n"),
@@ -275,19 +273,15 @@ main(int argc, char *argv[])
 	printf(_("Time of latest checkpoint:            %s\n"),
 		   ckpttime_str);
 	printf(_("Fake LSN counter for unlogged rels:   %X/%X\n"),
-		   (uint32) (ControlFile->unloggedLSN >> 32),
-		   (uint32) ControlFile->unloggedLSN);
+		   LSN_FORMAT_ARGS(ControlFile->unloggedLSN));
 	printf(_("Minimum recovery ending location:     %X/%X\n"),
-		   (uint32) (ControlFile->minRecoveryPoint >> 32),
-		   (uint32) ControlFile->minRecoveryPoint);
+		   LSN_FORMAT_ARGS(ControlFile->minRecoveryPoint));
 	printf(_("Min recovery ending loc's timeline:   %u\n"),
 		   ControlFile->minRecoveryPointTLI);
 	printf(_("Backup start location:                %X/%X\n"),
-		   (uint32) (ControlFile->backupStartPoint >> 32),
-		   (uint32) ControlFile->backupStartPoint);
+		   LSN_FORMAT_ARGS(ControlFile->backupStartPoint));
 	printf(_("Backup end location:                  %X/%X\n"),
-		   (uint32) (ControlFile->backupEndPoint >> 32),
-		   (uint32) ControlFile->backupEndPoint);
+		   LSN_FORMAT_ARGS(ControlFile->backupEndPoint));
 	printf(_("End-of-backup record required:        %s\n"),
 		   ControlFile->backupEndRequired ? _("yes") : _("no"));
 	printf(_("wal_level setting:                    %s\n"),
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 7117ae5229..59ebac7d6a 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -87,11 +87,11 @@ extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
 
 			if (errormsg)
 				pg_fatal("could not read WAL record at %X/%X: %s",
-						 (uint32) (errptr >> 32), (uint32) (errptr),
+						 LSN_FORMAT_ARGS(errptr),
 						 errormsg);
 			else
 				pg_fatal("could not read WAL record at %X/%X",
-						 (uint32) (errptr >> 32), (uint32) (errptr));
+						 LSN_FORMAT_ARGS(errptr));
 		}
 
 		extractPageInfo(xlogreader);
@@ -140,10 +140,10 @@ readOneRecord(const char *datadir, XLogRecPtr ptr, int tliIndex,
 	{
 		if (errormsg)
 			pg_fatal("could not read WAL record at %X/%X: %s",
-					 (uint32) (ptr >> 32), (uint32) (ptr), errormsg);
+					 LSN_FORMAT_ARGS(ptr), errormsg);
 		else
 			pg_fatal("could not read WAL record at %X/%X",
-					 (uint32) (ptr >> 32), (uint32) (ptr));
+					 LSN_FORMAT_ARGS(ptr));
 	}
 	endptr = xlogreader->EndRecPtr;
 
@@ -206,11 +206,11 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 		{
 			if (errormsg)
 				pg_fatal("could not find previous WAL record at %X/%X: %s",
-						 (uint32) (searchptr >> 32), (uint32) (searchptr),
+						 LSN_FORMAT_ARGS(searchptr),
 						 errormsg);
 			else
 				pg_fatal("could not find previous WAL record at %X/%X",
-						 (uint32) (searchptr >> 32), (uint32) (searchptr));
+						 LSN_FORMAT_ARGS(searchptr));
 		}
 
 		/*
@@ -428,7 +428,7 @@ extractPageInfo(XLogReaderState *record)
 		 */
 		pg_fatal("WAL record modifies a relation, but record type is not recognized: "
 				 "lsn: %X/%X, rmgr: %s, info: %02X",
-				 (uint32) (record->ReadRecPtr >> 32), (uint32) (record->ReadRecPtr),
+				 LSN_FORMAT_ARGS(record->ReadRecPtr),
 				 RmgrNames[rmid], info);
 	}
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 359a6a587c..9df08ab2b0 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -344,7 +344,7 @@ main(int argc, char **argv)
 
 		findCommonAncestorTimeline(&divergerec, &lastcommontliIndex);
 		pg_log_info("servers diverged at WAL location %X/%X on timeline %u",
-					(uint32) (divergerec >> 32), (uint32) divergerec,
+					LSN_FORMAT_ARGS(divergerec),
 					targetHistory[lastcommontliIndex].tli);
 
 		/*
@@ -401,8 +401,7 @@ main(int argc, char **argv)
 	findLastCheckpoint(datadir_target, divergerec, lastcommontliIndex,
 					   &chkptrec, &chkpttli, &chkptredo, restore_command);
 	pg_log_info("rewinding from last common checkpoint at %X/%X on timeline %u",
-				(uint32) (chkptrec >> 32), (uint32) chkptrec,
-				chkpttli);
+				LSN_FORMAT_ARGS(chkptrec), chkpttli);
 
 	/* Initialize the hash table to track the status of each file */
 	filehash_init();
@@ -859,8 +858,8 @@ getTimelineHistory(ControlFileData *controlFile, int *nentries)
 
 			entry = &history[i];
 			pg_log_debug("%d: %X/%X - %X/%X", entry->tli,
-						 (uint32) (entry->begin >> 32), (uint32) (entry->begin),
-						 (uint32) (entry->end >> 32), (uint32) (entry->end));
+						 LSN_FORMAT_ARGS(entry->begin),
+						 LSN_FORMAT_ARGS(entry->end));
 		}
 	}
 
@@ -954,8 +953,8 @@ createBackupLabel(XLogRecPtr startpoint, TimeLineID starttli, XLogRecPtr checkpo
 				   "BACKUP FROM: standby\n"
 				   "START TIME: %s\n",
 	/* omit LABEL: line */
-				   (uint32) (startpoint >> 32), (uint32) startpoint, xlogfilename,
-				   (uint32) (checkpointloc >> 32), (uint32) checkpointloc,
+				   LSN_FORMAT_ARGS(startpoint), xlogfilename,
+				   LSN_FORMAT_ARGS(checkpointloc),
 				   strfbuf);
 	if (len >= sizeof(buf))
 		pg_fatal("backup label buffer too small");	/* shouldn't happen */
diff --git a/src/bin/pg_verifybackup/pg_verifybackup.c b/src/bin/pg_verifybackup/pg_verifybackup.c
index bb3f2783d0..f5ebd57a47 100644
--- a/src/bin/pg_verifybackup/pg_verifybackup.c
+++ b/src/bin/pg_verifybackup/pg_verifybackup.c
@@ -816,10 +816,8 @@ parse_required_wal(verifier_context *context, char *pg_waldump_path,
 
 		pg_waldump_cmd = psprintf("\"%s\" --quiet --path=\"%s\" --timeline=%u --start=%X/%X --end=%X/%X\n",
 								  pg_waldump_path, wal_directory, this_wal_range->tli,
-								  (uint32) (this_wal_range->start_lsn >> 32),
-								  (uint32) this_wal_range->start_lsn,
-								  (uint32) (this_wal_range->end_lsn >> 32),
-								  (uint32) this_wal_range->end_lsn);
+								  LSN_FORMAT_ARGS(this_wal_range->start_lsn),
+								  LSN_FORMAT_ARGS(this_wal_range->end_lsn));
 		if (system(pg_waldump_cmd) != 0)
 			report_backup_error(context,
 								"WAL parsing failed for timeline %u",
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index 164868d16e..610f65e471 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -467,8 +467,8 @@ XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record)
 		   desc->rm_name,
 		   rec_len, XLogRecGetTotalLen(record),
 		   XLogRecGetXid(record),
-		   (uint32) (record->ReadRecPtr >> 32), (uint32) record->ReadRecPtr,
-		   (uint32) (xl_prev >> 32), (uint32) xl_prev);
+		   LSN_FORMAT_ARGS(record->ReadRecPtr),
+		   LSN_FORMAT_ARGS(xl_prev));
 
 	id = desc->rm_identify(info);
 	if (id == NULL)
@@ -972,8 +972,7 @@ main(int argc, char **argv)
 		else if (!XLByteInSeg(private.startptr, segno, WalSegSz))
 		{
 			pg_log_error("start WAL location %X/%X is not inside file \"%s\"",
-						 (uint32) (private.startptr >> 32),
-						 (uint32) private.startptr,
+						 LSN_FORMAT_ARGS(private.startptr),
 						 fname);
 			goto bad_argument;
 		}
@@ -1015,8 +1014,7 @@ main(int argc, char **argv)
 			private.endptr != (segno + 1) * WalSegSz)
 		{
 			pg_log_error("end WAL location %X/%X is not inside file \"%s\"",
-						 (uint32) (private.endptr >> 32),
-						 (uint32) private.endptr,
+						 LSN_FORMAT_ARGS(private.endptr),
 						 argv[argc - 1]);
 			goto bad_argument;
 		}
@@ -1048,8 +1046,7 @@ main(int argc, char **argv)
 
 	if (first_record == InvalidXLogRecPtr)
 		fatal_error("could not find a valid record after %X/%X",
-					(uint32) (private.startptr >> 32),
-					(uint32) private.startptr);
+					LSN_FORMAT_ARGS(private.startptr));
 
 	/*
 	 * Display a message that we're skipping data if `from` wasn't a pointer
@@ -1061,8 +1058,8 @@ main(int argc, char **argv)
 		printf(ngettext("first record is after %X/%X, at %X/%X, skipping over %u byte\n",
 						"first record is after %X/%X, at %X/%X, skipping over %u bytes\n",
 						(first_record - private.startptr)),
-			   (uint32) (private.startptr >> 32), (uint32) private.startptr,
-			   (uint32) (first_record >> 32), (uint32) first_record,
+			   LSN_FORMAT_ARGS(private.startptr),
+			   LSN_FORMAT_ARGS(first_record),
 			   (uint32) (first_record - private.startptr));
 
 	for (;;)
@@ -1110,8 +1107,7 @@ main(int argc, char **argv)
 
 	if (errormsg)
 		fatal_error("error in WAL record at %X/%X: %s",
-					(uint32) (xlogreader_state->ReadRecPtr >> 32),
-					(uint32) xlogreader_state->ReadRecPtr,
+					LSN_FORMAT_ARGS(xlogreader_state->ReadRecPtr),
 					errormsg);
 
 	XLogReaderFree(xlogreader_state);
diff --git a/src/include/access/xlogdefs.h b/src/include/access/xlogdefs.h
index 65836d5bc6..b7ccd10a39 100644
--- a/src/include/access/xlogdefs.h
+++ b/src/include/access/xlogdefs.h
@@ -35,6 +35,13 @@ typedef uint64 XLogRecPtr;
  */
 #define FirstNormalUnloggedLSN	((XLogRecPtr) 1000)
 
+/*
+ * Handy macro for printing XLogRecPtr in conventional format, e.g.,
+ *
+ * printf("%X/%X", LSN_FORMAT_ARGS(lsn));
+ */
+#define LSN_FORMAT_ARGS(lsn) (AssertVariableIsOfTypeMacro((lsn), XLogRecPtr), (unsigned) ((lsn) >> 32)), ((unsigned) (lsn))
+
 /*
  * XLogSegNo - physical log file sequence number.
  */
-- 
2.30.1

#23Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Peter Eisentraut (#22)
Re: Printing LSN made easy

On Thu, Feb 18, 2021 at 6:19 PM Peter Eisentraut <
peter.eisentraut@2ndquadrant.com> wrote:

Here is an updated patch that just introduces LSN_FORMAT_ARGS(). I
think the result is quite pleasant.

Thanks a lot Peter for producing this patch. I am fine with it. The way
this is defined someone could write xyz = LSN_FORMAT_ARGS(lsn). But then
they are misusing it so I won't care. Even my proposal had that problem.

--
Best Wishes,
Ashutosh

#24Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Ashutosh Bapat (#23)
Re: Printing LSN made easy

At Thu, 18 Feb 2021 18:51:37 +0530, Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote in

On Thu, Feb 18, 2021 at 6:19 PM Peter Eisentraut <
peter.eisentraut@2ndquadrant.com> wrote:

Here is an updated patch that just introduces LSN_FORMAT_ARGS(). I
think the result is quite pleasant.

Thanks a lot Peter for producing this patch. I am fine with it. The way
this is defined someone could write xyz = LSN_FORMAT_ARGS(lsn). But then
they are misusing it so I won't care. Even my proposal had that problem.

As for the side effect by expressions as the parameter, unary
operators are seldom (or never) applied to LSN. I think there's no
need to fear about other (modifying) expressions, too.

As a double-checking, I checked that the patch covers all output by
'%X/%X' and " ?>> ?32)" that are handling LSNs, and there's no
mis-replacing of the source variables.

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#25Michael Paquier
michael@paquier.xyz
In reply to: Kyotaro Horiguchi (#24)
Re: Printing LSN made easy

On Fri, Feb 19, 2021 at 10:54:05AM +0900, Kyotaro Horiguchi wrote:

At Thu, 18 Feb 2021 18:51:37 +0530, Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote in

On Thu, Feb 18, 2021 at 6:19 PM Peter Eisentraut <
peter.eisentraut@2ndquadrant.com> wrote:

Here is an updated patch that just introduces LSN_FORMAT_ARGS(). I
think the result is quite pleasant.

This result is nice for the eye. Thanks Peter.
--
Michael