init
This commit is contained in:
commit
7b4a1a6c1f
8 changed files with 296 additions and 0 deletions
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Set the language to Oberon
|
||||||
|
*.Mod linguist-language=Oberon
|
||||||
|
*.mod linguist-language=Oberon
|
||||||
26
Makefile
Normal file
26
Makefile
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
.POSIX:
|
||||||
|
|
||||||
|
SRC = ../src
|
||||||
|
TESTS = ../tests
|
||||||
|
BUILD = build
|
||||||
|
|
||||||
|
OBJS = $(BUILD)/time.o $(BUILD)/Logger.o
|
||||||
|
LOGTEST = $(BUILD)/LoggerTest
|
||||||
|
|
||||||
|
all: build
|
||||||
|
|
||||||
|
build:
|
||||||
|
mkdir -p $(BUILD)
|
||||||
|
cd $(BUILD) && voc $(SRC)/time.Mod $(SRC)/Logger.Mod
|
||||||
|
|
||||||
|
test: $(LOGTEST)
|
||||||
|
cd $(BUILD) \
|
||||||
|
&& ./LoggerTest > actual.txt \
|
||||||
|
&& awk -f $(TESTS)/test.awk actual.txt $(TESTS)/expected.txt
|
||||||
|
|
||||||
|
$(LOGTEST): build
|
||||||
|
cd $(BUILD) \
|
||||||
|
&& voc $(SRC)/time.Mod $(SRC)/Logger.Mod $(TESTS)/LoggerTest.Mod -m
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILD)
|
||||||
0
README.md
Normal file
0
README.md
Normal file
124
src/Logger.Mod
Normal file
124
src/Logger.Mod
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
MODULE Logger;
|
||||||
|
|
||||||
|
IMPORT Out, time;
|
||||||
|
|
||||||
|
CONST
|
||||||
|
ERROR* = 0;
|
||||||
|
WARN* = 1;
|
||||||
|
INFO* = 2;
|
||||||
|
DEBUG* = 3;
|
||||||
|
|
||||||
|
TYPE
|
||||||
|
Logger* = POINTER TO LoggerDesc;
|
||||||
|
LoggerDesc = RECORD
|
||||||
|
level: INTEGER;
|
||||||
|
prefix: ARRAY 64 OF CHAR;
|
||||||
|
END;
|
||||||
|
|
||||||
|
PROCEDURE New*(): Logger;
|
||||||
|
VAR self: Logger;
|
||||||
|
BEGIN
|
||||||
|
NEW(self);
|
||||||
|
self.level := INFO;
|
||||||
|
self.prefix[0] := 0X;
|
||||||
|
RETURN self
|
||||||
|
END New;
|
||||||
|
|
||||||
|
PROCEDURE (self: Logger) SetLevel*(level: INTEGER);
|
||||||
|
BEGIN
|
||||||
|
self.level := level
|
||||||
|
END SetLevel;
|
||||||
|
|
||||||
|
PROCEDURE (self: Logger) SetPrefix*(p: ARRAY OF CHAR);
|
||||||
|
VAR i: INTEGER;
|
||||||
|
BEGIN
|
||||||
|
i := 0;
|
||||||
|
WHILE (i < LEN(p)) & (p[i] # 0X) DO
|
||||||
|
self.prefix[i] := p[i]; INC(i)
|
||||||
|
END;
|
||||||
|
self.prefix[i] := 0X
|
||||||
|
END SetPrefix;
|
||||||
|
|
||||||
|
PROCEDURE (self: Logger) ClearPrefix*;
|
||||||
|
BEGIN
|
||||||
|
self.prefix[0] := 0X
|
||||||
|
END ClearPrefix;
|
||||||
|
|
||||||
|
PROCEDURE PrintTime;
|
||||||
|
VAR year, month, day, hour, min, sec: LONGINT;
|
||||||
|
BEGIN
|
||||||
|
time.Now(year, month, day, hour, min, sec);
|
||||||
|
|
||||||
|
Out.Int(year, 0); Out.Char("-");
|
||||||
|
IF month < 10 THEN Out.Char("0") END; Out.Int(month, 0); Out.Char("-");
|
||||||
|
IF day < 10 THEN Out.Char("0") END; Out.Int(day, 0); Out.Char("T");
|
||||||
|
IF hour < 10 THEN Out.Char("0") END; Out.Int(hour, 0); Out.Char(":");
|
||||||
|
IF min < 10 THEN Out.Char("0") END; Out.Int(min, 0); Out.Char(":");
|
||||||
|
IF sec < 10 THEN Out.Char("0") END; Out.Int(sec, 0);
|
||||||
|
Out.Char("Z")
|
||||||
|
END PrintTime;
|
||||||
|
|
||||||
|
PROCEDURE PrintMsg(self: Logger; levelStr, msg: ARRAY OF CHAR);
|
||||||
|
BEGIN
|
||||||
|
Out.String("["); PrintTime(); Out.String("] ");
|
||||||
|
Out.String("["); Out.String(levelStr); Out.String("] ");
|
||||||
|
IF self.prefix[0] # 0X THEN
|
||||||
|
Out.String("["); Out.String(self.prefix); Out.String("] ");
|
||||||
|
END;
|
||||||
|
Out.String(msg); Out.Ln
|
||||||
|
END PrintMsg;
|
||||||
|
|
||||||
|
PROCEDURE PrintMsgWithInt(self: Logger; levelStr, msg: ARRAY OF CHAR; n: INTEGER);
|
||||||
|
BEGIN
|
||||||
|
Out.String("["); PrintTime(); Out.String("] ");
|
||||||
|
Out.String("["); Out.String(levelStr); Out.String("] ");
|
||||||
|
IF self.prefix[0] # 0X THEN
|
||||||
|
Out.String("["); Out.String(self.prefix); Out.String("] ");
|
||||||
|
END;
|
||||||
|
Out.String(msg); Out.Int(n, 0); Out.Ln
|
||||||
|
END PrintMsgWithInt;
|
||||||
|
|
||||||
|
(* ===== Logging Methods ===== *)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PROCEDURE (self: Logger) Error*(msg: ARRAY OF CHAR);
|
||||||
|
BEGIN
|
||||||
|
IF self.level >= ERROR THEN PrintMsg(self, "ERROR", msg) END
|
||||||
|
END Error;
|
||||||
|
|
||||||
|
PROCEDURE (self: Logger) ErrorInt*(msg: ARRAY OF CHAR; n: INTEGER);
|
||||||
|
BEGIN
|
||||||
|
IF self.level >= ERROR THEN PrintMsgWithInt(self, "ERROR", msg, n) END
|
||||||
|
END ErrorInt;
|
||||||
|
|
||||||
|
PROCEDURE (self: Logger) Warn*(msg: ARRAY OF CHAR);
|
||||||
|
BEGIN
|
||||||
|
IF self.level >= WARN THEN PrintMsg(self, "WARN", msg) END
|
||||||
|
END Warn;
|
||||||
|
|
||||||
|
PROCEDURE (self: Logger) WarnInt*(msg: ARRAY OF CHAR; n: INTEGER);
|
||||||
|
BEGIN
|
||||||
|
IF self.level >= WARN THEN PrintMsgWithInt(self, "WARN", msg, n) END
|
||||||
|
END WarnInt;
|
||||||
|
|
||||||
|
PROCEDURE (self: Logger) Info*(msg: ARRAY OF CHAR);
|
||||||
|
BEGIN
|
||||||
|
IF self.level >= INFO THEN PrintMsg(self, "INFO", msg) END
|
||||||
|
END Info;
|
||||||
|
|
||||||
|
PROCEDURE (self: Logger) InfoInt*(msg: ARRAY OF CHAR; n: INTEGER);
|
||||||
|
BEGIN
|
||||||
|
IF self.level >= INFO THEN PrintMsgWithInt(self, "INFO", msg, n) END
|
||||||
|
END InfoInt;
|
||||||
|
|
||||||
|
PROCEDURE (self: Logger) Debug*(msg: ARRAY OF CHAR);
|
||||||
|
BEGIN
|
||||||
|
IF self.level >= DEBUG THEN PrintMsg(self, "DEBUG", msg) END
|
||||||
|
END Debug;
|
||||||
|
|
||||||
|
PROCEDURE (self: Logger) DebugInt*(msg: ARRAY OF CHAR; n: INTEGER);
|
||||||
|
BEGIN
|
||||||
|
IF self.level >= DEBUG THEN PrintMsgWithInt(self, "DEBUG", msg, n) END
|
||||||
|
END DebugInt;
|
||||||
|
END Logger.
|
||||||
45
src/time.Mod
Normal file
45
src/time.Mod
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
MODULE time;
|
||||||
|
IMPORT SYSTEM;
|
||||||
|
TYPE
|
||||||
|
unxTime* = HUGEINT;
|
||||||
|
|
||||||
|
PROCEDURE -Aincludesystime '#include <sys/time.h>'; (* for gettimeofday *)
|
||||||
|
PROCEDURE -Aincludetime '#include <time.h>'; (* for localtime *)
|
||||||
|
PROCEDURE -Aincludesystypes '#include <sys/types.h>';
|
||||||
|
|
||||||
|
PROCEDURE -gettimeval "struct timeval tv; gettimeofday(&tv,0)";
|
||||||
|
PROCEDURE -tvsec(): LONGINT "tv.tv_sec";
|
||||||
|
PROCEDURE -tvusec(): LONGINT "tv.tv_usec";
|
||||||
|
PROCEDURE -sectotm(s: LONGINT) "struct tm *time = localtime((time_t*)&s)";
|
||||||
|
PROCEDURE -tmsec(): LONGINT "(LONGINT)time->tm_sec";
|
||||||
|
PROCEDURE -tmmin(): LONGINT "(LONGINT)time->tm_min";
|
||||||
|
PROCEDURE -tmhour(): LONGINT "(LONGINT)time->tm_hour";
|
||||||
|
PROCEDURE -tmmday(): LONGINT "(LONGINT)time->tm_mday";
|
||||||
|
PROCEDURE -tmmon(): LONGINT "(LONGINT)time->tm_mon";
|
||||||
|
PROCEDURE -tmyear(): LONGINT "(LONGINT)time->tm_year";
|
||||||
|
|
||||||
|
PROCEDURE -unixtime(VAR tmtype: unxTime) "time(tmtype)";
|
||||||
|
PROCEDURE -unixtimediff(endtime, starttime: unxTime): LONGREAL "(LONGREAL)difftime(endtime, starttime)";
|
||||||
|
|
||||||
|
PROCEDURE Now*(VAR year, month, day, hour, min, sec: LONGINT);
|
||||||
|
BEGIN
|
||||||
|
gettimeval; sectotm(tvsec());
|
||||||
|
year := tmyear() + 1900;
|
||||||
|
month := tmmon()+1;
|
||||||
|
day := tmmday();
|
||||||
|
hour := tmhour();
|
||||||
|
min := tmmin();
|
||||||
|
sec := tmsec();
|
||||||
|
END Now;
|
||||||
|
|
||||||
|
PROCEDURE unixTime*(VAR t: unxTime);
|
||||||
|
BEGIN
|
||||||
|
unixtime(t);
|
||||||
|
END unixTime;
|
||||||
|
|
||||||
|
PROCEDURE unixTimeDiff*(endTime, startTime: unxTime): LONGREAL;
|
||||||
|
BEGIN
|
||||||
|
RETURN unixtimediff(endTime, startTime)
|
||||||
|
END unixTimeDiff;
|
||||||
|
|
||||||
|
END time.
|
||||||
37
tests/LoggerTest.Mod
Normal file
37
tests/LoggerTest.Mod
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
MODULE LoggerTest;
|
||||||
|
|
||||||
|
IMPORT Logger;
|
||||||
|
|
||||||
|
VAR
|
||||||
|
log: Logger.Logger;
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
(* Initialize logger instance *)
|
||||||
|
log := Logger.New();
|
||||||
|
|
||||||
|
(* Show default level (INFO) in action *)
|
||||||
|
log.Info("Logger initialized");
|
||||||
|
log.InfoInt("Connected users: ", 42);
|
||||||
|
|
||||||
|
(* Prefix usage *)
|
||||||
|
log.SetPrefix("unit-test");
|
||||||
|
log.Warn("Warning with prefix");
|
||||||
|
log.WarnInt("Sessions: ", 5);
|
||||||
|
|
||||||
|
log.Error("Something went wrong");
|
||||||
|
log.ErrorInt("Error code: ", -7);
|
||||||
|
|
||||||
|
(* Switch to DEBUG level *)
|
||||||
|
log.SetLevel(Logger.DEBUG);
|
||||||
|
log.Debug("This is a debug message");
|
||||||
|
log.DebugInt("File descriptor: ", 3);
|
||||||
|
|
||||||
|
(* Clear prefix *)
|
||||||
|
log.ClearPrefix();
|
||||||
|
log.Info("Prefix cleared");
|
||||||
|
|
||||||
|
(* Suppress all output *)
|
||||||
|
log.SetLevel(-1);
|
||||||
|
log.Error("This should NOT print");
|
||||||
|
log.Debug("This should NOT print");
|
||||||
|
END LoggerTest.
|
||||||
9
tests/expected.txt
Normal file
9
tests/expected.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[2025-06-15T03:27:27Z] [INFO] Logger initialized
|
||||||
|
[2025-06-15T03:27:27Z] [INFO] Connected users: 42
|
||||||
|
[2025-06-15T03:27:27Z] [WARN] [unit-test] Warning with prefix
|
||||||
|
[2025-06-15T03:27:27Z] [WARN] [unit-test] Sessions: 5
|
||||||
|
[2025-06-15T03:27:27Z] [ERROR] [unit-test] Something went wrong
|
||||||
|
[2025-06-15T03:27:27Z] [ERROR] [unit-test] Error code: -7
|
||||||
|
[2025-06-15T03:27:27Z] [DEBUG] [unit-test] This is a debug message
|
||||||
|
[2025-06-15T03:27:27Z] [DEBUG] [unit-test] File descriptor: 3
|
||||||
|
[2025-06-15T03:27:27Z] [INFO] Prefix cleared
|
||||||
52
tests/test.awk
Normal file
52
tests/test.awk
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/usr/bin/awk -f
|
||||||
|
|
||||||
|
# test.awk: Compare actual.txt with expected.txt, allowing [TIME] as a timestamp placeholder
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
passed = 1
|
||||||
|
line = 0
|
||||||
|
|
||||||
|
# Load expected lines from second file (expected.txt)
|
||||||
|
while ((getline expectedLine < ARGV[2]) > 0) {
|
||||||
|
line++
|
||||||
|
expected[line] = expectedLine
|
||||||
|
}
|
||||||
|
|
||||||
|
delete ARGV[2] # remove expected.txt so AWK processes only actual.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
line++
|
||||||
|
actual = $0
|
||||||
|
expectedLine = expected[line]
|
||||||
|
|
||||||
|
# Match timestamp in the form [YYYY-MM-DDTHH:MM:SS]
|
||||||
|
if (match(actual, /\[([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2})Z\] (.*)/)) {
|
||||||
|
ts_len = RLENGTH
|
||||||
|
msg = substr(actual, ts_len + 1)
|
||||||
|
} else {
|
||||||
|
printf("FAIL: Line %d: Invalid or missing timestamp: %s\n", line, actual)
|
||||||
|
passed = 0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
# If expected line begins with [TIME], strip it
|
||||||
|
if (index(expectedLine, "[TIME] ") == 1) {
|
||||||
|
expectedLine = substr(expectedLine, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg != expectedLine) {
|
||||||
|
printf("FAIL: Line %d:\n expected: %s\n actual: %s\n", line, expectedLine, msg)
|
||||||
|
passed = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
if (passed) {
|
||||||
|
print "PASS: all lines matched with valid timestamps"
|
||||||
|
exit 0
|
||||||
|
} else {
|
||||||
|
print "FAIL: differences found"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue