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 public List<MemoryLogRecord> getEvents(long fromTimestamp) 100 { 101 return getEvents(fromTimestamp, Collections.EMPTY_LIST); 102 } 103 104 /** 105 * Get the logs from a given timestamp and filtered by specific categories 106 * @param fromTimestamp If greater than 0, get the events more recent than this timestamp parameter. If equals or less than 0, returns all events 107 * @param filterCategories The filter categories 108 * @return The log events 109 */ 110 public List<MemoryLogRecord> getEvents(long fromTimestamp, List<String> filterCategories) 111 { 112 // New events can occur at current time, retrieve only the fixed logs list: 1ms before now. 113 long toTimestamp = System.currentTimeMillis() - 1; 114 115 synchronized (_logsQueue) 116 { 117 if (fromTimestamp <= 0 && filterCategories.isEmpty()) 118 { 119 // returns all records 120 return new ArrayList<>(_logsQueue); 121 } 122 else if (toTimestamp < fromTimestamp) 123 { 124 return new ArrayList<>(); 125 } 126 else 127 { 128 List<MemoryLogRecord> recentRecords = new ArrayList<>(); 129 130 Iterator<MemoryLogRecord> it = _logsQueue.descendingIterator(); 131 while (it.hasNext()) 132 { 133 MemoryLogRecord record = it.next(); 134 if ((fromTimestamp <= 0 || record.getMillis() >= fromTimestamp) && record.getMillis() <= toTimestamp) 135 { 136 if (_matchCategoryFilter(filterCategories, record)) 137 { 138 recentRecords.add(record); 139 } 140 } 141 else 142 { 143 // Stop iteration 144 break; 145 } 146 } 147 148 return recentRecords; 149 } 150 } 151 } 152 153 private boolean _matchCategoryFilter(List<String> filterCategories, MemoryLogRecord record) 154 { 155 String recordCategory = record.getCategory(); 156 if (filterCategories.isEmpty() || filterCategories.contains(recordCategory)) 157 { 158 return true; 159 } 160 161 for (String category : filterCategories) 162 { 163 if (recordCategory.startsWith(category.concat("."))) 164 { 165 return true; 166 } 167 } 168 169 return false; 170 } 171 172 /** 173 * Remove the expired log records every 10 seconds 174 * @param from The expiration date in milliseconds 175 */ 176 public void removeExpiredEvents(long from) 177 { 178 synchronized (_logsQueue) 179 { 180 if (_lastCleanTime != 0 && _lastCleanTime + __TIME_DURATION_BEFORE_NEXT_CLEAN > System.currentTimeMillis()) 181 { 182 // The time between 2 cleansing has not yet expired, do nothing 183 return; 184 } 185 186 // Remove logs before 'from' 187 MemoryLogRecord record = _logsQueue.peek(); 188 while (record != null && record.getMillis() < from) 189 { 190 _logsQueue.remove(); 191 record = _logsQueue.peek(); 192 } 193 194 _lastCleanTime = System.currentTimeMillis(); 195 } 196 } 197 198 @Override 199 public boolean requiresLayout() 200 { 201 return false; 202 } 203 204 @Override 205 public void close() 206 { 207 _logsQueue.clear(); 208 } 209 210}