001/* 002 * Copyright 2016 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.runtime.log; 017 018import java.util.ArrayList; 019import java.util.Collections; 020import java.util.Iterator; 021import java.util.LinkedList; 022import java.util.List; 023 024import org.apache.log4j.spi.LoggingEvent; 025 026import org.ametys.core.trace.ForensicLogger; 027 028 029/** 030 * Log appender that stores a pile of logs in memory. 031 */ 032public class MemoryAppender extends org.apache.log4j.AppenderSkeleton 033{ 034 /** Time of expiration of a log (10 minutes) */ 035 private static final long __LOG_EXPIRATION_TIME = 10 * 60 * 1000; 036 /** Time duration between 2 cleansing of logs queue (10 seconds) */ 037 private static final long __TIME_DURATION_BEFORE_NEXT_CLEAN = 10 * 1000; 038 039 private long _lastCleanTime; 040 041 private LinkedList<MemoryLogRecord> _logsQueue; 042 043 /** 044 * Default constructor for the memory appender 045 */ 046 public MemoryAppender() 047 { 048 _logsQueue = new LinkedList<>(); 049 } 050 051 @Override 052 protected void append(LoggingEvent event) 053 { 054 if (event.getLoggerName().startsWith(ForensicLogger.class.getName() + ".")) 055 { 056 return; // Do not log forensic events in the memory appender... it floods the UI. Especially in production when logs are in error when those are info. 057 } 058 059 // Add the logging event information to a LogRecord 060 // Retrieve the information from the log4j LoggingEvent. 061 MemoryLogRecord record = new MemoryLogRecord(event); 062 063 // Remove logs older than 10 minutes 064 removeExpiredEvents(System.currentTimeMillis() - __LOG_EXPIRATION_TIME); 065 066 // Add logs to the queue 067 synchronized (_logsQueue) 068 { 069 _logsQueue.add(record); 070 } 071 } 072 073 /** 074 * Get the logs from a given timestamp 075 * @param fromTimestamp If greater than 0, get the events more recent than this timestamp parameter. If equals or less than 0, returns all events 076 * @return The log events 077 */ 078 @SuppressWarnings("unchecked") 079 public List<MemoryLogRecord> getEvents(long fromTimestamp) 080 { 081 return getEvents(fromTimestamp, Collections.EMPTY_LIST); 082 } 083 084 /** 085 * Get the logs from a given timestamp and filtered by specific categories 086 * @param fromTimestamp If greater than 0, get the events more recent than this timestamp parameter. If equals or less than 0, returns all events 087 * @param filterCategories The filter categories 088 * @return The log events 089 */ 090 public List<MemoryLogRecord> getEvents(long fromTimestamp, List<String> filterCategories) 091 { 092 // New events can occur at current time, retrieve only the fixed logs list: 1ms before now. 093 long toTimestamp = System.currentTimeMillis() - 1; 094 095 synchronized (_logsQueue) 096 { 097 if (fromTimestamp <= 0 && filterCategories.isEmpty()) 098 { 099 // returns all records 100 return new ArrayList<>(_logsQueue); 101 } 102 else if (toTimestamp < fromTimestamp) 103 { 104 return new ArrayList<>(); 105 } 106 else 107 { 108 List<MemoryLogRecord> recentRecords = new ArrayList<>(); 109 110 Iterator<MemoryLogRecord> it = _logsQueue.descendingIterator(); 111 while (it.hasNext()) 112 { 113 MemoryLogRecord record = it.next(); 114 if ((fromTimestamp <= 0 || record.millis() >= fromTimestamp) && record.millis() <= toTimestamp) 115 { 116 if (_matchCategoryFilter(filterCategories, record)) 117 { 118 recentRecords.add(record); 119 } 120 } 121 else 122 { 123 // Stop iteration 124 break; 125 } 126 } 127 128 return recentRecords; 129 } 130 } 131 } 132 133 private boolean _matchCategoryFilter(List<String> filterCategories, MemoryLogRecord record) 134 { 135 String recordCategory = record.category(); 136 if (filterCategories.isEmpty() || filterCategories.contains(recordCategory)) 137 { 138 return true; 139 } 140 141 for (String category : filterCategories) 142 { 143 if (recordCategory.startsWith(category.concat("."))) 144 { 145 return true; 146 } 147 } 148 149 return false; 150 } 151 152 /** 153 * Remove the expired log records every 10 seconds 154 * @param from The expiration date in milliseconds 155 */ 156 public void removeExpiredEvents(long from) 157 { 158 synchronized (_logsQueue) 159 { 160 if (_lastCleanTime != 0 && _lastCleanTime + __TIME_DURATION_BEFORE_NEXT_CLEAN > System.currentTimeMillis()) 161 { 162 // The time between 2 cleansing has not yet expired, do nothing 163 return; 164 } 165 166 // Remove logs before 'from' 167 MemoryLogRecord record = _logsQueue.peek(); 168 while (record != null && record.millis() < from) 169 { 170 _logsQueue.remove(); 171 record = _logsQueue.peek(); 172 } 173 174 _lastCleanTime = System.currentTimeMillis(); 175 } 176 } 177 178 @Override 179 public boolean requiresLayout() 180 { 181 return false; 182 } 183 184 @Override 185 public void close() 186 { 187 _logsQueue.clear(); 188 } 189 190}