From 0e7ee82265dcb8f5e999dcf88873cbc4f4b093df Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Thu, 27 Apr 2023 21:08:12 +0300 Subject: [PATCH 1/3] first test --- .gitignore | 2 + dub.json | 15 ++++++ source/singlog.d | 134 +++++++++++++++++++++++++++++------------------ tests/test.d | 11 ++++ 4 files changed, 112 insertions(+), 50 deletions(-) create mode 100644 tests/test.d diff --git a/.gitignore b/.gitignore index 3a55aef..27e3830 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .dub *.o lib +bin +test.log diff --git a/dub.json b/dub.json index e2dbe17..a2c64ed 100644 --- a/dub.json +++ b/dub.json @@ -10,6 +10,21 @@ "targetType": "library", "targetPath": "lib", "targetName": "singlog", + "configurations": [ + { + "name": "library", + "targetType": "library", + "targetPath": "lib" + }, + { + "name": "test", + "targetType": "executable", + "targetPath": "bin", + "targetName": "app", + "importPaths": ["source","tests"], + "sourcePaths": ["tests"] + } + ], "dependencies": { "datefmt": "~>1.0.4" } diff --git a/source/singlog.d b/source/singlog.d index 52d316a..1d16720 100644 --- a/source/singlog.d +++ b/source/singlog.d @@ -1,9 +1,13 @@ module singlog; -import core.sys.posix.syslog; +version(Windows) + import core.sys.windows.windows; +else + import core.sys.posix.syslog; + +import std.string; import std.stdio; import std.conv; -import std.meta; import std.file; import std.datetime; import datefmt; @@ -40,57 +44,89 @@ alias log = Log.msg; log.debugging("Debugging message"); --- +/ -class Log -{ - private static Log log; - private string path; - private bool writeToFile = true; - private static SysTime time; +class Log { +private: + static Log log; + string path; + string nameProgram = "singlog"; + bool writeToFile = true; + + this() {} - // Target output - enum { +version(Windows) { + public enum { + DEBUGGING = 0, + ALERT = 1, + CRITICAL = 1, + ERROR = 1, + WARNING = 2, + NOTICE = 3, + INFORMATION = 3, + } + WORD[] sysLevel = [ + EVENTLOG_SUCCESS, + EVENTLOG_ERROR_TYPE, + EVENTLOG_WARNING_TYPE, + EVENTLOG_INFORMATION_TYPE + ]; + + void syslog(WORD priority, LPCSTR message) { + HANDLE handleEventLog = RegisterEventSourceA(NULL, this.nameProgram.toStringz()); + + if (handleEventLog == NULL) + return; + + ReportEventA(handleEventLog, priority, 0, 0, NULL, 1, 0, &message, NULL); + DeregisterEventSource(handleEventLog); + } +} else version(Posix) { + public enum { + DEBUGGING = 0, + ALERT = 1, + CRITICAL = 2, + ERROR = 3, + WARNING = 4, + NOTICE = 5, + INFORMATION = 6 + } + int[] sysLevel = [ + LOG_DEBUG, + LOG_ALERT, + LOG_CRIT, + LOG_ERR, + LOG_WARNING, + LOG_NOTICE, + LOG_INFO + ]; +} + + public enum { SYSLOG = 1, STDOUT = 2, FILE = 4 } - // Message output level - enum { - DEBUG = 0, - CRIT = 1, - ERR = 2, - WARNING = 3, - NOTICE = 4, - INFO = 5, - ALERT = 6 - } - int msgOutput = STDOUT; - int msgLevel = INFO; + int msgLevel = INFORMATION; - private this() {} - - private void writeLog(string message, int msgLevel, int priority) - { + void writeLog(string message, int msgLevel) { if (this.msgLevel > msgLevel) return; if (this.msgOutput & 1) - syslog(priority, (message ~ "\0").ptr); + syslog(sysLevel[msgLevel], message.toStringz()); if (this.msgOutput & 2) writeln(message); if (this.msgOutput & 4) writeFile(message); } - private void writeFile(string message) - { + void writeFile(string message) { if (!this.writeToFile) return; if (this.path.exists) this.writeToFile = true; - else - { + else { this.writeToFile = false; this.warning("The log file does not exist: " ~ this.path); } @@ -103,16 +139,16 @@ class Log } catch (Exception e) { this.writeToFile = false; this.error("Unable to open the log file " ~ this.path); - this.critical(e); + this.information(e); return; } try { - file.writeln(this.time.format("%Y.%m.%d %H:%M:%S: ") ~ message); + file.writeln(Clock.currTime().format("%Y.%m.%d %H:%M:%S: ") ~ message); } catch (Exception e) { this.writeToFile = false; this.error("Unable to write to the log file " ~ this.path); - this.critical(e); + this.information(e); return; } @@ -121,33 +157,31 @@ class Log } catch (Exception e) { this.writeToFile = false; this.error("Unable to close the log file " ~ this.path); - this.critical(e); + this.information(e); return; } } - @property static Log msg() - { +public: + @property static Log msg() { if (this.log is null) - { this.log = new Log; - this.time = Clock.currTime(); - } return this.log; } - void output(int msgOutput) { this.msgOutput = msgOutput; } - void level(int msgLevel) { this.msgLevel = msgLevel; } - void file(string path) { this.path = path; } + Log output(int msgOutput) { this.msgOutput = msgOutput; return this.log; } + Log name(string nameProgram) { this.nameProgram = nameProgram; return this.log; } + Log file(string path) { this.path = path; return this.log; } + Log level(int msgLevel) { this.msgLevel = msgLevel; return this.log; } - void alert(T)(T message) { writeLog(message.to!string, ALERT, LOG_ALERT); } - void critical(T)(T message) { writeLog(message.to!string, CRIT, LOG_CRIT); } - void error(T)(T message) { writeLog(message.to!string, ERR, LOG_ERR); } - void warning(T)(T message) { writeLog(message.to!string, WARNING, LOG_WARNING); } - void notice(T)(T message) { writeLog(message.to!string, NOTICE, LOG_NOTICE); } - void information(T)(T message) { writeLog(message.to!string, INFO, LOG_INFO); } - void debugging(T)(T message) {writeLog(message.to!string, DEBUG, LOG_DEBUG); } + void alert(T)(T message) { writeLog(message.to!string, ALERT); } + void critical(T)(T message) { writeLog(message.to!string, CRITICAL); } + void error(T)(T message) { writeLog(message.to!string, ERROR); } + void warning(T)(T message) { writeLog(message.to!string, WARNING); } + void notice(T)(T message) { writeLog(message.to!string, NOTICE); } + void information(T)(T message) { writeLog(message.to!string, INFORMATION); } + void debugging(T)(T message) { writeLog(message.to!string, DEBUG); } alias a = alert; alias c = critical; diff --git a/tests/test.d b/tests/test.d new file mode 100644 index 0000000..ad4fd32 --- /dev/null +++ b/tests/test.d @@ -0,0 +1,11 @@ +import singlog; + +void main(string[] argv) { + log.output(log.SYSLOG | log.STDOUT | log.FILE) + .name(argv[0]) + .level(Log.DEBUGGING) + .file("./test.log"); + log.e("hello!"); + log.w("hello!"); + log.i("hello!"); +} From dd68352dd33bdcd5e84b9b498cbd690e51f45bef Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Thu, 27 Apr 2023 23:53:45 +0300 Subject: [PATCH 2/3] change description --- CHANGELOG.md | 6 ++++++ README.md | 13 ++++++++++--- source/singlog.d | 4 ++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ed29d1..347204d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [v0.3.0](https://git.zhirov.kz/dlang/singlog/compare/v0.2.1...v0.3.0) (2023.04.28) + +### New + +- Windows OS Logging support + ## [v0.2.1](https://git.zhirov.kz/dlang/singlog/compare/v0.2.0...v0.2.1) (2023.03.29) ### New diff --git a/README.md b/README.md index 4ec6a2a..13e4d74 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ [![main](https://img.shields.io/badge/dynamic/json.svg?label=git.zhirov.kz&style=for-the-badge&url=https://git.zhirov.kz/api/v1/repos/dlang/singlog/tags&query=$[0].name&color=violet)](https://git.zhirov.kz/dlang/singlog) [![githab](https://img.shields.io/github/v/tag/AlexanderZhirov/singlog.svg?sort=semver&style=for-the-badge&color=blue&label=github)](https://github.com/AlexanderZhirov/singlog) [![dub](https://img.shields.io/dub/v/singlog.svg?sort=semver&style=for-the-badge&color=orange)](https://code.dlang.org/packages/singlog) +[![linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black)](https://www.linux.org/) +[![windows](https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white)](https://support.microsoft.com/en-US/windows) Singleton for simple logging @@ -25,6 +27,12 @@ void main() ## Examples +Setting the name of the logged program (it matters for Windows OS): + +```d +log.name("My program"); +``` + Setting the error output level: ```d @@ -42,7 +50,6 @@ Assigning a target output: ```d log.output(log.SYSLOG); log.output(log.STDOUT); - ``` Setup and allowing writing to a file: @@ -64,6 +71,6 @@ log.i("Information message") => log.information("Information message"); log.d("Debugging message") => log.debugging("Debugging message"); ``` -## Dub +## DUB -Add a dependency on `"singlog": "~>0.2.1"`. +Add a dependency on `"singlog": "~>0.3.0"`. diff --git a/source/singlog.d b/source/singlog.d index 1d16720..717c1a6 100644 --- a/source/singlog.d +++ b/source/singlog.d @@ -18,6 +18,8 @@ alias log = Log.msg; Singleton for simple logging --- + // Setting the name of the logged program + log.name("My program"); // Setting the error output level log.level(log.DEBUG); log.level(log.ALERT); @@ -32,8 +34,6 @@ alias log = Log.msg; log.output(log.FILE); // Setup and allowing writing to a file log.file("./file.log"); - log.fileOn(); - log.fileOff(); // Output of messages to the log log.alert("Alert message"); log.critical("Critical message"); From c8e73f989c480ccea443e601fe5f3b3ecaebcad7 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Fri, 28 Apr 2023 11:32:15 +0300 Subject: [PATCH 3/3] release v0.3.0 --- CHANGELOG.md | 2 ++ README.md | 20 +++++++++++--------- dub.json | 2 -- singlog.png | Bin 0 -> 11435 bytes source/singlog.d | 4 +--- tests/test.d | 16 +++++++++------- 6 files changed, 23 insertions(+), 21 deletions(-) create mode 100644 singlog.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 347204d..f6bd7b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [v0.3.0](https://git.zhirov.kz/dlang/singlog/compare/v0.2.1...v0.3.0) (2023.04.28) +- Minor changes + ### New - Windows OS Logging support diff --git a/README.md b/README.md index 13e4d74..c81f8a5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# singlog +![singlog](singlog.png) [![license](https://img.shields.io/github/license/AlexanderZhirov/singlog.svg?sort=semver&style=for-the-badge&color=green)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html) [![main](https://img.shields.io/badge/dynamic/json.svg?label=git.zhirov.kz&style=for-the-badge&url=https://git.zhirov.kz/api/v1/repos/dlang/singlog/tags&query=$[0].name&color=violet)](https://git.zhirov.kz/dlang/singlog) @@ -14,14 +14,16 @@ Singleton for simple logging ```d import singlog; -void main() -{ - log.level(log.DEBUG); - // write to syslog and file - log.output(log.SYSLOG | log.FILE); - log.file("./file.log"); - log.warning("Hello, World!"); - log.w("The same thing"); +void main(string[] argv) { + log.output(log.SYSLOG | log.STDOUT | log.FILE) // write to syslog, standard output stream and file + .name(argv[0]) // program name as an identifier (for Windows OS) + .level(log.DEBUGGING) // logging level + .file("./test.log"); // the path to the log file + + log.e("This is an error message"); + log.error("And this is also an error message"); + log.w("This is a warning message"); + log.i("This is an information message"); } ``` diff --git a/dub.json b/dub.json index a2c64ed..97549a7 100644 --- a/dub.json +++ b/dub.json @@ -7,8 +7,6 @@ "license": "GPL-2.0", "copyright": "© Alexander Zhirov, 2023", "description": "Singleton for simple logging", - "targetType": "library", - "targetPath": "lib", "targetName": "singlog", "configurations": [ { diff --git a/singlog.png b/singlog.png new file mode 100644 index 0000000000000000000000000000000000000000..966037c696564b63c36581b79f0fac469cf653e2 GIT binary patch literal 11435 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