/*
* RHQ Management Platform
* Copyright (C) 2005-2014 Red Hat, Inc.
* All rights reserved.
*
* 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 version 2 of the License.
*
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.core.pluginapi.event.log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.sigar.FileInfo;
import org.hyperic.sigar.SigarException;
import org.hyperic.sigar.SigarProxy;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.rhq.core.domain.event.Event;
import org.rhq.core.pluginapi.event.EventContext;
import org.rhq.core.pluginapi.event.EventPoller;
/**
* An Event poller that polls a log file for new entries.
*
* @author Ian Springer
*/
public class LogFileEventPoller implements EventPoller {
private static final Log LOG = LogFactory.getLog(LogFileEventPoller.class);
private String eventType;
private File logFile;
private FileInfo logFileInfo;
private LogEntryProcessor entryProcessor;
private EventContext eventContext;
private boolean initialized;
public LogFileEventPoller(EventContext eventContext, String eventType, File logFile,
LogEntryProcessor entryProcessor) {
this.eventType = eventType;
this.logFile = logFile;
this.entryProcessor = entryProcessor;
this.eventContext = eventContext;
}
@NotNull
public String getEventType() {
return this.eventType;
}
@NotNull
public String getSourceLocation() {
return this.logFile.getPath();
}
@Nullable
public Set<Event> poll() {
if (!this.logFile.exists()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Log file [" + this.logFile + "] being polled does not exist.");
}
return null;
}
if (this.logFile.isDirectory()) {
LOG.error("Log file [" + this.logFile + "] being polled is a directory, not a regular file.");
return null;
}
if (!this.initialized) {
init();
}
if (this.logFileInfo == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Cannot poll log file [" + this.logFile
+ "] because native integration is either disabled or unavailable.");
}
return null;
}
try {
if (!this.logFileInfo.changed()) {
return null;
}
} catch (SigarException e) {
throw new RuntimeException(e);
}
return processNewLines(this.logFileInfo);
}
/**
* This performs any initialization that requires using the EventContext. It must *not* be called from our
* constructor, because pollers are constructed during PC initialization, and at that time the PC EventManager,
* which the EventContext relies on, is not yet available. Instead it is called from {@link #poll()} on the first
* invocation of that method, at which point the PC will be initialized.
*/
protected void init() {
SigarProxy sigar = this.eventContext.getSigar();
if (sigar != null) {
try {
this.logFileInfo = new LogFileInfo(sigar.getFileInfo(logFile.getPath()));
} catch (SigarException e) {
throw new RuntimeException("Failed to obtain file info for log file [" + this.logFile + "].", e);
}
} else {
LOG.warn("SIGAR is unavailable - cannot poll log file [" + this.logFile + "] for events.");
}
this.initialized = true;
}
private Set<Event> processNewLines(FileInfo fileInfo) {
Set<Event> events = null;
Reader reader = null;
try {
reader = new FileReader(this.logFile);
long offset = getOffset(fileInfo);
if (offset > 0) {
reader.skip(offset);
}
BufferedReader bufferedReader = new BufferedReader(reader);
events = this.entryProcessor.processLines(bufferedReader);
} catch (IOException e) {
LOG.error("Failed to read log file being tailed: " + this.logFile, e);
} finally {
if (reader != null) {
//noinspection EmptyCatchBlock
try {
reader.close();
} catch (IOException e) {
}
}
}
return events;
}
private long getOffset(FileInfo fileInfo) {
FileInfo previousFileInfo = fileInfo.getPreviousInfo();
if (previousFileInfo == null) {
if (LOG.isDebugEnabled()) {
LOG.debug(this.logFile + ": first stat");
}
return fileInfo.getSize();
}
if (fileInfo.getInode() != previousFileInfo.getInode()) {
if (LOG.isDebugEnabled()) {
LOG.debug(this.logFile + ": file inode changed");
}
return -1;
}
if (fileInfo.getSize() < previousFileInfo.getSize()) {
if (LOG.isDebugEnabled()) {
LOG.debug(this.logFile + ": file truncated");
}
return -1;
}
if (LOG.isDebugEnabled()) {
long diff = fileInfo.getSize() - previousFileInfo.getSize();
LOG.debug(this.logFile + ": " + diff + " new bytes");
}
return previousFileInfo.getSize();
}
}