/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2016 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package org.pentaho.di.core.logging;
import java.io.PrintStream;
import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.util.EnvUtil;
public class KettleLogStore {
public static PrintStream OriginalSystemOut = System.out;
public static PrintStream OriginalSystemErr = System.err;
private static KettleLogStore store;
private LoggingBuffer appender;
private Timer logCleanerTimer;
private static AtomicBoolean initialized = new AtomicBoolean( false );
private static LogChannelInterfaceFactory logChannelInterfaceFactory = new LogChannelFactory();
public static LogChannelInterfaceFactory getLogChannelInterfaceFactory() {
return logChannelInterfaceFactory;
}
public static void setLogChannelInterfaceFactory( LogChannelInterfaceFactory logChannelInterfaceFactory ) {
KettleLogStore.logChannelInterfaceFactory = logChannelInterfaceFactory;
}
/**
* Create the central log store with optional limitation to the size
*
* @param maxSize
* the maximum size
* @param maxLogTimeoutMinutes
* The maximum time that a log line times out in Minutes.
*/
private KettleLogStore( int maxSize, int maxLogTimeoutMinutes, boolean redirectStdOut, boolean redirectStdErr ) {
this.appender = new LoggingBuffer( maxSize );
replaceLogCleaner( maxLogTimeoutMinutes );
if ( redirectStdOut ) {
System.setOut( new LoggingPrintStream( OriginalSystemOut ) );
}
if ( redirectStdErr ) {
System.setErr( new LoggingPrintStream( OriginalSystemErr ) );
}
}
public void replaceLogCleaner( final int maxLogTimeoutMinutes ) {
if ( logCleanerTimer != null ) {
logCleanerTimer.cancel();
}
logCleanerTimer = new Timer( true );
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
if ( maxLogTimeoutMinutes > 0 ) {
long minTimeBoundary = new Date().getTime() - maxLogTimeoutMinutes * 60 * 1000;
// Get the old lines to be removed
//
List<BufferLine> linesToRemove = appender.getBufferLinesBefore( minTimeBoundary );
// Remove all lines at once to prevent concurrent modification problems.
//
appender.removeBufferLines( linesToRemove );
}
}
};
// Clean out the rows every 10 seconds to get a nice steady purge operation...
//
logCleanerTimer.schedule( timerTask, 10000, 10000 );
}
/**
* Initialize the central log store with optional limitation to the size and redirect of stdout and stderr
*
* @param maxSize
* the maximum size
* @param maxLogTimeoutMinutes
* the maximum time that a log line times out in hours
* @param redirectStdOut
* a boolean indicating whether to redirect stdout to the logging framework
* @param redirectStdErr
* a boolean indicating whether to redirect stderr to the logging framework
*/
public static void init( int maxSize, int maxLogTimeoutMinutes, boolean redirectStdOut, boolean redirectStdErr ) {
if ( maxSize > 0 || maxLogTimeoutMinutes > 0 ) {
init0( maxSize, maxLogTimeoutMinutes, redirectStdOut, redirectStdErr );
} else {
init( redirectStdOut, redirectStdErr );
}
}
/**
* Initialize the central log store with optional limitation to the size
*
* @param maxSize
* the maximum size
* @param maxLogTimeoutHours
* The maximum time that a log line times out in hours.
*/
public static void init( int maxSize, int maxLogTimeoutMinutes ) {
init( maxSize, maxLogTimeoutMinutes, EnvUtil
.getSystemProperty( Const.KETTLE_REDIRECT_STDOUT, "N" ).equalsIgnoreCase( "Y" ), EnvUtil
.getSystemProperty( Const.KETTLE_REDIRECT_STDERR, "N" ).equalsIgnoreCase( "Y" ) );
}
public static void init() {
init( EnvUtil.getSystemProperty( Const.KETTLE_REDIRECT_STDOUT, "N" ).equalsIgnoreCase( "Y" ), EnvUtil
.getSystemProperty( Const.KETTLE_REDIRECT_STDERR, "N" ).equalsIgnoreCase( "Y" ) );
}
/**
* Initialize the central log store with arguments specifying whether to redirect of stdout and stderr
*
* @param redirectStdOut
* a boolean indicating whether to redirect stdout to the logging framework
* @param redirectStdErr
* a boolean indicating whether to redirect stderr to the logging framework
*/
public static void init( boolean redirectStdOut, boolean redirectStdErr ) {
int maxSize = Const.toInt( EnvUtil.getSystemProperty( Const.KETTLE_MAX_LOG_SIZE_IN_LINES ), 5000 );
int maxLogTimeoutMinutes =
Const.toInt( EnvUtil.getSystemProperty( Const.KETTLE_MAX_LOG_TIMEOUT_IN_MINUTES ), 1440 );
init0( maxSize, maxLogTimeoutMinutes, redirectStdOut, redirectStdErr );
}
/**
* Initialize the central log store. If it has already been initialized the configuration will be updated.
*
* @param maxSize
* the maximum size of the log buffer
* @param maxLogTimeoutMinutes
* The maximum time that a log line times out in minutes
*/
private static synchronized void init0( int maxSize, int maxLogTimeoutMinutes, boolean redirectStdOut,
boolean redirectStdErr ) {
if ( store != null ) {
// CentralLogStore already initialized. Just update the values.
store.appender.setMaxNrLines( maxSize );
store.replaceLogCleaner( maxLogTimeoutMinutes );
} else {
store = new KettleLogStore( maxSize, maxLogTimeoutMinutes, redirectStdOut, redirectStdErr );
}
initialized.set( true );
}
public static KettleLogStore getInstance() {
if ( store == null ) {
throw new RuntimeException( "Central Log Store is not initialized!!!" );
}
return store;
}
/**
* @return the number (sequence, 1..N) of the last log line. If no records are present in the buffer, 0 is returned.
*/
public static int getLastBufferLineNr() {
return getInstance().appender.getLastBufferLineNr();
}
/**
*
* Get all the log lines pertaining to the specified parent log channel id (including all children)
*
* @param parentLogChannelId
* the parent log channel ID to grab
* @param includeGeneral
* include general log lines
* @param from
* @param to
* @return the log lines found
*/
public static List<KettleLoggingEvent> getLogBufferFromTo( String parentLogChannelId, boolean includeGeneral,
int from, int to ) {
return getInstance().appender.getLogBufferFromTo( parentLogChannelId, includeGeneral, from, to );
}
/**
* Get all the log lines for the specified parent log channel id (including all children)
*
* @param channelId
* channel IDs to grab
* @param includeGeneral
* include general log lines
* @param from
* @param to
* @return
*/
public static List<KettleLoggingEvent> getLogBufferFromTo( List<String> channelId, boolean includeGeneral,
int from, int to ) {
return getInstance().appender.getLogBufferFromTo( channelId, includeGeneral, from, to );
}
/**
* @return The appender that represents the central logging store. It is capable of giving back log rows in an
* incremental fashion, etc.
*/
public static LoggingBuffer getAppender() {
return getInstance().appender;
}
/**
* Discard all the lines for the specified log channel id AND all the children.
*
* @param parentLogChannelId
* the parent log channel id to be removed along with all its children.
*/
public static void discardLines( String parentLogChannelId, boolean includeGeneralMessages ) {
LoggingRegistry registry = LoggingRegistry.getInstance();
MetricsRegistry metricsRegistry = MetricsRegistry.getInstance();
List<String> ids = registry.getLogChannelChildren( parentLogChannelId );
// Remove all the rows for these ids
//
LoggingBuffer bufferAppender = getInstance().appender;
// int beforeSize = bufferAppender.size();
for ( String id : ids ) {
// Remove it from the central log buffer
//
bufferAppender.removeChannelFromBuffer( id );
// Also remove the item from the registry.
//
registry.getMap().remove( id );
metricsRegistry.getSnapshotLists().remove( id );
metricsRegistry.getSnapshotMaps().remove( id );
}
// Now discard the general lines if this is required
//
if ( includeGeneralMessages ) {
bufferAppender.removeGeneralMessages();
}
}
public static boolean isInitialized() {
return initialized.get();
}
}