Issue #2359 initial checkin of remote log service

Change-Id: Idd48062f204002956f63c6d449868a7bd9695263

Former-commit-id: 3162ae260b [formerly 3162ae260b [formerly 12988c63aeccc678f25f4210ad484a080ddd8aa7]]
Former-commit-id: f148663bd1
Former-commit-id: ebf89475c5
This commit is contained in:
Nate Jensen 2013-09-11 15:40:34 -05:00
parent d64d1ffad3
commit d70275ed1c
22 changed files with 2071 additions and 0 deletions

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>com.raytheon.uf.logsrv</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View file

@ -0,0 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.6

View file

@ -0,0 +1,15 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Logsrv
Bundle-SymbolicName: com.raytheon.uf.logsrv
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: com.raytheon.uf.logsrv.Activator
Bundle-Vendor: RAYTHEON
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Bundle-ActivationPolicy: lazy
Require-Bundle: ch.qos.logback;bundle-version="1.0.13",
org.slf4j;bundle-version="1.7.5",
org.apache.derby;bundle-version="10.10.1",
javax.mail,
org.quartz;bundle-version="1.8.6",
org.apache.commons.lang;bundle-version="2.3.0"

View file

@ -0,0 +1,4 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<logSrvConfig>
<clusterName>ts1-oma</clusterName>
<databaseDir>/awips2/edex/data/utility/nate</databaseDir>
<fromAddress>Nathan.Jensen@raytheon.com</fromAddress>
<smtpHost>mk2-msg10.raymail.ray.com</smtpHost>
<smtpPort>143</smtpPort>
<!-- toAddress>awipsctl@list.app.ray.com</toAddress-->
<toAddress>Nathan.Jensen@raytheon.com</toAddress>
<timeToSend>01:30</timeToSend>
</logSrvConfig>

View file

@ -0,0 +1,13 @@
----------------------------------------------------------------
Wed Sep 11 15:37:35 CDT 2013:
Booting Derby version The Apache Software Foundation - Apache Derby - 10.10.1.1 - (1458268): instance a816c00e-0141-0ebe-4f2c-000009e7d350
on database directory /awips2/edex/data/utility/nate with class loader sun.misc.Launcher$AppClassLoader@4aad3ba4
Loaded from file:/common/njensen/git/development/cots/org.apache.derby/derby.jar
java.vendor=Sun Microsystems Inc.
java.runtime.version=1.6.0_43-b01
user.dir=/common/njensen/git/development/javaUtilities/com.raytheon.uf.logsrv
os.name=Linux
os.arch=amd64
os.version=2.6.18-238.el5
derby.system.home=null
Database Class Loader started - derby.database.classpath=''

View file

@ -0,0 +1,48 @@
<configuration debug="true">
<appender name="DERBY" class="com.raytheon.uf.logsrv.derby.DerbyAppender">
</appender>
<appender name="InternalLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/home/njensen/logs/logService-internal-%d{yyyyMMdd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%-5p %d [%t] %c{0}: %m%n</pattern>
</encoder>
</appender>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-5p %d [%t] %c{0}: %m%n</pattern>
</encoder>
</appender>
<appender name="asyncConsole" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="console" />
</appender>
<appender name="InternalLogAsync" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="InternalLog" />
</appender>
<logger name="InternalLogger" additivity="false">
<level value="INFO"/>
<appender-ref ref="asyncConsole" />
<appender-ref ref="InternalLogAsync" />
</logger>
<root level="WARN">
<appender-ref ref="DERBY" />
</root>
<receiver class="ch.qos.logback.classic.net.server.ServerSocketReceiver">
<address>dev33.oma.us.ray.com</address>
<port>5477</port>
</receiver>
</configuration>

View file

@ -0,0 +1,94 @@
/**
* 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.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import com.raytheon.uf.logsrv.config.LogSrvConfig;
import com.raytheon.uf.logsrv.derby.DerbyDao;
import com.raytheon.uf.logsrv.quartz.JobScheduler;
/**
* The main class of the logging service that loads the config files and
* therefore listens for incoming logging events.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Aug 27, 2013 njensen Initial creation
*
* </pre>
*
* @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;
}
}

View file

@ -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.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Aug 27, 2013 njensen Initial creation
*
* </pre>
*
* @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);
}
}

View file

@ -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.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Aug 27, 2013 njensen Initial creation
*
* </pre>
*
* @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 + "]";
}
}

View file

@ -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.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Aug 29, 2013 njensen Initial creation
*
* </pre>
*
* @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");
}
}
}

View file

@ -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.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Aug 27, 2013 njensen Initial creation
*
* </pre>
*
* @author njensen
* @version 1.0
*/
public class DerbyAppender extends AppenderBase<ILoggingEvent> {
@Override
protected void append(ILoggingEvent eventObject) {
StoredMsg msg = new StoredMsg(eventObject);
try {
DerbyDao.getInstance().insert(msg);
} catch (LogServiceException e) {
LogService.getLogger().error(
"Error inserting message into derby database", e);
}
}
}

View file

@ -0,0 +1,536 @@
/**
* 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 java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import com.raytheon.uf.logsrv.LogService;
import com.raytheon.uf.logsrv.LogServiceException;
import com.raytheon.uf.logsrv.StoredMsg;
import com.raytheon.uf.logsrv.config.LogSrvConfig;
import com.raytheon.uf.logsrv.report.data.LogReportContainer;
import com.raytheon.uf.logsrv.report.data.LogReportEvent;
/**
* DAO for interacting with the derby database to add rows or create report
* objects.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Aug 27, 2013 njensen Initial creation
*
* </pre>
*
* @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);
}
}
}
}

View file

@ -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.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Sep 5, 2013 njensen Initial creation
*
* </pre>
*
* @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);
}
}
}

View file

@ -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.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Aug 30, 2013 njensen Initial creation
*
* </pre>
*
* @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");
}
}
}

View file

@ -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.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Aug 30, 2013 njensen Initial creation
*
* </pre>
*
* @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);
}
}

View file

@ -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.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Aug 28, 2013 njensen Initial creation
*
* </pre>
*
* @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();
}
}

View file

@ -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.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Aug 28, 2013 njensen Initial creation
*
* </pre>
*
* @author njensen
* @version 1.0
*/
public class LogReportContainer {
private Map<String, LogReportEvent> map = new HashMap<String, LogReportEvent>();
private Timestamp earliestTime;
private Timestamp latestTime;
public void addError(LogReportEvent event, String machineName,
int occurrences) {
String key = event.getKey();
if (!map.containsKey(key)) {
map.put(key, event);
}
LogReportEvent current = map.get(key);
Map<String, Integer> machineCount = current.getMachineCount();
Integer count = machineCount.get(machineName);
if (count == null) {
count = 0;
}
count += occurrences;
machineCount.put(machineName, count);
}
public Collection<LogReportEvent> getEvents() {
return map.values();
}
public Timestamp getEarliestTime() {
return earliestTime;
}
public void setEarliestTime(Timestamp earliestTime) {
this.earliestTime = earliestTime;
}
public Timestamp getLatestTime() {
return latestTime;
}
public void setLatestTime(Timestamp latestTime) {
this.latestTime = latestTime;
}
}

View file

@ -0,0 +1,184 @@
/**
* 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.util.HashMap;
import java.util.Map;
import ch.qos.logback.classic.Level;
/**
* An event representing a unique error that was originally reported as a
* logging event, with a count of occurrences per machine.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Aug 28, 2013 njensen Initial creation
*
* </pre>
*
* @author njensen
* @version 1.0
*/
public class LogReportEvent implements Comparable<LogReportEvent> {
private int lineNumber;
private String javaClass;
private String sampleStacktrace;
private Map<String, Integer> machineCount = new HashMap<String, Integer>();
private String sampleMessage;
private String level;
private String threadName;
public int getLineNumber() {
return lineNumber;
}
public void setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}
public String getJavaClass() {
return javaClass;
}
public void setJavaClass(String javaClass) {
this.javaClass = javaClass;
}
public String getSampleStacktrace() {
return sampleStacktrace;
}
public void setSampleStacktrace(String sampleStacktrace) {
this.sampleStacktrace = sampleStacktrace;
}
public Map<String, Integer> getMachineCount() {
return machineCount;
}
public void setMachineCount(Map<String, Integer> machineCount) {
this.machineCount = machineCount;
}
public String getSampleMessage() {
return sampleMessage;
}
public void setSampleMessage(String sampleMessage) {
this.sampleMessage = sampleMessage;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public boolean receivedStack() {
return lineNumber > 0;
}
public String getKey() {
String key = null;
if (receivedStack()) {
key = javaClass + ":" + lineNumber;
} else {
key = sampleMessage;
if (key == null) {
key = "null";
} else if (key.length() > 20) {
// cut off the key to try and match it to others
key = key.substring(0, 20);
}
}
return key;
}
public int getTotalOccurences() {
int count = 0;
for (Integer i : machineCount.values()) {
count += i;
}
return count;
}
/**
* Compares the LogReportEvents to enable sorting. Sort order goes as
* follows: warn, no stacktraces, and least number of occurrences error, no
* stacktraces, and least number of occurrences stacktrace, warn, and least
* number of occurrences stacktrace, error, and least number of occurrences
*
* For example, the highest/last in the sort would be the ERROR with a
* stacktrace that occurred the most.
*/
@Override
public int compareTo(LogReportEvent o) {
int retVal = 0;
if (!this.receivedStack() && o.receivedStack()) {
retVal = -1;
} else if (this.receivedStack() && !o.receivedStack()) {
retVal = 1;
} else {
// both match on having a stacktrace or not, rank them
// based on error or warning
Level thisLevel = Level.valueOf(level);
Level oLevel = Level.valueOf(o.getLevel());
if (thisLevel.toInt() < oLevel.toInt()) {
retVal = -1;
} else if (thisLevel.toInt() > oLevel.toInt()) {
retVal = 1;
} else {
// both match on having a stacktrace or not, and match
// on being WARNs or ERRORs, so rank them on number
// of times they occurred
int count1 = this.getTotalOccurences();
int count2 = o.getTotalOccurences();
retVal = (count1 < count2) ? -1 : ((count1 == count2) ? 0 : 1);
}
}
return retVal;
}
public String getThreadName() {
return threadName;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
}

View file

@ -0,0 +1,194 @@
/**
* 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.email;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.raytheon.uf.logsrv.report.data.LogReportContainer;
import com.raytheon.uf.logsrv.report.data.LogReportEvent;
/**
* Uses a LogReportContainer to generate an HTML report that can be emailed.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Aug 28, 2013 njensen Initial creation
*
* </pre>
*
* @author njensen
* @version 1.0
*/
public class HtmlGenerator {
private static final int MAX_ERRORS = 100;
private static final String LINE_BREAK = "<BR/>";
private static final String HR = "<HR/>";
private static final SimpleDateFormat SDF = new SimpleDateFormat(
"yyyy-MM-dd HH:mm z");
/**
* Generates an HTML string based on the report container
*
* @param container
* the container of events to generate HTML for
* @return an HTML formatted report
*/
public static String generateHtml(LogReportContainer container) {
String report = null;
if (container != null) {
Collection<LogReportEvent> collection = container.getEvents();
LogReportEvent[] array = collection.toArray(new LogReportEvent[0]);
Arrays.sort(array, Collections.reverseOrder());
StringBuilder sb = new StringBuilder();
sb.append(buildHeader(array.length, container.getEarliestTime(),
container.getLatestTime()));
sb.append(LINE_BREAK);
boolean foundFirstWithoutStacktrace = false;
for (int i = 0; i < array.length && i < MAX_ERRORS; i++) {
LogReportEvent event = array[i];
if (!event.receivedStack() && !foundFirstWithoutStacktrace) {
foundFirstWithoutStacktrace = true;
sb.append(buildNoStacktraceDisclaimer());
}
sb.append(HR);
sb.append(LINE_BREAK);
sb.append(buildEvent(array[i]));
sb.append(LINE_BREAK);
sb.append(LINE_BREAK);
}
report = sb.toString();
}
return report;
}
private static CharSequence buildHeader(int distinctErrors, Date earliest,
Date latest) {
StringBuilder sb = new StringBuilder();
sb.append("Auto-generated error report");
sb.append(LINE_BREAK);
sb.append("Created on ");
sb.append(SDF.format(new Date()));
sb.append(LINE_BREAK);
if (distinctErrors > 0) {
sb.append("Timeframe of errors: <span style=\"font-style:italic;\">");
sb.append(SDF.format(earliest));
sb.append("</span>");
sb.append(" to <span style=\"font-style:italic;\">");
sb.append(SDF.format(latest));
sb.append("</span>");
sb.append(LINE_BREAK);
}
sb.append("Number of distinct errors reported: <span style=\"font-weight:bold;\">");
sb.append(distinctErrors);
sb.append("</span>");
sb.append(LINE_BREAK);
if (distinctErrors > MAX_ERRORS) {
sb.append("Report truncated to top ");
sb.append(MAX_ERRORS);
sb.append(" errors/warnings");
sb.append(LINE_BREAK);
}
return sb;
}
private static CharSequence buildEvent(LogReportEvent event) {
StringBuilder sb = new StringBuilder();
sb.append("<span style=\"font-weight:bold;\">");
sb.append(event.getLevel());
if (!event.receivedStack()) {
// thread name is really only useful if we don't have a stacktrace
sb.append(" [");
sb.append(event.getThreadName());
sb.append("]");
}
sb.append(" ");
sb.append(event.getSampleMessage());
sb.append("</span>");
sb.append(LINE_BREAK);
sb.append("Occurred a total of <span style=\"font-weight:bold;\">");
sb.append(event.getTotalOccurences());
sb.append("</span> times on the following machines:");
sb.append(LINE_BREAK);
Map<String, Integer> machineCount = event.getMachineCount();
Set<Entry<String, Integer>> set = machineCount.entrySet();
List<Entry<String, Integer>> list = new ArrayList<Entry<String, Integer>>(
set);
// sort it so the machines with the most errors are listed first
Collections.sort(list, new Comparator<Entry<String, Integer>>() {
@Override
public int compare(Entry<String, Integer> o1,
Entry<String, Integer> o2) {
int val1 = o1.getValue();
int val2 = o2.getValue();
return (val2 < val1) ? -1 : ((val1 == val2) ? 0 : 1);
}
});
for (Entry<String, Integer> entry : list) {
sb.append("<span style=\"font-style:italic; padding-left: 30px;\">");
sb.append(entry.getKey());
sb.append("</span>: ");
sb.append(entry.getValue());
sb.append(" occurrences");
sb.append(LINE_BREAK);
}
sb.append(LINE_BREAK);
if (event.receivedStack()) {
sb.append(event.getSampleStacktrace().replaceAll("\n", LINE_BREAK));
}
sb.append(LINE_BREAK);
return sb;
}
private static CharSequence buildNoStacktraceDisclaimer() {
StringBuilder sb = new StringBuilder();
sb.append(LINE_BREAK);
sb.append(HR);
sb.append("<b>Disclaimer</b>: The following errors were received without stacktraces and...");
sb.append("<ul><li>Are estimated/inexact</li>");
sb.append("<li>Should be considered for downgrade to INFO or DEBUG messages");
sb.append("<li>Should include stacktraces if remaining as ERROR</li></ul>");
return sb;
}
}

View file

@ -0,0 +1,83 @@
/**
* 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.email;
import java.util.Properties;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import com.raytheon.uf.logsrv.config.LogSrvConfig;
/**
* Emails a report using the options specified in the config.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Aug 29, 2013 njensen Initial creation
*
* </pre>
*
* @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();
}
}