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.lf5.LogLevel; 025import org.apache.log4j.lf5.LogLevelFormatException; 026import org.apache.log4j.spi.LoggingEvent; 027import org.apache.log4j.spi.ThrowableInformation; 028 029 030/** 031 * Log appender that stores a pile of logs in memory. 032 */ 033public class MemoryAppender extends org.apache.log4j.AppenderSkeleton 034{ 035 /** Time of expiration of a log (10 minutes) */ 036 private static final long __LOG_EXPIRATION_TIME = 10 * 60 * 1000; 037 /** Time duration between 2 cleansing of logs queue (10 seconds) */ 038 private static final long __TIME_DURATION_BEFORE_NEXT_CLEAN = 10 * 1000; 039 040 private long _lastCleanTime; 041 042 private LinkedList<MemoryLogRecord> _logsQueue; 043 044 /** 045 * Default constructor for the memory appender 046 */ 047 public MemoryAppender() 048 { 049 _logsQueue = new LinkedList<>(); 050 } 051 052 @Override 053 protected void append(LoggingEvent event) 054 { 055 // Add the logging event information to a LogRecord 056 // Retrieve the information from the log4j LoggingEvent. 057 MemoryLogRecord record = new MemoryLogRecord(); 058 059 record.setCategory(event.getLoggerName()); 060 record.setMessage(event.getRenderedMessage()); 061 record.setLocation(event.getLocationInformation().fullInfo); 062 record.setMillis(event.timeStamp); 063 record.setUser((String) event.getMDC("user")); 064 record.setRequestURI((String) event.getMDC("requestURI")); 065 066 ThrowableInformation throwableInformation = event.getThrowableInformation(); 067 if (throwableInformation != null) 068 { 069 record.setThrownStackTrace(throwableInformation); 070 } 071 072 try 073 { 074 String level = event.getLevel().toString(); 075 record.setLevel(LogLevel.valueOf(level)); 076 } 077 catch (LogLevelFormatException e) 078 { 079 // If the priority level doesn't match one of the predefined 080 // log levels, then set the level to warning. 081 record.setLevel(LogLevel.WARN); 082 } 083 084 // Remove logs older than 10 minutes 085 removeExpiredEvents(System.currentTimeMillis() - __LOG_EXPIRATION_TIME); 086 087 // Add logs to the queue 088 synchronized (_logsQueue) 089 { 090 _logsQueue.add(record); 091 } 092 } 093 094 /** 095 * Get the logs from a given timestamp 096 * @param fromTimestamp If greater than 0, get the events more recent than this timestamp parameter. If equals or less than 0, returns all events 097 * @return The log events 098 */ 099 @SuppressWarnings("unchecked") 100 public List<MemoryLogRecord> getEvents(long fromTimestamp) 101 { 102 return getEvents(fromTimestamp, Collections.EMPTY_LIST); 103 } 104 105 /** 106 * Get the logs from a given timestamp and filtered by specific categories 107 * @param fromTimestamp If greater than 0, get the events more recent than this timestamp parameter. If equals or less than 0, returns all events 108 * @param filterCategories The filter categories 109 * @return The log events 110 */ 111 public List<MemoryLogRecord> getEvents(long fromTimestamp, List<String> filterCategories) 112 { 113 // New events can occur at current time, retrieve only the fixed logs list: 1ms before now. 114 long toTimestamp = System.currentTimeMillis() - 1; 115 116 synchronized (_logsQueue) 117 { 118 if (fromTimestamp <= 0 && filterCategories.isEmpty()) 119 { 120 // returns all records 121 return new ArrayList<>(_logsQueue); 122 } 123 else if (toTimestamp < fromTimestamp) 124 { 125 return new ArrayList<>(); 126 } 127 else 128 { 129 List<MemoryLogRecord> recentRecords = new ArrayList<>(); 130 131 Iterator<MemoryLogRecord> it = _logsQueue.descendingIterator(); 132 while (it.hasNext()) 133 { 134 MemoryLogRecord record = it.next(); 135 if ((fromTimestamp <= 0 || record.getMillis() >= fromTimestamp) && record.getMillis() <= toTimestamp) 136 { 137 if (_matchCategoryFilter(filterCategories, record)) 138 { 139 recentRecords.add(record); 140 } 141 } 142 else 143 { 144 // Stop iteration 145 break; 146 } 147 } 148 149 return recentRecords; 150 } 151 } 152 } 153 154 private boolean _matchCategoryFilter(List<String> filterCategories, MemoryLogRecord record) 155 { 156 String recordCategory = record.getCategory(); 157 if (filterCategories.isEmpty() || filterCategories.contains(recordCategory)) 158 { 159 return true; 160 } 161 162 for (String category : filterCategories) 163 { 164 if (recordCategory.startsWith(category.concat("."))) 165 { 166 return true; 167 } 168 } 169 170 return false; 171 } 172 173 /** 174 * Remove the expired log records every 10 seconds 175 * @param from The expiration date in milliseconds 176 */ 177 public void removeExpiredEvents(long from) 178 { 179 synchronized (_logsQueue) 180 { 181 if (_lastCleanTime != 0 && _lastCleanTime + __TIME_DURATION_BEFORE_NEXT_CLEAN > System.currentTimeMillis()) 182 { 183 // The time between 2 cleansing has not yet expired, do nothing 184 return; 185 } 186 187 // Remove logs before 'from' 188 MemoryLogRecord record = _logsQueue.peek(); 189 while (record != null && record.getMillis() < from) 190 { 191 _logsQueue.remove(); 192 record = _logsQueue.peek(); 193 } 194 195 _lastCleanTime = System.currentTimeMillis(); 196 } 197 } 198 199 @Override 200 public boolean requiresLayout() 201 { 202 return false; 203 } 204 205 @Override 206 public void close() 207 { 208 _logsQueue.clear(); 209 } 210 211}