2024-01-08 23:13:17 +01:00
// What is this module called?
module log ;
// What does this module require to function?
2016-08-04 23:35:58 +02:00
import std.stdio ;
2018-03-14 05:43:40 +01:00
import std.file ;
import std.datetime ;
2024-01-08 23:13:17 +01:00
import std.concurrency ;
import std.typecons ;
2024-02-12 06:33:44 +01:00
import core.sync.condition ;
2024-01-08 23:13:17 +01:00
import core.sync.mutex ;
import core.thread ;
import std.format ;
import std.string ;
2018-12-05 20:19:00 +01:00
version ( Notifications ) {
import dnotify ;
}
2018-04-07 09:06:57 +02:00
2024-01-08 23:13:17 +01:00
// Shared module object
shared LogBuffer logBuffer ;
2024-01-27 00:03:46 +01:00
// Timer for logging
shared MonoTime lastInsertedTime ;
2024-01-08 23:13:17 +01:00
class LogBuffer {
private :
2024-02-12 06:45:55 +01:00
string [ 3 ] [ ] buffer ;
Mutex bufferLock ;
2024-02-12 06:33:44 +01:00
Condition condReady ;
2024-02-12 06:45:55 +01:00
string logFilePath ;
bool writeToFile ;
bool verboseLogging ;
bool debugLogging ;
Thread flushThread ;
bool isRunning ;
2024-01-08 23:13:17 +01:00
bool sendGUINotification ;
public :
this ( bool verboseLogging , bool debugLogging ) {
// Initialise the mutex
bufferLock = new Mutex ( ) ;
2024-02-12 06:33:44 +01:00
condReady = new Condition ( bufferLock ) ;
2024-01-08 23:13:17 +01:00
// Initialise other items
this . logFilePath = logFilePath ;
this . writeToFile = writeToFile ;
this . verboseLogging = verboseLogging ;
this . debugLogging = debugLogging ;
this . isRunning = true ;
this . sendGUINotification = true ;
this . flushThread = new Thread ( & flushBuffer ) ;
flushThread . isDaemon ( true ) ;
flushThread . start ( ) ;
}
2024-02-12 06:33:44 +01:00
void shutdown ( ) {
synchronized ( bufferLock ) {
isRunning = false ;
condReady . notify ( ) ;
}
2024-01-08 23:13:17 +01:00
flushThread . join ( ) ;
flush ( ) ;
2024-02-12 06:33:44 +01:00
}
2024-01-08 23:13:17 +01:00
shared void logThisMessage ( string message , string [ ] levels = [ "info" ] ) {
// Generate the timestamp for this log entry
auto timeStamp = leftJustify ( Clock . currTime ( ) . toString ( ) , 28 , '0' ) ;
synchronized ( bufferLock ) {
foreach ( level ; levels ) {
// Normal application output
if ( ! debugLogging ) {
if ( ( level = = "info" ) | | ( ( verboseLogging ) & & ( level = = "verbose" ) ) | | ( level = = "logFileOnly" ) | | ( level = = "consoleOnly" ) | | ( level = = "consoleOnlyNoNewLine" ) ) {
// Add this message to the buffer, with this format
buffer ~ = [ timeStamp , level , format ( "%s" , message ) ] ;
}
} else {
// Debug Logging (--verbose --verbose | -v -v | -vv) output
// Add this message, regardless of 'level' to the buffer, with this format
buffer ~ = [ timeStamp , level , format ( "DEBUG: %s" , message ) ] ;
// If there are multiple 'levels' configured, ignore this and break as we are doing debug logging
break ;
}
// Submit the message to the dbus / notification daemon for display within the GUI being used
// Will not send GUI notifications when running in debug mode
if ( ( ! debugLogging ) & & ( level = = "notify" ) ) {
version ( Notifications ) {
if ( sendGUINotification ) {
notify ( message ) ;
}
}
}
}
2024-02-12 06:33:44 +01:00
( cast ( ) condReady ) . notify ( ) ;
2024-01-08 23:13:17 +01:00
}
}
shared void notify ( string message ) {
// Use dnotify's functionality for GUI notifications, if GUI notifications is enabled
version ( Notifications ) {
2024-03-10 01:18:54 +01:00
try {
auto n = new Notification ( "Log Notification" , message , "IGNORED" ) ;
n . show ( ) ;
} catch ( NotificationError e ) {
sendGUINotification = false ;
addLogEntry ( "Unable to send notification; disabled in the following: " ~ e . message ) ;
}
2018-12-05 20:19:00 +01:00
}
2024-01-08 23:13:17 +01:00
}
private void flushBuffer ( ) {
while ( isRunning ) {
flush ( ) ;
}
2024-02-12 06:33:44 +01:00
stdout . flush ( ) ;
2024-01-08 23:13:17 +01:00
}
2022-01-02 00:22:23 +01:00
2024-01-08 23:13:17 +01:00
private void flush ( ) {
string [ 3 ] [ ] messages ;
synchronized ( bufferLock ) {
2024-02-12 06:33:44 +01:00
while ( buffer . empty & & isRunning ) {
condReady . wait ( ) ;
}
2024-01-08 23:13:17 +01:00
messages = buffer ;
buffer . length = 0 ;
}
foreach ( msg ; messages ) {
// timestamp, logLevel, message
// Always write the log line to the console, if level != logFileOnly
if ( msg [ 1 ] ! = "logFileOnly" ) {
// Console output .. what sort of output
if ( msg [ 1 ] = = "consoleOnlyNoNewLine" ) {
// This is used write out a message to the console only, without a new line
// This is used in non-verbose mode to indicate something is happening when downloading JSON data from OneDrive or when we need user input from --resync
write ( msg [ 2 ] ) ;
} else {
// write this to the console with a new line
writeln ( msg [ 2 ] ) ;
}
}
// Was this just console only output?
if ( ( msg [ 1 ] ! = "consoleOnlyNoNewLine" ) & & ( msg [ 1 ] ! = "consoleOnly" ) ) {
// Write to the logfile only if configured to do so - console only items should not be written out
if ( writeToFile ) {
string logFileLine = format ( "[%s] %s" , msg [ 0 ] , msg [ 2 ] ) ;
std . file . append ( logFilePath , logFileLine ~ "\n" ) ;
}
}
}
}
2018-04-07 09:06:57 +02:00
}
2024-01-08 23:13:17 +01:00
// Function to initialize the logging system
void initialiseLogging ( bool verboseLogging = false , bool debugLogging = false ) {
logBuffer = cast ( shared ) new LogBuffer ( verboseLogging , debugLogging ) ;
2024-01-27 00:03:46 +01:00
lastInsertedTime = MonoTime . currTime ( ) ;
2018-12-05 20:19:00 +01:00
}
2020-05-20 03:37:11 +02:00
2024-01-08 23:13:17 +01:00
// Function to add a log entry with multiple levels
void addLogEntry ( string message = "" , string [ ] levels = [ "info" ] ) {
logBuffer . logThisMessage ( message , levels ) ;
2020-05-20 03:37:11 +02:00
}
2024-02-04 05:34:45 +01:00
void addProcessingLogHeaderEntry ( string message , long verbosityCount ) {
if ( verbosityCount = = 0 ) {
addLogEntry ( message , [ "logFileOnly" ] ) ;
// Use the dots to show the application is 'doing something' if verbosityCount == 0
addLogEntry ( message ~ " ." , [ "consoleOnlyNoNewLine" ] ) ;
} else {
// Fallback to normal logging if in verbose or above level
addLogEntry ( message ) ;
}
2024-01-27 00:03:46 +01:00
}
void addProcessingDotEntry ( ) {
if ( MonoTime . currTime ( ) - lastInsertedTime < dur ! "seconds" ( 1 ) ) {
// Don't flood the log buffer
return ;
}
lastInsertedTime = MonoTime . currTime ( ) ;
addLogEntry ( "." , [ "consoleOnlyNoNewLine" ] ) ;
}
2024-01-08 23:13:17 +01:00
// Function to set logFilePath and enable logging to a file
void enableLogFileOutput ( string configuredLogFilePath ) {
logBuffer . logFilePath = configuredLogFilePath ;
logBuffer . writeToFile = true ;
2020-05-20 03:37:11 +02:00
}
2024-01-08 23:13:17 +01:00
void disableGUINotifications ( bool userConfigDisableNotifications ) {
logBuffer . sendGUINotification = userConfigDisableNotifications ;
}