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}