SolarWinds Logs MCP Server
by jakenuts
- src
- utils
import { format, parseISO, startOfMinute, startOfHour, startOfDay } from 'date-fns';
import { formatInTimeZone } from 'date-fns-tz';
import { LogsEvent, HistogramData, HistogramOptions } from './types.js';
/**
* Generate histogram data from SolarWinds Observability logs
* @param events Array of log events
* @param options Histogram options
* @returns Histogram data
*/
export function generateHistogram(
events: any[], // Accept any array type for backward compatibility
options: HistogramOptions
): HistogramData {
if (events.length === 0) {
return {
data: [],
total: 0,
startTime: '',
endTime: '',
options,
};
}
// Sort events by time
// Handle both old PapertrailEvent (received_at) and new LogsEvent (time) formats
const sortedEvents = [...events].sort(
(a, b) => {
const timeA = a.time || a.received_at;
const timeB = b.time || b.received_at;
return new Date(timeA).getTime() - new Date(timeB).getTime();
}
);
// Get the time field based on the event format
const getEventTime = (event: any): string => event.time || event.received_at;
const startTime = parseISO(getEventTime(sortedEvents[0]));
const endTime = parseISO(getEventTime(sortedEvents[sortedEvents.length - 1]));
// Group events by time bucket
const buckets = new Map<string, number>();
for (const event of sortedEvents) {
const date = parseISO(getEventTime(event));
let bucketDate: Date;
// Determine the start of the time bucket based on the interval
switch (options.interval) {
case 'minute':
bucketDate = startOfMinute(date);
break;
case 'hour':
bucketDate = startOfHour(date);
break;
case 'day':
bucketDate = startOfDay(date);
break;
default:
bucketDate = startOfHour(date);
}
// Format the bucket key based on the interval and timezone preference
let bucketKey: string;
if (options.useUtc) {
// For UTC, use formatInTimeZone instead of format with timeZone option
bucketKey = formatInTimeZone(bucketDate, 'UTC', getFormatPattern(options.interval));
} else {
bucketKey = formatInTimeZone(bucketDate, Intl.DateTimeFormat().resolvedOptions().timeZone,
getFormatPattern(options.interval));
}
// Increment the count for this bucket
buckets.set(bucketKey, (buckets.get(bucketKey) || 0) + 1);
}
// Convert the buckets map to an array of data points
const data = Array.from(buckets.entries())
.map(([time, count]) => ({ time, count }))
.sort((a, b) => a.time.localeCompare(b.time));
// Format the start and end times
const timeFormat = 'yyyy-MM-dd HH:mm:ss';
const timeZone = options.useUtc
? 'UTC'
: Intl.DateTimeFormat().resolvedOptions().timeZone;
const formattedStartTime = formatInTimeZone(startTime, timeZone,
options.useUtc ? `${timeFormat} 'UTC'` : `${timeFormat} z`);
const formattedEndTime = formatInTimeZone(endTime, timeZone,
options.useUtc ? `${timeFormat} 'UTC'` : `${timeFormat} z`);
return {
data,
total: events.length,
startTime: formattedStartTime,
endTime: formattedEndTime,
options,
};
}
/**
* Get the format pattern for a given interval
* @param interval Time interval
* @returns Format pattern
*/
function getFormatPattern(interval: string): string {
switch (interval) {
case 'minute':
return 'yyyy-MM-dd HH:mm';
case 'hour':
return 'yyyy-MM-dd HH:00';
case 'day':
return 'yyyy-MM-dd';
default:
return 'yyyy-MM-dd HH:mm';
}
}
/**
* Generate an ASCII chart from histogram data
* @param histogramData Histogram data
* @returns ASCII chart as a string
*/
export function generateAsciiChart(histogramData: HistogramData): string {
if (histogramData.data.length === 0) {
return 'No data available for histogram';
}
const { data, total, startTime, endTime, options } = histogramData;
// Find the maximum count to scale the chart
const maxCount = Math.max(...data.map(d => d.count));
const chartWidth = 50; // Width of the chart in characters
let chart = `Histogram of log events (${options.interval} intervals, ${options.useUtc ? 'UTC' : 'local time'})\n`;
chart += `Time range: ${startTime} to ${endTime}\n`;
chart += `Total events: ${total}\n\n`;
// Generate the chart
for (const point of data) {
const barLength = Math.max(1, Math.round((point.count / maxCount) * chartWidth));
const bar = '█'.repeat(barLength);
chart += `${point.time.padEnd(20)} | ${bar} ${point.count}\n`;
}
return chart;
}