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, useCURLOPT_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 β theDEBUGFUNCTIONhas no effect until we enableVERBOSE. 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 π₯
CURLINFO_CONN_ID(3)CURLINFO_XFER_ID(3)CURLOPT_DEBUGDATA(3)CURLOPT_VERBOSE(3)curl_global_trace(3)
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
- fedora 44: libcurl-devel 8.18.0-14.fc44 Β· curl
- fedora 44: libcurl-devel 8.18.0-14.fc44 Β· curl
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms