CURLOPT_DEBUGFUNCTION(3)

πŸ’… CURLOPT_DEBUGFUNCTION(3) πŸ’…

ok so this is a page from the Library Functions Manual and it's all about CURLOPT_DEBUGFUNCTION, bestie. buckle up, it's a good one ✨


🌸 NAME 🌸

CURLOPT_DEBUGFUNCTION β€” the debug callback, aka the little spy you install to watch what libcurl is doing πŸ•΅οΈβ€β™€οΈ


✨ SYNOPSIS ✨

#include <curl/curl.h>

typedef enum {
  CURLINFO_TEXT = 0,
  CURLINFO_HEADER_IN,    /* 1 */
  CURLINFO_HEADER_OUT,   /* 2 */
  CURLINFO_DATA_IN,      /* 3 */
  CURLINFO_DATA_OUT,     /* 4 */
  CURLINFO_SSL_DATA_IN,  /* 5 */
  CURLINFO_SSL_DATA_OUT, /* 6 */
  CURLINFO_END
} curl_infotype;

int debug_callback(CURL *handle,
                   curl_infotype type,
                   char *data,
                   size_t size,
                   void *clientp);

CURLcode curl_easy_setopt(CURL *handle, CURLOPT_DEBUGFUNCTION,
                          debug_callback);

πŸ¦‹ DESCRIPTION πŸ¦‹

ok so here's the vibe: you pass in a pointer to your callback function, and it has to match the prototype up there exactly β€” no improvising, this is not a vibes-only situation πŸ’…

CURLOPT_DEBUGFUNCTION(3) replaces the standard debug function that gets used whenever CURLOPT_VERBOSE(3) is in effect. so instead of curl doing its own little debug printing, you get to handle it. the callback receives debug info, and which kind it is gets specified in the type argument. ur function must return 0 β€” that's the rule, periodt. and here's the important bit: the data pointed to by the char * passed to this function is not null-terminated, it's exactly the size told by the size argument. so don't go strlen()-ing it, i'm begging u πŸ™ˆ

⚠️ WARNING: this callback may receive sensitive contents from headers and data, including information sent as CURLINFO_TEXT. like, actual secrets. so maybe don't log it to a public slack channel, bestie 😭

the clientp argument is the pointer you set with CURLOPT_DEBUGDATA(3) 🫢

the curl_infotype values, ranked πŸ†

  • CURLINFO_TEXT β€” the data is informational text. just lil notes, iykyk πŸ’¬
  • CURLINFO_HEADER_IN β€” the data is header (or header-like) data received from the peer πŸ“₯
  • CURLINFO_HEADER_OUT β€” the data is header (or header-like) data sent to the peer πŸ“€
  • CURLINFO_DATA_IN β€” the data is the unprocessed protocol data received from the peer. even if the data is encoded or compressed, it is not provided decoded nor decompressed to this callback. so if u need the data in decoded and decompressed form, use CURLOPT_WRITEFUNCTION(3) instead, ok? that's the one for the pretty version πŸ’–
  • CURLINFO_DATA_OUT β€” the data is protocol data sent to the peer πŸš€
  • CURLINFO_SSL_DATA_OUT β€” the data is SSL/TLS (binary) data sent to the peer πŸ”
  • CURLINFO_SSL_DATA_IN β€” the data is SSL/TLS (binary) data received from the peer πŸ”“
⚠️ WARNING: this callback may be called with the curl handle set to an internal handle. (Added in 8.4.0) β€” yes this is real, yes it surprised everyone πŸ’€

if u need to distinguish ur own curl handle from internal handles, then set CURLOPT_PRIVATE(3) on ur handle. that's the move πŸ‘‘


πŸŽ€ DEFAULT πŸŽ€

NULL β€” nothing installed, no spy, no drama 🫧


🌈 PROTOCOLS 🌈

this functionality affects all supported protocols. every single one. no cap 🌍


πŸ“ EXAMPLE πŸ“

here's a full-on example, copied exactly, go off:

static
void dump(const char *text,
          FILE *stream, unsigned char *ptr, size_t size)
{
  size_t i;
  size_t c;
  unsigned int width = 0x10;

  fprintf(stream, "%s, %10.10ld bytes (0x%8.8lx)\n",
          text, (long)size, (long)size);

  for(i = 0; i < size; i += width) {
    fprintf(stream, "%4.4lx: ", (long)i);

    /* show hex to the left */
    for(c = 0; c < width; c++) {
      if(i + c < size)
        fprintf(stream, "%02x ", ptr[i + c]);
      else
        fputs("   ", stream);
    }

    /* show data on the right */
    for(c = 0; (c < width) && (i + c < size); c++) {
      char x = (ptr[i + c] >= 0x20 && ptr[i + c] < 0x80) ? ptr[i + c] : '.';
      fputc(x, stream);
    }

    fputc('\n', stream); /* newline */
  }
}

static
int my_trace(CURL *handle, curl_infotype type,
             char *data, size_t size,
             void *clientp)
{
  const char *text;
  (void)handle;
  (void)clientp;

  switch(type) {
  case CURLINFO_TEXT:
    fputs("== Info: ", stderr);
    fwrite(data, size, 1, stderr);
  default: /* in case a new one is introduced to shock us */
    return 0;

  case CURLINFO_HEADER_OUT:
    text = "=> Send header";
    break;
  case CURLINFO_DATA_OUT:
    text = "=> Send data";
    break;
  case CURLINFO_SSL_DATA_OUT:
    text = "=> Send SSL data";
    break;
  case CURLINFO_HEADER_IN:
    text = "<= Recv header";
    break;
  case CURLINFO_DATA_IN:
    text = "<= Recv data";
    break;
  case CURLINFO_SSL_DATA_IN:
    text = "<= Recv SSL data";
    break;
  }

  dump(text, stderr, (unsigned char *)data, size);
  return 0;
}

int main(void)
{
  CURL *curl;
  CURLcode result;

  curl = curl_easy_init();
  if(curl) {
    curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, my_trace);

    /* the DEBUGFUNCTION has no effect until we enable VERBOSE */
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

    /* example.com is redirected, so we tell libcurl to follow redirection */
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);

    curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/");
    result = curl_easy_perform(curl);
    /* Check for errors */
    if(result != CURLE_OK)
      fprintf(stderr, "curl_easy_perform() failed: %s\n",
              curl_easy_strerror(result));

    /* always cleanup */
    curl_easy_cleanup(curl);
  }
  return 0;
}
πŸ’‘ note: see that comment in there β€” the DEBUGFUNCTION has no effect until we enable VERBOSE. so if ur callback is never firing, that's why, bestie 😭

πŸ’œ AVAILABILITY πŸ’œ

added in curl 7.9.6. vintage icon behavior, honestly πŸ‘‘


⭐ RETURN VALUE ⭐

curl_easy_setopt(3) returns a CURLcode indicating success or error.

CURLE_OK (0) means everything was OK β€” slay. non-zero means an error occurred, see libcurl-errors(3) for the whole list 🧚


πŸ”₯ SEE ALSO πŸ”₯


and that's the whole page, bestie 🫢 this was brought to you by libcurl, dated 2026-10-12 β€” go forth and debug responsibly πŸ’…βœ¨

πŸ“¦ where this page comes from