diff options
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | include/serd/serd.h | 132 | ||||
-rw-r--r-- | src/node_syntax.c | 12 | ||||
-rw-r--r-- | src/reader.c | 4 | ||||
-rw-r--r-- | src/reader.h | 8 | ||||
-rw-r--r-- | src/serd_config.h | 13 | ||||
-rw-r--r-- | src/serdi.c | 10 | ||||
-rw-r--r-- | src/world.c | 210 | ||||
-rw-r--r-- | src/world.h | 32 | ||||
-rw-r--r-- | src/writer.c | 40 |
10 files changed, 367 insertions, 95 deletions
@@ -2,6 +2,7 @@ serd (1.0.1) unstable; * Add SerdBuffer for mutable buffers to keep SerdChunk const-correct * Add SerdWorld for shared library state + * Add extensible logging API * Add option for writing terse output without newlines * Add support for parsing variables * Add support for writing terse collections diff --git a/include/serd/serd.h b/include/serd/serd.h index 4cf00f02..04d8708b 100644 --- a/include/serd/serd.h +++ b/include/serd/serd.h @@ -67,6 +67,12 @@ SERD_API \ SERD_MALLOC_FUNC +#if defined(__GNUC__) +# define SERD_LOG_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1))) +#else +# define SERD_LOG_FUNC(fmt, arg1) +#endif + #ifdef __cplusplus extern "C" { # if defined(__GNUC__) @@ -268,14 +274,6 @@ typedef struct { size_t len; ///< Size of buffer in bytes } SerdBuffer; -/// An error description -typedef struct { - SerdStatus status; ///< Error code - const SerdCursor* SERD_NULLABLE cursor; ///< Origin of error - const char* SERD_NONNULL fmt; ///< Printf-style format string - va_list* SERD_NONNULL args; ///< Arguments for fmt -} SerdError; - /** A parsed URI. @@ -1050,15 +1048,6 @@ serd_node_compare(const SerdNode* SERD_NULLABLE a, @{ */ -/** - Callback function for errors. - - @param handle Handle for user data. - @param error Error description. -*/ -typedef SerdStatus (*SerdErrorFunc)(void* SERD_NULLABLE handle, - const SerdError* SERD_NONNULL error); - /// Type of a SerdEvent typedef enum { SERD_BASE = 1, ///< Base URI changed @@ -1175,16 +1164,113 @@ const SerdNode* SERD_NONNULL serd_world_get_blank(SerdWorld* SERD_NONNULL world); /** - Set a function to be called when errors occur. + @} + @defgroup serd_logging Logging + @{ +*/ + +/// Log message level, compatible with syslog +typedef enum { + SERD_LOG_LEVEL_EMERG, ///< Emergency, system is unusable + SERD_LOG_LEVEL_ALERT, ///< Action must be taken immediately + SERD_LOG_LEVEL_CRIT, ///< Critical condition + SERD_LOG_LEVEL_ERR, ///< Error + SERD_LOG_LEVEL_WARNING, ///< Warning + SERD_LOG_LEVEL_NOTICE, ///< Normal but significant condition + SERD_LOG_LEVEL_INFO, ///< Informational message + SERD_LOG_LEVEL_DEBUG ///< Debug message +} SerdLogLevel; + +/** + A structured log field. + + This can be used to pass additional information along with log messages. + Syslog-compatible keys should be used where possible, otherwise, keys should + be namespaced to prevent clashes. + + Serd itself uses the following keys: + - ERRNO + - SERD_COL + - SERD_FILE + - SERD_LINE + - SERD_STATUS +*/ +typedef struct { + const char* SERD_NONNULL key; ///< Field name + const char* SERD_NONNULL value; ///< Field value +} SerdLogField; + +/** + A log entry (message). + + This is the description of a log entry which is passed to log functions. + It is only valid in the stack frame it appears in, and may not be copied. +*/ +typedef struct { + const char* SERD_NONNULL domain; ///< Library/program/module name + const SerdLogField* SERD_NULLABLE fields; ///< Extra log fields + const char* SERD_NONNULL fmt; ///< Printf-style format string + va_list* SERD_NONNULL args; ///< Arguments for `fmt` + SerdLogLevel level; ///< Log level + size_t n_fields; ///< Number of `fields` +} SerdLogEntry; + +/** + Sink function for log messages. + + @param handle Handle for user data. + @param entry Pointer to log entry description. +*/ +typedef SerdStatus (*SerdLogFunc)(void* SERD_NULLABLE handle, + const SerdLogEntry* SERD_NONNULL entry); + +/// A SerdLogFunc that does nothing, for suppressing log output +SERD_API +SerdStatus +serd_quiet_error_func(void* SERD_NULLABLE handle, + const SerdLogEntry* SERD_NONNULL entry); + +/// Return the value of the log field named `key`, or NULL if none exists +SERD_PURE_API +const char* SERD_NULLABLE +serd_log_entry_get_field(const SerdLogEntry* SERD_NONNULL entry, + const char* SERD_NONNULL key); + +/** + Set a function to be called with log messages (typically errors). - The `error_func` will be called with `handle` as its first argument. If - no error function is set, errors are printed to stderr. + The `log_func` will be called with `handle` as its first argument. If + no function is set, messages are printed to stderr. */ SERD_API void -serd_world_set_error_func(SerdWorld* SERD_NONNULL world, - SerdErrorFunc SERD_NULLABLE error_func, - void* SERD_NULLABLE handle); +serd_world_set_log_func(SerdWorld* SERD_NONNULL world, + SerdLogFunc SERD_NULLABLE log_func, + void* SERD_NULLABLE handle); + +/// Write a message to the log +SERD_API +SERD_LOG_FUNC(6, 0) +SerdStatus +serd_world_vlogf(const SerdWorld* SERD_NONNULL world, + const char* SERD_NONNULL domain, + SerdLogLevel level, + size_t n_fields, + const SerdLogField* SERD_NULLABLE fields, + const char* SERD_NONNULL fmt, + va_list args); + +/// Write a message to the log +SERD_API +SERD_LOG_FUNC(6, 7) +SerdStatus +serd_world_logf(const SerdWorld* SERD_NONNULL world, + const char* SERD_NONNULL domain, + SerdLogLevel level, + size_t n_fields, + const SerdLogField* SERD_NULLABLE fields, + const char* SERD_NONNULL fmt, + ...); /** @} diff --git a/src/node_syntax.c b/src/node_syntax.c index fc442f33..f14300e6 100644 --- a/src/node_syntax.c +++ b/src/node_syntax.c @@ -23,14 +23,6 @@ #include <string.h> static SerdStatus -quiet_error_func(void* const handle, const SerdError* const e) -{ - (void)handle; - (void)e; - return SERD_SUCCESS; -} - -static SerdStatus on_node_string_event(void* const handle, const SerdEvent* const event) { if (event->type == SERD_STATEMENT) { @@ -67,7 +59,7 @@ serd_node_from_syntax(const char* const str, const SerdSyntax syntax) sink, 1024 + doc_len); - serd_world_set_error_func(world, quiet_error_func, NULL); + serd_world_set_log_func(world, serd_quiet_error_func, NULL); serd_reader_start(reader, source); serd_reader_read_document(reader); serd_reader_finish(reader); @@ -90,7 +82,7 @@ serd_node_to_syntax(const SerdNode* const node, const SerdSyntax syntax) SerdByteSink* const out = serd_byte_sink_new_buffer(&buffer); SerdWriter* const writer = serd_writer_new(world, syntax, 0, env, out); - serd_world_set_error_func(world, quiet_error_func, NULL); + serd_world_set_log_func(world, serd_quiet_error_func, NULL); SerdStatus st = SERD_SUCCESS; char* result = NULL; diff --git a/src/reader.c b/src/reader.c index ba3559bc..b7e18d2a 100644 --- a/src/reader.c +++ b/src/reader.c @@ -35,8 +35,8 @@ r_err(SerdReader* reader, SerdStatus st, const char* fmt, ...) { va_list args; va_start(args, fmt); - const SerdError e = {st, &reader->source->cur, fmt, &args}; - serd_world_error(reader->world, &e); + serd_world_vlogf_internal( + reader->world, st, SERD_LOG_LEVEL_ERR, &reader->source->cur, fmt, args); va_end(args); return st; } diff --git a/src/reader.h b/src/reader.h index 8fba7504..26a19633 100644 --- a/src/reader.h +++ b/src/reader.h @@ -28,12 +28,6 @@ #include <stdint.h> #include <stdio.h> -#if defined(__GNUC__) -# define SERD_LOG_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1))) -#else -# define SERD_LOG_FUNC(fmt, arg1) -#endif - #ifdef SERD_STACK_CHECK # define SERD_STACK_ASSERT_TOP(reader, ref) \ assert(ref == reader->allocs[reader->n_allocs - 1]); @@ -52,7 +46,7 @@ typedef struct { struct SerdReaderImpl { SerdWorld* world; const SerdSink* sink; - SerdErrorFunc error_func; + SerdLogFunc log_func; void* error_handle; SerdNode* rdf_first; SerdNode* rdf_rest; diff --git a/src/serd_config.h b/src/serd_config.h index 7f58696e..12930b1b 100644 --- a/src/serd_config.h +++ b/src/serd_config.h @@ -51,6 +51,13 @@ # endif # endif +// POSIX.1-2001: isatty() +# ifndef HAVE_ISATTY +# if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L +# define HAVE_ISATTY +# endif +# endif + // POSIX.1-2001: posix_fadvise() # ifndef HAVE_POSIX_FADVISE # ifndef __APPLE__ @@ -83,6 +90,12 @@ # define USE_FILENO 0 #endif +#ifdef HAVE_ISATTY +# define USE_ISATTY 1 +#else +# define USE_ISATTY 0 +#endif + #ifdef HAVE_POSIX_FADVISE # define USE_POSIX_FADVISE 1 #else diff --git a/src/serdi.c b/src/serdi.c index 78de1ad3..2eea3919 100644 --- a/src/serdi.c +++ b/src/serdi.c @@ -84,14 +84,6 @@ missing_arg(const char* name, char opt) } static SerdStatus -quiet_error_func(void* const handle, const SerdError* const e) -{ - (void)handle; - (void)e; - return SERD_SUCCESS; -} - -static SerdStatus read_file(SerdWorld* const world, SerdSyntax syntax, const SerdReaderFlags flags, @@ -310,7 +302,7 @@ main(int argc, char** argv) serd_writer_new(world, output_syntax, writer_flags, env, byte_sink); if (quiet) { - serd_world_set_error_func(world, quiet_error_func, NULL); + serd_world_set_log_func(world, serd_quiet_error_func, NULL); } SerdNode* root = serd_new_uri(SERD_MEASURE_STRING(root_uri)); diff --git a/src/world.c b/src/world.c index d166b1d3..a6e32dc6 100644 --- a/src/world.c +++ b/src/world.c @@ -21,6 +21,11 @@ #include "cursor.h" #include "namespaces.h" #include "node.h" +#include "serd_config.h" + +#if USE_FILENO && USE_ISATTY +# include <unistd.h> +#endif #include <errno.h> #include <stdarg.h> @@ -30,32 +35,199 @@ #define BLANK_CHARS 12 +static inline int +level_color(const SerdLogLevel level) +{ + switch (level) { + case SERD_LOG_LEVEL_EMERG: + case SERD_LOG_LEVEL_ALERT: + case SERD_LOG_LEVEL_CRIT: + case SERD_LOG_LEVEL_ERR: + return 31; // Red + case SERD_LOG_LEVEL_WARNING: + return 33; // Yellow + case SERD_LOG_LEVEL_NOTICE: + case SERD_LOG_LEVEL_INFO: + case SERD_LOG_LEVEL_DEBUG: + break; + } + + return 1; // White +} + +static inline bool +terminal_supports_color(void) +{ + const char* const term = getenv("TERM"); + return !getenv("NO_COLOR") && term && strstr(term, "color"); +} + +static inline bool +serd_ansi_start(FILE* stream, int color, bool bold) +{ +#if USE_FILENO && USE_ISATTY + if (isatty(fileno(stream)) && terminal_supports_color()) { + return fprintf(stream, bold ? "\033[0;%d;1m" : "\033[0;%dm", color); + } +#endif + + return 0; +} + +static inline void +serd_ansi_reset(FILE* stream) +{ +#if USE_FILENO && USE_ISATTY + if (isatty(fileno(stream)) && terminal_supports_color()) { + fprintf(stream, "\033[0m"); + fflush(stream); + } +#endif +} + +static const char* const log_level_strings[] = {"emergengy", + "alert", + "critical", + "error", + "warning", + "note", + "info", + "debug"}; + +SERD_CONST_FUNC +SerdStatus +serd_quiet_error_func(void* handle, const SerdLogEntry* entry) +{ + (void)handle; + (void)entry; + return SERD_SUCCESS; +} + +static const char* +get_log_field(const SerdLogField* const fields, + const size_t n_fields, + const char* const key) +{ + for (size_t i = 0; i < n_fields; ++i) { + if (!strcmp(fields[i].key, key)) { + return fields[i].value; + } + } + + return NULL; +} + +const char* +serd_log_entry_get_field(const SerdLogEntry* const entry, const char* const key) +{ + return get_log_field(entry->fields, entry->n_fields, key); +} + SerdStatus -serd_world_error(const SerdWorld* world, const SerdError* e) +serd_world_vlogf(const SerdWorld* world, + const char* domain, + SerdLogLevel level, + const size_t n_fields, + const SerdLogField* fields, + const char* fmt, + va_list args) { - if (world->error_func) { - world->error_func(world->error_handle, e); + // Copy args (which may be an array) to portably get a pointer + va_list ap; + va_copy(ap, args); + + const SerdLogEntry e = {domain, fields, fmt, &ap, level, n_fields}; + SerdStatus st = SERD_SUCCESS; + + if (world->log_func) { + st = world->log_func(world->log_handle, &e); } else { - fprintf(stderr, "error: "); - if (e->cursor) { - fprintf(stderr, - "%s:%u:%u: ", - serd_node_string(e->cursor->file), - e->cursor->line, - e->cursor->col); + // Print input file and position prefix if available + const char* const file = serd_log_entry_get_field(&e, "SERD_FILE"); + const char* const line = serd_log_entry_get_field(&e, "SERD_LINE"); + const char* const col = serd_log_entry_get_field(&e, "SERD_COL"); + if (file && line && col) { + serd_ansi_start(stderr, 1, true); + fprintf(stderr, "%s:%s:%s: ", file, line, col); + serd_ansi_reset(stderr); } - vfprintf(stderr, e->fmt, *e->args); + + // Print GCC-style level prefix (error, warning, etc) + serd_ansi_start(stderr, level_color(level), true); + fprintf(stderr, "%s: ", log_level_strings[level]); + serd_ansi_reset(stderr); + + // Using a copy isn't necessary here, but it avoids a clang-tidy bug + vfprintf(stderr, fmt, ap); + } + + va_end(ap); + return st; +} + +SerdStatus +serd_world_logf(const SerdWorld* world, + const char* domain, + SerdLogLevel level, + const size_t n_fields, + const SerdLogField* fields, + const char* fmt, + ...) +{ + va_list args; + va_start(args, fmt); + + const SerdStatus st = + serd_world_vlogf(world, domain, level, n_fields, fields, fmt, args); + + va_end(args); + return st; +} + +SerdStatus +serd_world_vlogf_internal(const SerdWorld* world, + SerdStatus st, + SerdLogLevel level, + const SerdCursor* cursor, + const char* fmt, + va_list args) +{ + char st_str[12]; + snprintf(st_str, sizeof(st_str), "%u", st); + if (cursor) { + const char* file = serd_node_string(serd_cursor_name(cursor)); + + char line[24]; + snprintf(line, sizeof(line), "%u", serd_cursor_line(cursor)); + + char col[24]; + snprintf(col, sizeof(col), "%u", serd_cursor_column(cursor)); + + const SerdLogField fields[] = {{"SERD_STATUS", st_str}, + {"SERD_FILE", file}, + {"SERD_LINE", line}, + {"SERD_COL", col}}; + + serd_world_vlogf(world, "serd", level, 4, fields, fmt, args); + } else { + const SerdLogField fields[] = {{"SERD_STATUS", st_str}}; + serd_world_vlogf(world, "serd", level, 1, fields, fmt, args); } - return e->status; + + return st; } SerdStatus -serd_world_errorf(const SerdWorld* world, SerdStatus st, const char* fmt, ...) +serd_world_logf_internal(const SerdWorld* world, + SerdStatus st, + SerdLogLevel level, + const SerdCursor* cursor, + const char* fmt, + ...) { va_list args; va_start(args, fmt); - const SerdError e = {st, NULL, fmt, &args}; - serd_world_error(world, &e); + serd_world_vlogf_internal(world, st, level, cursor, fmt, args); va_end(args); return st; } @@ -118,10 +290,8 @@ serd_world_get_blank(SerdWorld* world) } void -serd_world_set_error_func(SerdWorld* world, - SerdErrorFunc error_func, - void* handle) +serd_world_set_log_func(SerdWorld* world, SerdLogFunc log_func, void* handle) { - world->error_func = error_func; - world->error_handle = handle; + world->log_func = log_func; + world->log_handle = handle; } diff --git a/src/world.h b/src/world.h index f76b9483..7a4a129d 100644 --- a/src/world.h +++ b/src/world.h @@ -19,13 +19,14 @@ #include "serd/serd.h" +#include <stdarg.h> #include <stdint.h> #include <stdio.h> struct SerdWorldImpl { SerdNodes* nodes; - SerdErrorFunc error_func; - void* error_handle; + SerdLogFunc log_func; + void* log_handle; SerdNode* blank_node; const SerdNode* rdf_first; const SerdNode* rdf_nil; @@ -38,10 +39,33 @@ struct SerdWorldImpl { uint32_t next_blank_id; }; +/// Write a message to the log +SERD_API +SERD_LOG_FUNC(5, 0) SerdStatus -serd_world_error(const SerdWorld* world, const SerdError* e); +serd_world_vlogf_internal(const SerdWorld* world, + SerdStatus st, + SerdLogLevel level, + const SerdCursor* cursor, + const char* fmt, + va_list args); +/// Write a message to the log +SERD_API +SERD_LOG_FUNC(5, 6) SerdStatus -serd_world_errorf(const SerdWorld* world, SerdStatus st, const char* fmt, ...); +serd_world_logf_internal(const SerdWorld* world, + SerdStatus st, + SerdLogLevel level, + const SerdCursor* cursor, + const char* fmt, + ...); + +#define SERD_LOG_ERRORF(world, st, fmt, ...) \ + serd_world_logf_internal( \ + world, st, SERD_LOG_LEVEL_ERR, NULL, fmt, __VA_ARGS__) + +#define SERD_LOG_ERROR(world, st, msg) \ + serd_world_logf_internal(world, st, SERD_LOG_LEVEL_ERR, NULL, msg) #endif // SERD_WORLD_H diff --git a/src/writer.c b/src/writer.c index f55b47d4..d15320ae 100644 --- a/src/writer.c +++ b/src/writer.c @@ -135,8 +135,8 @@ struct SerdWriterImpl { WriteContext* anon_stack; size_t anon_stack_size; SerdByteSink* byte_sink; - SerdErrorFunc error_func; - void* error_handle; + SerdLogFunc log_func; + void* log_handle; WriteContext context; char* bprefix; size_t bprefix_len; @@ -237,12 +237,12 @@ sink(const void* buf, size_t len, SerdWriter* writer) const size_t written = serd_byte_sink_write(buf, len, writer->byte_sink); if (written != len) { if (errno) { - serd_world_errorf(writer->world, - SERD_ERR_BAD_WRITE, - "write error (%s)\n", - strerror(errno)); + SERD_LOG_ERRORF(writer->world, + SERD_ERR_BAD_WRITE, + "write error (%s)\n", + strerror(errno)); } else { - serd_world_errorf(writer->world, SERD_ERR_BAD_WRITE, "write error\n"); + SERD_LOG_ERROR(writer->world, SERD_ERR_BAD_WRITE, "write error\n"); } } @@ -267,7 +267,7 @@ write_character(SerdWriter* writer, const uint32_t c = parse_utf8_char(utf8, size); switch (*size) { case 0: - serd_world_errorf( + SERD_LOG_ERRORF( writer->world, SERD_ERR_BAD_ARG, "invalid UTF-8 start: %X\n", utf8[0]); *st = SERD_ERR_BAD_TEXT; return 0; @@ -767,10 +767,10 @@ write_uri_node(SerdWriter* const writer, if (!has_scheme && !supports_uriref(writer) && !serd_env_base_uri(writer->env)) { - serd_world_errorf(writer->world, - SERD_ERR_BAD_ARG, - "syntax does not support URI reference <%s>\n", - node_str); + SERD_LOG_ERRORF(writer->world, + SERD_ERR_BAD_ARG, + "syntax does not support URI reference <%s>\n", + node_str); return SERD_ERR_BAD_ARG; } @@ -817,10 +817,10 @@ write_curie(SerdWriter* const writer, case SERD_NTRIPLES: case SERD_NQUADS: if ((st = serd_env_expand_in_place(writer->env, node, &prefix, &suffix))) { - serd_world_errorf(writer->world, - st, - "undefined namespace prefix `%s'\n", - serd_node_string(node)); + SERD_LOG_ERRORF(writer->world, + st, + "undefined namespace prefix in `%s'\n", + serd_node_string(node)); return st; } TRY(st, esink("<", 1, writer)); @@ -1113,10 +1113,10 @@ serd_writer_end_anon(SerdWriter* writer, const SerdNode* node) } if (writer->anon_stack_size == 0) { - return serd_world_errorf(writer->world, - SERD_ERR_UNKNOWN, - "unexpected end of anonymous node `%s'\n", - serd_node_string(node)); + return SERD_LOG_ERRORF(writer->world, + SERD_ERR_UNKNOWN, + "unexpected end of anonymous node `%s'\n", + serd_node_string(node)); } SerdStatus st = write_sep(writer, writer->context.flags, SEP_ANON_END); |