org.cleverbus.admin.web.msg.MessageLogParser.java Source code

Java tutorial

Introduction

Here is the source code for org.cleverbus.admin.web.msg.MessageLogParser.java

Source

/*
 * Copyright (C) 2015
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.cleverbus.admin.web.msg;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.zip.GZIPInputStream;

import javax.annotation.Nullable;

import org.cleverbus.common.log.Log;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.apache.commons.io.comparator.LastModifiedFileComparator;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * Parses log file and returns log lines which corresponds with specified correlation ID.
 * <p/>
 * Prerequisites/known limitations:
 * <ul>
 *     <li>application logs in DEBUG level
 *     <li>known log format
 *     <li>there is only one log file (no increment parts)
 * </ul>
 *
 * @author <a href="mailto:petr.juza@cleverlance.com">Petr Juza</a>
 * @author <a href="mailto:tomas.hanus@cleverlance.com">Tomas Hanus</a>
 */
@Component
public class MessageLogParser {

    // log file format: logFile_%d{yyyy-MM-dd}_%i.log
    private SimpleDateFormat fileFormat = new SimpleDateFormat("yyyy-MM-dd");
    public static final String BASE_FILE_EXTENSION = "log";
    public static final String GZIP_FILE_EXTENSION = "gz";

    @Autowired
    private IOFileFilter logNameFilter;

    /**
     * Absolute path to the folder with log files.
     */
    @Value("${log.folder.path}")
    private String logFolderPath;

    /**
     * Gets lines from the log file which corresponds with specified correlation ID.
     *
     * @param correlationId the correlation ID
     * @param logDate which date to search log files for
     * @return log lines
     * @throws IOException when error occurred during file reading
     */
    List<String> getLogLines(String correlationId, Date logDate) throws IOException {
        File logFolder = new File(logFolderPath);
        if (!logFolder.exists() || !logFolder.canRead()) {
            throw new FileNotFoundException("there is no readable log folder - " + logFolderPath);
        }

        final String logDateFormatted = fileFormat.format(logDate);

        // filter log files for current date
        IOFileFilter nameFilter = new IOFileFilter() {
            @Override
            public boolean accept(File file) {
                return logNameFilter.accept(file) && (StringUtils.contains(file.getName(), logDateFormatted)
                        || file.getName().endsWith(BASE_FILE_EXTENSION));
            }

            @Override
            public boolean accept(File dir, String name) {
                return StringUtils.contains(name, logDateFormatted) || name.endsWith(BASE_FILE_EXTENSION);

            }
        };

        List<File> logFiles = new ArrayList<File>(FileUtils.listFiles(logFolder, nameFilter, null));
        Collections.sort(logFiles, LastModifiedFileComparator.LASTMODIFIED_COMPARATOR);

        // go through all log files
        List<String> logLines = new ArrayList<String>();
        for (File logFile : logFiles) {
            logLines.addAll(getLogLines(logFile, correlationId));
        }

        return logLines;
    }

    /**
     * Gets lines which corresponds with specified correlation ID from the specified log file.
     *
     * @param logFile the log file
     * @param correlationId the correlation ID
     * @return log lines
     * @throws IOException when error occurred during file reading
     */
    private List<String> getLogLines(File logFile, String correlationId) throws IOException {
        List<String> logLines = new ArrayList<String>();

        Log.debug("Go through the following log file: " + logFile);

        int year = Calendar.getInstance().get(Calendar.YEAR);
        String[] possibleYears = new String[] { String.valueOf(year - 1), String.valueOf(year) };

        InputStream stream = null;
        try {
            if (logFile.getName().endsWith(GZIP_FILE_EXTENSION)) {
                stream = new GZIPInputStream(new BufferedInputStream(new FileInputStream(logFile)));
            } else {
                stream = new BufferedInputStream(new FileInputStream(logFile));
            }

            LineIterator it = IOUtils.lineIterator(stream, Charset.defaultCharset());

            String requestId = null;
            boolean lastCorrectLine = false; // if previous log line belongs to requestId
            while (it.hasNext()) {
                String line = it.nextLine();

                if (requestId == null) {
                    if (StringUtils.contains(line, correlationId)) {
                        logLines.add(formatLogLine(line));

                        // finds requestID
                        requestId = getRequestId(line);

                        if (requestId != null) {
                            Log.debug("correlationId (" + correlationId + ") => requestId (" + requestId + ")");
                        }
                    }
                } else {
                    // adds lines with requestID and lines that belongs to previous log record (e.g. XML request)
                    //  it's better to check also correlationID because it's not one request ID
                    //  for all repeated scheduled jobs for processing of partly failed messages

                    // 2013-05-23 20:22:36,754 [MACHINE_IS_UNDEFINED, ajp-bio-8009-exec-19, /esb/ws/account/v1, ...
                    // <checkCustomerCreditRequest xmlns="http://cleverbus.org/ws/AccountService-v1">
                    //    <firstName>csd</firstName>
                    //    <lastName>acs</lastName>
                    //    <birthNumber>111111/1111</birthNumber>
                    // </checkCustomerCreditRequest>

                    if (StringUtils.contains(line, requestId) || (StringUtils.contains(line, correlationId))
                            || (lastCorrectLine && !StringUtils.startsWithAny(line, possibleYears))) {
                        logLines.add(formatLogLine(line));
                        lastCorrectLine = true;
                    } else {
                        lastCorrectLine = false;
                    }
                }
            }
        } finally {
            IOUtils.closeQuietly(stream);
        }

        return logLines;
    }

    @Nullable
    private String getRequestId(String line) {
        // 2013-05-23 20:22:36,754 [MACHINE_IS_UNDEFINED, ajp-bio-8009-exec-19, /esb/ws/account/v1, 10.10.0.95:72cab819:13ecdbd371c:-7eff, ] DEBUG

        String logHeader = StringUtils.substringBetween(line, "[", "]");
        if (logHeader == null) {
            // no match - the line doesn't contain []
            return null;
        }
        String[] headerParts = StringUtils.split(logHeader, ",");
        String requestId = StringUtils.trim(headerParts[3]);

        // note: if request starts from scheduled job, then there is request ID information
        // 2013-05-27 16:37:25,633 [MACHINE_IS_UNDEFINED, DefaultQuartzScheduler-camelContext_Worker-8, , , ]
        //  WARN  c.c.c.i.c.a.d.RepairMessageServiceDbImpl$2 - The message (msg_id = 372, correlationId = ...

        return StringUtils.trimToNull(requestId);
    }

    private String formatLogLine(String line) {
        String resLine = StringEscapeUtils.escapeHtml(line);

        // highlight ERROR log line
        if (StringUtils.contains(line, " ERROR ") || StringUtils.contains(line, "StackTrace:")
                || StringUtils.contains(line, "AbstractSoapExceptionFilter - get new exception")) {
            resLine = "<span style=\"font-weight: bold; color: red;\">" + resLine + "</span>";

            // highlight log lines which contains sent/receive SOAP messages
        } else if (StringUtils.contains(line, "MessageTracing.sent")
                || StringUtils.contains(line, "MessageTracing.received")) {
            resLine = "<span class=\".soapMessage\">" + resLine + "</span>";
        }

        return resLine;
    }
}