Environment
.evn
ENV=TEST
BVB_LOG=someapp/logs/wrapper.log
BVB_START=09:00
BVB_END=18:50
FROM=admin@example.com
MAIL_TO=admin@example.com
MAIL_CC=admin@example.com
ASSIGN_TO=admin@example.com
BACKUP_TO=adminBB@example.com
# Log config (1 MB, append on existing file on restart)
LOG_PATH=logs/watchdog.log
LOG_MAX_SIZE=1048576
LOG_ROTATE_COUNT=2
LOG_APPEND=true
Application
App.java
package watchdog;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import io.github.cdimascio.dotenv.Dotenv;
public class App {
private static Dotenv dotenv;
private final static int HOURS_16 = 60*60*16;
private final static int HOURS_12 = 60*60*12;
private static String weekDay;
private static String currTime;
public static void main(String[] args) throws InterruptedException {
dotenv = Dotenv.configure().load();
Tools.initLogger(dotenv);
int START_TIME = Tools.time(dotenv.get("WATCH_START"));
int END_TIME = Tools.time(dotenv.get("WATCH_END"));
File FILE_WATCHED = new File(dotenv.get("WATCH_LOG"));
Date fileDate; String fileTime; long diff;
Tools.print("Watchdog starting | ENV=" + dotenv.get("ENV") +
" | LOG_PATH=" + dotenv.get("LOG_PATH") +
" | LIMIT=" + dotenv.get("LOG_MAX_SIZE") +
" | ROTATE=" + dotenv.get("LOG_ROTATE_COUNT"), Tools.ANSI_CYAN);
while(true) {
Tools.sleep(10);
weekDay = new SimpleDateFormat("EEEE").format(new Date());
if (weekDay.equals("Saturday") || weekDay.equals("Sunday")) {
Tools.print("WATCH / Watch scheduled not to run on weekends / " + currTime);
Tools.sleep(HOURS_16);
continue;
}
if (Tools.now("HHmm") < START_TIME) {
Tools.print("WATCH / watch will start at ... " + START_TIME);
continue;
} else
if (Tools.now("HHmm") >= END_TIME) {
Tools.print("WATCH / watch is going to sleep ... 12h");
Tools.sleep(HOURS_12);
continue;
}
fileDate = new Date(FILE_WATCHED.lastModified());
fileTime = new SimpleDateFormat("HH:mm:ss").format(fileDate);
diff = (new Date().getTime() - FILE_WATCHED.lastModified()) / 1000;
Tools.print("Watch is running ... ", Tools.ANSI_YELLOW);
if ( diff > 120) {
Tools.print("WATCH is offline / " + fileTime + " / " + diff + "s diff", Tools.ANSI_PURPLE);
Tools.sendAlert(dotenv);
break;
}
Tools.print("WATCH OK / " + fileTime + " / 120s check, " + diff + "s diff", Tools.ANSI_GREEN);
}
}
}
Tools.java
package watchdog;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import io.github.cdimascio.dotenv.Dotenv;
class Tools {
private static Dotenv dotenv;
private static Logger logger;
private static boolean loggerInitialized = false;
private static Level logLevel = Level.INFO;
public static final String ANSI_RESET = "\033[0m";
public static final String ANSI_BLACK = "\033[30m";
public static final String ANSI_RED = "\033[31m";
public static final String ANSI_GREEN = "\033[32m";
public static final String ANSI_YELLOW = "\033[33m";
public static final String ANSI_BLUE = "\033[34m";
public static final String ANSI_PURPLE = "\033[35m";
public static final String ANSI_CYAN = "\033[36m";
public static final String ANSI_WHITE = "\033[37m";
public static synchronized void initLogger(Dotenv env) {
if (loggerInitialized) return;
dotenv = env;
logger = Logger.getLogger("WatchdogLogger");
logger.setUseParentHandlers(false);
String logPath = dotenv.get("LOG_PATH");
int maxBytes = Integer.parseInt(dotenv.get("LOG_MAX_SIZE"));
int count = Integer.parseInt(dotenv.get("LOG_ROTATE_COUNT"));
boolean append = Boolean.parseBoolean(dotenv.get("LOG_APPEND"));
try {
FileHandler fh = new FileHandler(logPath, maxBytes, count, append);
fh.setLevel(logLevel);
fh.setFormatter(new Formatter() {
@Override
public String format(LogRecord record) {
return String.format(
"%1$tF %1$tT [%2$s] %3$s%n",
new Date(record.getMillis()),
record.getLevel().getName(),
record.getMessage()
);
}
});
logger.addHandler(fh);
} catch (IOException e) {
System.out.println("Failed to setup file logging: " + e.getMessage());
}
loggerInitialized = true;
logger.info("Logging initialized: level=" + logLevel + ", path=" + logPath);
}
public static void print(String message) {
print(message, ANSI_WHITE);
}
public static void print(String message, String color) {
System.out.println(color + message + ANSI_RESET);
logger.log(Level.INFO, message);
}
public static int time(String start) {
return Integer.parseInt(start.replace(":", ));
}
public static int now(String frm) {
return Integer.parseInt(new SimpleDateFormat(frm).format(new Date()));
}
public static File getLastFile(String dir) {
File[] files = new File(dir).listFiles();
Arrays.sort(files,
Comparator.comparingLong(File::lastModified).reversed()
);
File lastFile = files[0];
if (lastFile.isDirectory()) {
lastFile = getLastFile(lastFile.toString());
}
return lastFile;
}
public static void sendAlert(Dotenv env) {
dotenv = env;
Properties propsMail = new Properties();
propsMail.put("mail.smtp.host", "smtp.brd.ro");
Session session = Session.getDefaultInstance(propsMail, null);
String msgText = +
"Please check the server: \n " +
"MY_SERVER / WATCH \n\n" +
"Assigned to: " + dotenv.get("ASSIGN_TO") + "\n" +
"Backup: " + dotenv.get("BACKUP_TO") +
;
final String from = dotenv.get("FROM");
final String to = dotenv.get("MAIL_TO");
final String cc = dotenv.get("MAIL_CC");
final String classification = "C1";
final String subject = "[" + classification + "] Watch 02P Alert - WATCH";
try{
Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(from));
msg.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
msg.setRecipient(Message.RecipientType.CC, new InternetAddress(cc));
msg.setSubject(subject);
msg.setText(msgText);
msg.setHeader("X-Classification", classification);
msg.setHeader("Sensitivity", "BRD-Restricted");
Transport.send(msg);
System.out.println("Watch 02 Alert - sent");
} catch (MessagingException e) {
e.printStackTrace();
System.err.println("Failed to send email.");
}
}
public static void sleep(int seconds) {
try { TimeUnit.SECONDS.sleep(seconds); } catch(InterruptedException e) {}
}
}
Serive Wrapper (Python)
" WATCHDOG SERVICE - WRAPPER
python .\service\watchdog_service.py install
python .\service\watchdog_service.py start
python .\service\watchdog_service.py stop
python .\service\watchdog_service.py remove
"
import win32serviceutil
import win32service
import win32event
import servicemanager
import subprocess
import sys
import os
JAVA_EXE = r"C:\Program Files\Java\jdk-1.8_462\bin\java.exe"
JAR_PATH = r"D:\bvb\watchdog\watchdog-1.3.jar"
ENV_PATH = r"D:\bvb\watchdog\.env"
WORK_DIR = r"D:\bvb\watchdog"
class WatchdogService(win32serviceutil.ServiceFramework):
_svc_name_ = "WatchdogService"
_svc_display_name_ = "Watchdog v1.3"
_svc_description_ = "Monitors log file and alerts when stale"
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
self.process = None
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
if self.child:
try:
self.child.terminate()
self.child.wait(timeout=10) # wait for JVM to exit
except Exception:
pass
win32event.SetEvent(self.hWaitStop)
if self.process:
self.process.terminate()
def SvcDoRun(self):
servicemanager.LogInfoMsg("Starting Watchdog Service...")
# Ensure working dir and environment
os.chdir(WORK_DIR)
cmd = [JAVA_EXE, "-jar", JAR_PATH, f"-Ddotenv.file={ENV_PATH}"]
# Start Watchdog app
self.child = subprocess.Popen(cmd, cwd=WORK_DIR)
win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)
if __name__ == '__main__':
win32serviceutil.HandleCommandLine(WatchdogService)