loganalyzer/src/classes/logstreamdisk.class.php

753 lines
19 KiB
PHP

<?php
/*
*********************************************************************
* -> www.phplogcon.org <- *
* ----------------------------------------------------------------- *
* Some constants *
* *
* LogStreamDisk provides access to the data on disk. In the most
* cases this will be plain text files. If we need access to e.g.
* zipped files, this will be handled by a separate driver.
*
* \version 2.0.1 2nd Version
* \version 2.0.0 Init Version
* *
* All directives are explained within this file *
*
* Copyright (C) 2008 Adiscon GmbH.
*
* This file is part of phpLogCon.
*
* PhpLogCon 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.
*
* PhpLogCon 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 phpLogCon. If not, see <http://www.gnu.org/licenses/>.
*
* A copy of the GPL can be found in the file "COPYING" in this
* distribution.
*********************************************************************
*/
// --- Avoid directly accessing this file!
if ( !defined('IN_PHPLOGCON') )
{
die('Hacking attempt');
exit;
}
// ---
// --- Required Includes!
require_once($gl_root_path . 'include/constants_errors.php');
// ---
class LogStreamDisk extends LogStream {
private $_currentOffset = -1;
private $_currentStartPos = -1;
private $_fp = null;
private $_bEOS = false;
const _BUFFER_length = 8192;
private $_buffer = false;
private $_buffer_length = 0;
private $_p_buffer = -1;
private $_previousPageUID = -1;
private $_lastPageUID = -1;
// Constructor
public function LogStreamDisk($streamConfigObj) {
$this->_logStreamConfigObj = $streamConfigObj;
}
/**
* Open the file with read access.
*
* @param arrProperties array in: Properties wish list.
* @return integer Error stat
*/
public function Open($arrProperties)
{
// Initialise Basic stuff within the Classs
$this->RunBasicInits();
// Check if file exists!
$result = $this->Verify();
if ( $result != SUCCESS)
return $result;
// Now open the file
$this->_fp = fopen($this->_logStreamConfigObj->FileName, 'r');
$this->_currentOffset = ftell($this->_fp);
$this->_currentStartPos = $this->_currentOffset;
$this->_arrProperties = $arrProperties;
return SUCCESS;
}
/**
* Close the file.
*
* @return integer Error state
*/
public function Close() {
if ( isset($this->_fp) )
{
if (!fclose($this->_fp)) {
return ERROR_FILE_CANT_CLOSE;
}
}
// return result
return SUCCESS;
}
/**
* Verify if the file exists!
*
* @return integer Error state
*/
public function Verify() {
// Check if file exists!
if(!file_exists($this->_logStreamConfigObj->FileName)) {
return ERROR_FILE_NOT_FOUND;
}
// Check if file is readable!
if(!is_readable($this->_logStreamConfigObj->FileName)) {
return ERROR_FILE_NOT_READABLE;
}
// reached this point means success ;)!
return SUCCESS;
}
private function ReadNextBlock() {
$this->_bEOS = false;
$bCheckForLastLf = false;
if ($this->_readDirection == EnumReadDirection::Backward) {
// in this case we have to adjust a few settings
$this->_p_buffer = self::_BUFFER_length ; // set the point to the right index
// first of all, check if this is the first read
if ($this->_buffer == false) {
// this means that we have to read from the end
fseek($this->_fp, 0, SEEK_END);
$this->_currentOffset = ftell($this->_fp);
$this->_p_buffer -= 1; // eat EOF
$bCheckForLastLf = true;
}
$orig_offset = ftell($this->_fp) - $this->_buffer_length;
if ($orig_offset <= 0) {
// apparently we are at BOF so nothing to read
return ERROR_EOS;
}
// jumb to the new position
$orig_offset -= self::_BUFFER_length;
if ($orig_offset <= 0) {
// ok, we have to adjust the buffer pointer
$this->_p_buffer += $orig_offset; // note orig_offset is negative, see if
$orig_offset = 0;
}
fseek($this->_fp, $orig_offset);
} else {
$this->_p_buffer = 0;
}
$this->_buffer = fread($this->_fp, self::_BUFFER_length);
$this->_buffer_length = strlen($this->_buffer);
if ($bCheckForLastLf && $this->_buffer[$this->_p_buffer] == "\n") {
// skip it (can only occur if you read backwards)
$this->_p_buffer--;
$this->_currentOffset--;
}
if ($this->_buffer == false)
return ERROR_FILE_EOF;
return SUCCESS;
}
/**
* Read the data from a specific uID which means in this
* case from a given offset of the file.
*
* @param uID integer in/out: unique id of the data row
* @param arrProperitesOut array out: array filled with properties
* @return integer Error state
* @see ReadNext()
*/
public function Read($uID, &$arrProperitesOut) {
$this->Sseek($uID, EnumSeek::UID, 0);
$tmp = $this->_readDirection;
$this->_readDirection = EnumReadDirection::Forward;
$ret = $this->ReadNext($uID, $arrProperitesOut);
if ($tmp == EnumReadDirection::Backward) {
$this->_p_buffer -= 2;
$this->_currentStartPos = $this->_currentOffset -= 1;
$this->_readDirection = $tmp;
// we have to skip one line that we are back on the right position
$this->ReadNext($dummy1, $dummy2);
}
return $ret;
}
/**
* Read the next line from the file depending on the current
* read direction.
*
* Hint: If the current stream becomes unavailable an error
* stated is retuned. A typical case is if a log rotation
* changed the original data source.
*
* @param uID integer out: uID is the offset of data row
* @param arrProperitesOut array out: properties
* @return integer Error state
* @see ReadNext
*/
public function ReadNext(&$uID, &$arrProperitesOut, $bParseMessage = true)
{
global $content, $gl_starttime;
do
{
// Read next entry first!
if ($this->_readDirection == EnumReadDirection::Forward)
$ret = $this->ReadNextForwards($uID, $arrProperitesOut);
else
$ret = $this->ReadNextBackwards($uID, $arrProperitesOut);
// Only PARSE on success!
if ( $ret == SUCCESS && $bParseMessage)
{
// Line Parser Hook here
$retParser = $this->_logStreamConfigObj->_lineParser->ParseLine($arrProperitesOut[SYSLOG_MESSAGE], $arrProperitesOut);
// Run optional Message Parsers now
$retParser = $this->_logStreamConfigObj->ProcessMsgParsers($arrProperitesOut[SYSLOG_MESSAGE], $arrProperitesOut);
// Check if we have to skip the message!
if ( $retParser == ERROR_MSG_SKIPMESSAGE )
$ret = $retParser;
// Set uID to the PropertiesOut!
$arrProperitesOut[SYSLOG_UID] = $uID;
}
// Check how long we are running. If only two seconds of execution time are left, we abort further reading!
$scriptruntime = intval(microtime_float() - $gl_starttime);
if ( $scriptruntime > ($content['MaxExecutionTime']-2) )
{
// This may display a warning message, so the user knows we stopped reading records because of the script timeout.
$content['logstream_warning'] = "false";
$content['logstream_warning_details'] = $content['LN_WARNING_LOGSTREAMDISK_TIMEOUT'];
$content['logstream_warning_code'] = ERROR_FILE_NOMORETIME;
// Return error code
return ERROR_FILE_NOMORETIME;
}
// Loop until the filter applies, or another error occurs, and we still have TIME!
} while ( $this->ApplyFilters($ret, $arrProperitesOut) != SUCCESS && $ret == SUCCESS );
// reached here means return result!
return $ret;
}
private function ReadNextForwards(&$uID, &$arrProperitesOut) {
if ($this->_bEOS) {
return ERROR_EOS;
}
if ($this->_p_buffer < 0) {
// init read
$this->ReadNextBlock();
}
if (($this->_p_buffer == $this->_buffer_length || $this->_p_buffer == -1) && ($this->ReadNextBlock() != SUCCESS)) {
return ERROR_UNDEFINED;
}
// Init variables dynamically
$line = '';
if ( $this->_arrProperties != null )
{
foreach ( $this->_arrProperties as $property )
$arrProperitesOut[$property] = '';
}
do {
$pos = -1;
if (($pos = strpos($this->_buffer, "\n", $this->_p_buffer)) !== false) {
$uID = $this->_currentStartPos;
$logLine = $line . substr($this->_buffer, $this->_p_buffer, $pos - $this->_p_buffer);
$arrProperitesOut[SYSLOG_MESSAGE] = $logLine;
// the buffer pointer currently points to the linefeed
// so we have to increment the pointer to eat it
$this->_currentOffset += $pos - $this->_p_buffer + 1;
$this->_p_buffer = $pos + 1;
$this->_currentStartPos = $this->_currentOffset;
return SUCCESS;
}
$line .= substr($this->_buffer, $this->_p_buffer, $this->_buffer_length - $this->_p_buffer);
$this->_currentOffset += $this->_buffer_length - $this->_p_buffer;
} while ($this->ReadNextBlock() == SUCCESS);
if ( strlen($line) > 0 ) {
$uID = $this->_currentStartPos;
$arrProperitesOut[SYSLOG_MESSAGE] = $line;
$this->_currentStartPos = $this->_currentOffset;
return SUCCESS;
}
return ERROR_UNDEFINED;
}
private function ReadNextBackwards(&$uID, &$arrProperitesOut) {
if ($this->_bEOS) {
return ERROR_EOS;
}
if ($this->_p_buffer < 0) {
// a negative buffer means that the we have to adjust
// the offset
$this->_currentOffset++;
if ($this->ReadNextBlock() != SUCCESS) {
return ERROR_UNDEFINED;
}
}
// Init variables dynamically
$line = '';
foreach ( $this->_arrProperties as $property )
$arrProperitesOut[$property] = '';
do {
$pos = -1;
$neg_offset = ($this->_buffer_length - $this->_p_buffer) * -1;
if (($pos = strrpos($this->_buffer, "\n", $neg_offset)) !== false) {
// note that we are at the position of the linefeed,
// this is recognize in the next few calculation
$uID = $this->_currentOffset -= $this->_p_buffer - $pos;
$arrProperitesOut[SYSLOG_MESSAGE] = substr($this->_buffer, $pos + 1, $this->_p_buffer - $pos) . $line;
$this->_currentOffset--; // eat the lf
$this->_p_buffer = $pos - 1;
return SUCCESS;
}
$line = substr($this->_buffer, 0, $this->_p_buffer) . $line;
$this->_currentOffset -= $this->_p_buffer;
} while ($this->ReadNextBlock() == SUCCESS);
if ( strlen($line) > 0 ) {
// this case should only happend if we are on BOF
$this->_bEOS = true;
$uID = 0;
$arrProperitesOut[SYSLOG_MESSAGE] = $line;
return SUCCESS;
}
return ERROR_EOS;
}
/**
* Implementation of Seek
*/
public function Sseek(&$uID, $mode, $numrecs) {
// in any case we reset the buffer
$this->ResetBuffer();
$ret = -1;
switch ($mode) {
case EnumSeek::BOS:
$ret = fseek($this->_fp, 0);
$this->_currentOffset = $this->_currentStartPos = 0;
break;
case EnumSeek::EOS:
// a simple ReadNextBackup will do all the work
// for us, because we have reset the buffer
// remember the current readDirection
$tmp = $this->_readDirection;
$this->_readDirection = EnumReadDirection::Backward;
$ret = $this->ReadNextBackwards($uID, $dummy2);
if ($tmp == EnumReadDirection::Forward) {
// in this case we have to correct the buffer,
// because we have read backwards even the current
// readDirection is forwards
$this->_p_buffer += 2;
$this->_currentStartPos = $this->_currentOffset;
}
$this->_readDirection = $tmp;
break;
case EnumSeek::UID:
$ret = fseek($this->_fp, $uID);
$this->_currentOffset = $this->_currentStartPos = $uID;
break;
}
if ($ret != SUCCESS)
return ERROR_UNDEFINED;
return $this->Skip($uID, $numrecs);
}
/**
*
* @param numrecs integer in: If positiv, skip
* @return uid integer Error state
*/
private function Skip($uID, $numrecs) {
if ($numrecs == 0)
return SUCCESS;
if ($numrecs > 0) {
/* due to performance reason we use php's fgets instead of ReadNext method
while (!feof($this->_fp)) {
fgets($this->_fp);
$numrecs--;
if ($numrecs == 0) {
break;
}
$this->_currentOffset = ftell($this->_fp);
}
*/
while ($this->ReadNextForwards($dummy1, $dummy2) == SUCCESS)
{
fgets($this->_fp);
$numrecs--;
//--- Extra check to set the correct $_previousPageUID!
if ( $numrecs == $this->_logStreamConfigObj->_pageCount )
$this->_previousPageUID = $this->_currentOffset;
//---
if ($numrecs == 0) {
break;
}
$this->_currentOffset = ftell($this->_fp);
}
}
else
{
while ($this->ReadNextBackwards($dummy1, $dummy2) == SUCCESS)
{
$numrecs++;
//--- Extra check to set the correct $_previousPageUID!
if ( $numrecs == $this->_logStreamConfigObj->_pageCount )
$this->_previousPageUID = $this->_currentOffset;
//---
if ($numrecs == 0) {
break;
}
}
}
// where we are?
$uID = $this->_currentOffset;
if ($numrecs != 0) {
// obviously there were not enough records to skip
return ERROR_NOMORERECORDS;
}
return SUCCESS;
}
/**
* Set the filter for the current stream.
*
* @param filter object in: filter object
* @return integer Error state
public function SetFilter($filter) {
return SUCCESS;
}
*/
/**
* GetMessageCount will always return -1 which means
* that the message count is not available. We refuse
* the request of the message count due to a count would
* require to read the whole file which would be a big
* pain if the file is e.g. 1 gb.
*/
public function GetMessageCount() {
return -1;
}
/**
* This function returns the first UID for previous PAGE, if availbale!
* Otherwise will return -1!
*/
public function GetPreviousPageUID()
{
return $this->_previousPageUID;
}
/**
* This function returns the FIRST UID for the FIRST PAGE!
* NOT IMPLEMENTED RIGHT NOW!
*/
public function GetFirstPageUID()
{
return -1;
}
/**
* This function returns the first UID for the last PAGE!
* This is not possible in this logstream, so it always returns -1!
*/
public function GetLastPageUID()
{
// Only perform lastUID scan if there are NO filters, for performance REASONS!
if ( $this->_filters != null )
return UID_UNKNOWN;
// Helper variables
$myuid = -1;
$counter = 0;
$tmpOldDirection = $this->_readDirection; // Store for later use
$tmpuID = $this->_currentOffset+1; // Store for later use
$tmpArray = array();
if ( $this->_sortOrder == EnumSortingOrder::Ascending )
{
// Move to the beginning of END file!
$this->Sseek($myuid, EnumSeek::EOS, 0);
// Switch reading direction!
$this->_readDirection = EnumReadDirection::Backward;
}
else if ( $this->_sortOrder == EnumSortingOrder::Descending )
{
// Move to the beginning of the file!
$this->Sseek($myuid, EnumSeek::BOS, 0);
// Switch reading direction!
$this->_readDirection = EnumReadDirection::Forward;
}
// Now we move for one page, we do not need to process the syslog messages!
$ret = $this->ReadNext($myuid, $tmpArray, false);
// Save the current UID as LastPage UID!
$this->_lastPageUID = $myuid;
// --- Restore reading direction and file position!
$this->_readDirection = $tmpOldDirection;
$ret = $this->Read($tmpuID, $tmpArray);
// if ( $this->_readDirection == EnumReadDirection::Forward )
// $this->Sseek($myuid, EnumSeek::BOS, 0);
// else
// $this->Sseek($myuid, EnumSeek::EOS, 0);
// ---
// Return result!
return $this->_lastPageUID;
}
/**
* This function returns the current Page number, if availbale!
* Otherwise will return -1!
*/
public function GetCurrentPageNumber()
{
return -1;
}
/*
* Implementation of IsPropertySortable
*
* For now, sorting is only possible for the UID Property!
*/
public function IsPropertySortable($myProperty)
{
global $fields;
// TODO: HARDCODED | FOR NOW only FALSE!
return false;
if ( isset($fields[$myProperty]) && $myProperty == SYSLOG_UID )
return true;
else
return false;
}
/**
* Implementation of GetLogStreamStats
*
* Returns an Array og logstream statsdata
* Count of Data Items
* Total Filesize
*/
public function GetLogStreamStats()
{
// Get some file data!
/*
// return results!
return $stats;
}
else
*/
// NOT IMPLEMENTED YET!
return null;
}
/**
* Implementation of GetLogStreamTotalRowCount
*
* not implemented yet!
*/
public function GetLogStreamTotalRowCount()
{
//not implemented
return null;
}
/**
* Implementation of the CleanupLogdataByDate
*
* not implemented yet!
*/
public function CleanupLogdataByDate( $nDateTimeStamp )
{
//not implemented
return null;
}
/**
* Implementation of GetCountSortedByField
*
* For now, the disk source needs to loop through the whole file
* to consolidate and sort the data
*
* @return integer Error stat
*/
public function GetCountSortedByField($szFieldId, $nFieldType, $nRecordLimit)
{
global $content;
// We loop through all loglines! this may take a while!
$uID = UID_UNKNOWN;
$ret = $this->ReadNext($uID, $logArray);
if ( $ret == SUCCESS )
{
// Initialize Array variable
$aResult = array();
// Loop through messages
do
{
if ( isset($logArray[$szFieldId]) )
{
if ( $nFieldType == FILTER_TYPE_DATE )
{
// Convert to FULL Day Date for now!
$myFieldData = date( "Y-m-d", $logArray[$szFieldId][EVTIME_TIMESTAMP] );
}
else // Just copy the value!
$myFieldData = $logArray[$szFieldId];
if ( isset($aResult[ $myFieldData ]) )
$aResult[ $myFieldData ]++;
else
{
// Initialize entry if we haven't exceeded the RecordLImit yet!
if ( count($aResult) < ($nRecordLimit-1) ) // -1 because the last entry will become all others
$aResult[ $myFieldData ] = 1;
else
{
// Count record to others
if ( isset($aResult[ $content['LN_STATS_OTHERS'] ]) )
$aResult[ $content['LN_STATS_OTHERS'] ]++;
else
$aResult[ $content['LN_STATS_OTHERS'] ] = 1;
}
}
}
} while ( ($ret = $this->ReadNext($uID, $logArray)) == SUCCESS );
// Sort Array, so the highest count comes first!
arsort($aResult);
// array_multisort($aResult, SORT_NUMERIC, SORT_DESC);
if ( isset($aResult[ $content['LN_STATS_OTHERS'] ]) )
{
// This will move the "Others" Element to the last position!
$arrEntryCopy = $aResult[ $content['LN_STATS_OTHERS'] ];
unset($aResult[ $content['LN_STATS_OTHERS'] ]);
$aResult[ $content['LN_STATS_OTHERS'] ] = $arrEntryCopy;
}
// finally return result!
if ( count($aResult) > 0 )
return $aResult;
else
return ERROR_NOMORERECORDS;
}
else
return ERROR_NOMORERECORDS;
}
/**
* Set the direction the stream should read data.
*
*
*
* @param enumReadDirectionfilter EnumReadDirection in: The new direction.
* @return integer Error state
*
public function SetReadDirection($enumReadDirection) {
// only if the read direction change we have do do anything
if ($this->_readDirection == $enumReadDirection)
return SUCCESS;
$this->_readDirection = $enumReadDirection;
return SUCCESS;
}
*/
private function ResetBuffer() {
$this->_bEOS = false;
$this->_buffer = false;
$this->_buffer_length = 0;
$this->_p_buffer = -1;
}
}
?>