A structured lesson workspace with readable content, hands-on examples, and a clean path to completion.
The compiler predefines several macros that give you source location information: __FILE__ expands to the current filename as a string literal. __LINE__ expands to the current line number as an integer. __func__ (C99) is a static variable containing the current function name. __DATE__ and __TIME__ give the compilation timestamp. These are the building blocks of every logging macro, every assertion message, and every debug trace in C.
#include <stdio.h>#include <time.h>#define LOG(level, fmt, ...) \fprintf(stderr, "[%s] %s:%d %s(): " fmt "\n", \level, __FILE__, __LINE__, __func__, ##__VA_ARGS__)int main(void) {LOG("INFO", "Program started");LOG("DEBUG", "Processing %d items", 42);LOG("WARN", "Disk usage at %d%%", 87);printf("Compiled on %s at %s\n", __DATE__, __TIME__);printf("C standard: %ld\n", __STDC_VERSION__);return 0;}
The LOG macro embeds the file, line, and function name automatically. You do not pass them. The preprocessor fills them in at the call site. The ##__VA_ARGS__ is a GNU extension that removes the trailing comma when there are no variadic arguments. In C23, __VA_OPT__(,) handles this portably. The output goes to stderr so it does not contaminate the program's stdout data.
#include <stdio.h>#include <time.h>static FILE *log_fp = NULL;#define LOG_OPEN(path) log_fp = fopen(path, "a")#define LOG_CLOSE() do { if (log_fp) fclose(log_fp); log_fp = NULL; } while(0)#define LOG(fmt, ...) do { \if (log_fp) { \time_t t = time(NULL); \struct tm *tm = localtime(&t); \char ts[20]; \strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", tm); \fprintf(log_fp, "[%s] %s:%d: " fmt "\n", \ts, __FILE__, __LINE__, ##__VA_ARGS__); \fflush(log_fp); \} \} while(0)int main(void) {LOG_OPEN("app.log");LOG("Server started on port %d", 8080);LOG("Connection from %s", "192.168.1.100");LOG_CLOSE();return 0;}
The do-while(0) wrapper makes the macro safe in if-else chains. The fflush ensures the log entry is written immediately, so it survives a crash. The timestamp comes from the time library. The location comes from the predefined macros. This is a production-quality logging macro in twenty lines of C.
Predefined macros are the compiler's gift to your debugging infrastructure. They cost nothing at runtime (the strings are in the binary's read-only section) and they give every log message precise source location. Use them in every logging, assertion, and tracing macro you write. Next: the #line directive, which overrides __FILE__ and __LINE__.
Finish the lesson once you have worked through the material. This awards ★ 30 XP.