diff --git a/Makefile b/Makefile index adc360a..a673b9c 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ out/obj/%.o: src/%.c | $(OBJDIR) OBJ += out/obj/main.o OBJ += out/obj/cli.o +OBJ += out/obj/log.o OBJ += out/obj/notify.o out/bin/sci: $(OBJ) | $(BINDIR) $(CC) -o $@ $(CFLAGS) $^ diff --git a/README.md b/README.md index b19b647..b8f00fd 100644 --- a/README.md +++ b/README.md @@ -79,9 +79,10 @@ If you want `compile_commands.json` files, you should use [bear](https://github. ### Progress - [x] Zeroth things first, let's create a simple CLI application with `--verbosity VAL` and `--help` options. - [x] First things first, let's implement something that reacts when some provided file changes (not poll please). - - [ ] Second things second, implement a simple logging system with differing levels of verbosity and configurable + - [x] Second things second, implement a simple logging system with differing levels of verbosity and configurable output file using cli options. - [ ] Third things third, implement a thing that simultaneously watches two different files (multithreading). + it should be cancellable with ctrl+c, but it should just contiuously print event notifications. - [ ] Fourth things fourth, implement a prototype that reads a space-separated file and populates a struct. ### Note Regarding `inotify` usage diff --git a/include/cli.h b/include/cli.h index 5ed0f31..730a24a 100644 --- a/include/cli.h +++ b/include/cli.h @@ -3,23 +3,25 @@ #include #include "optional.h" -struct cli_options { +typedef struct { optional_str file; int verbosity; bool help; bool version; -}; + bool use_colors; + optional_str log_file; +} cli_options; // Construct a new cli_options struct instance. -struct cli_options new_options(); +cli_options new_options(); // Delete a cli_options struct instance. -void free_options(struct cli_options v); +void free_options(cli_options v); // Print the help message. void print_help(FILE * out, char* prog_name); // Parse the command line arguments and give a new cli_options struct instance. -struct cli_options parse(int argc, char** argv); +cli_options parse(int argc, char** argv); #endif diff --git a/include/log.h b/include/log.h new file mode 100644 index 0000000..f46554c --- /dev/null +++ b/include/log.h @@ -0,0 +1,28 @@ +#ifndef SCI_LOG_H +#define SCI_LOG_H +#include +#include + +// TODO: Thread safety! +enum { + LOG_TRACE = 4, + LOG_INFO = 3, + LOG_WARN = 2, + LOG_ERROR = 1, + LOG_NOTHING = 0 +}; +typedef struct { + int level; + bool use_colors; + FILE* out_file; +} log_settings; + +void log_log(const char* file, int line, int level, const char* fmt, ...); +void log_init(log_settings settings); + +#define log_trace(...) log_log(__FILE__, __LINE__, LOG_TRACE, __VA_ARGS__) +#define log_info(...) log_log( __FILE__, __LINE__, LOG_INFO, __VA_ARGS__) +#define log_warn(...) log_log( __FILE__, __LINE__, LOG_WARN, __VA_ARGS__) +#define log_error(...) log_log(__FILE__, __LINE__, LOG_ERROR, __VA_ARGS__) + +#endif diff --git a/src/cli.c b/src/cli.c index 23e018f..ae4b96f 100644 --- a/src/cli.c +++ b/src/cli.c @@ -4,24 +4,37 @@ #include #include "cli.h" -struct cli_options new_options() { - struct cli_options result; +cli_options new_options() { + cli_options result; result.file.has_value = false; + result.file.value = NULL; + result.verbosity = 1; + result.help = false; + result.version = false; + + char *no_color = getenv("NO_COLOR"); + bool color = true; + if(no_color != NULL && no_color[0] != '\0') + color = false; + result.use_colors = color; + + result.log_file.has_value = false; + result.log_file.value = NULL; return result; } -void free_options(struct cli_options v) { +void free_options(cli_options v) { if(v.file.has_value) free(v.file.value); } // +#include +#include + +log_settings g_log_settings; +bool g_log_initialized = false; + +void log_init(log_settings settings) { + g_log_settings.level = settings.level; + g_log_settings.use_colors = settings.use_colors; + g_log_settings.out_file = settings.out_file; + g_log_initialized = true; +} + +#define COLOR_TRACE "\x1b[94m" +#define COLOR_INFO "\x1b[32m" +#define COLOR_WARN "\x1b[33m" +#define COLOR_ERROR "\x1b[31m" +#define COLOR_RESET "\x1b[0m" +#define COLOR_FILE "\x1b[90m" + +const char* get_level_color(int level) { + switch(level) { + case LOG_TRACE: + return COLOR_TRACE; + case LOG_INFO: + return COLOR_INFO; + case LOG_WARN: + return COLOR_WARN; + case LOG_ERROR: + return COLOR_ERROR; + default: + return COLOR_INFO; + } +} + +const char* get_level_name(int level) { + switch(level) { + case LOG_TRACE: + return "TRACE"; + case LOG_INFO: + return "INFO"; + case LOG_WARN: + return "WARN"; + case LOG_ERROR: + return "ERROR"; + default: + return "LOG"; + } +} + +struct tm* g_log_tm = NULL; +void log_log(const char* file, int line, int level, const char* fmt, ...) { + if(level > g_log_settings.level) + return; + char timestamp[16]; + if(g_log_tm == NULL) { + time_t t = time(NULL); + g_log_tm = localtime(&t); + } + strftime(timestamp, sizeof(timestamp), "%H:%M:%S", g_log_tm); + + const char* level_color = get_level_color(level); + const char* level_name = get_level_name(level); + + if(g_log_settings.use_colors) + fprintf(g_log_settings.out_file, "%s %s%-5s"COLOR_RESET" "COLOR_FILE"%s:%d:"COLOR_RESET" ", timestamp, + level_color, level_name, file, line); + else + fprintf(g_log_settings.out_file, "%s %-5s %s:%d: ", timestamp, level_name, file, line); + va_list args; + va_start(args, format); + vfprintf(g_log_settings.out_file, fmt, args); + va_end(args); + fprintf(g_log_settings.out_file, "\n"); + fflush(g_log_settings.out_file); +} diff --git a/src/main.c b/src/main.c index cdfa7cd..cc20d16 100644 --- a/src/main.c +++ b/src/main.c @@ -1,19 +1,29 @@ #include "cli.h" +#include "log.h" #include "notify.h" +#include #include #include -void on_notify_event(struct inotify_event* const e) { - fprintf(stdout, "got an event:\n"); - fprintf(stdout, " wd: %d\n", e->wd); - fprintf(stdout, " mask: %d\n", e->mask); - fprintf(stdout, " cookie: %d\n", e->cookie); - fprintf(stdout, " len: %d\n", e->len); - fprintf(stdout, " name: %s\n", e->name); +void on_event(struct inotify_event* const e) { + const char* msg = + "got an event:\n" + " wd: %d\n" + " mask: %d\n" + " cookie: %d\n" + " len: %d\n" + " name: %s" + ; + log_info(msg, e->wd, e->mask, e->cookie, e->len, e->name); } int main(int argc, char** argv) { - struct cli_options args = parse(argc, argv); + cli_options args = parse(argc, argv); + log_settings settings; + settings.level = args.verbosity; + settings.use_colors = args.use_colors; + settings.out_file = args.log_file.has_value ? fopen(args.log_file.value, "w+") : stdout; + log_init(settings); if(args.help) { print_help(stdout, argv[0]); @@ -30,7 +40,7 @@ int main(int argc, char** argv) { fprintf(stderr, "no such file or directory %s\n", args.file.value); exit(EXIT_FAILURE); } - listen_for_changes(args.file.value, &on_notify_event); + listen_for_changes(args.file.value, &on_event); } free_options(args); diff --git a/src/notify.c b/src/notify.c index fab4cd1..39167e7 100644 --- a/src/notify.c +++ b/src/notify.c @@ -1,5 +1,6 @@ #include "notify.h" #include "util.h" +#include "log.h" #define EV_SIZE sizeof(struct inotify_event) #define BUF_LEN EV_SIZE * 32 @@ -9,7 +10,7 @@ void listen_for_changes(const char* filename, notify_callback callback) { ASSERT_SYSCALL_SUCCESS(fd); inotify_add_watch(fd, filename, IN_ATTRIB); - fprintf(stdout, "listening for changes in file: %s\n", filename); + log_info("listening for changes in file: %s", filename); char buffer[BUF_LEN]; int r = read(fd, buffer, BUF_LEN);