/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS,
* <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) 2008 frentix GmbH, Switzerland<br>
* http://www.frentix.com
* <p>
*/
package org.olat.core.logging;
import java.io.IOException;
import java.io.StringWriter;
import java.text.ParseException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Layout;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.WriterAppender;
import org.olat.core.CoreSpringFactory;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.htmlheader.jscss.JSAndCSSComponent;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.components.link.LinkFactory;
import org.olat.core.gui.components.velocity.VelocityContainer;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.controller.BasicController;
import org.olat.core.util.Formatter;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.jobs.NoOpJob;
/**
* Description:<br>
* The log real time viewer controller offers the possibility to live-view the log file.
* This can be handy for debugging or to monitor certain actions like a user
* import.
*
* <P>
* Initial Date: 22.08.2008 <br>
*
* @author gnaegi
*/
public class LogRealTimeViewerController extends BasicController implements JobListener {
private static final String LOG_DISPLAYER_GROUP = "LogDisplayer_Group";
private static final Pattern logNoiseReducePattern = Pattern.compile("(.*) \\[.*%\\^(.*) .*%\\^(.*)");
private VelocityContainer logViewerVC;
private Logger log4JLogger;
private WriterAppender writerAppender;
private StringWriter writer;
private JobDetail jobDetail;
private String jobName;
private Link updateLink, startLink, stopLink;
private boolean removeLogNoise;
/**
* Constructor for creating a real time log viewer controller
*
* @param ureq The user request object
* @param control The window control object
* @param loggingPackage The logging package or a specific class. All messages
* from the given package will be displayed
* @param level The log level to include in the list. Note that this will not
* change the log level on the packages itself, use the admin console
* to do this. This filters only the messages that are below this
* level
* @param removeLogNoise true: remove a lot of brasato specific stuff that is
* normally not interesting; false: show log entries as they are
* logged. Example if set to true: <br>
* <code>2008-08-22 11:20:40 [QuartzScheduler_Worker-1] INFO LDAPUserSynchronizerJob - OLAT::INFO ^%^ I441 ^%^ org.olat.ldap ^%^ n/a ^%^ n/a ^%^ n/a ^%^ n/a ^%^ n/a ^%^ Starting LDAP user synchronize job</code>
* <br>will become<br>
* <code>2008-08-22 11:20:40 - n/a - Starting LDAP user synchronize job</code>
*/
public LogRealTimeViewerController(UserRequest ureq, WindowControl control, String loggingPackage, Level level, boolean removeLogNoise) {
super(ureq, control);
this.removeLogNoise = removeLogNoise;
logViewerVC = createVelocityContainer("logviewer");
logViewerVC.contextPut("loggingPackage", loggingPackage);
// Create logger for requested package and add a string writer appender
log4JLogger = Logger.getLogger(loggingPackage);
writer = new StringWriter();
Layout layout = new PatternLayout("%d{HH:mm:ss} %-5p [%t]: %m%n");
writerAppender = new WriterAppender(layout, writer);
writerAppender.setThreshold(level);
log4JLogger.addAppender(writerAppender);
updateLogViewFromWriter();
// Add job to read from the string writer every second
try {
jobName = "Log_Displayer_Job_" + this.hashCode();
jobDetail = new JobDetail(jobName, LOG_DISPLAYER_GROUP, NoOpJob.class);
jobDetail.addJobListener(jobName);
CronTrigger trigger = new CronTrigger();
trigger.setName(jobName);
trigger.setGroup(LOG_DISPLAYER_GROUP);
trigger.setCronExpression("* * * * * ?");
// Schedule job now
Scheduler scheduler = (Scheduler) CoreSpringFactory.getBean("schedulerFactoryBean");
scheduler.addJobListener(this);
scheduler.scheduleJob(jobDetail, trigger);
} catch (ParseException e) {
logError("Can not parse log viewer cron expression", e);
} catch (SchedulerException e) {
logError("Problem when creating log viewer scheduler", e);
}
// Add one second interval to update the log view every second
JSAndCSSComponent jsc = new JSAndCSSComponent("intervall", this.getClass(), null, null, false, null, 3000);
jsc.requireFullPageRefresh(); // interval not working otherwise
logViewerVC.put("updatecontrol", jsc);
// Add manual update link in case the automatic refresh does not work
updateLink = LinkFactory.createButtonSmall("logviewer.link.update", logViewerVC, this);
stopLink = LinkFactory.createButtonSmall("logviewer.link.stop", logViewerVC, this);
putInitialPanel(logViewerVC);
}
/**
* @see org.olat.core.gui.control.DefaultController#doDispose()
*/
@Override
protected void doDispose() {
if (logViewerVC != null) { // don't clean up twice
Scheduler scheduler = (Scheduler) CoreSpringFactory.getBean("schedulerFactoryBean");
// remove scheduler job first
try {
scheduler.deleteJob(jobName, LOG_DISPLAYER_GROUP);
scheduler.removeJobListener(jobName);
} catch (SchedulerException e) {
logError("Can not delete log viewer job", e);
}
// remove logger appender and release StringWriter
log4JLogger.removeAppender(writerAppender);
log4JLogger = null;
writerAppender.close();
writerAppender = null;
try {
writer.close();
} catch (IOException e) {
logError("Error while closing log viewer string writer", e);
}
writer = null;
updateLink = null;
logViewerVC = null;
}
}
private void updateLogViewFromWriter() {
StringBuffer sb = writer.getBuffer();
String log = sb.toString();
if (removeLogNoise) {
Matcher m = logNoiseReducePattern.matcher(log);
log = m.replaceAll("$1 - $2 - $3");
}
logViewerVC.contextPut("log", Formatter.escWithBR(log));
// don't let the writer grow endlessly, reduce to half of size when larger than 100'000 characters (1.6MB)
if (sb.length() > 100000) {
int nextLineBreakAfterHalfPos = sb.indexOf("\n", sb.length() / 2);
sb.delete(0, nextLineBreakAfterHalfPos);
}
}
/**
* @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
* org.olat.core.gui.components.Component,
* org.olat.core.gui.control.Event)
*/
@Override
protected void event(UserRequest ureq, Component source, Event event) {
if (source == updateLink) {
updateLogViewFromWriter();
}
if (source == stopLink) {
// update viewable links
logViewerVC.remove(stopLink);
logViewerVC.remove(updateLink);
startLink = LinkFactory.createButtonSmall("logviewer.link.start", logViewerVC, this);
// remove logger appender
log4JLogger.removeAppender(writerAppender);
// pause log update trigger job
try {
Scheduler scheduler = (Scheduler) CoreSpringFactory.getBean("schedulerFactoryBean");
scheduler.pauseJob(jobName, LOG_DISPLAYER_GROUP);
} catch (SchedulerException e) {
logError("Can not pause log viewer job", e);
}
}
if (source == startLink) {
// update viewable links
logViewerVC.remove(startLink);
updateLink = LinkFactory.createButtonSmall("logviewer.link.update", logViewerVC, this);
stopLink = LinkFactory.createButtonSmall("logviewer.link.stop", logViewerVC, this);
// re-add appender to logger
log4JLogger.addAppender(writerAppender);
// resume trigger job
try {
Scheduler scheduler = (Scheduler) CoreSpringFactory.getBean("schedulerFactoryBean");
scheduler.resumeJob(jobName, LOG_DISPLAYER_GROUP);
} catch (SchedulerException e) {
logError("Can not resume log viewer job", e);
}
}
}
/**
* @see org.quartz.JobListener#getName()
*/
public String getName() {
return jobName;
}
/**
* @see org.quartz.JobListener#jobExecutionVetoed(org.quartz.JobExecutionContext)
*/
public void jobExecutionVetoed(@SuppressWarnings("unused")
JobExecutionContext arg0) {
// nothing to do, see jobWasExecuted()
}
/**
* @see org.quartz.JobListener#jobToBeExecuted(org.quartz.JobExecutionContext)
*/
public void jobToBeExecuted(@SuppressWarnings("unused")
JobExecutionContext arg0) {
// nothing to do, see jobWasExecuted()
}
/**
* @see org.quartz.JobListener#jobWasExecuted(org.quartz.JobExecutionContext,
* org.quartz.JobExecutionException)
*/
public void jobWasExecuted(@SuppressWarnings("unused")
JobExecutionContext arg0, JobExecutionException arg1) {
updateLogViewFromWriter();
}
}