opcua-service/main/java/de/opcua/app/logging/ScriptLogger.java
2026-05-11 19:40:18 +02:00

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();
}
}
}