1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
|
/** Copyright © Charliecloud contributors.
Logging, some of which also exits with error if a value is not as expected.
This interface provides eight (8) assert-like macros with cryptic
three-letter names (i.e., optimized for brevity). These verify the
truth/falseness of their argument, exiting an error message including
source file and line number if not. They are macros because we want the
file and line number of the calling location and C has no stack
introspection.
The three letters each encode a binary “argument”:
1. Assert that the argument is true (`T`) or false (`Z` for zero).
We use `Z` instead of `F` (for false) because this is usually used to
error-check standard library functions or syscalls that return zero to
indicate success, e.g. symlink(2), and I thought `F` might confusingly
suggest failure or false == bad.
2. Use a specified `printf(3)`-style format string (`f`) or the default
error message (`_`). Note that the default is very unhelpful for users,
so use it only for “pure” asserts that you believe are unlikely to be
violated or reflect a bug that users can’t do anything about anyway.
3. Include `errno` in the error message (`e`) or not (`_`). Note that if
`errno` is zero, the message will include confusingly include
“success”; be careful about whether `errno` is meaningful.
For example:
char *d = "/does/not/exist";
T__ (d != NULL && d[0] != '/');
-> ch-run[27720]: error: assertion failure: please report this bug (ch-run.c:128)
This is a good example of a “pure” assertion.
Z__ (d == NULL || d[0] == '/');
-> ch-run[27720]: error: assertion failure: please report this bug (ch-run.c:128)
Typically inadvisable (i.e., don’t use Z?? on expressions) because the
implicit negation of the expression can be confusing.
Z_e (chdir(d));
-> ch-run[27720]: error: assertion failure: please report this bug: No such file or directory (ch-run.c:128 2 ENOENT)
Typically inadviseable because `chdir(2)` failing is reasonably expected
and likely due to external causes, so we should explain to the user what
was being attempted.
Zf_ (chdir(c), "can't cd: %s", d);
-> ch-run[27720]: error: can't cd: /does/not/exist (ch-run.c:128)
Also typically inadviseable because the *reason* for the `chdir(2)`
failure (as encoded in `errno`) is likely to be important for debugging,
so we should pass it on to the user.
Zfe (chdir(d), "can't cd: %s", d);
-> ch-run[27720]: error: can't cd: /does/not/exist: No such file or directory (ch-run.c:128 2 ENOENT)
Best practice for typical error conditions. This explains both what we
were trying to do and what went wrong.
errno = 0;
Z_e (false);
-> ch-run[27720]: error: assertion failure: please report this bug: Success (ch-run.c:128 0 0)
Example of a maximally un-helpful error message.
Note the style of including a space between the macro name and the opening
parenthesis, to distinguish it from standard function call. */
#define _GNU_SOURCE
#pragma once
#include <errno.h>
#include <stddef.h>
#include <syslog.h>
#include "misc.h"
/** Macros **/
/** Verify that @p x is true (non-zero); otherwise, exit with a generic error
message. */
#define T__(x) do { \
if (!(x)) \
msg_fatal(__FILE__, __LINE__, -1, NULL); \
} while (0)
/** Verify that @p x is true (non-zero); otherwise, exit with an error message
specified by a `printf(3)` format string in the second argument along with
appropriate additional arguments. */
#define Tf_(x, ...) do { \
if (!(x)) \
msg_fatal(__FILE__, __LINE__, -1, __VA_ARGS__); \
} while (0)
/** Verify that @p x is true (non-zero); otherwise, exit with a generic error
message followed by errno and its stringified form. */
#define T_e(x) do { \
if (!(x)) \
msg_fatal(__FILE__, __LINE__, errno, NULL); \
} while (0);
/** Verify that @p x is true (non-zero); otherwise, exit with an error message
specified by a `printf(3)` format string in the second argument along with
appropriate additional arguments, followed by errno and its stringified
form. */
#define Tfe(x, ...) do { \
if (!(x)) \
msg_fatal(__FILE__, __LINE__, errno, __VA_ARGS__); \
} while (0)
/** Verify that @p x is zero (false); otherwise, exit with a generic error
message. */
#define Z__(x) do { \
if (x) \
msg_fatal(__FILE__, __LINE__, -1, NULL); \
} while (0)
/** Verify that @p x is zero (false); otherwise, exit with an error message
specified by a `printf(3)` format string in the second argument along with
appropriate additional arguments. */
#define Zf_(x, ...) do { \
if (x) \
msg_fatal(__FILE__, __LINE__, -1, __VA_ARGS__); \
} while (0)
/** Verify that @p x is zero (false); otherwise, exit with a generic error
message followed by errno and its stringified form. */
#define Z_e(x) do { \
if (x) \
msg_fatal(__FILE__, __LINE__, errno, NULL); \
} while (0)
/** Verify that @p x is zero (false); otherwise, exit with an error message
specified by a `printf(3)` format string in the second argument along with
appropriate additional arguments, followed by errno and its stringified
form. */
#define Zfe(x, ...) do { \
if (x) \
msg_fatal(__FILE__, __LINE__, errno, __VA_ARGS__); \
} while (0)
/* Log the current UIDs. */
#define LOG_IDS log_ids(__func__, __LINE__)
/* Use these instead of printf(3), sprintf(3), etc. to explain to the user
what is going on. Do not use these for output. */
#define FATAL(e, ...) msg_fatal( __FILE__, __LINE__, e, __VA_ARGS__)
#define ERROR(e, ...) msg_error( __FILE__, __LINE__, e, __VA_ARGS__)
#define WARNING(...) msg(LL_WARNING, __FILE__, __LINE__, -1, __VA_ARGS__)
#define INFO(...) msg(LL_INFO, __FILE__, __LINE__, -1, __VA_ARGS__)
#define VERBOSE(...) msg(LL_VERBOSE, __FILE__, __LINE__, -1, __VA_ARGS__)
#define DEBUG(...) msg(LL_DEBUG, __FILE__, __LINE__, -1, __VA_ARGS__)
#define TRACE(...) msg(LL_TRACE, __FILE__, __LINE__, -1, __VA_ARGS__)
/* Syslog facility and level we use. */
#ifdef ENABLE_SYSLOG
#define SYSLOG_PRI (LOG_USER|LOG_INFO)
#endif
/* Size of “warnings” buffer, in bytes. We want this to be big enough that we
don’t need to worry about running out of room. */
#define WARNINGS_SIZE (4*1024)
/** Types **/
enum log_level { LL_FATAL = -3,
LL_STDERR = -2,
LL_WARNING = -1,
LL_INFO = 0, // minimum number of -v to print the msg
LL_VERBOSE = 1,
LL_DEBUG = 2,
LL_TRACE = 3 };
enum log_color_when { LL_COLOR_NULL = 0,
LL_COLOR_AUTO,
LL_COLOR_YES,
LL_COLOR_NO };
enum log_test { LL_TEST_NONE = 0,
LL_TEST_YES = 1,
LL_TEST_FATAL = 2 };
/** External variables **/
extern bool abort_fatal;
extern bool log_color_p;
extern enum log_level verbose;
extern char *warnings;
extern size_t warnings_offset;
/** Function prototypes **/
void log_ids(const char *func, int line);
void logging_init(enum log_color_when when, enum log_test test);
void msg(enum log_level level, const char *file, int line, int errno_,
const char *fmt, ...)
__attribute__ ((format (printf, 5, 6)));
void msg_error(const char *file, int line, int errno_,
const char *fmt, ...)
__attribute__ ((format (printf, 4, 5)));
noreturn msg_fatal(const char *file, int line, int errno_,
const char *fmt, ...)
__attribute__ ((format (printf, 4, 5)));
void warnings_reprint(void);
|