feat: add simple logging library
This commit is contained in:
parent
6c3ad9ffa5
commit
9d1408c174
1
Makefile
1
Makefile
@ -30,6 +30,7 @@ out/obj/%.o: src/%.c | $(OBJDIR)
|
|||||||
|
|
||||||
OBJ += out/obj/main.o
|
OBJ += out/obj/main.o
|
||||||
OBJ += out/obj/cli.o
|
OBJ += out/obj/cli.o
|
||||||
|
OBJ += out/obj/log.o
|
||||||
OBJ += out/obj/notify.o
|
OBJ += out/obj/notify.o
|
||||||
out/bin/sci: $(OBJ) | $(BINDIR)
|
out/bin/sci: $(OBJ) | $(BINDIR)
|
||||||
$(CC) -o $@ $(CFLAGS) $^
|
$(CC) -o $@ $(CFLAGS) $^
|
||||||
|
@ -79,9 +79,10 @@ If you want `compile_commands.json` files, you should use [bear](https://github.
|
|||||||
### Progress
|
### Progress
|
||||||
- [x] Zeroth things first, let's create a simple CLI application with `--verbosity VAL` and `--help` options.
|
- [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).
|
- [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.
|
output file using cli options.
|
||||||
- [ ] Third things third, implement a thing that simultaneously watches two different files (multithreading).
|
- [ ] 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.
|
- [ ] Fourth things fourth, implement a prototype that reads a space-separated file and populates a struct.
|
||||||
|
|
||||||
### Note Regarding `inotify` usage
|
### Note Regarding `inotify` usage
|
||||||
|
@ -3,23 +3,25 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "optional.h"
|
#include "optional.h"
|
||||||
|
|
||||||
struct cli_options {
|
typedef struct {
|
||||||
optional_str file;
|
optional_str file;
|
||||||
int verbosity;
|
int verbosity;
|
||||||
bool help;
|
bool help;
|
||||||
bool version;
|
bool version;
|
||||||
};
|
bool use_colors;
|
||||||
|
optional_str log_file;
|
||||||
|
} cli_options;
|
||||||
|
|
||||||
// Construct a new cli_options struct instance.
|
// Construct a new cli_options struct instance.
|
||||||
struct cli_options new_options();
|
cli_options new_options();
|
||||||
|
|
||||||
// Delete a cli_options struct instance.
|
// Delete a cli_options struct instance.
|
||||||
void free_options(struct cli_options v);
|
void free_options(cli_options v);
|
||||||
|
|
||||||
// Print the help message.
|
// Print the help message.
|
||||||
void print_help(FILE * out, char* prog_name);
|
void print_help(FILE * out, char* prog_name);
|
||||||
|
|
||||||
// Parse the command line arguments and give a new cli_options struct instance.
|
// 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
|
#endif
|
||||||
|
28
include/log.h
Normal file
28
include/log.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#ifndef SCI_LOG_H
|
||||||
|
#define SCI_LOG_H
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
// 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
|
38
src/cli.c
38
src/cli.c
@ -4,24 +4,37 @@
|
|||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include "cli.h"
|
#include "cli.h"
|
||||||
|
|
||||||
struct cli_options new_options() {
|
cli_options new_options() {
|
||||||
struct cli_options result;
|
cli_options result;
|
||||||
result.file.has_value = false;
|
result.file.has_value = false;
|
||||||
|
result.file.value = NULL;
|
||||||
|
|
||||||
result.verbosity = 1;
|
result.verbosity = 1;
|
||||||
|
|
||||||
result.help = false;
|
result.help = false;
|
||||||
|
|
||||||
result.version = 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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void free_options(struct cli_options v) {
|
void free_options(cli_options v) {
|
||||||
if(v.file.has_value)
|
if(v.file.has_value)
|
||||||
free(v.file.value);
|
free(v.file.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// <max
|
// <max
|
||||||
const char* optstring = "f:v:hV";
|
const char* optstring = "f:v:Cl:hV";
|
||||||
const char* help_msg =
|
const char* help_msg =
|
||||||
"Usage: %s [-v level] [-h] [-V]\n"
|
"Usage: %s [-v level] [-C] [-l file] [-h] [-V]\n"
|
||||||
"\n"
|
"\n"
|
||||||
SCI_DESCRIPTION "\n"
|
SCI_DESCRIPTION "\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -30,7 +43,9 @@ const char* help_msg =
|
|||||||
"\n"
|
"\n"
|
||||||
"OPTIONS:\n"
|
"OPTIONS:\n"
|
||||||
" -f file set file\n"
|
" -f file set file\n"
|
||||||
" -v level Set verbosity level [0-3]\n"
|
" -v level Set verbosity level [0-4]\n"
|
||||||
|
" -C Force color output, ignoring $NO_COLOR\n"
|
||||||
|
" -l file Set log to output to a file\n"
|
||||||
" -h Show this message and exit\n"
|
" -h Show this message and exit\n"
|
||||||
" -V Show version and exit\n"
|
" -V Show version and exit\n"
|
||||||
;
|
;
|
||||||
@ -40,8 +55,8 @@ void print_help(FILE * out, char* prog_name) {
|
|||||||
fprintf(out, help_msg, prog_name);
|
fprintf(out, help_msg, prog_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct cli_options parse(int argc, char** argv) {
|
cli_options parse(int argc, char** argv) {
|
||||||
struct cli_options options = new_options();
|
cli_options options = new_options();
|
||||||
int opt;
|
int opt;
|
||||||
while((opt = getopt(argc, argv, optstring)) != -1) {
|
while((opt = getopt(argc, argv, optstring)) != -1) {
|
||||||
switch(opt) {
|
switch(opt) {
|
||||||
@ -52,6 +67,13 @@ struct cli_options parse(int argc, char** argv) {
|
|||||||
case 'v':
|
case 'v':
|
||||||
options.verbosity = atoi(optarg);
|
options.verbosity = atoi(optarg);
|
||||||
break;
|
break;
|
||||||
|
case 'C':
|
||||||
|
options.use_colors = true;
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
options.log_file.value = strdup(optarg);
|
||||||
|
options.log_file.has_value = true;
|
||||||
|
break;
|
||||||
case 'V':
|
case 'V':
|
||||||
options.version = true;
|
options.version = true;
|
||||||
break;
|
break;
|
||||||
|
78
src/log.c
Normal file
78
src/log.c
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
#include "log.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
28
src/main.c
28
src/main.c
@ -1,19 +1,29 @@
|
|||||||
#include "cli.h"
|
#include "cli.h"
|
||||||
|
#include "log.h"
|
||||||
#include "notify.h"
|
#include "notify.h"
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
void on_notify_event(struct inotify_event* const e) {
|
void on_event(struct inotify_event* const e) {
|
||||||
fprintf(stdout, "got an event:\n");
|
const char* msg =
|
||||||
fprintf(stdout, " wd: %d\n", e->wd);
|
"got an event:\n"
|
||||||
fprintf(stdout, " mask: %d\n", e->mask);
|
" wd: %d\n"
|
||||||
fprintf(stdout, " cookie: %d\n", e->cookie);
|
" mask: %d\n"
|
||||||
fprintf(stdout, " len: %d\n", e->len);
|
" cookie: %d\n"
|
||||||
fprintf(stdout, " name: %s\n", e->name);
|
" len: %d\n"
|
||||||
|
" name: %s"
|
||||||
|
;
|
||||||
|
log_info(msg, e->wd, e->mask, e->cookie, e->len, e->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
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) {
|
if(args.help) {
|
||||||
print_help(stdout, argv[0]);
|
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);
|
fprintf(stderr, "no such file or directory %s\n", args.file.value);
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
listen_for_changes(args.file.value, &on_notify_event);
|
listen_for_changes(args.file.value, &on_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
free_options(args);
|
free_options(args);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "notify.h"
|
#include "notify.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
#define EV_SIZE sizeof(struct inotify_event)
|
#define EV_SIZE sizeof(struct inotify_event)
|
||||||
#define BUF_LEN EV_SIZE * 32
|
#define BUF_LEN EV_SIZE * 32
|
||||||
@ -9,7 +10,7 @@ void listen_for_changes(const char* filename, notify_callback callback) {
|
|||||||
ASSERT_SYSCALL_SUCCESS(fd);
|
ASSERT_SYSCALL_SUCCESS(fd);
|
||||||
inotify_add_watch(fd, filename, IN_ATTRIB);
|
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];
|
char buffer[BUF_LEN];
|
||||||
int r = read(fd, buffer, BUF_LEN);
|
int r = read(fd, buffer, BUF_LEN);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user