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