+package org.simantics.audit;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystemException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class AuditLogging {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AuditLogging.class);
+
+ private static ObjectMapper mapper = new ObjectMapper();
+
+ public enum Level {
+ INFO,
+ ERROR,
+ TRACE
+ }
+
+ public static String register(String id) throws AuditLoggingException {
+ try {
+ String entryRoot = id + "_" + UUID.randomUUID().toString();
+ Files.createDirectories(getEntryRoot(entryRoot));
+ return entryRoot;
+ } catch (Exception e) {
+ throw new AuditLoggingException("Could not register service with id " + id, e);
+ }
+ }
+
+ public static List<String> getLogEvents(String uuid, String level, String startDate, String endDate) throws AuditLoggingException {
+ Path entryRoot = getEntryRoot(uuid);
+ try {
+ LocalDate localStartDate = LocalDate.parse(startDate);
+ LocalDate localEndDate = LocalDate.parse(endDate).plusDays(1);
+ List<String> allLines = new ArrayList<>();
+ while (localStartDate.isBefore(localEndDate)) {
+ String fileName = resolveLogFileName(uuid, Level.valueOf(level.toUpperCase()), localStartDate);
+ try {
+ List<String> lines = Files.readAllLines(entryRoot.resolve(fileName));
+ allLines.addAll(lines);
+ } catch (FileSystemException e) {
+ // presumably file not found but lets not throw this cause forward, log here
+ LOGGER.error("Could not read file {}", fileName, e);
+ } finally {
+ localStartDate = localStartDate.plusDays(1);
+ }
+ }
+ return allLines;
+ } catch (Exception e) {
+ throw new AuditLoggingException(e);
+ }
+ }
+
+
+ public static Path getEntryRoot(String uuid) {
+ return Activator.getLogLocation().resolve(uuid);
+ }
+
+ public static Path getLogFile(String uuid, Level level) {
+ Path root = getEntryRoot(uuid);
+ String fileName = resolveLogFileName(uuid, level, LocalDate.now());
+ return root.resolve(fileName);
+ }
+
+ private static String resolveLogFileName(String uuid, Level level, LocalDate date) {
+ return date.toString() + "_" + uuid + "." + level.toString().toLowerCase();
+ }
+
+ public static void log(String uuid, Map<String, Object> json) throws AuditLoggingException {
+ write(uuid, Level.INFO, json);
+ }
+
+ public static void error(String uuid, Map<String, Object> json) throws AuditLoggingException {
+ write(uuid, Level.ERROR, json);
+ }
+
+ public static void trace(String uuid, Map<String, Object> json) throws AuditLoggingException {
+ write(uuid, Level.TRACE, json);
+ }
+
+ private static void write(String uuid, Level level, Map<String, Object> json) throws AuditLoggingException {
+ Map<String, Object> wrappedAuditEvent = wrapAndAddAuditMetadata(uuid, level, json);
+ try {
+ String jsonLine = mapper.writeValueAsString(wrappedAuditEvent);
+ Path logFile = getLogFile(uuid, level);
+ if (!Files.exists(logFile))
+ Files.createFile(logFile);
+ String lineWithNewline = jsonLine + "\n";
+ Files.write(logFile, lineWithNewline.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE, StandardOpenOption.APPEND);
+ } catch (JsonProcessingException e) {
+ throw new AuditLoggingException("Could not serialize input", e);
+ } catch (IOException e) {
+ throw new AuditLoggingException("Could not write line to log", e);
+ }
+ }
+
+ private static final String timestamp = "timestamp";
+
+ private static Map<String, Object> wrapAndAddAuditMetadata(String uuid, Level level, Map<String, Object> original) {
+ Map<String, Object> wrapped = new HashMap<>();
+ long newValue = System.currentTimeMillis();
+ Object possibleExisting = wrapped.put(timestamp, newValue);
+ if (possibleExisting != null) {
+ LOGGER.warn("Replacing existing value {} for key {} - new value is {}", possibleExisting, timestamp, newValue);
+ }
+ possibleExisting = wrapped.put("uuid", uuid);
+ if (possibleExisting != null) {
+ LOGGER.warn("Replacing existing value {} for key {} - new value is {}", possibleExisting, "uuid", uuid);
+ }
+ possibleExisting = wrapped.put("level", level.toString());
+ if (possibleExisting != null) {
+ LOGGER.warn("Replacing existing value {} for key {} - new value is {}", possibleExisting, "level", level.toString());
+ }
+ wrapped.put("original", original);
+ return wrapped;
+ }
+}