diff --git a/javaUtilities/com.raytheon.uf.logsrv/.classpath b/javaUtilities/com.raytheon.uf.logsrv/.classpath
new file mode 100644
index 0000000000..ad32c83a78
--- /dev/null
+++ b/javaUtilities/com.raytheon.uf.logsrv/.classpath
@@ -0,0 +1,7 @@
+
+
+ * + * SOFTWARE HISTORY + * + * Date Ticket# Engineer Description + * ------------ ---------- ----------- -------------------------- + * Aug 27, 2013 njensen Initial creation + * + *+ * + * @author njensen + * @version 1.0 + */ + +public class LogService { + + private static final String LOGBACK_CONFIG = "receiver.xml"; + + private static final String SERVICE_CONFIG = "config.xml"; + + private static final Logger logger = LoggerFactory + .getLogger("InternalLogger"); + + /** + * @param args + * @throws Exception + */ + public static void main(String[] args) throws Exception { + logger.info("Starting log analytics service"); + + JAXBContext context = JAXBContext.newInstance(LogSrvConfig.class); + Unmarshaller m = context.createUnmarshaller(); + LogSrvConfig config = (LogSrvConfig) m.unmarshal(new File( + SERVICE_CONFIG)); + config.validate(); + DerbyDao.setConfig(config); + logger.info("Logging events from " + config.getClusterName()); + + logger.info("Starting socket listener"); + LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); + lc.reset(); + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(lc); + configurator.doConfigure(LOGBACK_CONFIG); + + logger.info("Scheduling report generation"); + JobScheduler.scheduleJobs(config); + } + + public static Logger getLogger() { + return logger; + } + +} diff --git a/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/LogServiceException.java b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/LogServiceException.java new file mode 100644 index 0000000000..4acd3f1b7f --- /dev/null +++ b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/LogServiceException.java @@ -0,0 +1,73 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.logsrv; + +/** + * An exception in the logging service. + * + *
+ * + * SOFTWARE HISTORY + * + * Date Ticket# Engineer Description + * ------------ ---------- ----------- -------------------------- + * Aug 27, 2013 njensen Initial creation + * + *+ * + * @author njensen + * @version 1.0 + */ + +public class LogServiceException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * Default Constructor + * + */ + public LogServiceException() { + super(); + } + + /** + * @param message + */ + public LogServiceException(String message) { + super(message); + } + + /** + * @param message + * @param cause + */ + public LogServiceException(String message, Throwable cause) { + super(message, cause); + } + + /** + * @param cause + */ + public LogServiceException(Throwable cause) { + super(cause); + } + +} diff --git a/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/StoredMsg.java b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/StoredMsg.java new file mode 100644 index 0000000000..f0c15b7ff9 --- /dev/null +++ b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/StoredMsg.java @@ -0,0 +1,169 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.logsrv; + +import java.util.Date; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.classic.spi.StackTraceElementProxy; +import ch.qos.logback.classic.spi.ThrowableProxyUtil; + +/** + * A translation of a logging event to a POJO that closely resembles what is in + * the database. + * + *
+ * + * SOFTWARE HISTORY + * + * Date Ticket# Engineer Description + * ------------ ---------- ----------- -------------------------- + * Aug 27, 2013 njensen Initial creation + * + *+ * + * @author njensen + * @version 1.0 + */ + +public class StoredMsg { + + private Date eventTime; + + private String message; + + private String javaClass; + + private int lineNumber; + + // TODO build date or build number? not sure how to get logback to send that + // along + + private String stacktrace; + + private String threadName; + + private String machineName; + + private String level; + + public StoredMsg(ILoggingEvent event) { + eventTime = new Date(event.getTimeStamp()); + + // find the most underlying cause + IThrowableProxy cause = event.getThrowableProxy(); + if (cause != null) { + stacktrace = ThrowableProxyUtil.asString(cause); + while (cause.getCause() != null) { + cause = cause.getCause(); + } + + message = cause.getMessage(); + StackTraceElementProxy[] stack = cause + .getStackTraceElementProxyArray(); + if (stack != null && stack.length > 0) { + StackTraceElement ste = stack[0].getStackTraceElement(); + lineNumber = ste.getLineNumber(); + javaClass = ste.getClassName(); + } + } else { + message = event.getMessage(); + } + threadName = event.getThreadName(); + machineName = event.getLoggerContextVO().getPropertyMap() + .get("HOSTNAME"); + level = event.getLevel().toString(); + + } + + public Date getEventTime() { + return eventTime; + } + + public void setEventTime(Date eventTime) { + this.eventTime = eventTime; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getJavaClass() { + return javaClass; + } + + public void setJavaClass(String javaClass) { + this.javaClass = javaClass; + } + + public int getLineNumber() { + return lineNumber; + } + + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + public String getThreadName() { + return threadName; + } + + public void setThreadName(String threadName) { + this.threadName = threadName; + } + + public String getMachineName() { + return machineName; + } + + public void setMachineName(String machineName) { + this.machineName = machineName; + } + + public String getStacktrace() { + return stacktrace; + } + + public void setStacktrace(String stacktrace) { + this.stacktrace = stacktrace; + } + + public String getLevel() { + return level; + } + + public void setLevel(String level) { + this.level = level; + } + + @Override + public String toString() { + return "StoredMsg [eventTime=" + eventTime + ", message=" + message + + ", javaClass=" + javaClass + ", lineNumber=" + lineNumber + + ", stacktrace=" + stacktrace + ", threadName=" + threadName + + ", machineName=" + machineName + ", level=" + level + "]"; + } + +} diff --git a/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/config/LogSrvConfig.java b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/config/LogSrvConfig.java new file mode 100644 index 0000000000..3e4bffca58 --- /dev/null +++ b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/config/LogSrvConfig.java @@ -0,0 +1,143 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.logsrv.config; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import org.apache.commons.lang.Validate; + +/** + * A configuration for the logging service. + * + *
+ * + * SOFTWARE HISTORY + * + * Date Ticket# Engineer Description + * ------------ ---------- ----------- -------------------------- + * Aug 29, 2013 njensen Initial creation + * + *+ * + * @author njensen + * @version 1.0 + */ + +@XmlRootElement +@XmlAccessorType(XmlAccessType.NONE) +public class LogSrvConfig { + + @XmlElement() + private String clusterName; + + @XmlElement + private String databaseDir; + + @XmlElement + private String fromAddress; + + @XmlElement + private String smtpHost; + + @XmlElement + private int smtpPort; + + @XmlElement + private String toAddress; + + @XmlElement + private String timeToSend; + + public String getFromAddress() { + return fromAddress; + } + + public void setFromAddress(String fromAddress) { + this.fromAddress = fromAddress; + } + + public String getSmtpHost() { + return smtpHost; + } + + public void setSmtpHost(String smtpHost) { + this.smtpHost = smtpHost; + } + + public int getSmtpPort() { + return smtpPort; + } + + public void setSmtpPort(int smtpPort) { + this.smtpPort = smtpPort; + } + + public String getToAddress() { + return toAddress; + } + + public void setToAddress(String toAddress) { + this.toAddress = toAddress; + } + + public String getClusterName() { + return clusterName; + } + + public void setClusterName(String clusterName) { + this.clusterName = clusterName; + } + + public String getTimeToSend() { + return timeToSend; + } + + public void setTimeToSend(String timeToSend) { + this.timeToSend = timeToSend; + } + + public String getDatabaseDir() { + return databaseDir; + } + + public void setDatabaseDir(String databaseDir) { + this.databaseDir = databaseDir; + } + + /** + * Validates that the config has every value set. + */ + public void validate() { + Validate.notEmpty(clusterName, "Config must include a clusterName"); + Validate.notEmpty(databaseDir, "Config must include a databaseDir"); + Validate.notEmpty(fromAddress, "Config must include a fromAddress"); + Validate.notEmpty(smtpHost, "Config must include an smtpHost"); + Validate.notEmpty(timeToSend, "Config must include a timeToSend"); + Validate.notEmpty(toAddress, "Config must include a toAddress"); + if (smtpPort <= 0) { + throw new IllegalArgumentException( + "Config must include an smtpPort"); + } + } + +} diff --git a/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/derby/DerbyAppender.java b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/derby/DerbyAppender.java new file mode 100644 index 0000000000..31db60bad7 --- /dev/null +++ b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/derby/DerbyAppender.java @@ -0,0 +1,58 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.logsrv.derby; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; + +import com.raytheon.uf.logsrv.LogService; +import com.raytheon.uf.logsrv.LogServiceException; +import com.raytheon.uf.logsrv.StoredMsg; + +/** + * A logback appender that stores a logging event to the database. + * + *
+ * + * SOFTWARE HISTORY + * + * Date Ticket# Engineer Description + * ------------ ---------- ----------- -------------------------- + * Aug 27, 2013 njensen Initial creation + * + *+ * + * @author njensen + * @version 1.0 + */ + +public class DerbyAppender extends AppenderBase
+ * + * SOFTWARE HISTORY + * + * Date Ticket# Engineer Description + * ------------ ---------- ----------- -------------------------- + * Aug 27, 2013 njensen Initial creation + * + *+ * + * @author njensen + * @version 1.0 + */ + +public class DerbyDao { + + private static final String DRIVER = "org.apache.derby.jdbc.EmbeddedDriver"; + + private static final String TABLE = "log"; + + private static final String DB_CREATE = "CREATE TABLE " + TABLE + + "(pk INT NOT NULL GENERATED BY DEFAULT AS IDENTITY, " + + "eventTime TIMESTAMP, " + "message VARCHAR(32672), " + + "javaClass VARCHAR(128), " + "lineNumber INT, " + + "stacktrace VARCHAR(32672), " + "threadName VARCHAR(128), " + + "machineName VARCHAR(128), " + "level VARCHAR(32))"; + + private static final String INSERT_PREPARED_STATEMENT = "INSERT INTO " + + TABLE + + "(eventTime, message, javaClass, lineNumber, stacktrace, threadName, machineName, level) " + + "values (?, ?, ?, ?, ?, ?, ?, ?)"; + + private static final String PURGE_STATEMENT = "DELETE from " + TABLE; + + private static final String TIME_STATEMENT = "select min(eventTime), max(eventTime) FROM " + + TABLE; + + private static final String COUNT_STATEMENT = "select count(*) from " + + TABLE; + + private PreparedStatement saveStatement; + + private PreparedStatement purgeStatement; + + private PreparedStatement timeStatement; + + private PreparedStatement countStatement; + + private Connection connection; + + private static DerbyDao instance = new DerbyDao(); + + private static LogSrvConfig config; + + private DerbyDao() { + + } + + public static DerbyDao getInstance() { + return instance; + } + + public static void setConfig(LogSrvConfig cfg) { + config = cfg; + } + + /** + * Creates a connection to derby + * + * @return + * @throws LogServiceException + */ + private Connection createConnection() throws LogServiceException { + Statement statement = null; + boolean errorOccurred = false; + Connection connection = null; + try { + Class.forName(DRIVER).newInstance(); + connection = DriverManager.getConnection("jdbc:derby:directory:" + + config.getDatabaseDir() + ";create=true"); + DatabaseMetaData dmd = connection.getMetaData(); + ResultSet tables = dmd.getTables(null, null, null, + new String[] { "TABLE" }); + boolean needCreation = true; + + while (tables.next()) { + String tableName = tables.getString("TABLE_NAME"); + if (tableName.equalsIgnoreCase(TABLE)) { + needCreation = false; + break; + } + } + + if (needCreation) { + statement = connection.createStatement(); + statement.execute(DB_CREATE); + } + connection.commit(); + } catch (Exception e) { + errorOccurred = true; + throw new LogServiceException("Error setting up database", e); + } finally { + closeStatement(statement); + if (errorOccurred) { + closeConnection(connection); + } + } + + return connection; + } + + /** + * Inserts a StoredMsg as a row in the database table + * + * @param event + * @throws LogServiceException + */ + public synchronized void insert(StoredMsg event) throws LogServiceException { + boolean errorOccurred = false; + Connection conn = null; + + try { + conn = getConnection(); + if (saveStatement == null) { + saveStatement = conn + .prepareStatement(INSERT_PREPARED_STATEMENT); + } + + saveStatement.setTimestamp(1, new Timestamp(event.getEventTime() + .getTime())); + saveStatement.setString(2, event.getMessage()); + saveStatement.setString(3, event.getJavaClass()); + saveStatement.setInt(4, event.getLineNumber()); + saveStatement.setString(5, event.getStacktrace()); + saveStatement.setString(6, event.getThreadName()); + saveStatement.setString(7, event.getMachineName()); + saveStatement.setString(8, event.getLevel()); + int result = saveStatement.executeUpdate(); + if (result < 1) { + errorOccurred = true; + LogService.getLogger().error("Insert of logging event failed"); + } else { + conn.commit(); + } + } catch (Exception e) { + errorOccurred = true; + throw new LogServiceException( + "Error inserting event into log database", e); + } finally { + if (errorOccurred) { + closeStatement(saveStatement); + saveStatement = null; + closeConnection(conn); + } + } + } + + /** + * Gets the cached connection to the database + * + * @return + * @throws LogServiceException + */ + private synchronized Connection getConnection() throws LogServiceException { + if (connection != null) { + try { + if (connection.isClosed()) { + closeConnection(connection); + } + } catch (SQLException e) { + throw new LogServiceException("Error closing connection", e); + } + } + if (connection == null) { + try { + connection = createConnection(); + } catch (Exception e) { + throw new LogServiceException("Error creating connection", e); + } + } + + return connection; + } + + /** + * Closes a statement + * + * @param statement + */ + private void closeStatement(Statement statement) { + if (statement != null) { + try { + statement.close(); + } catch (SQLException e) { + // ignore + } + } + } + + /** + * Closes the connection to the database + * + * @param connection + */ + private void closeConnection(Connection connection) { + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + // ignore + } + connection = null; + } + + try { + DriverManager.getConnection("jdbc:derby:directory:" + + config.getDatabaseDir() + ";shutdown=true"); + } catch (SQLException e) { + // ignore as stop database will always throw an exception + } + } + + /** + * Closes a result set + * + * @param rs + */ + private void closeResultSet(ResultSet rs) { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + // ignore + } + } + } + + /** + * Queries for rows that include stacktraces and correlates them into a + * LogReportContainer + * + * @return + * @throws LogServiceException + */ + private LogReportContainer queryEventsWithStacks() + throws LogServiceException { + Connection conn = null; + boolean errorOccurred = false; + Statement statement = null; + ResultSet rs = null; + LogReportContainer container = new LogReportContainer(); + try { + conn = getConnection(); + statement = conn.createStatement(); + // line number of 0 indicates that there was no stacktrace with + // the error, those will be handled separately + rs = statement + .executeQuery("select javaClass, lineNumber, machineName, count(*) " + + "from log where lineNumber != 0 " + + "group by javaClass, lineNumber, machineName"); + conn.commit(); + while (rs.next()) { + String javaClass = rs.getString(1); + int lineNumber = rs.getInt(2); + String machineName = rs.getString(3); + int count = rs.getInt(4); + LogReportEvent occ = new LogReportEvent(); + // errors sent in without a stacktrace will not have a line + // number of java class + occ.setJavaClass(javaClass); + occ.setLineNumber(lineNumber); + container.addError(occ, machineName, count); + } + return container; + } catch (SQLException e) { + errorOccurred = true; + throw new LogServiceException("Error executing query", e); + } finally { + closeResultSet(rs); + closeStatement(statement); + if (errorOccurred) { + closeConnection(conn); + } + } + } + + /** + * Loops through the LogReportContainer's events and queries for sample + * stacktraces and messages for them. + * + * @param container + * @throws LogServiceException + */ + private void addSamples(LogReportContainer container) + throws LogServiceException { + for (LogReportEvent event : container.getEvents()) { + addSamples(event); + } + } + + /** + * Fills in a LogReportEvent with a sample stacktrace and message + * + * @param event + * @throws LogServiceException + */ + private void addSamples(LogReportEvent event) throws LogServiceException { + Connection conn = null; + boolean errorOccurred = false; + Statement statement = null; + ResultSet rs = null; + try { + conn = getConnection(); + statement = conn.createStatement(); + statement.setFetchSize(1); + statement.setMaxRows(1); + rs = statement.executeQuery("select message, stacktrace, level " + + "from log where javaClass ='" + event.getJavaClass() + + "' and lineNumber = " + event.getLineNumber()); + conn.commit(); + while (rs.next()) { + String message = rs.getString(1); + String stacktrace = rs.getString(2); + String level = rs.getString(3); + event.setSampleMessage(message); + event.setSampleStacktrace(stacktrace); + event.setLevel(level); + } + } catch (SQLException e) { + errorOccurred = true; + throw new LogServiceException("Error executing query", e); + } finally { + closeResultSet(rs); + closeStatement(statement); + if (errorOccurred) { + closeConnection(conn); + } + } + } + + /** + * Queries for rows that don't have stacktraces and adds them to the + * LogReportContainer + * + * @param container + * @throws LogServiceException + */ + private void addNoStacktraceMessages(LogReportContainer container) + throws LogServiceException { + Connection conn = null; + boolean errorOccurred = false; + Statement statement = null; + ResultSet rs = null; + try { + conn = getConnection(); + statement = conn.createStatement(); + rs = statement + .executeQuery("select message, machineName, level, threadName, count(*) from log " + + "where lineNumber = 0 group by message, machineName, level, threadName"); + conn.commit(); + while (rs.next()) { + String message = rs.getString(1); + String machineName = rs.getString(2); + String level = rs.getString(3); + String thread = rs.getString(4); + int count = rs.getInt(5); + LogReportEvent event = new LogReportEvent(); + event.setSampleMessage(message); + event.setLevel(level); + event.setThreadName(thread); + container.addError(event, machineName, count); + } + } catch (SQLException e) { + errorOccurred = true; + throw new LogServiceException("Error executing query", e); + } finally { + closeResultSet(rs); + closeStatement(statement); + if (errorOccurred) { + closeConnection(conn); + } + } + } + + /** + * Queries for the earliest and latest time in the database and sets them on + * the LogReportContainer + * + * @param container + * @throws LogServiceException + */ + private void addTimes(LogReportContainer container) + throws LogServiceException { + boolean errorOccurred = false; + Connection conn = null; + ResultSet rs = null; + try { + conn = getConnection(); + if (timeStatement == null) { + timeStatement = conn.prepareStatement(TIME_STATEMENT); + } + rs = timeStatement.executeQuery(); + conn.commit(); + rs.next(); + container.setEarliestTime(rs.getTimestamp(1)); + container.setLatestTime(rs.getTimestamp(2)); + } catch (Exception e) { + errorOccurred = true; + throw new LogServiceException( + "Error determining min and max times", e); + } finally { + closeResultSet(rs); + if (errorOccurred) { + closeStatement(timeStatement); + timeStatement = null; + closeConnection(conn); + } + } + } + + /** + * Builds a LogReportContainer that correlates different rows as the same + * event, fills in sample stacktraces and messages, and includes the + * earliest and latest times of the events. + * + * @return + * @throws LogServiceException + */ + public LogReportContainer buildReport() throws LogServiceException { + LogReportContainer container = queryEventsWithStacks(); + addSamples(container); + addNoStacktraceMessages(container); + addTimes(container); + return container; + } + + /** + * Clears all rows from the database + * + * @throws LogServiceException + */ + public void clearEntries() throws LogServiceException { + boolean errorOccurred = false; + Connection conn = null; + + try { + conn = getConnection(); + if (purgeStatement == null) { + purgeStatement = conn.prepareStatement(PURGE_STATEMENT); + } + purgeStatement.executeUpdate(); + conn.commit(); + } catch (Exception e) { + errorOccurred = true; + throw new LogServiceException("Error purging message database", e); + } finally { + if (errorOccurred) { + closeStatement(purgeStatement); + purgeStatement = null; + closeConnection(conn); + } + } + } + + /** + * Queries the database to determine the number of rows + * + * @return + * @throws LogServiceException + */ + public int getCurrentRowCount() throws LogServiceException { + boolean errorOccurred = false; + Connection conn = null; + try { + conn = getConnection(); + if (countStatement == null) { + countStatement = conn.prepareStatement(COUNT_STATEMENT); + } + ResultSet rs = countStatement.executeQuery(); + conn.commit(); + rs.next(); + return rs.getInt(1); + } catch (Exception e) { + errorOccurred = true; + throw new LogServiceException("Error determining row count", e); + } finally { + if (errorOccurred) { + closeStatement(countStatement); + countStatement = null; + closeConnection(conn); + } + } + } + +} diff --git a/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/quartz/CountRowsJob.java b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/quartz/CountRowsJob.java new file mode 100644 index 0000000000..ab56aea202 --- /dev/null +++ b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/quartz/CountRowsJob.java @@ -0,0 +1,69 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.logsrv.quartz; + +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +import com.raytheon.uf.logsrv.LogService; +import com.raytheon.uf.logsrv.LogServiceException; +import com.raytheon.uf.logsrv.derby.DerbyDao; + +/** + * A simple quartz job that logs how many log events are currently in the + * database. + * + *
+ * + * SOFTWARE HISTORY + * + * Date Ticket# Engineer Description + * ------------ ---------- ----------- -------------------------- + * Sep 5, 2013 njensen Initial creation + * + *+ * + * @author njensen + * @version 1.0 + */ + +public class CountRowsJob implements Job { + + /* + * (non-Javadoc) + * + * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) + */ + @Override + public void execute(JobExecutionContext arg0) throws JobExecutionException { + try { + int rowCount = DerbyDao.getInstance().getCurrentRowCount(); + LogService.getLogger() + .info("Database currently has " + rowCount + + " messages reported"); + } catch (LogServiceException e) { + LogService.getLogger().error( + "Error determining database row count", e); + } + + } + +} diff --git a/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/quartz/CreateSendReportJob.java b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/quartz/CreateSendReportJob.java new file mode 100644 index 0000000000..0882400b02 --- /dev/null +++ b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/quartz/CreateSendReportJob.java @@ -0,0 +1,103 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.logsrv.quartz; + +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +import com.raytheon.uf.logsrv.LogService; +import com.raytheon.uf.logsrv.LogServiceException; +import com.raytheon.uf.logsrv.config.LogSrvConfig; +import com.raytheon.uf.logsrv.derby.DerbyDao; +import com.raytheon.uf.logsrv.report.data.LogReportContainer; +import com.raytheon.uf.logsrv.report.email.HtmlGenerator; +import com.raytheon.uf.logsrv.report.email.ReportEmailer; + +/** + * A quartz job that queries the database to build report data, transforms the + * report data into HTML, emails the HTML to the configured address, and then + * purges the database of all logging events that were included in the report. + * + *
+ * + * SOFTWARE HISTORY + * + * Date Ticket# Engineer Description + * ------------ ---------- ----------- -------------------------- + * Aug 30, 2013 njensen Initial creation + * + *+ * + * @author njensen + * @version 1.0 + */ + +public class CreateSendReportJob implements Job { + + /* + * (non-Javadoc) + * + * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) + */ + @Override + public void execute(JobExecutionContext ctx) throws JobExecutionException { + LogService.getLogger().info( + "Create report job triggered, preparing to send report"); + DerbyDao dao = DerbyDao.getInstance(); + // synchronize on dao to prevent new log entries from being added + // while we're querying and then purging + synchronized (dao) { + LogReportContainer container = null; + try { + container = dao.buildReport(); + } catch (LogServiceException e) { + LogService.getLogger().error("Error building report", e); + throw new JobExecutionException("Error building report", e); + } + + String html = HtmlGenerator.generateHtml(container); + LogSrvConfig config = (LogSrvConfig) ctx.getJobDetail() + .getJobDataMap().get("config"); + + try { + ReportEmailer.email(html, config); + } catch (Exception e) { + LogService.getLogger().error("Error emailing report", e); + throw new JobExecutionException("Error emailing report", e); + } + LogService.getLogger().info( + "Report has been sent to: " + config.getToAddress()); + + // only clear out the database if the analysis report was + // successfully emailed + LogService.getLogger().info( + "Purging database of messages that were just reported"); + try { + dao.clearEntries(); + } catch (LogServiceException e) { + LogService.getLogger().error("Error purging database", e); + throw new JobExecutionException("Error purging database", e); + } + LogService.getLogger().info("Database purging complete"); + } + } + +} diff --git a/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/quartz/JobScheduler.java b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/quartz/JobScheduler.java new file mode 100644 index 0000000000..eada4ad713 --- /dev/null +++ b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/quartz/JobScheduler.java @@ -0,0 +1,75 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.logsrv.quartz; + +import org.quartz.JobDetail; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.SchedulerFactory; +import org.quartz.Trigger; +import org.quartz.TriggerUtils; +import org.quartz.impl.StdSchedulerFactory; + +import com.raytheon.uf.logsrv.config.LogSrvConfig; + +/** + * Schedules the quartz jobs in the main log process so at timed intervals, the + * number of logging events in the db are reported and the error report is + * generated and emailed. + * + *
+ * + * SOFTWARE HISTORY + * + * Date Ticket# Engineer Description + * ------------ ---------- ----------- -------------------------- + * Aug 30, 2013 njensen Initial creation + * + *+ * + * @author njensen + * @version 1.0 + */ + +public class JobScheduler { + + public static void scheduleJobs(LogSrvConfig config) + throws SchedulerException { + SchedulerFactory factory = new StdSchedulerFactory(); + Scheduler sched = factory.getScheduler(); + sched.start(); + + JobDetail job = new JobDetail("Create and Send Report Job", + CreateSendReportJob.class); + job.getJobDataMap().put("config", config); + String[] split = config.getTimeToSend().split(":"); + int hour = Integer.parseInt(split[0]); + int minute = Integer.parseInt(split[1]); + Trigger trigger = TriggerUtils.makeDailyTrigger(hour, minute); + trigger.setName("Report Trigger"); + sched.scheduleJob(job, trigger); + + JobDetail countJob = new JobDetail("Count Rows Job", CountRowsJob.class); + Trigger countTrigger = TriggerUtils.makeHourlyTrigger(); + countTrigger.setName("Count Trigger"); + sched.scheduleJob(countJob, countTrigger); + } + +} diff --git a/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/report/TestReportOutputter.java b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/report/TestReportOutputter.java new file mode 100644 index 0000000000..e954e8ec76 --- /dev/null +++ b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/report/TestReportOutputter.java @@ -0,0 +1,67 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.logsrv.report; + +import java.io.File; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Unmarshaller; + +import com.raytheon.uf.logsrv.config.LogSrvConfig; +import com.raytheon.uf.logsrv.derby.DerbyDao; +import com.raytheon.uf.logsrv.report.data.LogReportContainer; +import com.raytheon.uf.logsrv.report.email.HtmlGenerator; +import com.raytheon.uf.logsrv.report.email.ReportEmailer; + +/** + * A simple main that generates a report based on the current entries in the + * database, emails the report, and then clears the database. Allows skipping + * waiting for the once a day email. + * + *
+ * + * SOFTWARE HISTORY + * + * Date Ticket# Engineer Description + * ------------ ---------- ----------- -------------------------- + * Aug 28, 2013 njensen Initial creation + * + *+ * + * @author njensen + * @version 1.0 + */ + +public class TestReportOutputter { + + public static void main(String[] args) throws Exception { + JAXBContext context = JAXBContext.newInstance(LogSrvConfig.class); + Unmarshaller m = context.createUnmarshaller(); + LogSrvConfig config = (LogSrvConfig) m + .unmarshal(new File("config.xml")); + config.validate(); + DerbyDao.setConfig(config); + LogReportContainer container = DerbyDao.getInstance().buildReport(); + String report = HtmlGenerator.generateHtml(container); + ReportEmailer.email(report, config); + DerbyDao.getInstance().clearEntries(); + } + +} diff --git a/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/report/data/LogReportContainer.java b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/report/data/LogReportContainer.java new file mode 100644 index 0000000000..1ea68b8096 --- /dev/null +++ b/javaUtilities/com.raytheon.uf.logsrv/src/com/raytheon/uf/logsrv/report/data/LogReportContainer.java @@ -0,0 +1,90 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.logsrv.report.data; + +import java.sql.Timestamp; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * A container that holds a concept of a report based on the frequency of + * occurrences of logging events (ie errors) in the database. + * + *
+ * + * SOFTWARE HISTORY + * + * Date Ticket# Engineer Description + * ------------ ---------- ----------- -------------------------- + * Aug 28, 2013 njensen Initial creation + * + *+ * + * @author njensen + * @version 1.0 + */ + +public class LogReportContainer { + + private Map
+ * + * SOFTWARE HISTORY + * + * Date Ticket# Engineer Description + * ------------ ---------- ----------- -------------------------- + * Aug 28, 2013 njensen Initial creation + * + *+ * + * @author njensen + * @version 1.0 + */ + +public class LogReportEvent implements Comparable
+ * + * SOFTWARE HISTORY + * + * Date Ticket# Engineer Description + * ------------ ---------- ----------- -------------------------- + * Aug 28, 2013 njensen Initial creation + * + *+ * + * @author njensen + * @version 1.0 + */ + +public class HtmlGenerator { + + private static final int MAX_ERRORS = 100; + + private static final String LINE_BREAK = "
+ * + * SOFTWARE HISTORY + * + * Date Ticket# Engineer Description + * ------------ ---------- ----------- -------------------------- + * Aug 29, 2013 njensen Initial creation + * + *+ * + * @author njensen + * @version 1.0 + */ + +public class ReportEmailer { + + /** + * Emails the provided string, using the options from the config. + * + * @param report + * the text to email + * @param config + * the config of email options + * @throws Exception + */ + public static void email(String report, LogSrvConfig config) + throws Exception { + Properties props = new Properties(); + props.put("host", config.getSmtpHost()); + props.put("mail.smtp.user", config.getFromAddress()); + props.put("port", config.getSmtpPort()); + + Session session = Session.getDefaultInstance(props, null); + MimeMessage message = new MimeMessage(session); + message.setFrom(new InternetAddress(config.getFromAddress())); + String[] split = config.getToAddress().split(","); + for (String to : split) { + message.addRecipient(Message.RecipientType.TO, new InternetAddress( + to.trim())); + } + message.setSubject(config.getClusterName() + " error report"); + message.setContent(report, "text/html; charset=utf-8"); + Transport transport = session.getTransport("smtp"); + transport.connect(config.getSmtpHost(), config.getFromAddress(), null); + transport.sendMessage(message, message.getAllRecipients()); + transport.close(); + } + +}