Add logging

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-01-21 19:24:49 +0100
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-01-21 19:24:49 +0100
Commit 61ec593c95f3210bdfcd22539d43ef507210981f (patch)
-rw-r--r-- README.md 19
-rw-r--r-- examples/.gitignore 1
-rw-r--r-- examples/Makefile 7
-rw-r--r-- examples/logging.c 25
-rw-r--r-- nonstd.h 103
-rw-r--r-- tests.c 66
6 files changed, 212 insertions, 9 deletions
diff --git a/README.md b/README.md
...
18
- **Slices (`slice`)**: Generic non-owning views into arrays.
18
- **Slices (`slice`)**: Generic non-owning views into arrays.
19
- **Memory Arena**: Simple block-based arena allocator for bulk memory management.
19
- **Memory Arena**: Simple block-based arena allocator for bulk memory management.
20
- **File I/O**: Helper functions to read and write entire files with a single call.
20
- **File I/O**: Helper functions to read and write entire files with a single call.
  
21
- **Logging**: Simple, leveled logging with ANSI colors and timestamps.
21
  
22
  
22
## Installation
23
## Installation
23
  
24
  
...
163
// ... use file_sb ...
164
// ... use file_sb ...
164
sb_free(&file_sb);
165
sb_free(&file_sb);
165
  
166
  
  
167
```
  
168
  
  
169
### 6. Logging
  
170
  
  
171
Simple logging with levels (`ERROR`, `WARN`, `INFO`, `DEBUG`), timestamps, and colors.
  
172
  
  
173
```c
  
174
// Set log level (default is INFO)
  
175
set_log_level(LOG_DEBUG);
  
176
  
  
177
// Use macros for logging
  
178
LOG_INFO_MSG("Starting application...");
  
179
LOG_DEBUG_MSG("Variable x = %d", 42);
  
180
LOG_WARN_MSG("Low memory warning");
  
181
LOG_ERROR_MSG("Connection failed");
  
182
  
  
183
// Environment variable override supported:
  
184
// LOG_LEVEL=0 (ERROR) ... 3 (DEBUG)
166
```
185
```
167
  
186
  
168
## Testing
187
## Testing
...
diff --git a/examples/.gitignore b/examples/.gitignore
...
5
slice
5
slice
6
arena
6
arena
7
files
7
files
  
8
logging
8
  
9
  
diff --git a/examples/Makefile b/examples/Makefile
...
5
LDFLAGS =
5
LDFLAGS =
6
  
6
  
7
# Example targets
7
# Example targets
8
EXAMPLES = foreach stringv stringb array slice arena files
8
EXAMPLES = foreach stringv stringb array slice arena files logging
9
  
9
  
10
# Default target
10
# Default target
11
all: $(EXAMPLES)
11
all: $(EXAMPLES)
...
32
files: files.c
32
files: files.c
33
	$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
33
	$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
34
  
34
  
  
35
logging: logging.c
  
36
	$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
  
37
  
35
# Run all examples
38
# Run all examples
36
run: all
39
run: all
37
	@echo "\n=== Running stringv ===\n"
40
	@echo "\n=== Running stringv ===\n"
...
48
	@./arena
51
	@./arena
49
	@echo "\n=== Running files ===\n"
52
	@echo "\n=== Running files ===\n"
50
	@./files
53
	@./files
  
54
	@echo "\n=== Running logging ===\n"
  
55
	@./logging
51
  
56
  
52
# Clean build artifacts
57
# Clean build artifacts
53
clean:
58
clean:
...
diff --git a/examples/logging.c b/examples/logging.c
  
1
#define NONSTD_IMPLEMENTATION
  
2
#include "../nonstd.h"
  
3
  
  
4
int main(void) {
  
5
    // Default level is LOG_INFO
  
6
    LOG_INFO_MSG("This is an info message: %d", 42);
  
7
    LOG_DEBUG_MSG("This debug message will NOT be shown by default");
  
8
  
  
9
    // Change level to LOG_DEBUG
  
10
    set_log_level(LOG_DEBUG);
  
11
    LOG_DEBUG_MSG("Now debug messages are shown: %s", "hello");
  
12
  
  
13
    // Warnings and Errors
  
14
    LOG_WARN_MSG("This is a warning!");
  
15
    LOG_ERROR_MSG("This is an error!");
  
16
  
  
17
    // Environment variable override test
  
18
    // You can set LOG_LEVEL=1 (WARN) etc.
  
19
    LogLevel env_level = get_log_level_from_env();
  
20
    if (env_level != LOG_DEBUG) {
  
21
        printf("Environment overrides level to: %d\n", env_level);
  
22
    }
  
23
  
  
24
    return 0;
  
25
}
diff --git a/nonstd.h b/nonstd.h
  
1
#ifdef NONSTD_IMPLEMENTATION
  
2
#ifndef _POSIX_C_SOURCE
  
3
#define _POSIX_C_SOURCE 200809L
  
4
#endif
  
5
#endif
  
6
  
1
#ifndef NONSTD_H
7
#ifndef NONSTD_H
2
#define NONSTD_H
8
#define NONSTD_H
3
  
9
  
...
6
#include <stdio.h>
12
#include <stdio.h>
7
#include <stdlib.h>
13
#include <stdlib.h>
8
#include <string.h>
14
#include <string.h>
  
15
#include <stdarg.h>
  
16
#include <time.h>
  
17
#include <sys/time.h>
  
18
#include <unistd.h>
9
  
19
  
10
#ifndef NONSTD_DEF
20
#ifndef NONSTD_DEF
11
#ifdef NONSTD_STATIC
21
#ifdef NONSTD_STATIC
...
88
NONSTD_DEF stringv sb_as_sv(const stringb *sb);
98
NONSTD_DEF stringv sb_as_sv(const stringb *sb);
89
  
99
  
90
// Slice - generic non-owning view into an array
100
// Slice - generic non-owning view into an array
91
// Usage:
101
// Usage: SLICE_DEF(int); slice(int) view = ...;
92
//   SLICE_DEF(int);        // Define slice_int type
  
93
//   slice(int) view = ...; // Use it
  
94
#define SLICE_DEF(T)   \
102
#define SLICE_DEF(T)   \
95
	typedef struct {   \
103
	typedef struct {   \
96
		T *data;       \
104
		T *data;       \
...
228
		 index < (arr).length && ((var) = (arr).data[index], 1); ++index)
236
		 index < (arr).length && ((var) = (arr).data[index], 1); ++index)
229
  
237
  
230
// Arena - block-based memory allocator
238
// Arena - block-based memory allocator
231
// Usage: Arena a = arena_make(); void* p = arena_alloc(&a, 100);
  
232
// arena_free(&a);
  
233
typedef struct {
239
typedef struct {
234
	char *ptr;
240
	char *ptr;
235
	char *end;
241
	char *end;
...
246
// File I/O helpers
252
// File I/O helpers
247
NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size);
253
NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size);
248
NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size);
254
NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size);
249
// read_entire_file_sv removed for security (ownership confusion)
  
250
NONSTD_DEF stringb read_entire_file_sb(const char *filepath);
255
NONSTD_DEF stringb read_entire_file_sb(const char *filepath);
251
NONSTD_DEF int write_file_sv(const char *filepath, stringv sv);
256
NONSTD_DEF int write_file_sv(const char *filepath, stringv sv);
252
NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb);
257
NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb);
253
  
258
  
  
259
// Logging
  
260
typedef enum {
  
261
	LOG_ERROR,
  
262
	LOG_WARN,
  
263
	LOG_INFO,
  
264
	LOG_DEBUG,
  
265
} LogLevel;
  
266
  
  
267
NONSTD_DEF void set_log_level(LogLevel level);
  
268
NONSTD_DEF LogLevel get_log_level_from_env(void);
  
269
NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...);
  
270
  
  
271
#define LOG_INFO_MSG(...) log_message(stdout, LOG_INFO, __VA_ARGS__)
  
272
#define LOG_DEBUG_MSG(...) log_message(stdout, LOG_DEBUG, __VA_ARGS__)
  
273
#define LOG_WARN_MSG(...) log_message(stderr, LOG_WARN, __VA_ARGS__)
  
274
#define LOG_ERROR_MSG(...) log_message(stderr, LOG_ERROR, __VA_ARGS__)
  
275
  
  
276
#define COLOR_RESET "\033[0m"
  
277
#define COLOR_INFO "\033[32m"
  
278
#define COLOR_DEBUG "\033[36m"
  
279
#define COLOR_WARNING "\033[33m"
  
280
#define COLOR_ERROR "\033[31m"
  
281
  
254
#endif // NONSTD_H
282
#endif // NONSTD_H
255
  
283
  
256
#ifdef NONSTD_IMPLEMENTATION
284
#ifdef NONSTD_IMPLEMENTATION
...
474
	return written == size;
502
	return written == size;
475
}
503
}
476
  
504
  
477
// read_entire_file_sv removed
  
478
  
  
479
NONSTD_DEF stringb read_entire_file_sb(const char *filepath) {
505
NONSTD_DEF stringb read_entire_file_sb(const char *filepath) {
480
	size_t size = 0;
506
	size_t size = 0;
481
	char *data = read_entire_file(filepath, &size);
507
	char *data = read_entire_file(filepath, &size);
...
494
  
520
  
495
NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb) {
521
NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb) {
496
	return write_entire_file(filepath, sb->data, sb->length);
522
	return write_entire_file(filepath, sb->data, sb->length);
  
523
}
  
524
  
  
525
// Logging Implementation
  
526
  
  
527
static LogLevel max_level = LOG_INFO;
  
528
  
  
529
static const char *level_strings[] = {
  
530
	"ERROR",
  
531
	"WARN",
  
532
	"INFO",
  
533
	"DEBUG",
  
534
};
  
535
  
  
536
static const char *level_colors[] = {
  
537
	COLOR_ERROR,
  
538
	COLOR_WARNING,
  
539
	COLOR_INFO,
  
540
	COLOR_DEBUG,
  
541
};
  
542
  
  
543
NONSTD_DEF void set_log_level(LogLevel level) {
  
544
	max_level = level;
  
545
}
  
546
  
  
547
NONSTD_DEF LogLevel get_log_level_from_env(void) {
  
548
	const char *env = getenv("LOG_LEVEL");
  
549
	if (env) {
  
550
		int level = atoi(env);
  
551
		if (level >= 0 && level <= 3) {
  
552
			return (LogLevel)level;
  
553
		}
  
554
	}
  
555
  
  
556
	return max_level;
  
557
}
  
558
  
  
559
NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...) {
  
560
	if (max_level < level)
  
561
		return;
  
562
  
  
563
	struct timeval tv;
  
564
	gettimeofday(&tv, NULL);
  
565
	struct tm *tm_info = localtime(&tv.tv_sec);
  
566
  
  
567
	char time_str[24];
  
568
	strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
  
569
  
  
570
	const char *color = isatty(fileno(stream)) ? level_colors[level] : "";
  
571
	const char *reset = isatty(fileno(stream)) ? COLOR_RESET : "";
  
572
  
  
573
	const char *log_format = "%s[%s.%03d] [%-5s] ";
  
574
	fprintf(stream, log_format, color, time_str, (int)(tv.tv_usec / 1000),
  
575
			level_strings[level]);
  
576
  
  
577
	va_list args;
  
578
	va_start(args, format);
  
579
	vfprintf(stream, format, args);
  
580
	va_end(args);
  
581
  
  
582
	fprintf(stream, "%s\n", reset);
  
583
	fflush(stream);
497
}
584
}
498
  
585
  
499
#endif // NONSTD_IMPLEMENTATION
586
#endif // NONSTD_IMPLEMENTATION
diff --git a/tests.c b/tests.c
...
857
	mu_assert_int_eq(0, sb.length);
857
	mu_assert_int_eq(0, sb.length);
858
}
858
}
859
  
859
  
  
860
// Logging tests
  
861
MU_TEST(test_logging_level_filtering) {
  
862
	FILE *tmp = tmpfile();
  
863
	mu_check(tmp != NULL);
  
864
  
  
865
	set_log_level(LOG_WARN);
  
866
  
  
867
	log_message(tmp, LOG_INFO, "Info message");   // Should not be logged
  
868
	log_message(tmp, LOG_ERROR, "Error message"); // Should be logged
  
869
  
  
870
	rewind(tmp);
  
871
	char buffer[1024];
  
872
	size_t read = fread(buffer, 1, sizeof(buffer), tmp);
  
873
	buffer[read] = '\0';
  
874
  
  
875
	mu_check(strstr(buffer, "Info message") == NULL);
  
876
	mu_check(strstr(buffer, "Error message") != NULL);
  
877
	mu_check(strstr(buffer, "[ERROR]") != NULL);
  
878
  
  
879
	fclose(tmp);
  
880
}
  
881
  
  
882
MU_TEST(test_logging_env_level) {
  
883
	// Enum: ERROR=0, WARN=1, INFO=2, DEBUG=3
  
884
	setenv("LOG_LEVEL", "0", 1); // ERROR
  
885
	mu_assert_int_eq(LOG_ERROR, get_log_level_from_env());
  
886
  
  
887
	setenv("LOG_LEVEL", "3", 1); // DEBUG
  
888
	mu_assert_int_eq(LOG_DEBUG, get_log_level_from_env());
  
889
  
  
890
	// Invalid level
  
891
	set_log_level(LOG_INFO); // Reset
  
892
	setenv("LOG_LEVEL", "99", 1);
  
893
	mu_assert_int_eq(LOG_INFO, get_log_level_from_env()); // Should return current max_level (defaults check)
  
894
  
  
895
	unsetenv("LOG_LEVEL");
  
896
}
  
897
  
  
898
MU_TEST(test_logging_format) {
  
899
	FILE *tmp = tmpfile();
  
900
	mu_check(tmp != NULL);
  
901
  
  
902
	set_log_level(LOG_INFO);
  
903
	log_message(tmp, LOG_INFO, "Test %d %s", 123, "format");
  
904
  
  
905
	rewind(tmp);
  
906
	char buffer[1024];
  
907
	size_t read = fread(buffer, 1, sizeof(buffer), tmp);
  
908
	buffer[read] = '\0';
  
909
  
  
910
	mu_check(strstr(buffer, "Test 123 format") != NULL);
  
911
	mu_check(strstr(buffer, "[INFO ]") != NULL);
  
912
	// Check timestamp format roughly (YYYY-MM-DD)
  
913
	mu_check(strstr(buffer, "20") != NULL); 
  
914
  
  
915
	fclose(tmp);
  
916
}
  
917
  
860
// Test suites
918
// Test suites
861
MU_TEST_SUITE(test_suite_stringv) {
919
MU_TEST_SUITE(test_suite_stringv) {
862
	printf("\n[String View Tests]\n");
920
	printf("\n[String View Tests]\n");
...
960
	RUN_TEST_WITH_NAME(test_file_io_read_missing_sb);
1018
	RUN_TEST_WITH_NAME(test_file_io_read_missing_sb);
961
}
1019
}
962
  
1020
  
  
1021
MU_TEST_SUITE(test_suite_logging) {
  
1022
	printf("\n[Logging Tests]\n");
  
1023
	RUN_TEST_WITH_NAME(test_logging_level_filtering);
  
1024
	RUN_TEST_WITH_NAME(test_logging_env_level);
  
1025
	RUN_TEST_WITH_NAME(test_logging_format);
  
1026
}
  
1027
  
963
int main(int argc, char *argv[]) {
1028
int main(int argc, char *argv[]) {
964
	(void)argc;
1029
	(void)argc;
965
	(void)argv;
1030
	(void)argv;
...
972
	MU_RUN_SUITE(test_suite_types);
1037
	MU_RUN_SUITE(test_suite_types);
973
	MU_RUN_SUITE(test_suite_arena);
1038
	MU_RUN_SUITE(test_suite_arena);
974
	MU_RUN_SUITE(test_suite_files);
1039
	MU_RUN_SUITE(test_suite_files);
  
1040
	MU_RUN_SUITE(test_suite_logging);
975
  
1041
  
976
	MU_REPORT();
1042
	MU_REPORT();
977
  
1043
  
...