summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md19
-rw-r--r--examples/.gitignore1
-rw-r--r--examples/Makefile7
-rw-r--r--examples/logging.c25
-rw-r--r--nonstd.h103
-rw-r--r--tests.c66
6 files changed, 212 insertions, 9 deletions
diff --git a/README.md b/README.md
index b509377..7004467 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@ ergonomic and productive. It aims to be C99 compliant.
- **Slices (`slice`)**: Generic non-owning views into arrays.
- **Memory Arena**: Simple block-based arena allocator for bulk memory management.
- **File I/O**: Helper functions to read and write entire files with a single call.
+- **Logging**: Simple, leveled logging with ANSI colors and timestamps.
## Installation
@@ -165,6 +166,24 @@ sb_free(&file_sb);
```
+### 6. Logging
+
+Simple logging with levels (`ERROR`, `WARN`, `INFO`, `DEBUG`), timestamps, and colors.
+
+```c
+// Set log level (default is INFO)
+set_log_level(LOG_DEBUG);
+
+// Use macros for logging
+LOG_INFO_MSG("Starting application...");
+LOG_DEBUG_MSG("Variable x = %d", 42);
+LOG_WARN_MSG("Low memory warning");
+LOG_ERROR_MSG("Connection failed");
+
+// Environment variable override supported:
+// LOG_LEVEL=0 (ERROR) ... 3 (DEBUG)
+```
+
## Testing
The project includes a test suite using `minunit`.
diff --git a/examples/.gitignore b/examples/.gitignore
index 0707ee1..dcb0ae9 100644
--- a/examples/.gitignore
+++ b/examples/.gitignore
@@ -5,4 +5,5 @@ array
slice
arena
files
+logging
diff --git a/examples/Makefile b/examples/Makefile
index a614627..cd83e92 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -5,7 +5,7 @@ CFLAGS = -Wall -Wextra -std=c99 -fsanitize=address -g -O0
LDFLAGS =
# Example targets
-EXAMPLES = foreach stringv stringb array slice arena files
+EXAMPLES = foreach stringv stringb array slice arena files logging
# Default target
all: $(EXAMPLES)
@@ -32,6 +32,9 @@ arena: arena.c
files: files.c
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+logging: logging.c
+ $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+
# Run all examples
run: all
@echo "\n=== Running stringv ===\n"
@@ -48,6 +51,8 @@ run: all
@./arena
@echo "\n=== Running files ===\n"
@./files
+ @echo "\n=== Running logging ===\n"
+ @./logging
# Clean build artifacts
clean:
diff --git a/examples/logging.c b/examples/logging.c
new file mode 100644
index 0000000..1d2744c
--- /dev/null
+++ b/examples/logging.c
@@ -0,0 +1,25 @@
+#define NONSTD_IMPLEMENTATION
+#include "../nonstd.h"
+
+int main(void) {
+ // Default level is LOG_INFO
+ LOG_INFO_MSG("This is an info message: %d", 42);
+ LOG_DEBUG_MSG("This debug message will NOT be shown by default");
+
+ // Change level to LOG_DEBUG
+ set_log_level(LOG_DEBUG);
+ LOG_DEBUG_MSG("Now debug messages are shown: %s", "hello");
+
+ // Warnings and Errors
+ LOG_WARN_MSG("This is a warning!");
+ LOG_ERROR_MSG("This is an error!");
+
+ // Environment variable override test
+ // You can set LOG_LEVEL=1 (WARN) etc.
+ LogLevel env_level = get_log_level_from_env();
+ if (env_level != LOG_DEBUG) {
+ printf("Environment overrides level to: %d\n", env_level);
+ }
+
+ return 0;
+}
diff --git a/nonstd.h b/nonstd.h
index 57e8da4..b826dfb 100644
--- a/nonstd.h
+++ b/nonstd.h
@@ -1,3 +1,9 @@
+#ifdef NONSTD_IMPLEMENTATION
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#endif
+
#ifndef NONSTD_H
#define NONSTD_H
@@ -6,6 +12,10 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+#include <sys/time.h>
+#include <unistd.h>
#ifndef NONSTD_DEF
#ifdef NONSTD_STATIC
@@ -88,9 +98,7 @@ NONSTD_DEF void sb_append_char(stringb *sb, char c);
NONSTD_DEF stringv sb_as_sv(const stringb *sb);
// Slice - generic non-owning view into an array
-// Usage:
-// SLICE_DEF(int); // Define slice_int type
-// slice(int) view = ...; // Use it
+// Usage: SLICE_DEF(int); slice(int) view = ...;
#define SLICE_DEF(T) \
typedef struct { \
T *data; \
@@ -228,8 +236,6 @@ NONSTD_DEF stringv sb_as_sv(const stringb *sb);
index < (arr).length && ((var) = (arr).data[index], 1); ++index)
// Arena - block-based memory allocator
-// Usage: Arena a = arena_make(); void* p = arena_alloc(&a, 100);
-// arena_free(&a);
typedef struct {
char *ptr;
char *end;
@@ -246,11 +252,33 @@ NONSTD_DEF void arena_free(Arena *a);
// File I/O helpers
NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size);
NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size);
-// read_entire_file_sv removed for security (ownership confusion)
NONSTD_DEF stringb read_entire_file_sb(const char *filepath);
NONSTD_DEF int write_file_sv(const char *filepath, stringv sv);
NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb);
+// Logging
+typedef enum {
+ LOG_ERROR,
+ LOG_WARN,
+ LOG_INFO,
+ LOG_DEBUG,
+} LogLevel;
+
+NONSTD_DEF void set_log_level(LogLevel level);
+NONSTD_DEF LogLevel get_log_level_from_env(void);
+NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...);
+
+#define LOG_INFO_MSG(...) log_message(stdout, LOG_INFO, __VA_ARGS__)
+#define LOG_DEBUG_MSG(...) log_message(stdout, LOG_DEBUG, __VA_ARGS__)
+#define LOG_WARN_MSG(...) log_message(stderr, LOG_WARN, __VA_ARGS__)
+#define LOG_ERROR_MSG(...) log_message(stderr, LOG_ERROR, __VA_ARGS__)
+
+#define COLOR_RESET "\033[0m"
+#define COLOR_INFO "\033[32m"
+#define COLOR_DEBUG "\033[36m"
+#define COLOR_WARNING "\033[33m"
+#define COLOR_ERROR "\033[31m"
+
#endif // NONSTD_H
#ifdef NONSTD_IMPLEMENTATION
@@ -474,8 +502,6 @@ NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t
return written == size;
}
-// read_entire_file_sv removed
-
NONSTD_DEF stringb read_entire_file_sb(const char *filepath) {
size_t size = 0;
char *data = read_entire_file(filepath, &size);
@@ -496,4 +522,65 @@ NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb) {
return write_entire_file(filepath, sb->data, sb->length);
}
+// Logging Implementation
+
+static LogLevel max_level = LOG_INFO;
+
+static const char *level_strings[] = {
+ "ERROR",
+ "WARN",
+ "INFO",
+ "DEBUG",
+};
+
+static const char *level_colors[] = {
+ COLOR_ERROR,
+ COLOR_WARNING,
+ COLOR_INFO,
+ COLOR_DEBUG,
+};
+
+NONSTD_DEF void set_log_level(LogLevel level) {
+ max_level = level;
+}
+
+NONSTD_DEF LogLevel get_log_level_from_env(void) {
+ const char *env = getenv("LOG_LEVEL");
+ if (env) {
+ int level = atoi(env);
+ if (level >= 0 && level <= 3) {
+ return (LogLevel)level;
+ }
+ }
+
+ return max_level;
+}
+
+NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...) {
+ if (max_level < level)
+ return;
+
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ struct tm *tm_info = localtime(&tv.tv_sec);
+
+ char time_str[24];
+ strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
+
+ const char *color = isatty(fileno(stream)) ? level_colors[level] : "";
+ const char *reset = isatty(fileno(stream)) ? COLOR_RESET : "";
+
+ const char *log_format = "%s[%s.%03d] [%-5s] ";
+ fprintf(stream, log_format, color, time_str, (int)(tv.tv_usec / 1000),
+ level_strings[level]);
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stream, format, args);
+ va_end(args);
+
+ fprintf(stream, "%s\n", reset);
+ fflush(stream);
+}
+
#endif // NONSTD_IMPLEMENTATION \ No newline at end of file
diff --git a/tests.c b/tests.c
index 32da92f..9eb7469 100644
--- a/tests.c
+++ b/tests.c
@@ -857,6 +857,64 @@ MU_TEST(test_file_io_read_missing_sb) {
mu_assert_int_eq(0, sb.length);
}
+// Logging tests
+MU_TEST(test_logging_level_filtering) {
+ FILE *tmp = tmpfile();
+ mu_check(tmp != NULL);
+
+ set_log_level(LOG_WARN);
+
+ log_message(tmp, LOG_INFO, "Info message"); // Should not be logged
+ log_message(tmp, LOG_ERROR, "Error message"); // Should be logged
+
+ rewind(tmp);
+ char buffer[1024];
+ size_t read = fread(buffer, 1, sizeof(buffer), tmp);
+ buffer[read] = '\0';
+
+ mu_check(strstr(buffer, "Info message") == NULL);
+ mu_check(strstr(buffer, "Error message") != NULL);
+ mu_check(strstr(buffer, "[ERROR]") != NULL);
+
+ fclose(tmp);
+}
+
+MU_TEST(test_logging_env_level) {
+ // Enum: ERROR=0, WARN=1, INFO=2, DEBUG=3
+ setenv("LOG_LEVEL", "0", 1); // ERROR
+ mu_assert_int_eq(LOG_ERROR, get_log_level_from_env());
+
+ setenv("LOG_LEVEL", "3", 1); // DEBUG
+ mu_assert_int_eq(LOG_DEBUG, get_log_level_from_env());
+
+ // Invalid level
+ set_log_level(LOG_INFO); // Reset
+ setenv("LOG_LEVEL", "99", 1);
+ mu_assert_int_eq(LOG_INFO, get_log_level_from_env()); // Should return current max_level (defaults check)
+
+ unsetenv("LOG_LEVEL");
+}
+
+MU_TEST(test_logging_format) {
+ FILE *tmp = tmpfile();
+ mu_check(tmp != NULL);
+
+ set_log_level(LOG_INFO);
+ log_message(tmp, LOG_INFO, "Test %d %s", 123, "format");
+
+ rewind(tmp);
+ char buffer[1024];
+ size_t read = fread(buffer, 1, sizeof(buffer), tmp);
+ buffer[read] = '\0';
+
+ mu_check(strstr(buffer, "Test 123 format") != NULL);
+ mu_check(strstr(buffer, "[INFO ]") != NULL);
+ // Check timestamp format roughly (YYYY-MM-DD)
+ mu_check(strstr(buffer, "20") != NULL);
+
+ fclose(tmp);
+}
+
// Test suites
MU_TEST_SUITE(test_suite_stringv) {
printf("\n[String View Tests]\n");
@@ -960,6 +1018,13 @@ MU_TEST_SUITE(test_suite_files) {
RUN_TEST_WITH_NAME(test_file_io_read_missing_sb);
}
+MU_TEST_SUITE(test_suite_logging) {
+ printf("\n[Logging Tests]\n");
+ RUN_TEST_WITH_NAME(test_logging_level_filtering);
+ RUN_TEST_WITH_NAME(test_logging_env_level);
+ RUN_TEST_WITH_NAME(test_logging_format);
+}
+
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
@@ -972,6 +1037,7 @@ int main(int argc, char *argv[]) {
MU_RUN_SUITE(test_suite_types);
MU_RUN_SUITE(test_suite_arena);
MU_RUN_SUITE(test_suite_files);
+ MU_RUN_SUITE(test_suite_logging);
MU_REPORT();