001/* 002 * Copyright 2015 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.plugins.admin.logs; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Calendar; 021import java.util.Date; 022import java.util.Enumeration; 023import java.util.GregorianCalendar; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.cocoon.ProcessingException; 031import org.apache.excalibur.source.ModifiableSource; 032import org.apache.excalibur.source.ModifiableTraversableSource; 033import org.apache.excalibur.source.SourceException; 034import org.apache.excalibur.source.SourceResolver; 035import org.apache.excalibur.source.TraversableSource; 036import org.apache.log4j.Level; 037import org.apache.log4j.LogManager; 038import org.apache.log4j.Logger; 039import org.apache.log4j.spi.LoggerRepository; 040 041import org.ametys.core.ui.Callable; 042import org.ametys.core.ui.StaticClientSideElement; 043 044/** 045 * Client side element for log actions 046 */ 047public class LogsClientSideElement extends StaticClientSideElement 048{ 049 /** The path to the application's logs folder */ 050 private static final String __LOGS_BASE = "ametys-home://logs/"; 051 052 /** The Excalibur source resolver */ 053 private SourceResolver _sourceResolver; 054 055 @Override 056 public void service(ServiceManager serviceManager) throws ServiceException 057 { 058 super.service(serviceManager); 059 _sourceResolver = (SourceResolver) serviceManager.lookup(SourceResolver.ROLE); 060 } 061 062 /** 063 * Delete the log files 064 * @param files the files to delete 065 * @return the result map with the successes and failures files. 066 * @throws IOException if an error occurred during deletion 067 * @throws ProcessingException if an error occurred during deletion 068 */ 069 @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin") 070 public Map<String, Object> deleteLogs(List<String> files) throws ProcessingException, IOException 071 { 072 Map<String, Object> result = new HashMap<>(); 073 List<String> failures = new ArrayList<> (); 074 List<String> successes = new ArrayList<> (); 075 076 if (getLogger().isInfoEnabled()) 077 { 078 getLogger().info("Administrator starts a deletion of " + files.size() + " logged file"); 079 } 080 081 // Delete the selected files one by one 082 for (String location : files) 083 { 084 if (_deleteFile(location)) 085 { 086 successes.add(location); 087 } 088 else 089 { 090 failures.add(location); 091 } 092 } 093 094 if (getLogger().isInfoEnabled()) 095 { 096 getLogger().info("Process terminated with following results : failure '" + failures.toString() + " successes '" + successes.toString()); 097 } 098 099 result.put("failures", failures); 100 result.put("successes", successes); 101 return result; 102 } 103 104 /** 105 * Delete the log entries that are at least 12 days old 106 * @return the result 107 * @throws ProcessingException if an exception occurs while processing the deletion 108 */ 109 @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin") 110 public Map<String, Object> purgeLogs() throws ProcessingException 111 { 112 Calendar purgeCalendar = new GregorianCalendar(); 113 purgeCalendar.add(Calendar.DAY_OF_MONTH, -12); 114 Date purgeDate = new Date(purgeCalendar.getTimeInMillis()); 115 116 if (getLogger().isDebugEnabled()) 117 { 118 getLogger().debug("Starting purge..."); 119 getLogger().debug("Purge date is " + purgeDate); 120 } 121 122 List<String> filesList = new ArrayList<>(); 123 TraversableSource logsSources = null; 124 try 125 { 126 logsSources = (TraversableSource) _sourceResolver.resolveURI(__LOGS_BASE); 127 for (Object log : logsSources.getChildren()) 128 { 129 ModifiableTraversableSource logSource = (ModifiableTraversableSource) log; 130 if (!logSource.isCollection() 131 && (logSource.getURI().endsWith(".log") || logSource.getURI().endsWith(".log.gz")) 132 && !(logSource.getName().startsWith("forensic-")) 133 && new Date(logSource.getLastModified()).before(purgeDate)) 134 { 135 String location = logSource.getURI(); 136 String name = location.substring(location.lastIndexOf('/') + 1); 137 filesList.add(name); 138 if (getLogger().isDebugEnabled()) 139 { 140 getLogger().debug("Adding file to purge : " + name); 141 } 142 } 143 } 144 145 return deleteLogs(filesList); 146 } 147 catch (SourceException e) 148 { 149 String message = "The purge of old log files failed"; 150 getLogger().error(message, e); 151 throw new ProcessingException(message, e); 152 } 153 catch (IOException e) 154 { 155 String message = "The log directory was not found"; 156 getLogger().error(message, e); 157 throw new ProcessingException(message, e); 158 } 159 finally 160 { 161 if (logsSources != null) 162 { 163 _sourceResolver.release(logsSources); 164 } 165 } 166 } 167 168 private boolean _deleteFile(String fileLocation) throws IOException, ProcessingException 169 { 170 if (fileLocation.indexOf("/") != -1 || fileLocation.indexOf('\\') != -1) 171 { 172 String message = "The LogsDeleteAction has been call with the forbiden parameter '" + fileLocation + "'"; 173 getLogger().error(message); 174 throw new ProcessingException(message); 175 } 176 177 ModifiableSource logsource = null; 178 try 179 { 180 if (fileLocation.endsWith(".log")) 181 { 182 logsource = (ModifiableSource) _sourceResolver.resolveURI(__LOGS_BASE + fileLocation); 183 if (getLogger().isInfoEnabled()) 184 { 185 getLogger().info("Removing log file " + logsource.getURI()); 186 } 187 logsource.delete(); 188 return true; 189 } 190 else if (getLogger().isWarnEnabled()) 191 { 192 getLogger().warn("Ignoring this file during deletion '" + "' because it does not ends with .log"); 193 } 194 return false; 195 } 196 catch (SourceException e) 197 { 198 getLogger().error("The administrator tried unsuccessfully to remove the following log file '" + (logsource != null ? logsource.getURI() : fileLocation) + "'.", e); 199 return false; 200 } 201 finally 202 { 203 if (logsource != null) 204 { 205 _sourceResolver.release(logsource); 206 } 207 } 208 } 209 210 /** 211 * Change the log level of the selected category to the selected level 212 * @param level the selected level 213 * @param category the selected category 214 * @return a map 215 */ 216 @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin") 217 public Map<String, Object> changeLogLevel(String level, String category) 218 { 219 Map<String, Object> result = new HashMap<>(); 220 221 if (getLogger().isInfoEnabled()) 222 { 223 getLogger().info("Administrator change log level"); 224 } 225 226 LoggerRepository loggerRepository = LogManager.getLoggerRepository(); 227 228 if (getLogger().isInfoEnabled()) 229 { 230 getLogger().info("Log level changing category '" + category + "' " + level); 231 } 232 233 try 234 { 235 _changeLogLevel(loggerRepository, category, level); 236 } 237 catch (Throwable t) 238 { 239 String errorMessage = "Cannot change log level correctly : changing category '" + category + "'"; 240 getLogger().error(errorMessage, t); 241 result.put("error", "error"); 242 return result; 243 } 244 245 return result; 246 } 247 248 private void _changeLogLevel(LoggerRepository loggerRepository, String category, String mode) 249 { 250 boolean inherited = "INHERIT".equals(mode) || "INHERITFORCED".equals(mode); 251 boolean force = "FORCE".equals(mode) || "INHERITFORCED".equals(mode); 252 253 Logger logger; 254 boolean isRoot = false; 255 256 if ("root".equals(category)) 257 { 258 isRoot = true; 259 logger = loggerRepository.getRootLogger(); 260 } 261 else 262 { 263 logger = loggerRepository.getLogger(category); 264 } 265 266 if (inherited && !isRoot) 267 { 268 logger.setLevel(null); 269 } 270 271 if (force) 272 { 273 Enumeration<Logger> e = loggerRepository.getCurrentLoggers(); 274 while (e.hasMoreElements()) 275 { 276 Logger l = e.nextElement(); 277 if (l.getParent() == logger) 278 { 279 _changeLogLevel(loggerRepository, l.getName(), "INHERITFORCED"); 280 } 281 } 282 } 283 284 if (!inherited && !force) 285 { 286 Level level = Level.toLevel(mode); 287 logger.setLevel(level); 288 } 289 } 290}