Commit 29b2054a authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

Added support to rollover by date and total max size. JM-298

git-svn-id: http://svn.igniterealtime.org/svn/repos/wildfire/trunk@3709 b35dd754-fafc-0310-a699-88a17e54d16e
parent b7cf0726
......@@ -54,6 +54,24 @@ public interface AuditManager {
*/
Auditor getAuditor();
/**
* Returns the maximum size in megabytes that all audit log files may have. When the
* limit is reached oldest audit log files will be removed until total size is under
* the limit.
*
* @return the maximum size of all audit logs in megabytes.
*/
int getMaxTotalSize();
/**
* Sets the maximum size in megabytes that all audit log files may have. When the
* limit is reached oldest audit log files will be removed until total size is under
* the limit.
*
* @param size the maximum size of all audit logs in megabytes.
*/
void setMaxTotalSize(int size);
/**
* Obtain the maximum size of audit log files in megabytes.
* Logs that exceed the max size will be rolled over to another
......@@ -71,23 +89,22 @@ public interface AuditManager {
void setMaxFileSize(int size);
/**
* Obtain the maximum number of audit files to create. Audit files that
* exceed the maximum file size will be rolled over to new files.
* If there are more log files than the max file count, then the
* oldest log file is overwritten.
* Returns the maximum number of days to keep audit information. Once the limit
* has been reached audit files that contain information that exceed the limit
* will be deleted.
*
* @return the maximum number of audit files that will be created
* @return the maximum number of days to keep audit information
* or -1 for unlimited
*/
int getMaxFileCount();
int getMaxDays();
/**
* Set the maximum number of audit files to create.
* Set the the maximum number of days to keep audit information.
*
* @param count the maximum number of audit files that will be
* created or -1 for unlimited
* @param count the maximum number of days to keep audit information
* or -1 for unlimited
*/
void setMaxFileCount(int count);
void setMaxDays(int count);
/**
* Returns the time in milliseconds between successive executions of the task that will save
......
......@@ -11,6 +11,7 @@
package org.jivesoftware.wildfire.audit.spi;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.wildfire.Session;
import org.jivesoftware.wildfire.XMPPServer;
import org.jivesoftware.wildfire.audit.AuditManager;
......@@ -18,9 +19,8 @@ import org.jivesoftware.wildfire.audit.Auditor;
import org.jivesoftware.wildfire.container.BasicModule;
import org.jivesoftware.wildfire.interceptor.InterceptorManager;
import org.jivesoftware.wildfire.interceptor.PacketInterceptor;
import org.jivesoftware.util.JiveGlobals;
import org.xmpp.packet.Packet;
import org.xmpp.packet.JID;
import org.xmpp.packet.Packet;
import java.io.File;
import java.util.*;
......@@ -37,13 +37,27 @@ public class AuditManagerImpl extends BasicModule implements AuditManager {
private boolean auditXPath;
private List xpath = new LinkedList();
private AuditorImpl auditor = null;
private int maxSize;
private int maxCount;
/**
* Max size in bytes that all audit log files may have. When the limit is reached
* oldest audit log files will be removed until total size is under the limit.
*/
private int maxTotalSize;
/**
* Max size in bytes that each audit log file may have. Once the limit has been
* reached a new audit file will be created.
*/
private int maxFileSize;
/**
* Max number of days to keep audit information. Once the limit has been reached
* audit files that contain information that exceed the limit will be deleted.
*/
private int maxDays;
private int logTimeout;
private String logDir;
private Collection<String> ignoreList = new ArrayList<String>();
private static final int MAX_TOTAL_SIZE = 1000;
private static final int MAX_FILE_SIZE = 10;
private static final int MAX_FILE_COUNT = 10;
private static final int MAX_DAYS = -1;
private static final int DEFAULT_LOG_TIMEOUT = 120000;
private AuditorInterceptor interceptor;
......@@ -74,14 +88,40 @@ public class AuditManagerImpl extends BasicModule implements AuditManager {
return auditor;
}
public int getMaxTotalSize() {
return maxTotalSize;
}
public void setMaxTotalSize(int size) {
maxTotalSize = size;
auditor.setMaxValues(maxTotalSize, maxFileSize, maxDays);
JiveGlobals.setProperty("xmpp.audit.totalsize", Integer.toString(size));
}
public int getMaxFileSize() {
return maxSize;
return maxFileSize;
}
public void setMaxFileSize(int size) {
maxSize = size;
auditor.setMaxValues(maxSize, maxCount);
JiveGlobals.setProperty("xmpp.audit.maxsize", Integer.toString(size));
maxFileSize = size;
auditor.setMaxValues(maxTotalSize, maxFileSize, maxDays);
JiveGlobals.setProperty("xmpp.audit.filesize", Integer.toString(size));
}
public int getMaxDays() {
return maxDays;
}
public void setMaxDays(int count) {
if (count < -1) {
count = -1;
}
if (count == 0) {
count = 1;
}
maxDays = count;
auditor.setMaxValues(maxTotalSize, maxFileSize, maxDays);
JiveGlobals.setProperty("xmpp.audit.days", Integer.toString(count));
}
public int getLogTimeout() {
......@@ -104,16 +144,6 @@ public class AuditManagerImpl extends BasicModule implements AuditManager {
JiveGlobals.setProperty("xmpp.audit.logdir", logDir);
}
public int getMaxFileCount() {
return maxCount;
}
public void setMaxFileCount(int count) {
maxCount = count;
auditor.setMaxValues(maxSize, maxCount);
JiveGlobals.setProperty("xmpp.audit.maxcount", Integer.toString(count));
}
public boolean isAuditMessage() {
return auditMessage;
}
......@@ -208,8 +238,9 @@ public class AuditManagerImpl extends BasicModule implements AuditManager {
// for (int i = 0; i < filters.length; i++) {
// xpath.add(filters[i]);
// }
maxSize = JiveGlobals.getIntProperty("xmpp.audit.maxsize", MAX_FILE_SIZE);
maxCount = JiveGlobals.getIntProperty("xmpp.audit.maxcount", MAX_FILE_COUNT);
maxTotalSize = JiveGlobals.getIntProperty("xmpp.audit.totalsize", MAX_TOTAL_SIZE);
maxFileSize = JiveGlobals.getIntProperty("xmpp.audit.filesize", MAX_FILE_SIZE);
maxDays = JiveGlobals.getIntProperty("xmpp.audit.days", MAX_DAYS);
logTimeout = JiveGlobals.getIntProperty("xmpp.audit.logtimeout", DEFAULT_LOG_TIMEOUT);
logDir = JiveGlobals.getProperty("xmpp.audit.logdir", JiveGlobals.getHomeDirectory() +
File.separator + "logs");
......@@ -222,9 +253,9 @@ public class AuditManagerImpl extends BasicModule implements AuditManager {
}
auditor = new AuditorImpl(this);
auditor.setMaxValues(maxSize, maxCount);
auditor.setLogTimeout(logTimeout);
auditor.setMaxValues(maxTotalSize, maxFileSize, maxDays);
auditor.setLogDir(logDir);
auditor.setLogTimeout(logTimeout);
interceptor = new AuditorInterceptor();
if (enabled) {
......
......@@ -13,9 +13,7 @@ package org.jivesoftware.wildfire.audit.spi;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.*;
import org.jivesoftware.wildfire.Session;
import org.jivesoftware.wildfire.audit.AuditManager;
import org.jivesoftware.wildfire.audit.Auditor;
......@@ -35,13 +33,38 @@ public class AuditorImpl implements Auditor {
private File currentAuditFile;
private Writer writer;
private org.jivesoftware.util.XMLWriter xmlWriter;
private int maxSize;
private long maxCount;
/**
* Limit date used to detect when we need to rollover files. This date will be
* configured as the last second of the day.
*/
private Date currentDateLimit;
/**
* Max size in bytes that all audit log files may have. When the limit is reached
* oldest audit log files will be removed until total size is under the limit.
*/
private int maxTotalSize;
/**
* Max size in bytes that each audit log file may have. Once the limit has been
* reached a new audit file will be created.
*/
private int maxFileSize;
/**
* Max number of days to keep audit information. Once the limit has been reached
* audit files that contain information that exceed the limit will be deleted.
*/
private int maxDays;
/**
* Flag that indicates if packets can still be accepted to be saved to the audit log.
*/
private boolean closed = false;
/**
* Directoty (absolute path) where the audit files will be saved.
*/
private String logDir;
/**
* File (or better say directory) of the folder that contains the audit logs.
*/
private File baseFolder;
/**
* Queue that holds the audited packets that will be later saved to an XML file.
......@@ -53,9 +76,42 @@ public class AuditorImpl implements Auditor {
*/
private Timer timer = new Timer("Auditor");
private SaveQueuedPacketsTask saveQueuedPacketsTask;
private FastDateFormat dateFormat;
public AuditorImpl(AuditManager manager) {
auditManager = manager;
dateFormat = FastDateFormat.getInstance("yyyyMMdd", TimeZone.getTimeZone("UTC"));
}
protected void setMaxValues(int totalSize, int fileSize, int days) {
maxTotalSize = totalSize * 1024*1024;
maxFileSize = fileSize * 1024*1024;
maxDays = days;
}
public void setLogTimeout(int logTimeout) {
// Cancel any existing task because the timeout has changed
if (saveQueuedPacketsTask != null) {
saveQueuedPacketsTask.cancel();
}
// Create a new task and schedule it with the new timeout
saveQueuedPacketsTask = new SaveQueuedPacketsTask();
timer.schedule(saveQueuedPacketsTask, logTimeout, logTimeout);
}
public void setLogDir(String logDir) {
this.logDir = logDir;
// Create and catch file of the base folder that will contain audit files
baseFolder = new File(logDir);
// Create the folder if it does not exist
if (!baseFolder.exists()) {
baseFolder.mkdir();
}
}
public int getQueuedPacketsNumber() {
return logQueue.size();
}
public void audit(Packet packet, Session session) {
......@@ -78,7 +134,16 @@ public class AuditorImpl implements Auditor {
}
}
private void writePacket(Packet packet, Session session) {
if (!closed) {
// Add to the logging queue this new entry that will be saved later
logQueue.add(new AuditPacket(packet.createCopy(), session));
}
}
public void stop() {
// Stop queuing packets since we are being stopped
closed = true;
// Stop the scheduled task for saving queued packets to the XML file
timer.cancel();
// Save all remaining queued packets to the XML file
......@@ -93,6 +158,7 @@ public class AuditorImpl implements Auditor {
writer.write("</jive>");
xmlWriter.close();
writer = null;
xmlWriter = null;
}
catch (Exception e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
......@@ -100,84 +166,166 @@ public class AuditorImpl implements Auditor {
}
}
private void writePacket(Packet packet, Session session) {
if (!closed) {
// Add to the logging queue this new entry that will be saved later
logQueue.add(new AuditPacket(packet.createCopy(), session));
}
}
private void prepareAuditFile(Date auditDate) throws IOException {
ensureMaxTotalSize();
private void prepareAuditFile() throws IOException {
if (currentAuditFile == null || currentAuditFile.length() > maxSize || xmlWriter == null) {
rotateFiles();
// Rotate file if: we just started, current file size exceeded limit or date has changed
if (currentAuditFile == null || currentAuditFile.length() > maxFileSize ||
xmlWriter == null || currentDateLimit == null || auditDate.after(currentDateLimit))
{
createAuditFile(auditDate);
}
}
protected void setMaxValues(int size, int count) {
maxSize = size * 1024*1024;
maxCount = count;
/**
* Ensures that max total size limit is not exceeded. If total size of audit files
* exceed the limit then oldest audit files will be removed until total size does
* not exceed limit.
*/
private void ensureMaxTotalSize() {
// Get list of existing audit files
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith("jive.audit-") && name.endsWith(".log");
}
};
File[] files = baseFolder.listFiles(filter);
long totalLength = 0;
for (File file : files) {
totalLength = totalLength + file.length();
}
// Check if total size has been exceeded
if (totalLength > maxTotalSize) {
// Sort files by name (chronological order)
List<File> sortedFiles = new ArrayList<File>(Arrays.asList(files));
Collections.sort(sortedFiles, new Comparator() {
public int compare(Object o1, Object o2) {
return ((File)o1).getName().compareTo(((File)o2).getName());
}
});
// Delete as many old files as required to be under the limit
while (totalLength > maxTotalSize && !sortedFiles.isEmpty()) {
File fileToDelete = sortedFiles.remove(0);
totalLength = totalLength - fileToDelete.length();
if (fileToDelete.equals(currentAuditFile)) {
// Close current file
close();
}
// Delete oldest file
fileToDelete.delete();
}
}
}
public void setLogTimeout(int logTimeout) {
// Cancel any existing task because the timeout has changed
if (saveQueuedPacketsTask != null) {
saveQueuedPacketsTask.cancel();
/**
* Deletes old audit files that exceeded the max number of days limit.
*/
private void ensureMaxDays() {
if (maxDays == -1) {
// Do nothing since we don't have any limit
return;
}
// Create a new task and schedule it with the new timeout
saveQueuedPacketsTask = new SaveQueuedPacketsTask();
timer.schedule(saveQueuedPacketsTask, logTimeout, logTimeout);
}
// Set limit date after which we need to delete old audit files
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, maxDays * -1);
public void setLogDir(String logDir) {
this.logDir = logDir;
}
final String oldestFile =
"jive.audit-" + dateFormat.format(calendar.getTime()) + "-000.log";
public int getQueuedPacketsNumber() {
return logQueue.size();
// Get list of audit files to delete
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith("jive.audit-") && name.endsWith(".log") &&
name.compareTo(oldestFile) < 0;
}
};
File[] files = baseFolder.listFiles(filter);
// Delete old audit files
for (File fileToDelete : files) {
if (fileToDelete.equals(currentAuditFile)) {
// Close current file
close();
}
fileToDelete.delete();
}
}
private void rotateFiles() throws IOException {
private void createAuditFile(Date auditDate) throws IOException {
close();
int i;
// Find the next available log file name
for (i = 0; maxCount < 1 || i < maxCount; i++) {
currentAuditFile = new File(logDir, "jive.audit-" + i + ".log");
if (!currentAuditFile.exists()) {
break;
if (currentDateLimit == null || auditDate.after(currentDateLimit)) {
// Set limit date after which we need to rollover the audit file (based on the date)
Calendar calendar = Calendar.getInstance();
calendar.setTime(auditDate);
calendar.set(Calendar.HOUR, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
currentDateLimit = calendar.getTime();
}
final String filePrefix = "jive.audit-" + dateFormat.format(auditDate) + "-";
// Get list of existing audit files
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith(filePrefix) && name.endsWith(".log");
}
};
File[] files = baseFolder.listFiles(filter);
if (files.length == 0) {
// This is the first audit file for the day
currentAuditFile = new File(logDir, filePrefix + "000.log");
}
// Two edge cases, i == 0 (no log files exist) and i == MAX_FILE_COUNT
// If i == 0 then the loop above has already set currentAuditFile to
// the correct file name, so we only need to setup a file name if i != 0
if (i != 0) {
if (i == maxCount) {
// We need to delete the last in the series to make room for the next file
// the currentAuditFile should be pointing at the last legitimate
// file name in the series (i < MAX_FILE_COUNT) so we just delete it
// so the previous file can be rotated to it
currentAuditFile.delete();
else {
// Search the last index used for the day
File lastFile = files[files.length - 1];
StringTokenizer tokenizer = new StringTokenizer(lastFile.getName(), "-.");
// Skip "jive"
tokenizer.nextToken();
// Skip "audit"
tokenizer.nextToken();
// Skip "date"
tokenizer.nextToken();
int index = Integer.parseInt(tokenizer.nextToken()) + 1;
if (index > 999) {
Log.warn("Failed to created audit file. Max limit of 999 files has been reached " +
"for the date: " + dateFormat.format(auditDate));
return;
}
// Rotate the files
for (i--; i >= 0; i--) {
String previousName = "jive.audit-" + i + ".log";
File previousFile = new File(logDir, previousName);
previousFile.renameTo(currentAuditFile);
currentAuditFile = new File(logDir, previousName);
currentAuditFile = new File(logDir,
filePrefix + StringUtils.zeroPadString(Integer.toString(index), 3) + ".log");
}
// Find the next available log file name
/*for (int i = 0; i < 1000; i++) {
currentAuditFile = new File(logDir,
filePrefix + StringUtils.zeroPadString(Integer.toString(i), 3) + ".log");
if (!currentAuditFile.exists()) {
break;
}
}
if (currentAuditFile == null) {
Log.warn("Audit log not create since there are more than 999 files for the date: " +
dateFormat.format(auditDate));
return;
}*/
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(currentAuditFile), "UTF-8"));
writer.write("<jive xmlns=\"http://www.jivesoftware.org\">");
xmlWriter = new org.jivesoftware.util.XMLWriter(writer);
}
/**
* Saves the queued entries to an XML file.
* Saves the queued entries to an XML file and checks that very old files are deleted.
*/
private class SaveQueuedPacketsTask extends TimerTask {
public void run() {
try {
// Ensure that saved audit logs are not too old
ensureMaxDays();
// Save queued packets to the audit logs
saveQueuedPackets();
}
catch (Throwable e) {
......@@ -191,7 +339,7 @@ public class AuditorImpl implements Auditor {
logQueue.drainTo(packets);
for (AuditPacket auditPacket : packets) {
try {
prepareAuditFile();
prepareAuditFile(auditPacket.getCreationDate());
Element element = auditPacket.getElement();
// Protect against null elements.
if (element != null) {
......@@ -228,9 +376,11 @@ public class AuditorImpl implements Auditor {
private static DocumentFactory docFactory = DocumentFactory.getInstance();
private Element element;
private Date creationDate;
public AuditPacket(Packet packet, Session session) {
element = docFactory.createElement("packet", "http://www.jivesoftware.org");
creationDate = new Date();
if (session.getStreamID() != null) {
element.addAttribute("streamID", session.getStreamID().toString());
}
......@@ -256,7 +406,7 @@ public class AuditorImpl implements Auditor {
element.addAttribute("status", "unknown");
break;
}
element.addAttribute("timestamp", JiveGlobals.formatDateTime(new Date()));
element.addAttribute("timestamp", JiveGlobals.formatDateTime(creationDate));
element.add(packet.getElement());
}
......@@ -268,5 +418,15 @@ public class AuditorImpl implements Auditor {
public Element getElement() {
return element;
}
/**
* Returns the date when the packet was audited. This is the time when the
* packet was queued to be saved.
*
* @return the date when the packet was audited.
*/
public Date getCreationDate() {
return creationDate;
}
}
}
\ No newline at end of file
......@@ -9,16 +9,15 @@
- a copy of which is included in this distribution.
--%>
<%@ page import="org.jivesoftware.wildfire.audit.AuditManager,
org.jivesoftware.util.*,
java.util.*,
java.io.File,
<%@ page import="org.jivesoftware.util.ParamUtils,
org.jivesoftware.wildfire.XMPPServer,
org.jivesoftware.wildfire.audit.AuditManager,
org.jivesoftware.wildfire.user.UserNotFoundException,
org.xmpp.packet.JID,
org.jivesoftware.wildfire.user.UserNotFoundException"
java.io.File"
errorPage="error.jsp"
%>
<%@ page import="org.jivesoftware.wildfire.XMPPServer"%>
<%@ page import="org.jivesoftware.wildfire.user.UserManager"%>
<%@ page import="java.util.*"%>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>
......@@ -44,8 +43,9 @@
boolean auditXPath = ParamUtils.getBooleanParameter(request,"auditXPath");
String newXpathQuery = ParamUtils.getParameter(request,"newXpathQuery");
String[] xpathQuery = ParamUtils.getParameters(request,"xpathQuery");
String maxCount = ParamUtils.getParameter(request,"maxCount");
String maxSize = ParamUtils.getParameter(request,"maxSize");
String maxTotalSize = ParamUtils.getParameter(request,"maxTotalSize");
String maxFileSize = ParamUtils.getParameter(request,"maxFileSize");
String maxDays = ParamUtils.getParameter(request,"maxDays");
String logTimeout = ParamUtils.getParameter(request,"logTimeout");
String logDir = ParamUtils.getParameter(request,"logDir");
String ignore = ParamUtils.getParameter(request,"ignore");
......@@ -70,14 +70,19 @@
}
*/
try {
auditManager.setMaxFileCount(Integer.parseInt(maxCount));
auditManager.setMaxTotalSize(Integer.parseInt(maxTotalSize));
} catch (Exception e){
errors.put("maxCount","maxCount");
errors.put("maxTotalSize","maxTotalSize");
}
try {
auditManager.setMaxFileSize(Integer.parseInt(maxSize));
auditManager.setMaxFileSize(Integer.parseInt(maxFileSize));
} catch (Exception e){
errors.put("maxSize","maxSize");
errors.put("maxFileSize","maxFileSize");
}
try {
auditManager.setMaxDays(Integer.parseInt(maxDays));
} catch (Exception e){
errors.put("maxDays","maxDays");
}
try {
auditManager.setLogTimeout(Integer.parseInt(logTimeout) * 1000);
......@@ -150,8 +155,9 @@
auditPresence = auditManager.isAuditPresence();
auditIQ = auditManager.isAuditIQ();
auditXPath = auditManager.isAuditXPath();
maxCount = Integer.toString(auditManager.getMaxFileCount());
maxSize = Integer.toString(auditManager.getMaxFileSize());
maxTotalSize = Integer.toString(auditManager.getMaxTotalSize());
maxFileSize = Integer.toString(auditManager.getMaxFileSize());
maxDays = Integer.toString(auditManager.getMaxDays());
logTimeout = Integer.toString(auditManager.getLogTimeout() / 1000);
logDir = auditManager.getLogDir();
StringBuilder ignoreList = new StringBuilder();
......@@ -225,15 +231,33 @@
</td>
</tr>
<tr valign="top">
<td width="1%" nowrap class="c1">
<fmt:message key="audit.policy.maxtotal_size" />
</td>
<td width="99%">
<input type="text" size="15" maxlength="50" name="maxTotalSize"
value="<%= ((maxTotalSize != null) ? maxTotalSize : "") %>">
<% if (errors.get("maxTotalSize") != null) { %>
<span class="jive-error-text">
<fmt:message key="audit.policy.validnumber" />
</span>
<% } %>
</td>
</tr>
<tr valign="top">
<td width="1%" nowrap class="c1">
<fmt:message key="audit.policy.maxfile_size" />
</td>
<td width="99%">
<input type="text" size="15" maxlength="50" name="maxSize"
value="<%= ((maxSize != null) ? maxSize : "") %>">
<input type="text" size="15" maxlength="50" name="maxFileSize"
value="<%= ((maxFileSize != null) ? maxFileSize : "") %>">
<% if (errors.get("maxSize") != null) { %>
<% if (errors.get("maxFileSize") != null) { %>
<span class="jive-error-text">
<fmt:message key="audit.policy.validnumber" />
......@@ -245,13 +269,13 @@
</tr>
<tr valign="top">
<td width="1%" nowrap class="c1">
<fmt:message key="audit.policy.maxfile_number" />
<fmt:message key="audit.policy.maxdays_number" />
</td>
<td width="99%">
<input type="text" size="15" maxlength="50" name="maxCount"
value="<%= ((maxCount != null) ? maxCount : "") %>">
<input type="text" size="15" maxlength="50" name="maxDays"
value="<%= ((maxDays != null) ? maxDays : "") %>">
<% if (errors.get("maxCount") != null) { %>
<% if (errors.get("maxDays") != null) { %>
<span class="jive-error-text">
<fmt:message key="audit.policy.validnumber" />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment