Observium_CE/html/includes/print/syslogs.inc.php

459 lines
17 KiB
PHP

<?php
/**
* Observium
*
* This file is part of Observium.
*
* @package observium
* @subpackage web
* @copyright (C) 2006-2013 Adam Armstrong, (C) 2013-2021 Observium Limited
*
*/
/**
* Display syslog messages.
*
* Display pages with device syslog messages.
* Examples:
* print_syslogs() - display last 10 syslog messages from all devices
* print_syslogs(array('pagesize' => 99)) - display last 99 syslog messages from all device
* print_syslogs(array('pagesize' => 10, 'pageno' => 3, 'pagination' => TRUE)) - display 10 syslog messages from page 3 with pagination header
* print_syslogs(array('pagesize' => 10, 'device' = 4)) - display last 10 syslog messages for device_id 4
* print_syslogs(array('short' => TRUE)) - show small block with last syslog messages
*
* @param array $vars
* @return null
*
*/
function print_syslogs($vars)
{
// Short events? (no pagination, small out)
$short = (isset($vars['short']) && $vars['short']);
// With pagination? (display page numbers in header)
$pagination = (isset($vars['pagination']) && $vars['pagination']);
pagination($vars, 0, TRUE); // Get default pagesize/pageno
$pageno = $vars['pageno'];
$pagesize = $vars['pagesize'];
$start = $pagesize * $pageno - $pagesize;
$device_single = FALSE; // Show syslog entries for single device or multiple (use approximate counts for multiple)
$param = array();
$where = ' WHERE 1 ';
foreach ($vars as $var => $value)
{
if ($value != '')
{
$cond = array();
switch ($var)
{
case 'device':
case 'device_id':
$device_single = is_numeric($value);
$where .= generate_query_values($value, 'device_id');
break;
case 'priority':
$value = get_var_csv($value);
foreach ($value as $k => $v)
{
// Rewrite priority strings to numbers
$value[$k] = priority_string_to_numeric($v);
}
$where .= generate_query_values($value, $var);
break;
case 'tag':
case 'program':
$condition = str_contains($value, '*') ? 'LIKE' : '=';
$value = get_var_csv($value);
$where .= generate_query_values($value, $var, $condition);
break;
case 'message':
if (preg_match('/^!(=)?\s*(?<msg>.+)/', $value, $matches)) {
$where .= generate_query_values($matches['msg'], 'msg', '%!=LIKE%');
} else {
$where .= generate_query_values($value, 'msg', '%LIKE%');
}
break;
case 'timestamp_from':
$where .= ' AND `timestamp` > ?';
$param[] = $value;
break;
case 'timestamp_to':
$where .= ' AND `timestamp` < ?';
$param[] = $value;
break;
}
}
}
// Show entries only for permitted devices
$query_permitted = generate_query_permitted();
/*
// Convert NOT IN to IN for correctly use indexes
$devices_permitted = dbFetchColumn('SELECT DISTINCT `device_id` FROM `syslog` WHERE 1 '.$query_permitted, NULL, TRUE);
$query_permitted = generate_query_values($devices_permitted, 'device_id');
//r($devices_permitted);
*/
$query = 'FROM `syslog` ';
$query .= $where . $query_permitted;
$query_count = 'SELECT COUNT(*) ' . $query;
$query_count_approx = 'EXPLAIN SELECT * ' . $query; // Fast approximate count
$query = 'SELECT * ' . $query;
$query .= ' ORDER BY `seq` DESC ';
$query .= "LIMIT $start,$pagesize";
// Query syslog messages
$entries = dbFetchRows($query, $param);
// Query syslog count
if ($pagination && !$short)
{
dbSetVariable('MAX_EXECUTION_TIME', 500); // Set 0.5 sec maximum query execution time
// Exactly count, but it's very SLOW on huge tables
$count = dbFetchCell($query_count, $param);
dbSetVariable('MAX_EXECUTION_TIME', 0); // Reset maximum query execution time
//r($count);
if (!is_numeric($count))
{
// Approximate count correctly around 100-80%
dbQuery('ANALYZE TABLE `syslog`;'); // Update INFORMATION_SCHEMA for more correctly count
$tmp = dbFetchRow($query_count_approx, $param);
$count = $tmp['rows'];
}
} else {
$count = safe_count($entries);
}
if (!$count)
{
// There have been no entries returned. Print the warning.
print_warning('<h4>No syslog entries found!</h4>
Check that the syslog daemon and Observium configuration options are set correctly, that your devices are configured to send syslog to Observium and that there are no firewalls blocking the messages.
See <a href="'.OBSERVIUM_DOCS_URL.'/syslog/" target="_blank">Syslog Integration</a> guide and <a href="'.OBSERVIUM_DOCS_URL.'/config_options/#syslog-settings" target="_blank">configuration options</a> for more information.');
} else {
// Entries have been returned. Print the table.
$list = array('device' => FALSE, 'priority' => TRUE); // For now (temporarily) priority always displayed
if (!isset($vars['device']) || empty($vars['device']) || $vars['page'] === 'syslog') { $list['device'] = TRUE; }
if ($short || !isset($vars['priority']) || empty($vars['priority'])) { $list['priority'] = TRUE; }
$string = generate_box_open($vars['header']);
if((isset($vars['short']) && $vars['short'])) {
$string .= '<table class="' . OBS_CLASS_TABLE_STRIPED_MORE . '">' . PHP_EOL;
} else {
$string .= '<table class="' . OBS_CLASS_TABLE_STRIPED . '">' . PHP_EOL;
}
// Generate table header
if (!$short)
{
$cols = [];
$cols[] = [ NULL, 'class="state-marker"' ];
//$cols[] = [ NULL, 'class="no-width"' ]; // Measured entity link
$cols[] = [ 'Date' ];
if ($list['device'])
{
//$cols['device'] = [ 'Device' ];
$cols[] = [ 'Device' ];
}
if ($list['priority'])
{
//$cols['priority'] = [ 'Priority' ];
$cols[] = [ 'Priority' ];
}
$cols[] = [ '[Program] [Tags] Message' ];
$string .= get_table_header($cols, $vars);
}
// Table body
$string .= ' <tbody>' . PHP_EOL;
foreach ($entries as $entry)
{
$string .= generate_syslog_row($entry, $vars, $list);
}
//print_vars($GLOBALS['cache']['syslog']);
$string .= ' </tbody>' . PHP_EOL;
$string .= '</table>' . PHP_EOL;
$string .= generate_box_close();
// Print pagination header
if ($pagination && !$short) { $string = pagination($vars, $count) . $string . pagination($vars, $count); }
// Print syslog
echo $string;
}
}
function generate_syslog_row($entry, $vars, $list = NULL)
{
// Short events? (no pagination, small out)
$short = (isset($vars['short']) && $vars['short']);
$priorities = $GLOBALS['config']['syslog']['priorities'];
$is_alert = isset($entry['la_id']); // This is syslog alert entry?
// List of displayed columns
if (is_null($list))
{
$list = [ 'device' => FALSE, 'priority' => TRUE ]; // For now (temporarily) priority always displayed
if (!isset($vars['device']) || empty($vars['device']) || $vars['page'] == 'syslog')
{
$list['device'] = TRUE;
}
if ($short || !isset($vars['priority']) || empty($vars['priority']))
{
$list['priority'] = TRUE;
}
}
$row_class = strlen($entry['html_row_class']) ? $entry['html_row_class'] : $priorities[$entry['priority']]['row-class'];
$string = ' <tr class="'.$row_class.'">' . PHP_EOL;
$string .= '<td class="state-marker"></td>' . PHP_EOL;
$timediff = $GLOBALS['config']['time']['now'] - strtotime($entry['timestamp']);
if ($short || $timediff < 3600)
{
$string .= ' <td class="syslog text-nowrap">';
$timediff = $GLOBALS['config']['time']['now'] - strtotime($entry['timestamp']);
$string .= generate_tooltip_link('', format_uptime($timediff, "short-3"), format_timestamp($entry['timestamp']), NULL) . '</td>' . PHP_EOL;
} else {
//$string .= ' <td style="width: 130px">';
$string .= ' <td>';
$string .= format_timestamp($entry['timestamp']) . '</td>' . PHP_EOL;
}
// Device column
if ($list['device'])
{
$dev = device_by_id_cache($entry['device_id']);
$device_vars = array('page' => 'device',
'device' => $entry['device_id'],
'tab' => 'logs',
'section' => 'syslog');
if ($is_alert) { $device_vars['section'] = 'logalert'; }
$string .= ' <td class="entity">' . generate_device_link_short($dev, $device_vars) . '</td>' . PHP_EOL;
}
// Alert Rule column (in syslog alerts)
if ($list['la_id'])
{
$syslog_rules = $GLOBALS['cache']['syslog']['syslog_rules']; // Cached syslog rules
$string .= '<td><strong><a href="'.generate_url(array('page' => 'syslog_rules', 'la_id' => $entry['la_id'])).'">' .
(is_array($syslog_rules[$entry['la_id']]) ? $syslog_rules[$entry['la_id']]['la_name'] : 'Rule Deleted') . '</td>' . PHP_EOL;
}
// Priority column
if ($list['priority'])
{
if (!$short)
{
$string .= ' <td style="width: 95px"><span class="label label-' . $priorities[$entry['priority']]['label-class'] . '">' .
nicecase($priorities[$entry['priority']]['name']) . ' (' . $entry['priority'] . ')</span></td>' . PHP_EOL;
}
}
// Program and Tags column
$entry['program'] = (empty($entry['program'])) ? '[[EMPTY]]' : $entry['program'];
if ($short)
{
$string .= ' <td class="syslog">';
$string .= '<span class="label label-' . $priorities[$entry['priority']]['label-class'] . '"><strong>' . escape_html($entry['program']) . '</strong></span>';
} else {
$string .= ' <td>';
$string .= '<span class="label label-' . $priorities[$entry['priority']]['label-class'] . '">' . escape_html($entry['program']) . '</span>';
/* Show tags if not short */
$tags = array();
foreach(explode(',', $entry['tag']) as $tag)
{
if (!str_istarts($tag, $entry['program']) &&
!preg_match('/^(\d+\:|[\da-f]{2})$/i', $tag) &&
!preg_match('/^<(Emer|Aler|Crit|Err|Warn|Noti|Info|Debu)/i', $tag)) // Skip tags same as program or old numeric tags or syslog-ng 2x hex numbers
{
$tags[] = escape_html($tag);
}
}
if ($tags)
{
$string .= '<span class="label">';
$string .= implode('</span><span class="label">', $tags);
$string .= '</span>';
}
/* End tags */
}
if ($list['program'])
{
// Program in separate column (from message)
$string .= $short ? '</td><td class="syslog">' : '</td><td>';
}
// Link with syslog ports cache
if (!isset($GLOBALS['cache']['syslog']['ports_links'])) {
$GLOBALS['cache']['syslog']['ports_links'] = [];
}
$ports_links = &$GLOBALS['cache']['syslog']['ports_links'];
// Highlight port links
if (!isset($ports_links[$entry['device_id']])) {
$ports_links[$entry['device_id']] = [];
$sql = 'SELECT `port_id`, `port_label_short`, `port_label_base`, `port_label_num`, `ifDescr`, `ifName` FROM `ports` WHERE `device_id` = ? AND `deleted` = ?';
foreach (dbFetchRows($sql, [ $entry['device_id'], 0 ]) as $port_descr) {
$search = [ $port_descr['ifDescr'], $port_descr['ifName'], $port_descr['port_label_short'] ];
// FIXME. Currently as hack for Extreme (should make universal with lots of examples), see:
// https://jira.observium.org/browse/OBS-3304
if (preg_match('/\s(port\s*\d.*)/i', $port_descr['ifDescr'], $matches)) {
$search[] = $matches[1];
} elseif (strlen($port_descr['port_label_base']) && str_contains($port_descr['port_label_num'], '/')) {
// Brocade NOS derp interfaces with rbridge ids, ie:
// TenGigabitEthernet 22/0/20 or Te 22/0/20 -> TenGigabitEthernet 0/20
$search[] = $port_descr['port_label_base'] . '\d+/' . $port_descr['port_label_num'];
// and short
$search[] = short_ifname($port_descr['port_label_base'] . '\d+/' . $port_descr['port_label_num']);
}
$ports_links[$entry['device_id']][$port_descr['port_id']] = [
'search' => $search,
'replace' => generate_entity_link('port', $port_descr['port_id'], '$2')
];
}
}
$entity_links = $ports_links[$entry['device_id']];
// Highlight bgp peer links (try only when program match BGP)
if (str_icontains_array($entry['program'], 'bgp'))
{
// Link with syslog bgp cache
if (!isset($GLOBALS['cache']['syslog']['bgp_links']))
{
$GLOBALS['cache']['syslog']['bgp_links'] = [];
}
$bgp_links = &$GLOBALS['cache']['syslog']['bgp_links'];
if (!isset($bgp_links[$entry['device_id']]))
{
$bgp_links[$entry['device_id']] = [];
//SELECT `bgpPeer_id`, `bgpPeerRemoteAs`, `bgpPeerIdentifier`, `bgpPeerRemoteAddr` FROM `bgpPeers` WHERE `device_id` = 2
foreach (dbFetchRows('SELECT * FROM `bgpPeers` WHERE `device_id` = ?', [ $entry['device_id'] ]) as $bgp_descr)
{
$search = [];
foreach ([ 'bgpPeerIdentifier', 'bgpPeerRemoteAddr' ] as $param)
{
if ($bgp_descr[$param] === '0.0.0.0') { continue; }
$search[] = 'Nbr ' . $bgp_descr[$param];
$search[] = 'Neighbor ' . $bgp_descr[$param];
if (get_ip_version($bgp_descr[$param]) == 6)
{
// For IPv6 append compressed form
$bgp_descr[$param] = Net_IPv6::compress($bgp_descr[$param], TRUE);
$search[] = 'Nbr ' . $bgp_descr[$param];
$search[] = 'Neighbor ' . $bgp_descr[$param];
}
}
$bgp_links[$entry['device_id']][] = [ 'search' => $search,
'replace' => generate_entity_link('bgp_peer', $bgp_descr, '$2') ];
// Additionally append AS text
if ($bgp_descr['astext'] && !isset($bgp_links[$entry['device_id']]['as'.$bgp_descr['bgpPeerRemoteAs']]))
{
$bgp_links[$entry['device_id']]['as'.$bgp_descr['bgpPeerRemoteAs']] = [
'search' => [ 'AS ' . $bgp_descr['bgpPeerRemoteAs'], 'AS: ' . $bgp_descr['bgpPeerRemoteAs'], 'AS' . $bgp_descr['bgpPeerRemoteAs'] ],
'replace' => generate_tooltip_link('', '$2', $bgp_descr['astext'])
];
}
}
}
$entity_links = array_merge($entity_links, $bgp_links[$entry['device_id']]);
}
// Linkify entities in syslog messages
if (isset($entry['msg']) && !isset($entry['message']))
{
// Different field in syslog alerts and syslog
$entry['message'] = $entry['msg'];
}
// Restore escaped quotes (for old entries)
$entry['message'] = str_replace([ '\"', "\'" ], [ '"', "'" ], $entry['message']);
$string .= ' ' . html_highlight(escape_html($entry['message']), $entity_links, NULL, TRUE) . '</td>' . PHP_EOL;
//$string .= ' ' . escape_html($entry['msg']) . '</td>' . PHP_EOL;
// if (!$short)
// {
// //$string .= '<td>' . escape_html($entry['log_type']) . '</td>' . PHP_EOL;
// //$string .= '<td style="text-align: right">'. ($entry['notified'] == '1' ? '<span class="label label-success">YES</span>' : ($entry['notified'] == '-1' ? '<span class="label">SKIP</span>' : '<span class="label label-warning">NO</span>')) . '</td>' . PHP_EOL;
// }
$string .= ' </tr>' . PHP_EOL;
return $string;
}
function generate_syslog_form_values($form_filter = FALSE, $column = NULL)
{
//global $cache;
$form_items = array();
$filter = is_array($form_filter); // Use filer or not
switch ($column)
{
case 'priorities':
case 'priority':
foreach ($GLOBALS['config']['syslog']['priorities'] as $p => $priority)
{
if ($filter && !in_array($p, $form_filter)) { continue; } // Skip filtered entries
if ($p > 7) { continue; }
$form_items[$p] = $priority;
$form_items[$p]['name'] = nicecase($priority['name']);
switch ($p)
{
case 0: // Emergency
case 1: // Alert
case 2: // Critical
case 3: // Error
$form_items[$p]['class'] = "bg-danger";
break;
case 4: // Warning
$form_items[$p]['class'] = "bg-warning";
break;
case 5: // Notification
$form_items[$p]['class'] = "bg-success";
break;
case 6: // Informational
$form_items[$p]['class'] = "bg-info";
break;
case 7: // Debugging
$form_items[$p]['class'] = "bg-suppressed";
break;
default:
$form_items[$p]['class'] = "bg-disabled";
}
}
krsort($form_items);
break;
case 'programs':
case 'program':
// Use filter as items
foreach ($form_filter as $program)
{
$name = ($program != '' ? $program : OBS_VAR_UNSET);
$form_items[$program] = $name;
}
break;
}
return $form_items;
}
// EOF