Improve PQtrace() output format
authorAlvaro Herrera <[email protected]>
Tue, 30 Mar 2021 23:12:34 +0000 (20:12 -0300)
committerAlvaro Herrera <[email protected]>
Tue, 30 Mar 2021 23:12:34 +0000 (20:12 -0300)
Transform the PQtrace output format from its ancient (and mostly
useless) byte-level output format to a logical-message-level output,
making it much more usable.  This implementation allows the printing
code to be written (as it indeed was) by looking at the protocol
documentation, which gives more confidence that the three (docs, trace
code and actual code) actually match.

Author: 岩田 彩 (Aya Iwata) <[email protected]>
Reviewed-by: 綱川 貴之 (Takayuki Tsunakawa) <[email protected]>
Reviewed-by: Kirk Jamison <[email protected]>
Reviewed-by: Kyotaro Horiguchi <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Reviewed-by: 黒田 隼人 (Hayato Kuroda) <[email protected]>
Reviewed-by: "Nagaura, Ryohei" <[email protected]>
Reviewed-by: Ryo Matsumura <[email protected]>
Reviewed-by: Greg Nancarrow <[email protected]>
Reviewed-by: Jim Doty <[email protected]>
Reviewed-by: Álvaro Herrera <[email protected]>
Discussion: https://postgr.es/m/71E660EB361DF14299875B198D4CE5423DE3FBA4@g01jpexmbkw25

doc/src/sgml/libpq.sgml
src/interfaces/libpq/Makefile
src/interfaces/libpq/exports.txt
src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/fe-exec.c
src/interfaces/libpq/fe-misc.c
src/interfaces/libpq/fe-protocol3.c
src/interfaces/libpq/fe-trace.c [new file with mode: 0644]
src/interfaces/libpq/libpq-fe.h
src/interfaces/libpq/libpq-int.h

index be674fbaa92756e19b3bb4945cf88876a41eaf0d..3ec458ce09d59b61f15fa0414df3ee9132ae0e86 100644 (file)
@@ -6459,12 +6459,27 @@ PGContextVisibility PQsetErrorContextVisibility(PGconn *conn, PGContextVisibilit
 
     <listitem>
      <para>
-      Enables  tracing of the client/server communication to a debugging file stream.
+      Enables tracing of the client/server communication to a debugging file
+      stream.
 <synopsis>
 void PQtrace(PGconn *conn, FILE *stream);
 </synopsis>
      </para>
 
+     <para>
+      Each line consists of: an optional timestamp, a direction indicator
+      (<literal>F</literal> for messages from client to server
+      or <literal>B</literal> for messages from server to client),
+      message length, message type, and message contents.
+      Non-message contents fields (timestamp, direction, length and message type)
+      are separated by a tab. Message contents are separated by a space.
+      Protocol strings are enclosed in double quotes, while strings used as data
+      values are enclosed in single quotes.  Non-printable chars are printed as
+      hexadecimal escapes.
+      Further message-type-specific detail can be found in
+      <xref linkend="protocol-message-formats"/>.
+     </para>
+
      <note>
       <para>
        On Windows, if the <application>libpq</application> library and an application are
@@ -6479,6 +6494,31 @@ void PQtrace(PGconn *conn, FILE *stream);
     </listitem>
    </varlistentry>
 
+   <varlistentry id="libpq-PQtraceSetFlags">
+    <term><function>PQtraceSetFlags</function><indexterm><primary>PQtraceSetFlags</primary></indexterm></term>
+
+    <listitem>
+     <para>
+      Controls the tracing behavior of client/server communication.
+<synopsis>
+void PQtraceSetFlags(PGconn *conn, int flags);
+</synopsis>
+     </para>
+
+     <para>
+      <literal>flags</literal> contains flag bits describing the operating mode
+      of tracing.
+      If <literal>flags</literal> contains <literal>PQTRACE_SUPPRESS_TIMESTAMPS</literal>,
+      then the timestamp is not included when printing each message.
+      If <literal>flags</literal> contains <literal>PQTRACE_REGRESS_MODE</literal>,
+      then some fields are redacted when printing each message, such as object
+      OIDs, to make the output more convenient to use in testing frameworks.
+      This function must be called after calling <function>PQtrace</function>.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="libpq-PQuntrace">
     <term><function>PQuntrace</function><indexterm><primary>PQuntrace</primary></indexterm></term>
 
index 2aca882a2beb568c83bbf82cacf63ea648b0fd03..0c4e55b6ad34d934c843f85db800c37c83207659 100644 (file)
@@ -39,6 +39,7 @@ OBJS = \
    fe-print.o \
    fe-protocol3.o \
    fe-secure.o \
+   fe-trace.o \
    legacy-pqsignal.o \
    libpq-events.o \
    pqexpbuffer.o \
index 5c48c14191479cc0947773640bb52234bdb4ae63..a00701f2c5fe66047327515d0a4677a6f3012a86 100644 (file)
@@ -183,3 +183,4 @@ PQenterPipelineMode       180
 PQexitPipelineMode        181
 PQpipelineSync            182
 PQpipelineStatus          183
+PQtraceSetFlags           184
index 53b354abb285167eb087956c67e43748d60c60ba..a90bdb8ab6d6e83c086e6b0aa2ab65b8b20db482 100644 (file)
@@ -6859,27 +6859,6 @@ PQsetErrorContextVisibility(PGconn *conn, PGContextVisibility show_context)
    return old;
 }
 
-void
-PQtrace(PGconn *conn, FILE *debug_port)
-{
-   if (conn == NULL)
-       return;
-   PQuntrace(conn);
-   conn->Pfdebug = debug_port;
-}
-
-void
-PQuntrace(PGconn *conn)
-{
-   if (conn == NULL)
-       return;
-   if (conn->Pfdebug)
-   {
-       fflush(conn->Pfdebug);
-       conn->Pfdebug = NULL;
-   }
-}
-
 PQnoticeReceiver
 PQsetNoticeReceiver(PGconn *conn, PQnoticeReceiver proc, void *arg)
 {
index f143b8b7fb83242089a4d8e512aa9d1b77279c08..03592bdce9fc9c0bebff75b282279993d0daf215 100644 (file)
@@ -970,10 +970,6 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
    pgParameterStatus *pstatus;
    pgParameterStatus *prev;
 
-   if (conn->Pfdebug)
-       fprintf(conn->Pfdebug, "pqSaveParameterStatus: '%s' = '%s'\n",
-               name, value);
-
    /*
     * Forget any old information about the parameter
     */
index ce2d24b91fcd6cfc5979bc6949476a0705241ba6..082b583c152c154dd57c156a150723f4b8594e4e 100644 (file)
@@ -84,9 +84,6 @@ pqGetc(char *result, PGconn *conn)
 
    *result = conn->inBuffer[conn->inCursor++];
 
-   if (conn->Pfdebug)
-       fprintf(conn->Pfdebug, "From backend> %c\n", *result);
-
    return 0;
 }
 
@@ -100,9 +97,6 @@ pqPutc(char c, PGconn *conn)
    if (pqPutMsgBytes(&c, 1, conn))
        return EOF;
 
-   if (conn->Pfdebug)
-       fprintf(conn->Pfdebug, "To backend> %c\n", c);
-
    return 0;
 }
 
@@ -138,10 +132,6 @@ pqGets_internal(PQExpBuffer buf, PGconn *conn, bool resetbuffer)
 
    conn->inCursor = ++inCursor;
 
-   if (conn->Pfdebug)
-       fprintf(conn->Pfdebug, "From backend> \"%s\"\n",
-               buf->data);
-
    return 0;
 }
 
@@ -167,9 +157,6 @@ pqPuts(const char *s, PGconn *conn)
    if (pqPutMsgBytes(s, strlen(s) + 1, conn))
        return EOF;
 
-   if (conn->Pfdebug)
-       fprintf(conn->Pfdebug, "To backend> \"%s\"\n", s);
-
    return 0;
 }
 
@@ -188,13 +175,6 @@ pqGetnchar(char *s, size_t len, PGconn *conn)
 
    conn->inCursor += len;
 
-   if (conn->Pfdebug)
-   {
-       fprintf(conn->Pfdebug, "From backend (%lu)> ", (unsigned long) len);
-       fwrite(s, 1, len, conn->Pfdebug);
-       fprintf(conn->Pfdebug, "\n");
-   }
-
    return 0;
 }
 
@@ -212,13 +192,6 @@ pqSkipnchar(size_t len, PGconn *conn)
    if (len > (size_t) (conn->inEnd - conn->inCursor))
        return EOF;
 
-   if (conn->Pfdebug)
-   {
-       fprintf(conn->Pfdebug, "From backend (%lu)> ", (unsigned long) len);
-       fwrite(conn->inBuffer + conn->inCursor, 1, len, conn->Pfdebug);
-       fprintf(conn->Pfdebug, "\n");
-   }
-
    conn->inCursor += len;
 
    return 0;
@@ -234,13 +207,6 @@ pqPutnchar(const char *s, size_t len, PGconn *conn)
    if (pqPutMsgBytes(s, len, conn))
        return EOF;
 
-   if (conn->Pfdebug)
-   {
-       fprintf(conn->Pfdebug, "To backend> ");
-       fwrite(s, 1, len, conn->Pfdebug);
-       fprintf(conn->Pfdebug, "\n");
-   }
-
    return 0;
 }
 
@@ -278,9 +244,6 @@ pqGetInt(int *result, size_t bytes, PGconn *conn)
            return EOF;
    }
 
-   if (conn->Pfdebug)
-       fprintf(conn->Pfdebug, "From backend (#%lu)> %d\n", (unsigned long) bytes, *result);
-
    return 0;
 }
 
@@ -314,9 +277,6 @@ pqPutInt(int value, size_t bytes, PGconn *conn)
            return EOF;
    }
 
-   if (conn->Pfdebug)
-       fprintf(conn->Pfdebug, "To backend (%lu#)> %d\n", (unsigned long) bytes, value);
-
    return 0;
 }
 
@@ -525,10 +485,6 @@ pqPutMsgStart(char msg_type, PGconn *conn)
    conn->outMsgEnd = endPos;
    /* length word, if needed, will be filled in by pqPutMsgEnd */
 
-   if (conn->Pfdebug)
-       fprintf(conn->Pfdebug, "To backend> Msg %c\n",
-               msg_type ? msg_type : ' ');
-
    return 0;
 }
 
@@ -563,10 +519,6 @@ pqPutMsgBytes(const void *buf, size_t len, PGconn *conn)
 int
 pqPutMsgEnd(PGconn *conn)
 {
-   if (conn->Pfdebug)
-       fprintf(conn->Pfdebug, "To backend> Msg complete, length %u\n",
-               conn->outMsgEnd - conn->outCount);
-
    /* Fill in length word if needed */
    if (conn->outMsgStart >= 0)
    {
@@ -576,6 +528,16 @@ pqPutMsgEnd(PGconn *conn)
        memcpy(conn->outBuffer + conn->outMsgStart, &msgLen, 4);
    }
 
+   /* trace client-to-server message */
+   if (conn->Pfdebug)
+   {
+       if (conn->outCount < conn->outMsgStart)
+           pqTraceOutputMessage(conn, conn->outBuffer + conn->outCount, true);
+       else
+           pqTraceOutputNoTypeByteMessage(conn,
+                                          conn->outBuffer + conn->outMsgStart);
+   }
+
    /* Make message eligible to send */
    conn->outCount = conn->outMsgEnd;
 
@@ -1002,11 +964,13 @@ pqSendSome(PGconn *conn, int len)
 int
 pqFlush(PGconn *conn)
 {
-   if (conn->Pfdebug)
-       fflush(conn->Pfdebug);
-
    if (conn->outCount > 0)
+   {
+       if (conn->Pfdebug)
+           fflush(conn->Pfdebug);
+
        return pqSendSome(conn, conn->outCount);
+   }
 
    return 0;
 }
index 306e89acfd2bc9be4236a272434c79c9c05b4572..b45fb7e70593a40edc9f4a54f13b1bc941fa7c73 100644 (file)
@@ -457,6 +457,10 @@ pqParseInput3(PGconn *conn)
        /* Successfully consumed this message */
        if (conn->inCursor == conn->inStart + 5 + msgLength)
        {
+           /* trace server-to-client message */
+           if (conn->Pfdebug)
+               pqTraceOutputMessage(conn, conn->inBuffer + conn->inStart, false);
+
            /* Normal case: parsing agrees with specified length */
            conn->inStart = conn->inCursor;
        }
@@ -1660,6 +1664,10 @@ getCopyDataMessage(PGconn *conn)
                return -1;
        }
 
+       /* trace server-to-client message */
+       if (conn->Pfdebug)
+           pqTraceOutputMessage(conn, conn->inBuffer + conn->inStart, false);
+
        /* Drop the processed message and loop around for another */
        conn->inStart = conn->inCursor;
    }
@@ -2119,6 +2127,11 @@ pqFunctionCall3(PGconn *conn, Oid fnid,
                conn->inStart += 5 + msgLength;
                return pqPrepareAsyncResult(conn);
        }
+
+       /* trace server-to-client message */
+       if (conn->Pfdebug)
+           pqTraceOutputMessage(conn, conn->inBuffer + conn->inStart, false);
+
        /* Completed this message, keep going */
        /* trust the specified message length as what to skip */
        conn->inStart += 5 + msgLength;
diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c
new file mode 100644 (file)
index 0000000..0bfae12
--- /dev/null
@@ -0,0 +1,714 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-trace.c
+ *   functions for libpq protocol tracing
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *   src/interfaces/libpq/fe-trace.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <limits.h>
+#include <time.h>
+
+#ifdef WIN32
+#include "win32.h"
+#else
+#include <unistd.h>
+#include <sys/time.h>
+#endif
+
+#include "libpq-fe.h"
+#include "libpq-int.h"
+#include "pgtime.h"
+#include "port/pg_bswap.h"
+
+/* Enable tracing */
+void
+PQtrace(PGconn *conn, FILE *debug_port)
+{
+   if (conn == NULL)
+       return;
+   PQuntrace(conn);
+   if (debug_port == NULL)
+       return;
+
+   /* Make the trace stream line-buffered */
+   setvbuf(debug_port, NULL, _IOLBF, 0);
+   conn->Pfdebug = debug_port;
+   conn->traceFlags = 0;
+}
+
+/* Disable tracing */
+void
+PQuntrace(PGconn *conn)
+{
+   if (conn == NULL)
+       return;
+   if (conn->Pfdebug)
+   {
+       fflush(conn->Pfdebug);
+       conn->Pfdebug = NULL;
+   }
+
+   conn->traceFlags = 0;
+}
+
+/* Set flags for current tracing session */
+void
+PQtraceSetFlags(PGconn *conn, int flags)
+{
+   if (conn == NULL)
+       return;
+   /* If PQtrace() failed, do nothing. */
+   if (conn->Pfdebug == NULL)
+       return;
+   conn->traceFlags = flags;
+}
+
+/*
+ * Print the current time, with microseconds, into a caller-supplied
+ * buffer.
+ * Cribbed from setup_formatted_log_time, but much simpler.
+ */
+static void
+pqTraceFormatTimestamp(char *timestr, size_t ts_len)
+{
+   struct timeval tval;
+   pg_time_t   stamp_time;
+
+   gettimeofday(&tval, NULL);
+   stamp_time = (pg_time_t) tval.tv_sec;
+
+   strftime(timestr, ts_len,
+            "%Y-%m-%d %H:%M:%S",
+            localtime(&stamp_time));
+   /* append microseconds */
+   sprintf(timestr + strlen(timestr), ".%06d", (int) (tval.tv_usec));
+}
+
+/*
+ *   pqTraceOutputByte1: output a 1-char message to the log
+ */
+static void
+pqTraceOutputByte1(FILE *pfdebug, const char *data, int *cursor)
+{
+   const char *v = data + *cursor;
+
+   /*
+    * Show non-printable data in hex format, including the terminating \0
+    * that completes ErrorResponse and NoticeResponse messages.
+    */
+   if (!isprint(*v))
+       fprintf(pfdebug, " \\x%02x", *v);
+   else
+       fprintf(pfdebug, " %c", *v);
+   *cursor += 1;
+}
+
+/*
+ *   pqTraceOutputInt16: output a 2-byte integer message to the log
+ */
+static int
+pqTraceOutputInt16(FILE *pfdebug, const char *data, int *cursor)
+{
+   uint16      tmp;
+   int         result;
+
+   memcpy(&tmp, data + *cursor, 2);
+   *cursor += 2;
+   result = (int) pg_ntoh16(tmp);
+   fprintf(pfdebug, " %d", result);
+
+   return result;
+}
+
+/*
+ *   pqTraceOutputInt32: output a 4-byte integer message to the log
+ *
+ * If 'suppress' is true, print a literal NNNN instead of the actual number.
+ */
+static int
+pqTraceOutputInt32(FILE *pfdebug, const char *data, int *cursor, bool suppress)
+{
+   int         result;
+
+   memcpy(&result, data + *cursor, 4);
+   *cursor += 4;
+   result = (int) pg_ntoh32(result);
+   if (suppress)
+       fprintf(pfdebug, " NNNN");
+   else
+       fprintf(pfdebug, " %d", result);
+
+   return result;
+}
+
+/*
+ *   pqTraceOutputString: output a string message to the log
+ */
+static void
+pqTraceOutputString(FILE *pfdebug, const char *data, int *cursor, bool suppress)
+{
+   int         len;
+
+   if (suppress)
+   {
+       fprintf(pfdebug, " \"SSSS\"");
+       *cursor += strlen(data + *cursor) + 1;
+   }
+   else
+   {
+       len = fprintf(pfdebug, " \"%s\"", data + *cursor);
+
+       /*
+        * This is a null-terminated string. So add 1 after subtracting 3
+        * which is the double quotes and space length from len.
+        */
+       *cursor += (len - 3 + 1);
+   }
+}
+
+/*
+ * pqTraceOutputNchar: output a string of exactly len bytes message to the log
+ */
+static void
+pqTraceOutputNchar(FILE *pfdebug, int len, const char *data, int *cursor)
+{
+   int         i,
+               next;           /* first char not yet printed */
+   const char *v = data + *cursor;
+
+   fprintf(pfdebug, " \'");
+
+   for (next = i = 0; i < len; ++i)
+   {
+       if (isprint(v[i]))
+           continue;
+       else
+       {
+           fwrite(v + next, 1, i - next, pfdebug);
+           fprintf(pfdebug, "\\x%02x", v[i]);
+           next = i + 1;
+       }
+   }
+   if (next < len)
+       fwrite(v + next, 1, len - next, pfdebug);
+
+   fprintf(pfdebug, "\'");
+   *cursor += len;
+}
+
+/*
+ * Output functions by protocol message type
+ */
+
+/* NotificationResponse */
+static void
+pqTraceOutputA(FILE *f, const char *message, int *cursor, bool regress)
+{
+   fprintf(f, "NotificationResponse\t");
+   pqTraceOutputInt32(f, message, cursor, regress);
+   pqTraceOutputString(f, message, cursor, false);
+   pqTraceOutputString(f, message, cursor, false);
+}
+
+/* Bind */
+static void
+pqTraceOutputB(FILE *f, const char *message, int *cursor)
+{
+   int         nparams;
+
+   fprintf(f, "Bind\t");
+   pqTraceOutputString(f, message, cursor, false);
+   pqTraceOutputString(f, message, cursor, false);
+   nparams = pqTraceOutputInt16(f, message, cursor);
+
+   for (int i = 0; i < nparams; i++)
+       pqTraceOutputInt16(f, message, cursor);
+
+   nparams = pqTraceOutputInt16(f, message, cursor);
+
+   for (int i = 0; i < nparams; i++)
+   {
+       int         nbytes;
+
+       nbytes = pqTraceOutputInt32(f, message, cursor, false);
+       if (nbytes == -1)
+           continue;
+       pqTraceOutputNchar(f, nbytes, message, cursor);
+   }
+
+   nparams = pqTraceOutputInt16(f, message, cursor);
+   for (int i = 0; i < nparams; i++)
+       pqTraceOutputInt16(f, message, cursor);
+}
+
+/* Close(F) or CommandComplete(B) */
+static void
+pqTraceOutputC(FILE *f, bool toServer, const char *message, int *cursor)
+{
+   if (toServer)
+   {
+       fprintf(f, "Close\t");
+       pqTraceOutputByte1(f, message, cursor);
+       pqTraceOutputString(f, message, cursor, false);
+   }
+   else
+   {
+       fprintf(f, "CommandComplete\t");
+       pqTraceOutputString(f, message, cursor, false);
+   }
+}
+
+/* Describe(F) or DataRow(B) */
+static void
+pqTraceOutputD(FILE *f, bool toServer, const char *message, int *cursor)
+{
+   if (toServer)
+   {
+       fprintf(f, "Describe\t");
+       pqTraceOutputByte1(f, message, cursor);
+       pqTraceOutputString(f, message, cursor, false);
+   }
+   else
+   {
+       int         nfields;
+       int         len;
+       int         i;
+
+       fprintf(f, "DataRow\t");
+       nfields = pqTraceOutputInt16(f, message, cursor);
+       for (i = 0; i < nfields; i++)
+       {
+           len = pqTraceOutputInt32(f, message, cursor, false);
+           if (len == -1)
+               continue;
+           pqTraceOutputNchar(f, len, message, cursor);
+       }
+   }
+}
+
+/* NoticeResponse / ErrorResponse */
+static void
+pqTraceOutputNR(FILE *f, const char *type, const char *message, int *cursor,
+               int length, bool regress)
+{
+   fprintf(f, "%s\t", type);
+   for (;;)
+   {
+       char        field;
+       bool        suppress;
+
+       pqTraceOutputByte1(f, message, cursor);
+       field = message[*cursor - 1];
+       if (field == '\0')
+           break;
+
+       suppress = regress && (field == 'L' || field == 'F' || field == 'R');
+       pqTraceOutputString(f, message, cursor, suppress);
+   }
+}
+
+/* Execute(F) or ErrorResponse(B) */
+static void
+pqTraceOutputE(FILE *f, bool toServer, const char *message, int *cursor, int length, bool regress)
+{
+   if (toServer)
+   {
+       fprintf(f, "Execute\t");
+       pqTraceOutputString(f, message, cursor, false);
+       pqTraceOutputInt32(f, message, cursor, false);
+   }
+   else
+       pqTraceOutputNR(f, "ErrorResponse", message, cursor, length, regress);
+}
+
+/* CopyFail */
+static void
+pqTraceOutputf(FILE *f, const char *message, int *cursor)
+{
+   fprintf(f, "CopyFail\t");
+   pqTraceOutputString(f, message, cursor, false);
+}
+
+/* FunctionCall */
+static void
+pqTraceOutputF(FILE *f, const char *message, int *cursor, bool regress)
+{
+   int         nfields;
+   int         nbytes;
+
+   fprintf(f, "FunctionCall\t");
+   pqTraceOutputInt32(f, message, cursor, regress);
+   nfields = pqTraceOutputInt16(f, message, cursor);
+
+   for (int i = 0; i < nfields; i++)
+       pqTraceOutputInt16(f, message, cursor);
+
+   nfields = pqTraceOutputInt16(f, message, cursor);
+
+   for (int i = 0; i < nfields; i++)
+   {
+       nbytes = pqTraceOutputInt32(f, message, cursor, false);
+       if (nbytes == -1)
+           continue;
+       pqTraceOutputNchar(f, nbytes, message, cursor);
+   }
+
+   pqTraceOutputInt16(f, message, cursor);
+}
+
+/* CopyInResponse */
+static void
+pqTraceOutputG(FILE *f, const char *message, int *cursor)
+{
+   int         nfields;
+
+   fprintf(f, "CopyInResponse\t");
+   pqTraceOutputByte1(f, message, cursor);
+   nfields = pqTraceOutputInt16(f, message, cursor);
+
+   for (int i = 0; i < nfields; i++)
+       pqTraceOutputInt16(f, message, cursor);
+}
+
+/* CopyOutResponse */
+static void
+pqTraceOutputH(FILE *f, const char *message, int *cursor)
+{
+   int         nfields;
+
+   fprintf(f, "CopyOutResponse\t");
+   pqTraceOutputByte1(f, message, cursor);
+   nfields = pqTraceOutputInt16(f, message, cursor);
+
+   for (int i = 0; i < nfields; i++)
+       pqTraceOutputInt16(f, message, cursor);
+}
+
+/* BackendKeyData */
+static void
+pqTraceOutputK(FILE *f, const char *message, int *cursor, bool regress)
+{
+   fprintf(f, "BackendKeyData\t");
+   pqTraceOutputInt32(f, message, cursor, regress);
+   pqTraceOutputInt32(f, message, cursor, regress);
+}
+
+/* Parse */
+static void
+pqTraceOutputP(FILE *f, const char *message, int *cursor, bool regress)
+{
+   int         nparams;
+
+   fprintf(f, "Parse\t");
+   pqTraceOutputString(f, message, cursor, false);
+   pqTraceOutputString(f, message, cursor, false);
+   nparams = pqTraceOutputInt16(f, message, cursor);
+
+   for (int i = 0; i < nparams; i++)
+       pqTraceOutputInt32(f, message, cursor, regress);
+}
+
+/* Query */
+static void
+pqTraceOutputQ(FILE *f, const char *message, int *cursor)
+{
+   fprintf(f, "Query\t");
+   pqTraceOutputString(f, message, cursor, false);
+}
+
+/* Authentication */
+static void
+pqTraceOutputR(FILE *f, const char *message, int *cursor)
+{
+   fprintf(f, "Authentication\t");
+   pqTraceOutputInt32(f, message, cursor, false);
+}
+
+/* ParameterStatus */
+static void
+pqTraceOutputS(FILE *f, const char *message, int *cursor)
+{
+   fprintf(f, "ParameterStatus\t");
+   pqTraceOutputString(f, message, cursor, false);
+   pqTraceOutputString(f, message, cursor, false);
+}
+
+/* ParameterDescription */
+static void
+pqTraceOutputt(FILE *f, const char *message, int *cursor, bool regress)
+{
+   int         nfields;
+
+   fprintf(f, "ParameterDescription\t");
+   nfields = pqTraceOutputInt16(f, message, cursor);
+
+   for (int i = 0; i < nfields; i++)
+       pqTraceOutputInt32(f, message, cursor, regress);
+}
+
+/* RowDescription */
+static void
+pqTraceOutputT(FILE *f, const char *message, int *cursor, bool regress)
+{
+   int         nfields;
+
+   fprintf(f, "RowDescription\t");
+   nfields = pqTraceOutputInt16(f, message, cursor);
+
+   for (int i = 0; i < nfields; i++)
+   {
+       pqTraceOutputString(f, message, cursor, false);
+       pqTraceOutputInt32(f, message, cursor, regress);
+       pqTraceOutputInt16(f, message, cursor);
+       pqTraceOutputInt32(f, message, cursor, regress);
+       pqTraceOutputInt16(f, message, cursor);
+       pqTraceOutputInt32(f, message, cursor, false);
+       pqTraceOutputInt16(f, message, cursor);
+   }
+}
+
+/* NegotiateProtocolVersion */
+static void
+pqTraceOutputv(FILE *f, const char *message, int *cursor)
+{
+   fprintf(f, "NegotiateProtocolVersion\t");
+   pqTraceOutputInt32(f, message, cursor, false);
+   pqTraceOutputInt32(f, message, cursor, false);
+}
+
+/* FunctionCallResponse */
+static void
+pqTraceOutputV(FILE *f, const char *message, int *cursor)
+{
+   int         len;
+
+   fprintf(f, "FunctionCallResponse\t");
+   len = pqTraceOutputInt32(f, message, cursor, false);
+   if (len != -1)
+       pqTraceOutputNchar(f, len, message, cursor);
+}
+
+/* CopyBothResponse */
+static void
+pqTraceOutputW(FILE *f, const char *message, int *cursor, int length)
+{
+   fprintf(f, "CopyBothResponse\t");
+   pqTraceOutputByte1(f, message, cursor);
+
+   while (length > *cursor)
+       pqTraceOutputInt16(f, message, cursor);
+}
+
+/* ReadyForQuery */
+static void
+pqTraceOutputZ(FILE *f, const char *message, int *cursor)
+{
+   fprintf(f, "ReadyForQuery\t");
+   pqTraceOutputByte1(f, message, cursor);
+}
+
+/*
+ * Print the given message to the trace output stream.
+ */
+void
+pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer)
+{
+   char        id;
+   int         length;
+   char       *prefix = toServer ? "F" : "B";
+   int         logCursor = 0;
+   bool        regress;
+
+   if ((conn->traceFlags & PQTRACE_SUPPRESS_TIMESTAMPS) == 0)
+   {
+       char        timestr[128];
+
+       pqTraceFormatTimestamp(timestr, sizeof(timestr));
+       fprintf(conn->Pfdebug, "%s\t", timestr);
+   }
+   regress = (conn->traceFlags & PQTRACE_REGRESS_MODE) != 0;
+
+   id = message[logCursor++];
+
+   memcpy(&length, message + logCursor, 4);
+   length = (int) pg_ntoh32(length);
+   logCursor += 4;
+
+   fprintf(conn->Pfdebug, "%s\t%d\t", prefix, length);
+
+   switch (id)
+   {
+       case '1':
+           fprintf(conn->Pfdebug, "ParseComplete");
+           /* No message content */
+           break;
+       case '2':
+           fprintf(conn->Pfdebug, "BindComplete");
+           /* No message content */
+           break;
+       case '3':
+           fprintf(conn->Pfdebug, "CloseComplete");
+           /* No message content */
+           break;
+       case 'A':               /* Notification Response */
+           pqTraceOutputA(conn->Pfdebug, message, &logCursor, regress);
+           break;
+       case 'B':               /* Bind */
+           pqTraceOutputB(conn->Pfdebug, message, &logCursor);
+           break;
+       case 'c':
+           fprintf(conn->Pfdebug, "CopyDone");
+           /* No message content */
+           break;
+       case 'C':               /* Close(F) or Command Complete(B) */
+           pqTraceOutputC(conn->Pfdebug, toServer, message, &logCursor);
+           break;
+       case 'd':               /* Copy Data */
+           /* Drop COPY data to reduce the overhead of logging. */
+           break;
+       case 'D':               /* Describe(F) or Data Row(B) */
+           pqTraceOutputD(conn->Pfdebug, toServer, message, &logCursor);
+           break;
+       case 'E':               /* Execute(F) or Error Response(B) */
+           pqTraceOutputE(conn->Pfdebug, toServer, message, &logCursor,
+                          length, regress);
+           break;
+       case 'f':               /* Copy Fail */
+           pqTraceOutputf(conn->Pfdebug, message, &logCursor);
+           break;
+       case 'F':               /* Function Call */
+           pqTraceOutputF(conn->Pfdebug, message, &logCursor, regress);
+           break;
+       case 'G':               /* Start Copy In */
+           pqTraceOutputG(conn->Pfdebug, message, &logCursor);
+           break;
+       case 'H':               /* Flush(F) or Start Copy Out(B) */
+           if (!toServer)
+               pqTraceOutputH(conn->Pfdebug, message, &logCursor);
+           else
+               fprintf(conn->Pfdebug, "Flush");    /* no message content */
+           break;
+       case 'I':
+           fprintf(conn->Pfdebug, "EmptyQueryResponse");
+           /* No message content */
+           break;
+       case 'K':               /* secret key data from the backend */
+           pqTraceOutputK(conn->Pfdebug, message, &logCursor, regress);
+           break;
+       case 'n':
+           fprintf(conn->Pfdebug, "NoData");
+           /* No message content */
+           break;
+       case 'N':
+           pqTraceOutputNR(conn->Pfdebug, "NoticeResponse", message,
+                           &logCursor, length, regress);
+           break;
+       case 'P':               /* Parse */
+           pqTraceOutputP(conn->Pfdebug, message, &logCursor, regress);
+           break;
+       case 'Q':               /* Query */
+           pqTraceOutputQ(conn->Pfdebug, message, &logCursor);
+           break;
+       case 'R':               /* Authentication */
+           pqTraceOutputR(conn->Pfdebug, message, &logCursor);
+           break;
+       case 's':
+           fprintf(conn->Pfdebug, "PortalSuspended");
+           /* No message content */
+           break;
+       case 'S':               /* Parameter Status(B) or Sync(F) */
+           if (!toServer)
+               pqTraceOutputS(conn->Pfdebug, message, &logCursor);
+           else
+               fprintf(conn->Pfdebug, "Sync"); /* no message content */
+           break;
+       case 't':               /* Parameter Description */
+           pqTraceOutputt(conn->Pfdebug, message, &logCursor, regress);
+           break;
+       case 'T':               /* Row Description */
+           pqTraceOutputT(conn->Pfdebug, message, &logCursor, regress);
+           break;
+       case 'v':               /* Negotiate Protocol Version */
+           pqTraceOutputv(conn->Pfdebug, message, &logCursor);
+           break;
+       case 'V':               /* Function Call response */
+           pqTraceOutputV(conn->Pfdebug, message, &logCursor);
+           break;
+       case 'W':               /* Start Copy Both */
+           pqTraceOutputW(conn->Pfdebug, message, &logCursor, length);
+           break;
+       case 'X':
+           fprintf(conn->Pfdebug, "Terminate");
+           /* No message content */
+           break;
+       case 'Z':               /* Ready For Query */
+           pqTraceOutputZ(conn->Pfdebug, message, &logCursor);
+           break;
+       default:
+           fprintf(conn->Pfdebug, "Unknown message: %02x", id);
+           break;
+   }
+
+   fputc('\n', conn->Pfdebug);
+
+   /*
+    * Verify the printing routine did it right.  Note that the one-byte
+    * message identifier is not included in the length, but our cursor does
+    * include it.
+    */
+   if (logCursor - 1 != length)
+       fprintf(conn->Pfdebug,
+               "mismatched message length: consumed %d, expected %d\n",
+               logCursor - 1, length);
+}
+
+/*
+ * Print special messages (those containing no type byte) to the trace output
+ * stream.
+ */
+void
+pqTraceOutputNoTypeByteMessage(PGconn *conn, const char *message)
+{
+   int         length;
+   int         logCursor = 0;
+
+   if ((conn->traceFlags & PQTRACE_SUPPRESS_TIMESTAMPS) == 0)
+   {
+       char        timestr[128];
+
+       pqTraceFormatTimestamp(timestr, sizeof(timestr));
+       fprintf(conn->Pfdebug, "%s\t", timestr);
+   }
+
+   memcpy(&length, message + logCursor, 4);
+   length = (int) pg_ntoh32(length);
+   logCursor += 4;
+
+   fprintf(conn->Pfdebug, "F\t%d\t", length);
+
+   switch (length)
+   {
+       case 16:                /* CancelRequest */
+           fprintf(conn->Pfdebug, "CancelRequest\t");
+           pqTraceOutputInt32(conn->Pfdebug, message, &logCursor, false);
+           pqTraceOutputInt32(conn->Pfdebug, message, &logCursor, false);
+           pqTraceOutputInt32(conn->Pfdebug, message, &logCursor, false);
+           break;
+       case 8:                 /* GSSENCRequest or SSLRequest */
+           /* These messages do not reach here. */
+       default:
+           fprintf(conn->Pfdebug, "Unknown message: length is %d", length);
+           break;
+   }
+
+   fputc('\n', conn->Pfdebug);
+}
index cee42d484398b1703308c5a8d3683b5c586acab8..227adde5a5e420dcf44cb31e085f0498141cdd62 100644 (file)
@@ -263,7 +263,7 @@ typedef struct pgresAttDesc
  * ----------------
  */
 
-/* === in fe-connect.c === */
+/* === in fe-connect.c === */
 
 /* make a new client connection to the backend */
 /* Asynchronous (non-blocking) */
@@ -375,10 +375,6 @@ extern PGVerbosity PQsetErrorVerbosity(PGconn *conn, PGVerbosity verbosity);
 extern PGContextVisibility PQsetErrorContextVisibility(PGconn *conn,
                                                       PGContextVisibility show_context);
 
-/* Enable/disable tracing */
-extern void PQtrace(PGconn *conn, FILE *debug_port);
-extern void PQuntrace(PGconn *conn);
-
 /* Override default notice handling routines */
 extern PQnoticeReceiver PQsetNoticeReceiver(PGconn *conn,
                                            PQnoticeReceiver proc,
@@ -398,6 +394,17 @@ typedef void (*pgthreadlock_t) (int acquire);
 
 extern pgthreadlock_t PQregisterThreadLock(pgthreadlock_t newhandler);
 
+/* === in fe-trace.c === */
+extern void PQtrace(PGconn *conn, FILE *debug_port);
+extern void PQuntrace(PGconn *conn);
+
+/* flags controlling trace output */
+/* omit timestamps from each line */
+#define PQTRACE_SUPPRESS_TIMESTAMPS        (1<<0)
+/* redact portions of some messages, for testing frameworks */
+#define PQTRACE_REGRESS_MODE           (1<<1)
+extern void PQtraceSetFlags(PGconn *conn, int flags);
+
 /* === in fe-exec.c === */
 
 /* Simple synchronous query */
index 6374ec657ac1c44f279c70110457821488a21ebd..3f7907127efa8cee54930dc88af05a8615165df8 100644 (file)
@@ -394,6 +394,7 @@ struct pg_conn
 
    /* Optional file to write trace info to */
    FILE       *Pfdebug;
+   int         traceFlags;
 
    /* Callback procedures for notice message processing */
    PGNoticeHooks noticeHooks;
@@ -818,6 +819,12 @@ extern ssize_t pg_GSS_write(PGconn *conn, const void *ptr, size_t len);
 extern ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len);
 #endif
 
+/* === in libpq-trace.c === */
+
+extern void pqTraceOutputMessage(PGconn *conn, const char *message,
+                                bool toServer);
+extern void pqTraceOutputNoTypeByteMessage(PGconn *conn, const char *message);
+
 /* === miscellaneous macros === */
 
 /*