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); } } /** * Gets audit events for the last 5 days * * @param uuid * @param level * @return * @throws AuditLoggingException */ public static List getLogEventsDays(String uuid, String level, int days) throws AuditLoggingException { LocalDate endDate = LocalDate.now().plusDays(1); LocalDate startDate = endDate.minusDays(days); return getLogEvents(uuid, level, startDate, endDate); } public static List getLogEvents(String uuid, String level, String startDate, String endDate) throws AuditLoggingException { try { LocalDate localStartDate = LocalDate.parse(startDate); LocalDate localEndDate = LocalDate.parse(endDate).plusDays(1); return getLogEvents(uuid, level, localStartDate, localEndDate); } catch (Exception e) { throw new AuditLoggingException(e); } } private static List getLogEvents(String uuid, String level, LocalDate localStartDate, LocalDate localEndDate) throws AuditLoggingException { Path entryRoot = getEntryRoot(uuid); try { List allLines = new ArrayList<>(); while (localStartDate.isBefore(localEndDate)) { String fileName = resolveLogFileName(uuid, Level.valueOf(level.toUpperCase()), localStartDate); try { Path fileToRead = entryRoot.resolve(fileName); if (Files.exists(fileToRead)) { List lines = Files.readAllLines(fileToRead); allLines.addAll(lines); } else { LOGGER.info("No logging events for " + fileName); } } 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 json) throws AuditLoggingException { write(uuid, Level.INFO, json); } public static void error(String uuid, Map json) throws AuditLoggingException { write(uuid, Level.ERROR, json); } public static void trace(String uuid, Map json) throws AuditLoggingException { write(uuid, Level.TRACE, json); } private static void write(String uuid, Level level, Map json) throws AuditLoggingException { Map 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 wrapAndAddAuditMetadata(String uuid, Level level, Map original) { Map 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; } }