/*
* Lilith - a log event viewer.
* Copyright (C) 2007-2015 Joern Huxhorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.huxhorn.lilith.tools;
import de.huxhorn.lilith.api.FileConstants;
import de.huxhorn.lilith.data.access.AccessEvent;
import de.huxhorn.lilith.data.eventsource.EventWrapper;
import de.huxhorn.lilith.data.logging.LoggingEvent;
import de.huxhorn.lilith.engine.AccessFileBufferFactory;
import de.huxhorn.lilith.engine.LogFileFactory;
import de.huxhorn.lilith.engine.LoggingFileBufferFactory;
import de.huxhorn.lilith.engine.impl.LogFileFactoryImpl;
import de.huxhorn.lilith.swing.callables.IndexingCallable;
import de.huxhorn.lilith.tools.formatters.AccessFormatter;
import de.huxhorn.lilith.tools.formatters.Formatter;
import de.huxhorn.lilith.tools.formatters.LoggingFormatter;
import de.huxhorn.sulky.buffers.Buffer;
import de.huxhorn.sulky.buffers.FileBuffer;
import de.huxhorn.sulky.codec.filebuffer.DefaultFileHeaderStrategy;
import de.huxhorn.sulky.codec.filebuffer.FileHeader;
import de.huxhorn.sulky.codec.filebuffer.FileHeaderStrategy;
import de.huxhorn.sulky.codec.filebuffer.MetaData;
import de.huxhorn.sulky.io.IOUtilities;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TailCommand
{
public static boolean tailFile(File inputFile, String pattern, long amount, boolean keepRunning)
{
final Logger logger = LoggerFactory.getLogger(TailCommand.class);
File inputDataFile = FileHelper.resolveDataFile(inputFile);
String inputDataFileStr = inputDataFile.getAbsolutePath();
if (!inputDataFile.isFile())
{
if (logger.isErrorEnabled()) logger.error("'{}' is not a file!", inputDataFileStr);
return false;
}
if (!inputDataFile.canRead())
{
if (logger.isErrorEnabled()) logger.error("Can't read '{}'!", inputDataFileStr);
return false;
}
File inputIndexFile = FileHelper.resolveIndexFile(inputFile);
//String inputIndexFileStr = inputIndexFile.getAbsolutePath();
long dataModified = inputDataFile.lastModified();
if (!inputIndexFile.isFile())
{
// Index file does not exist.
IndexCommand.indexLogFile(inputDataFile);
}
else
{
// Previous index file was found
long indexModified = inputIndexFile.lastModified();
if (indexModified < dataModified)
{
// Index file is outdated.
IndexCommand.indexLogFile(inputDataFile);
}
}
FileHeaderStrategy fileHeaderStrategy = new DefaultFileHeaderStrategy();
try
{
FileHeader header = fileHeaderStrategy.readFileHeader(inputDataFile);
if (header == null)
{
if (logger.isWarnEnabled())
{
logger.warn("Couldn't read file header from '{}'!", inputDataFileStr);
}
return false;
}
if (header.getMagicValue() != FileConstants.MAGIC_VALUE)
{
if (logger.isWarnEnabled())
{
logger.warn("Invalid magic value! ", Integer.toHexString(header.getMagicValue()));
}
return false;
}
MetaData metaData = header.getMetaData();
if (metaData == null || metaData.getData() == null)
{
if (logger.isWarnEnabled())
{
logger.warn("Couldn't read meta data from '{}'!", inputDataFileStr);
}
return false;
}
Map<String, String> data = metaData.getData();
String contentType = data.get(FileConstants.CONTENT_TYPE_KEY);
LogFileFactory logFileFactory = new LogFileFactoryImpl(new File("."));
if (FileConstants.CONTENT_TYPE_VALUE_LOGGING.equals(contentType))
{
Map<String, String> loggingMetaData = new HashMap<>();
loggingMetaData.put(FileConstants.CONTENT_TYPE_KEY, FileConstants.CONTENT_TYPE_VALUE_LOGGING);
loggingMetaData.put(FileConstants.CONTENT_FORMAT_KEY, FileConstants.CONTENT_FORMAT_VALUE_PROTOBUF);
loggingMetaData.put(FileConstants.COMPRESSION_KEY, FileConstants.COMPRESSION_VALUE_GZIP);
LoggingFileBufferFactory fileBufferFactory = new LoggingFileBufferFactory(logFileFactory, loggingMetaData);
FileBuffer<EventWrapper<LoggingEvent>> inputBuffer = fileBufferFactory.createBuffer(inputDataFile, inputIndexFile, data);
LoggingFormatter formatter = new LoggingFormatter();
formatter.setPattern(pattern);
long firstUnprinted=printContent(inputBuffer, formatter, amount);
if(keepRunning)
{
pollFile(inputBuffer, formatter, inputDataFile, inputIndexFile, firstUnprinted);
}
return true;
}
else if (FileConstants.CONTENT_TYPE_VALUE_ACCESS.equals(contentType))
{
Map<String, String> accessMetaData = new HashMap<>();
accessMetaData.put(FileConstants.CONTENT_TYPE_KEY, FileConstants.CONTENT_TYPE_VALUE_ACCESS);
accessMetaData.put(FileConstants.CONTENT_FORMAT_KEY, FileConstants.CONTENT_FORMAT_VALUE_PROTOBUF);
accessMetaData.put(FileConstants.COMPRESSION_KEY, FileConstants.COMPRESSION_VALUE_GZIP);
AccessFileBufferFactory fileBufferFactory = new AccessFileBufferFactory(logFileFactory, accessMetaData);
FileBuffer<EventWrapper<AccessEvent>> inputBuffer = fileBufferFactory.createBuffer(inputDataFile, inputIndexFile, data);
AccessFormatter formatter = new AccessFormatter();
formatter.setPattern(pattern);
long firstUnprinted=printContent(inputBuffer, formatter, amount);
if(keepRunning)
{
pollFile(inputBuffer, formatter, inputDataFile, inputIndexFile, firstUnprinted);
}
return true;
}
else
{
if (logger.isWarnEnabled()) logger.warn("Unexpected content type {}.", contentType);
return false;
}
}
catch (IOException ex)
{
if (logger.isWarnEnabled())
{
logger.warn("Exception while reading from file '{}'!", inputDataFileStr, ex);
}
}
return false;
}
private static <T extends Serializable> void pollFile(Buffer<EventWrapper<T>> buffer, Formatter<EventWrapper<T>> formatter, File dataFile, File indexFile, long index)
{
final Logger logger = LoggerFactory.getLogger(TailCommand.class);
for(;;)
{
long dataModified = dataFile.lastModified();
long indexModified = indexFile.lastModified();
if (indexModified < dataModified)
{
// Index file is outdated.
IndexingCallable callable=new IndexingCallable(dataFile, indexFile, true);
try
{
callable.call();
}
catch(Exception e)
{
if(logger.isWarnEnabled()) logger.warn("Exception while reindexing!", e);
break;
}
for(;index < buffer.getSize();index++)
{
EventWrapper<T> current = buffer.get(index);
if (current != null)
{
String msg = formatter.format(current);
if (msg != null)
{
System.out.print(msg);
System.out.flush();
}
}
}
}
try
{
Thread.sleep(5000);
}
catch(InterruptedException e)
{
if(logger.isInfoEnabled()) logger.info("Interrupted...");
IOUtilities.interruptIfNecessary(e);
break;
}
}
}
private static <T extends Serializable> long printContent(Buffer<EventWrapper<T>> buffer, Formatter<EventWrapper<T>> formatter, long amount)
{
long bufferSize=buffer.getSize();
if(amount < 1 || amount > bufferSize)
{
amount = 1;
}
long i;
for (i = bufferSize-amount; i < bufferSize; i++)
{
EventWrapper<T> current = buffer.get(i);
if (current != null)
{
String msg = formatter.format(current);
if (msg != null)
{
System.out.print(msg);
System.out.flush();
}
}
}
return i;
}
}