1 package org.simantics.audit;
3 import java.io.IOException;
4 import java.nio.charset.StandardCharsets;
5 import java.nio.file.FileSystemException;
6 import java.nio.file.Files;
7 import java.nio.file.Path;
8 import java.nio.file.StandardOpenOption;
9 import java.time.LocalDate;
10 import java.util.ArrayList;
11 import java.util.HashMap;
12 import java.util.List;
14 import java.util.UUID;
16 import org.slf4j.Logger;
17 import org.slf4j.LoggerFactory;
19 import com.fasterxml.jackson.core.JsonProcessingException;
20 import com.fasterxml.jackson.databind.ObjectMapper;
22 public class AuditLogging {
24 private static final Logger LOGGER = LoggerFactory.getLogger(AuditLogging.class);
26 private static ObjectMapper mapper = new ObjectMapper();
34 public static String register(String id) throws AuditLoggingException {
36 String entryRoot = id + "_" + UUID.randomUUID().toString();
37 Files.createDirectories(getEntryRoot(entryRoot));
39 } catch (Exception e) {
40 throw new AuditLoggingException("Could not register service with id " + id, e);
44 public static Map<String, List<String>> allLogEvents(String level, int days) throws AuditLoggingException {
45 Map<String, List<String>> results = new HashMap<>();
47 Files.walk(Activator.getLogLocation()).forEach(uuid -> {
48 String fileName = uuid.getFileName().toString();
50 List<String> events = getLogEventsDays(fileName, level, days);
51 results.put(fileName, events);
52 } catch (AuditLoggingException e) {
53 LOGGER.error("Could not get audit log events for {}", fileName, e);
56 } catch (IOException e) {
57 throw new AuditLoggingException(e);
63 * Gets audit events for the last 5 days
68 * @throws AuditLoggingException
70 public static List<String> getLogEventsDays(String uuid, String level, int days) throws AuditLoggingException {
71 LocalDate endDate = LocalDate.now().plusDays(1);
72 LocalDate startDate = endDate.minusDays(days);
73 return getLogEvents(uuid, level, startDate, endDate);
76 public static List<String> getLogEvents(String uuid, String level, String startDate, String endDate) throws AuditLoggingException {
78 LocalDate localStartDate = LocalDate.parse(startDate);
79 LocalDate localEndDate = LocalDate.parse(endDate).plusDays(1);
80 return getLogEvents(uuid, level, localStartDate, localEndDate);
81 } catch (Exception e) {
82 throw new AuditLoggingException(e);
86 private static List<String> getLogEvents(String uuid, String level, LocalDate localStartDate, LocalDate localEndDate) throws AuditLoggingException {
87 Path entryRoot = getEntryRoot(uuid);
89 List<String> allLines = new ArrayList<>();
90 while (localStartDate.isBefore(localEndDate)) {
91 String fileName = resolveLogFileName(uuid, Level.valueOf(level.toUpperCase()), localStartDate);
93 Path fileToRead = entryRoot.resolve(fileName);
94 if (Files.exists(fileToRead)) {
95 List<String> lines = Files.readAllLines(fileToRead);
96 allLines.addAll(lines);
98 LOGGER.info("No logging events for " + fileName);
100 } catch (FileSystemException e) {
101 // presumably file not found but lets not throw this cause forward, log here
102 LOGGER.error("Could not read file {}", fileName, e);
104 localStartDate = localStartDate.plusDays(1);
108 } catch (Exception e) {
109 throw new AuditLoggingException(e);
113 public static Path getEntryRoot(String uuid) {
114 return Activator.getLogLocation().resolve(uuid);
117 public static Path getLogFile(String uuid, Level level) {
118 Path root = getEntryRoot(uuid);
119 String fileName = resolveLogFileName(uuid, level, LocalDate.now());
120 return root.resolve(fileName);
123 private static String resolveLogFileName(String uuid, Level level, LocalDate date) {
124 return date.toString() + "_" + uuid + "." + level.toString().toLowerCase();
127 public static void log(String uuid, Map<String, Object> json) throws AuditLoggingException {
128 write(uuid, Level.INFO, json);
131 public static void error(String uuid, Map<String, Object> json) throws AuditLoggingException {
132 write(uuid, Level.ERROR, json);
135 public static void trace(String uuid, Map<String, Object> json) throws AuditLoggingException {
136 write(uuid, Level.TRACE, json);
139 private static void write(String uuid, Level level, Map<String, Object> json) throws AuditLoggingException {
140 Map<String, Object> wrappedAuditEvent = wrapAndAddAuditMetadata(uuid, level, json);
142 String jsonLine = mapper.writeValueAsString(wrappedAuditEvent);
143 Path logFile = getLogFile(uuid, level);
144 if (!Files.exists(logFile))
145 Files.createFile(logFile);
146 String lineWithNewline = jsonLine + "\n";
147 Files.write(logFile, lineWithNewline.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE, StandardOpenOption.APPEND);
148 } catch (JsonProcessingException e) {
149 throw new AuditLoggingException("Could not serialize input", e);
150 } catch (IOException e) {
151 throw new AuditLoggingException("Could not write line to log", e);
155 private static final String timestamp = "timestamp";
157 private static Map<String, Object> wrapAndAddAuditMetadata(String uuid, Level level, Map<String, Object> original) {
158 Map<String, Object> wrapped = new HashMap<>();
159 long newValue = System.currentTimeMillis();
160 Object possibleExisting = wrapped.put(timestamp, newValue);
161 if (possibleExisting != null) {
162 LOGGER.warn("Replacing existing value {} for key {} - new value is {}", possibleExisting, timestamp, newValue);
164 possibleExisting = wrapped.put("uuid", uuid);
165 if (possibleExisting != null) {
166 LOGGER.warn("Replacing existing value {} for key {} - new value is {}", possibleExisting, "uuid", uuid);
168 possibleExisting = wrapped.put("level", level.toString());
169 if (possibleExisting != null) {
170 LOGGER.warn("Replacing existing value {} for key {} - new value is {}", possibleExisting, "level", level.toString());
172 wrapped.put("original", original);