printf-debugging, logging, and log levels
The oldest, simplest debugging move in the world is to sprinkle a few 'I got here, and x is 5' messages through your code and watch them fly by as it runs. That is printf-debugging: you insert print statements at suspicious spots to reveal which code ran and what the values were, reconstructing the story of an execution from the trail of messages it leaves. It needs no special tools, works everywhere, and is often the fastest way to test a quick hypothesis.
Concretely, you add lines like printf("reached parse, n=%d\n", n); at points of interest, rebuild, and read the output. Logging is the grown-up version of the same idea: instead of ad-hoc prints you delete later, you write structured messages through a logging facility that adds timestamps, the source location, and a severity level. Log levels rank messages by importance, conventionally from most to least severe: ERROR (something failed), WARN (something suspicious), INFO (normal milestones), DEBUG (detailed developer breadcrumbs), and TRACE (extremely fine-grained). You set a threshold - 'show INFO and above' - so the same code can run quiet in production and verbose while you investigate, without editing it. A vital detail in C: send debug output to standard error (stderr), not standard output, so it does not corrupt the program's real output, and remember stdout is buffered, so a crash can swallow a printf that never got flushed.
Why it matters: printf-debugging and logging are universal and survive where debuggers struggle - across machines, in production, in timing-sensitive code where pausing would hide the bug, and in code you cannot easily step through. The honest limits are real: prints only show what you thought to print, so they confirm guesses but rarely surprise you with the cause you did not anticipate; adding a print changes timing and buffering and can make a heisenbug move or vanish; too many logs become noise; and stale debug prints left in shipped code are a classic mess. Use prints to test specific hypotheses, reach for a debugger when you need to inspect state you did not anticipate, and keep real logging structured and leveled rather than littering printf everywhere.
Quick check: fprintf(stderr, "after open, fd=%d, errno=%d\n", fd, errno); printed to stderr (not stdout) and reliably flushed reveals fd is -1 right after open() - so the open failed, and you investigate the path rather than the parsing that crashes later.
Print to stderr and flush; the value of fd right after open() points at the real failure.
stdout is buffered, so a printf right before a crash may never appear unless you flush (fflush) or use stderr - a missing message does not mean the line was not reached. Prints only reveal what you anticipated to log, and adding one can shift the timing of a heisenbug.