001/*
002 *  Copyright 2013 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.plugins.site.cache.monitoring.process.access;
017
018import java.io.BufferedReader;
019import java.io.File;
020import java.io.FileReader;
021import java.io.IOException;
022import java.text.DateFormat;
023import java.text.SimpleDateFormat;
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.Date;
027import java.util.List;
028import java.util.Locale;
029
030import org.apache.avalon.framework.activity.Disposable;
031import org.apache.avalon.framework.component.Component;
032import org.apache.avalon.framework.configuration.Configurable;
033import org.apache.avalon.framework.configuration.Configuration;
034import org.apache.avalon.framework.configuration.ConfigurationException;
035import org.apache.avalon.framework.logger.LogEnabled;
036import org.apache.avalon.framework.logger.Logger;
037import org.apache.avalon.framework.service.ServiceException;
038import org.apache.avalon.framework.service.ServiceManager;
039import org.apache.avalon.framework.service.Serviceable;
040import org.apache.commons.io.FilenameUtils;
041
042import org.ametys.core.util.DateUtils;
043import org.ametys.core.util.StringUtils;
044import org.ametys.plugins.site.cache.monitoring.process.access.impl.HTTPServerResourceAccess;
045import org.ametys.runtime.config.Config;
046
047
048/**
049 * Import HTTP server access log and pass them to the resource access monitor
050 */
051public class HTTPServerAccessLogImporter implements Component, Configurable, Serviceable, Disposable, LogEnabled
052{
053    /** Avalon ROLE. */
054    public static final String ROLE = HTTPServerAccessLogImporter.class.getName();
055
056    /** Logger */
057    protected Logger _logger;
058
059    /** The resource access monitoring component */
060    protected ResourceAccessComponent _resourceAccessComponent;
061
062    /**
063     * Date of the initialization of the component, to ensure that only newer
064     * log entries are importer
065     */
066    protected Date _initializationDate;
067
068    private List<LogFileImporter> _logFileImporters;
069
070    private boolean _enabled;
071
072    @Override
073    public void service(ServiceManager manager) throws ServiceException
074    {
075        _resourceAccessComponent = (ResourceAccessComponent) manager.lookup(ResourceAccessComponent.ROLE);
076    }
077
078    @Override
079    public void enableLogging(Logger logger)
080    {
081        _logger = logger;
082    }
083
084    @Override
085    public void configure(Configuration configuration) throws ConfigurationException
086    {
087        _enabled = Config.getInstance().getValueAsBoolean("front.cache.monitoring.schedulers.enable");
088        if (!_enabled)
089        {
090            return;
091        }
092
093        _initializationDate = new Date();
094        _logFileImporters = new ArrayList<>();
095
096        String frontConfig = Config.getInstance().getValueAsString("front.cache.monitoring.httpserver.log.paths");
097        Collection<String> logFilePaths = StringUtils.stringToCollection(frontConfig);
098
099        configureLogFiles(logFilePaths);
100
101        initializeLogFileImporters();
102    }
103
104    @Override
105    public void dispose()
106    {
107        _initializationDate = null;
108        _logFileImporters = null;
109    }
110
111    /**
112     * Scan the log files for each site importer.
113     */
114    public synchronized void scanLogFiles()
115    {
116        if (!_enabled)
117        {
118            return;
119        }
120
121        for (LogFileImporter importer : _logFileImporters)
122        {
123            importer.importEntries();
124        }
125    }
126
127    private void configureLogFiles(Collection<String> paths)
128    {
129        for (String path : paths)
130        {
131            String dirLog = FilenameUtils.getFullPathNoEndSeparator(path);
132            String logFilename = FilenameUtils.getName(path);
133            File logFile = new File(dirLog, logFilename);
134
135            _logFileImporters.add(new LogFileImporter(logFile));
136        }
137    }
138
139    /**
140     * Initialize the log file importers. This method must not be called during
141     * the initialization of the component. Instead the call should be delayed,
142     * and made during the first import attempt.
143     */
144    private void initializeLogFileImporters()
145    {
146        for (LogFileImporter importer : _logFileImporters)
147        {
148            importer.initialize();
149        }
150    }
151
152    /**
153     * A log file importer.
154     * This class is able to import logs from an access log file.
155     */
156    protected final class LogFileImporter
157    {
158        private final File _file;
159        // cached date format per importer, avoid synchronization issues while staying efficient
160        private final DateFormat _df = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z", Locale.US);
161
162        private BufferedReader _br;
163        private boolean _initialized;
164
165        /**
166         * Constructor
167         * @param logFile the log file to import
168         */
169        protected LogFileImporter(File logFile)
170        {
171            _file = logFile;
172        }
173
174        /**
175         * Initialize the log file importer.
176         */
177        protected synchronized void initialize()
178        {
179            if (_initialized)
180            {
181                return;
182            }
183
184            try
185            {
186                _br = new BufferedReader(new FileReader(_file));
187                skipEntriesUntilEOS();
188
189                _initialized = true;
190
191                if (_logger.isInfoEnabled())
192                {
193                    String msg = String.format("The log file importer for the file '%s' is now initialized", _file);
194                    _logger.info(msg);
195                }
196            }
197            catch (IOException e)
198            {
199                _logger.error("Exception when initializing the LogFileImporter for the file : '" + _file + "'", e);
200            }
201        }
202
203        /**
204         * Import new entries from the httpserver log file of this importer
205         */
206        protected synchronized void importEntries()
207        {
208            if (!_initialized)
209            {
210                _logger.error("LogFileImporter not initialized. Unable to import the new entries for the file : '" + _file + "'");
211                return;
212            }
213
214            try
215            {
216                scanLogEntries();
217            }
218            catch (IOException e)
219            {
220                _logger.error("IOException while importing httpserver access log.", e);
221            }
222            catch (Exception e)
223            {
224                _logger.error("Unexpected exception while importing httpserver access log.", e);
225            }
226        }
227
228        /**
229         * Skip to the end of the buffered stream
230         * @throws IOException if something goes wrong during the entry skipping process
231         */
232        private void skipEntriesUntilEOS() throws IOException
233        {
234            long start = System.currentTimeMillis();
235
236            while (_br.readLine() != null)
237            {
238                // do nothing, just consume the buffered reader.
239            }
240
241            if (_logger.isDebugEnabled())
242            {
243                String duration = DateUtils.formatDuration(System.currentTimeMillis() - start);
244                String msg = String.format("It tooks %s to consume the whole log file '%s'", duration, _file);
245                _logger.debug(msg);
246            }
247        }
248
249        private void scanLogEntries() throws IOException
250        {
251            int scanned = 0;
252            long start = System.currentTimeMillis();
253
254            // Scan the new lines of the log file line by line.
255            // For each line, create a new HTTPServerResourceAccess, and pass it to
256            // the resourceAccessMonitor component if this entry must be
257            // persisted.
258            boolean eosReached = false;
259            while (_br.ready() && !eosReached)
260            {
261                String entry = _br.readLine();
262                if (entry != null)
263                {
264                    HTTPServerResourceAccess r = HTTPServerResourceAccess.createRecord(entry, _df);
265                    if (r != null)
266                    {
267                        if (r.isOfInterest(_initializationDate))
268                        {
269                            _resourceAccessComponent.addAccessRecord(r);
270                            scanned++;
271                        }
272                        else
273                        {
274                            if (_logger.isDebugEnabled())
275                            {
276                                _logger.debug(String.format("This httpserver access log entry has been filtered out : %s", r));
277                            }
278                        }
279                    }
280                }
281                else
282                {
283                    // End of stream has been reached
284                    eosReached = true;
285                }
286            }
287
288            if (_logger.isDebugEnabled())
289            {
290                String durationStr = DateUtils.formatDuration(System.currentTimeMillis() - start);
291                _logger.debug(String.format("%s log entry(ies) scanned in %s", scanned, durationStr));
292            }
293        }
294    }
295}