277 lines
8.2 KiB
Java
277 lines
8.2 KiB
Java
package de.opcua.app.logging;
|
|
|
|
import java.io.*;
|
|
import java.nio.file.*;
|
|
import java.time.LocalDateTime;
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.util.concurrent.locks.ReentrantLock;
|
|
import java.util.zip.GZIPOutputStream;
|
|
|
|
/**
|
|
* JavaScript execution logger with rotation and debug levels
|
|
*/
|
|
public class ScriptLogger {
|
|
|
|
public enum Level {
|
|
DEBUG(0), INFO(1), WARN(2), ERROR(3), OFF(4);
|
|
|
|
private final int value;
|
|
Level(int value) { this.value = value; }
|
|
public int getValue() { return value; }
|
|
}
|
|
|
|
private final String logDirectory;
|
|
private final String logFileName;
|
|
private final long maxFileSize;
|
|
private final int maxBackupFiles;
|
|
private Level currentLevel;
|
|
private boolean enabled;
|
|
|
|
private PrintWriter writer;
|
|
private long currentFileSize;
|
|
private final ReentrantLock lock;
|
|
private final DateTimeFormatter formatter;
|
|
|
|
public ScriptLogger(String logDirectory, boolean enabled, Level level) {
|
|
this.logDirectory = logDirectory;
|
|
this.logFileName = "javascript.log";
|
|
this.maxFileSize = 10 * 1024 * 1024; // 10 MB
|
|
this.maxBackupFiles = 5;
|
|
this.currentLevel = level;
|
|
this.enabled = enabled;
|
|
this.lock = new ReentrantLock();
|
|
this.formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
|
|
|
if (enabled) {
|
|
initializeLogger();
|
|
}
|
|
}
|
|
|
|
private void initializeLogger() {
|
|
try {
|
|
Path logDir = Paths.get(logDirectory);
|
|
if (!Files.exists(logDir)) {
|
|
Files.createDirectories(logDir);
|
|
}
|
|
|
|
Path logFile = Paths.get(logDirectory, logFileName);
|
|
if (Files.exists(logFile)) {
|
|
currentFileSize = Files.size(logFile);
|
|
} else {
|
|
currentFileSize = 0;
|
|
}
|
|
|
|
writer = new PrintWriter(new BufferedWriter(
|
|
new FileWriter(logFile.toFile(), true)), true);
|
|
|
|
log(Level.INFO, "JavaScript logger initialized");
|
|
|
|
} catch (IOException e) {
|
|
System.err.println("[ScriptLogger] Failed to initialize: " + e.getMessage());
|
|
enabled = false;
|
|
}
|
|
}
|
|
|
|
public void setEnabled(boolean enabled) {
|
|
lock.lock();
|
|
try {
|
|
if (enabled && !this.enabled) {
|
|
initializeLogger();
|
|
} else if (!enabled && this.enabled) {
|
|
closeLogger();
|
|
}
|
|
this.enabled = enabled;
|
|
} finally {
|
|
lock.unlock();
|
|
}
|
|
}
|
|
|
|
public void setLevel(Level level) {
|
|
this.currentLevel = level;
|
|
}
|
|
|
|
public boolean isEnabled() {
|
|
return enabled;
|
|
}
|
|
|
|
public Level getLevel() {
|
|
return currentLevel;
|
|
}
|
|
|
|
public void debug(String message, Object... args) {
|
|
log(Level.DEBUG, message, args);
|
|
}
|
|
|
|
public void info(String message, Object... args) {
|
|
log(Level.INFO, message, args);
|
|
}
|
|
|
|
public void warn(String message, Object... args) {
|
|
log(Level.WARN, message, args);
|
|
}
|
|
|
|
public void error(String message, Object... args) {
|
|
log(Level.ERROR, message, args);
|
|
}
|
|
|
|
public void error(String message, Throwable throwable) {
|
|
if (!shouldLog(Level.ERROR)) return;
|
|
|
|
log(Level.ERROR, message);
|
|
|
|
lock.lock();
|
|
try {
|
|
if (writer != null) {
|
|
throwable.printStackTrace(writer);
|
|
writer.flush();
|
|
}
|
|
} finally {
|
|
lock.unlock();
|
|
}
|
|
}
|
|
|
|
private void log(Level level, String message, Object... args) {
|
|
if (!shouldLog(level)) return;
|
|
|
|
String formattedMessage = args.length > 0
|
|
? String.format(message, args)
|
|
: message;
|
|
|
|
String logLine = String.format("[%s] [%s] %s%n",
|
|
LocalDateTime.now().format(formatter),
|
|
level.name(),
|
|
formattedMessage
|
|
);
|
|
|
|
lock.lock();
|
|
try {
|
|
if (writer != null) {
|
|
writer.print(logLine);
|
|
writer.flush();
|
|
|
|
currentFileSize += logLine.length();
|
|
|
|
if (currentFileSize >= maxFileSize) {
|
|
rotateLog();
|
|
}
|
|
}
|
|
|
|
// Also print to console in debug mode
|
|
if (level == Level.DEBUG || level == Level.ERROR) {
|
|
System.out.print(logLine);
|
|
}
|
|
|
|
} finally {
|
|
lock.unlock();
|
|
}
|
|
}
|
|
|
|
private boolean shouldLog(Level level) {
|
|
return enabled && level.getValue() >= currentLevel.getValue();
|
|
}
|
|
|
|
private void rotateLog() {
|
|
try {
|
|
closeLogger();
|
|
|
|
Path currentLog = Paths.get(logDirectory, logFileName);
|
|
|
|
// Delete oldest backup if exists
|
|
Path oldestBackup = Paths.get(logDirectory,
|
|
logFileName + "." + maxBackupFiles + ".gz");
|
|
if (Files.exists(oldestBackup)) {
|
|
Files.delete(oldestBackup);
|
|
}
|
|
|
|
// Shift existing backups
|
|
for (int i = maxBackupFiles - 1; i >= 1; i--) {
|
|
Path from = Paths.get(logDirectory, logFileName + "." + i + ".gz");
|
|
Path to = Paths.get(logDirectory, logFileName + "." + (i + 1) + ".gz");
|
|
if (Files.exists(from)) {
|
|
Files.move(from, to, StandardCopyOption.REPLACE_EXISTING);
|
|
}
|
|
}
|
|
|
|
// Compress and move current log to .1.gz
|
|
Path backupLog = Paths.get(logDirectory, logFileName + ".1.gz");
|
|
compressFile(currentLog, backupLog);
|
|
|
|
// Delete original
|
|
Files.delete(currentLog);
|
|
|
|
// Reinitialize
|
|
currentFileSize = 0;
|
|
writer = new PrintWriter(new BufferedWriter(
|
|
new FileWriter(currentLog.toFile(), true)), true);
|
|
|
|
log(Level.INFO, "Log rotated - new file created");
|
|
|
|
} catch (IOException e) {
|
|
System.err.println("[ScriptLogger] Failed to rotate log: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
private void compressFile(Path source, Path target) throws IOException {
|
|
try (FileInputStream fis = new FileInputStream(source.toFile());
|
|
FileOutputStream fos = new FileOutputStream(target.toFile());
|
|
GZIPOutputStream gzipOS = new GZIPOutputStream(fos)) {
|
|
|
|
byte[] buffer = new byte[8192];
|
|
int len;
|
|
while ((len = fis.read(buffer)) != -1) {
|
|
gzipOS.write(buffer, 0, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void closeLogger() {
|
|
if (writer != null) {
|
|
writer.close();
|
|
writer = null;
|
|
}
|
|
}
|
|
|
|
public void close() {
|
|
lock.lock();
|
|
try {
|
|
closeLogger();
|
|
} finally {
|
|
lock.unlock();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log script execution with context
|
|
*/
|
|
public void logScriptExecution(String actionName, String nodeId,
|
|
String trigger, String value,
|
|
boolean success, String result) {
|
|
if (!enabled) return;
|
|
|
|
Level level = success ? Level.INFO : Level.ERROR;
|
|
|
|
log(level,
|
|
"Script: %s | Node: %s | Trigger: %s | Value: %s | Success: %s | Result: %s",
|
|
actionName, nodeId, trigger, value, success, result
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get current log file content (last N lines)
|
|
*/
|
|
public String getRecentLogs(int lines) {
|
|
Path logFile = Paths.get(logDirectory, logFileName);
|
|
if (!Files.exists(logFile)) {
|
|
return "No logs available";
|
|
}
|
|
|
|
try {
|
|
java.util.List<String> allLines = Files.readAllLines(logFile);
|
|
int start = Math.max(0, allLines.size() - lines);
|
|
return String.join("\n", allLines.subList(start, allLines.size()));
|
|
} catch (IOException e) {
|
|
return "Error reading logs: " + e.getMessage();
|
|
}
|
|
}
|
|
}
|