clog.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. /* clog: Extremely simple logger for C.
  2. *
  3. * Features:
  4. * - Implemented purely as a single header file.
  5. * - Create multiple loggers.
  6. * - Four log levels (debug, info, warn, error).
  7. * - Custom formats.
  8. * - Fast.
  9. *
  10. * Dependencies:
  11. * - Should conform to C89, C++98 (but requires vsnprintf, unfortunately).
  12. * - POSIX environment.
  13. *
  14. * USAGE:
  15. *
  16. * Include this header in any file that wishes to write to logger(s). In
  17. * exactly one file (per executable), define CLOG_MAIN first (e.g. in your
  18. * main .c file).
  19. *
  20. * #define CLOG_MAIN
  21. * #include "clog.h"
  22. *
  23. * This will define the actual objects that all the other units will use.
  24. *
  25. * Loggers are identified by integers (0 - 15). It's expected that you'll
  26. * create meaningful constants and then refer to the loggers as such.
  27. *
  28. * Example:
  29. *
  30. * const int MY_LOGGER = 0;
  31. *
  32. * int main() {
  33. * int r;
  34. * r = clog_init_path(MY_LOGGER, "my_log.txt");
  35. * if (r != 0) {
  36. * fprintf(stderr, "Logger initialization failed.\n");
  37. * return 1;
  38. * }
  39. * clog_info(CLOG(MY_LOGGER), "Hello, world!");
  40. * clog_free(MY_LOGGER);
  41. * return 0;
  42. * }
  43. *
  44. * The CLOG macro used in the call to clog_info is a helper that passes the
  45. * __FILE__ and __LINE__ parameters for you, so you don't have to type them
  46. * every time. (It could be prettier with variadic macros, but that requires
  47. * C99 or C++11 to be standards compliant.)
  48. *
  49. * Errors encountered by clog will be printed to stderr. You can suppress
  50. * these by defining a macro called CLOG_SILENT before including clog.h.
  51. *
  52. * License: Do whatever you want. It would be nice if you contribute
  53. * improvements as pull requests here:
  54. *
  55. * https://github.com/mmueller/clog
  56. *
  57. * Copyright 2013 Mike Mueller <mike@subfocal.net>.
  58. *
  59. * As is; no warranty is provided; use at your own risk.
  60. */
  61. #ifndef __CLOG_H__
  62. #define __CLOG_H__
  63. #include <sys/types.h>
  64. #include <sys/stat.h>
  65. #include <errno.h>
  66. #include <fcntl.h>
  67. #include <stdarg.h>
  68. #include <stdlib.h>
  69. #include <stdio.h>
  70. #include <string.h>
  71. #include <time.h>
  72. #include <unistd.h>
  73. /* Number of loggers that can be defined. */
  74. #define CLOG_MAX_LOGGERS 16
  75. /* Format strings cannot be longer than this. */
  76. #define CLOG_FORMAT_LENGTH 256
  77. /* Formatted times and dates should be less than this length. If they are not,
  78. * they will not appear in the log. */
  79. #define CLOG_DATETIME_LENGTH 256
  80. /* Default format strings. */
  81. #define CLOG_DEFAULT_FORMAT "%d %t %f(%n): %l: %m\n"
  82. #define CLOG_DEFAULT_DATE_FORMAT "%Y-%m-%d"
  83. #define CLOG_DEFAULT_TIME_FORMAT "%H:%M:%S"
  84. #ifdef __cplusplus
  85. extern "C" {
  86. #endif
  87. enum clog_level {
  88. CLOG_DEBUG,
  89. CLOG_INFO,
  90. CLOG_WARN,
  91. CLOG_ERROR
  92. };
  93. struct clog;
  94. /**
  95. * Create a new logger writing to the given file path. The file will always
  96. * be opened in append mode.
  97. *
  98. * @param id
  99. * A constant integer between 0 and 15 that uniquely identifies this logger.
  100. *
  101. * @param path
  102. * Path to the file where log messages will be written.
  103. *
  104. * @return
  105. * Zero on success, non-zero on failure.
  106. */
  107. int clog_init_path(int id, const char *const path);
  108. /**
  109. * Create a new logger writing to a file descriptor.
  110. *
  111. * @param id
  112. * A constant integer between 0 and 15 that uniquely identifies this logger.
  113. *
  114. * @param fd
  115. * The file descriptor where log messages will be written.
  116. *
  117. * @return
  118. * Zero on success, non-zero on failure.
  119. */
  120. int clog_init_fd(int id, int fd);
  121. /**
  122. * Destroy (clean up) a logger. You should do this at the end of execution,
  123. * or when you are done using the logger.
  124. *
  125. * @param id
  126. * The id of the logger to destroy.
  127. */
  128. void clog_free(int id);
  129. #define CLOG(id) __FILE__, __LINE__, id
  130. /**
  131. * Log functions (one per level). Call these to write messages to the log
  132. * file. The first three arguments can be replaced with a call to the CLOG
  133. * macro defined above, e.g.:
  134. *
  135. * clog_debug(CLOG(MY_LOGGER_ID), "This is a log message.");
  136. *
  137. * @param sfile
  138. * The name of the source file making this log call (e.g. __FILE__).
  139. *
  140. * @param sline
  141. * The line number of the call in the source code (e.g. __LINE__).
  142. *
  143. * @param id
  144. * The id of the logger to write to.
  145. *
  146. * @param fmt
  147. * The format string for the message (printf formatting).
  148. *
  149. * @param ...
  150. * Any additional format arguments.
  151. */
  152. void clog_debug(const char *sfile, int sline, int id, const char *fmt, ...);
  153. void clog_info(const char *sfile, int sline, int id, const char *fmt, ...);
  154. void clog_warn(const char *sfile, int sline, int id, const char *fmt, ...);
  155. void clog_error(const char *sfile, int sline, int id, const char *fmt, ...);
  156. // add for simple usage
  157. #define dlog_debug(...) clog_debug(CLOG(STDOUT_FILENO), __VA_ARGS__)
  158. #define dlog_info(...) clog_info(CLOG(STDOUT_FILENO), __VA_ARGS__)
  159. #define dlog_warn(...) clog_warn(CLOG(STDOUT_FILENO), __VA_ARGS__)
  160. #define dlog_error(...) clog_error(CLOG(STDOUT_FILENO), __VA_ARGS__)
  161. void dlog_set_level(int id, int level);
  162. /**
  163. * Set the minimum level of messages that should be written to the log.
  164. * Messages below this level will not be written. By default, loggers are
  165. * created with level == CLOG_DEBUG.
  166. *
  167. * @param id
  168. * The identifier of the logger.
  169. *
  170. * @param level
  171. * The new minimum log level.
  172. *
  173. * @return
  174. * Zero on success, non-zero on failure.
  175. */
  176. int clog_set_level(int id, enum clog_level level);
  177. /**
  178. * Set the format string used for times. See strftime(3) for how this string
  179. * should be defined. The default format string is CLOG_DEFAULT_TIME_FORMAT.
  180. *
  181. * @param fmt
  182. * The new format string, which must be less than CLOG_FORMAT_LENGTH bytes.
  183. *
  184. * @return
  185. * Zero on success, non-zero on failure.
  186. */
  187. int clog_set_time_fmt(int id, const char *fmt);
  188. /**
  189. * Set the format string used for dates. See strftime(3) for how this string
  190. * should be defined. The default format string is CLOG_DEFAULT_DATE_FORMAT.
  191. *
  192. * @param fmt
  193. * The new format string, which must be less than CLOG_FORMAT_LENGTH bytes.
  194. *
  195. * @return
  196. * Zero on success, non-zero on failure.
  197. */
  198. int clog_set_date_fmt(int id, const char *fmt);
  199. /**
  200. * Set the format string for log messages. Here are the substitutions you may
  201. * use:
  202. *
  203. * %f: Source file name generating the log call.
  204. * %n: Source line number where the log call was made.
  205. * %m: The message text sent to the logger (after printf formatting).
  206. * %d: The current date, formatted using the logger's date format.
  207. * %t: The current time, formatted using the logger's time format.
  208. * %l: The log level (one of "DEBUG", "INFO", "WARN", or "ERROR").
  209. * %%: A literal percent sign.
  210. *
  211. * The default format string is CLOG_DEFAULT_FORMAT.
  212. *
  213. * @param fmt
  214. * The new format string, which must be less than CLOG_FORMAT_LENGTH bytes.
  215. * You probably will want to end this with a newline (\n).
  216. *
  217. * @return
  218. * Zero on success, non-zero on failure.
  219. */
  220. int clog_set_fmt(int id, const char *fmt);
  221. /*
  222. * No need to read below this point.
  223. */
  224. /**
  225. * The C logger structure.
  226. */
  227. struct clog {
  228. /* The current level of this logger. Messages below it will be dropped. */
  229. enum clog_level level;
  230. /* The file being written. */
  231. int fd;
  232. /* The format specifier. */
  233. char fmt[CLOG_FORMAT_LENGTH];
  234. /* Date format */
  235. char date_fmt[CLOG_FORMAT_LENGTH];
  236. /* Time format */
  237. char time_fmt[CLOG_FORMAT_LENGTH];
  238. /* Tracks whether the fd needs to be closed eventually. */
  239. int opened;
  240. };
  241. void _clog_err(const char *fmt, ...);
  242. #ifdef CLOG_MAIN
  243. struct clog *_clog_loggers[CLOG_MAX_LOGGERS] = { 0 };
  244. #else
  245. extern struct clog *_clog_loggers[CLOG_MAX_LOGGERS];
  246. #endif
  247. #ifdef CLOG_MAIN
  248. const char *const CLOG_LEVEL_NAMES[] = {
  249. "DEBUG",
  250. "INFO",
  251. "WARN",
  252. "ERROR",
  253. };
  254. int
  255. clog_init_path(int id, const char *const path)
  256. {
  257. int fd = open(path, O_CREAT | O_WRONLY | O_APPEND, 0666);
  258. if (fd == -1) {
  259. _clog_err("Unable to open %s: %s\n", path, strerror(errno));
  260. return 1;
  261. }
  262. if (clog_init_fd(id, fd)) {
  263. close(fd);
  264. return 1;
  265. }
  266. _clog_loggers[id]->opened = 1;
  267. return 0;
  268. }
  269. int
  270. clog_init_fd(int id, int fd)
  271. {
  272. struct clog *logger;
  273. if (_clog_loggers[id] != NULL) {
  274. _clog_err("Logger %d already initialized.\n", id);
  275. return 1;
  276. }
  277. logger = (struct clog *) malloc(sizeof(struct clog));
  278. if (logger == NULL) {
  279. _clog_err("Failed to allocate logger: %s\n", strerror(errno));
  280. return 1;
  281. }
  282. logger->level = CLOG_DEBUG;
  283. logger->fd = fd;
  284. logger->opened = 0;
  285. strcpy(logger->fmt, CLOG_DEFAULT_FORMAT);
  286. strcpy(logger->date_fmt, CLOG_DEFAULT_DATE_FORMAT);
  287. strcpy(logger->time_fmt, CLOG_DEFAULT_TIME_FORMAT);
  288. _clog_loggers[id] = logger;
  289. return 0;
  290. }
  291. void
  292. clog_free(int id)
  293. {
  294. if (_clog_loggers[id]) {
  295. if (_clog_loggers[id]->opened) {
  296. close(_clog_loggers[id]->fd);
  297. }
  298. free(_clog_loggers[id]);
  299. }
  300. }
  301. int
  302. clog_set_level(int id, enum clog_level level)
  303. {
  304. if (_clog_loggers[id] == NULL) {
  305. return 1;
  306. }
  307. if ((unsigned) level > CLOG_ERROR) {
  308. return 1;
  309. }
  310. _clog_loggers[id]->level = level;
  311. return 0;
  312. }
  313. int
  314. clog_set_time_fmt(int id, const char *fmt)
  315. {
  316. struct clog *logger = _clog_loggers[id];
  317. if (logger == NULL) {
  318. _clog_err("clog_set_time_fmt: No such logger: %d\n", id);
  319. return 1;
  320. }
  321. if (strlen(fmt) >= CLOG_FORMAT_LENGTH) {
  322. _clog_err("clog_set_time_fmt: Format specifier too long.\n");
  323. return 1;
  324. }
  325. strcpy(logger->time_fmt, fmt);
  326. return 0;
  327. }
  328. int
  329. clog_set_date_fmt(int id, const char *fmt)
  330. {
  331. struct clog *logger = _clog_loggers[id];
  332. if (logger == NULL) {
  333. _clog_err("clog_set_date_fmt: No such logger: %d\n", id);
  334. return 1;
  335. }
  336. if (strlen(fmt) >= CLOG_FORMAT_LENGTH) {
  337. _clog_err("clog_set_date_fmt: Format specifier too long.\n");
  338. return 1;
  339. }
  340. strcpy(logger->date_fmt, fmt);
  341. return 0;
  342. }
  343. int
  344. clog_set_fmt(int id, const char *fmt)
  345. {
  346. struct clog *logger = _clog_loggers[id];
  347. if (logger == NULL) {
  348. _clog_err("clog_set_fmt: No such logger: %d\n", id);
  349. return 1;
  350. }
  351. if (strlen(fmt) >= CLOG_FORMAT_LENGTH) {
  352. _clog_err("clog_set_fmt: Format specifier too long.\n");
  353. return 1;
  354. }
  355. strcpy(logger->fmt, fmt);
  356. return 0;
  357. }
  358. /* Internal functions */
  359. size_t
  360. _clog_append_str(char **dst, char *orig_buf, const char *src, size_t cur_size)
  361. {
  362. size_t new_size = cur_size;
  363. while (strlen(*dst) + strlen(src) >= new_size) {
  364. new_size *= 2;
  365. }
  366. if (new_size != cur_size) {
  367. if (*dst == orig_buf) {
  368. *dst = (char *) malloc(new_size);
  369. strcpy(*dst, orig_buf);
  370. } else {
  371. *dst = (char *) realloc(*dst, new_size);
  372. }
  373. }
  374. strcat(*dst, src);
  375. return new_size;
  376. }
  377. size_t
  378. _clog_append_int(char **dst, char *orig_buf, long int d, size_t cur_size)
  379. {
  380. char buf[40]; /* Enough for 128-bit decimal */
  381. if (snprintf(buf, 40, "%ld", d) >= 40) {
  382. return cur_size;
  383. }
  384. return _clog_append_str(dst, orig_buf, buf, cur_size);
  385. }
  386. size_t
  387. _clog_append_time(char **dst, char *orig_buf, struct tm *lt,
  388. const char *fmt, size_t cur_size)
  389. {
  390. char buf[CLOG_DATETIME_LENGTH];
  391. size_t result = strftime(buf, CLOG_DATETIME_LENGTH, fmt, lt);
  392. if (result > 0) {
  393. return _clog_append_str(dst, orig_buf, buf, cur_size);
  394. }
  395. return cur_size;
  396. }
  397. const char *
  398. _clog_basename(const char *path)
  399. {
  400. const char *slash = strrchr(path, '/');
  401. if (slash) {
  402. path = slash + 1;
  403. }
  404. #ifdef _WIN32
  405. slash = strrchr(path, '\\');
  406. if (slash) {
  407. path = slash + 1;
  408. }
  409. #endif
  410. return path;
  411. }
  412. char *
  413. _clog_format(const struct clog *logger, char buf[], size_t buf_size,
  414. const char *sfile, int sline, const char *level,
  415. const char *message)
  416. {
  417. size_t cur_size = buf_size;
  418. char *result = buf;
  419. enum { NORMAL, SUBST } state = NORMAL;
  420. size_t fmtlen = strlen(logger->fmt);
  421. size_t i;
  422. time_t t = time(NULL);
  423. struct tm *lt = localtime(&t);
  424. sfile = _clog_basename(sfile);
  425. result[0] = 0;
  426. for (i = 0; i < fmtlen; ++i) {
  427. if (state == NORMAL) {
  428. if (logger->fmt[i] == '%') {
  429. state = SUBST;
  430. } else {
  431. char str[2] = { 0 };
  432. str[0] = logger->fmt[i];
  433. cur_size = _clog_append_str(&result, buf, str, cur_size);
  434. }
  435. } else {
  436. switch (logger->fmt[i]) {
  437. case '%':
  438. cur_size = _clog_append_str(&result, buf, "%", cur_size);
  439. break;
  440. case 't':
  441. cur_size = _clog_append_time(&result, buf, lt,
  442. logger->time_fmt, cur_size);
  443. break;
  444. case 'd':
  445. cur_size = _clog_append_time(&result, buf, lt,
  446. logger->date_fmt, cur_size);
  447. break;
  448. case 'l':
  449. cur_size = _clog_append_str(&result, buf, level, cur_size);
  450. break;
  451. case 'n':
  452. cur_size = _clog_append_int(&result, buf, sline, cur_size);
  453. break;
  454. case 'f':
  455. cur_size = _clog_append_str(&result, buf, sfile, cur_size);
  456. break;
  457. case 'm':
  458. cur_size = _clog_append_str(&result, buf, message,
  459. cur_size);
  460. break;
  461. }
  462. state = NORMAL;
  463. }
  464. }
  465. return result;
  466. }
  467. void
  468. _clog_log(const char *sfile, int sline, enum clog_level level,
  469. int id, const char *fmt, va_list ap)
  470. {
  471. /* For speed: Use a stack buffer until message exceeds 4096, then switch
  472. * to dynamically allocated. This should greatly reduce the number of
  473. * memory allocations (and subsequent fragmentation). */
  474. char buf[4096];
  475. size_t buf_size = 4096;
  476. char *dynbuf = buf;
  477. char *message;
  478. int result;
  479. struct clog *logger = _clog_loggers[id];
  480. if (!logger) {
  481. _clog_err("No such logger: %d\n", id);
  482. return;
  483. }
  484. if (level < logger->level) {
  485. return;
  486. }
  487. /* Format the message text with the argument list. */
  488. result = vsnprintf(dynbuf, buf_size, fmt, ap);
  489. if ((size_t) result >= buf_size) {
  490. buf_size = result + 1;
  491. dynbuf = (char *) malloc(buf_size);
  492. result = vsnprintf(dynbuf, buf_size, fmt, ap);
  493. if ((size_t) result >= buf_size) {
  494. /* Formatting failed -- too large */
  495. _clog_err("Formatting failed (1).\n");
  496. free(dynbuf);
  497. return;
  498. }
  499. }
  500. /* Format according to log format and write to log */
  501. {
  502. char message_buf[4096];
  503. message = _clog_format(logger, message_buf, 4096, sfile, sline,
  504. CLOG_LEVEL_NAMES[level], dynbuf);
  505. if (!message) {
  506. _clog_err("Formatting failed (2).\n");
  507. if (dynbuf != buf) {
  508. free(dynbuf);
  509. }
  510. return;
  511. }
  512. result = write(logger->fd, message, strlen(message));
  513. if (result == -1) {
  514. _clog_err("Unable to write to log file: %s\n", strerror(errno));
  515. }
  516. if (message != message_buf) {
  517. free(message);
  518. }
  519. if (dynbuf != buf) {
  520. free(dynbuf);
  521. }
  522. }
  523. }
  524. void
  525. clog_debug(const char *sfile, int sline, int id, const char *fmt, ...)
  526. {
  527. va_list ap;
  528. va_start(ap, fmt);
  529. _clog_log(sfile, sline, CLOG_DEBUG, id, fmt, ap);
  530. }
  531. void
  532. clog_info(const char *sfile, int sline, int id, const char *fmt, ...)
  533. {
  534. va_list ap;
  535. va_start(ap, fmt);
  536. _clog_log(sfile, sline, CLOG_INFO, id, fmt, ap);
  537. }
  538. void
  539. clog_warn(const char *sfile, int sline, int id, const char *fmt, ...)
  540. {
  541. va_list ap;
  542. va_start(ap, fmt);
  543. _clog_log(sfile, sline, CLOG_WARN, id, fmt, ap);
  544. }
  545. void
  546. clog_error(const char *sfile, int sline, int id, const char *fmt, ...)
  547. {
  548. va_list ap;
  549. va_start(ap, fmt);
  550. _clog_log(sfile, sline, CLOG_ERROR, id, fmt, ap);
  551. }
  552. void
  553. _clog_err(const char *fmt, ...)
  554. {
  555. #ifdef CLOG_SILENT
  556. (void) fmt;
  557. #else
  558. va_list ap;
  559. va_start(ap, fmt);
  560. vfprintf(stderr, fmt, ap);
  561. #endif
  562. }
  563. #endif /* CLOG_MAIN */
  564. #ifdef __cplusplus
  565. } /* extern "C" */
  566. #endif
  567. #endif /* __CLOG_H__ */