← back to Dear Bubbe Admin
log-all-users.js
271 lines
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
// Color codes for terminal output
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m'
};
// Track all unique users
const allUsers = new Map();
const userSessions = new Map();
// Read all log sources
const logSources = [
{
path: '/root/Projects/dear-bubbe-admin/logs/combined.log',
name: 'Admin Dashboard Logs'
},
{
path: '/root/Projects/dear-bubbe-nextjs/logs/chat.log',
name: 'Bubbe Chat Logs'
},
{
path: '/root/Projects/dear-bubbe-admin/logs/access.log',
name: 'Access Logs'
}
];
// Parse different log formats
function parseLogLine(line) {
// Try to parse as JSON first
try {
const parsed = JSON.parse(line);
return {
timestamp: parsed.timestamp || parsed.time,
ip: parsed.ip || parsed.userIp,
userName: parsed.userName || parsed.name || 'Anonymous',
email: parsed.userEmail || parsed.email || '',
location: parsed.location || 'Unknown',
message: parsed.message || parsed.userMessage || '',
mode: parsed.mode || 'unknown',
userId: parsed.userId || parsed.sessionId || null
};
} catch (e) {
// Try to extract from text format
const ipMatch = line.match(/IP:\s*([\d\.:a-f]+)/i);
const userMatch = line.match(/userName[":]+\s*([^",}]+)/i);
const emailMatch = line.match(/email[":]+\s*([^",}]+)/i);
const timeMatch = line.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
if (ipMatch || userMatch) {
return {
timestamp: timeMatch ? timeMatch[0] : null,
ip: ipMatch ? ipMatch[1] : 'Unknown',
userName: userMatch ? userMatch[1] : 'Anonymous',
email: emailMatch ? emailMatch[1] : '',
location: 'Unknown',
message: '',
mode: 'unknown',
userId: null
};
}
return null;
}
}
// Process all log files
console.log(`${colors.cyan}${'='.repeat(80)}${colors.reset}`);
console.log(`${colors.bright}${colors.blue}📊 COMPREHENSIVE USER LOG ANALYSIS${colors.reset}`);
console.log(`${colors.cyan}${'='.repeat(80)}${colors.reset}\n`);
logSources.forEach(source => {
if (!fs.existsSync(source.path)) {
console.log(`${colors.yellow}⚠️ ${source.name}: File not found${colors.reset}`);
return;
}
console.log(`${colors.green}📁 Processing: ${source.name}${colors.reset}`);
const content = fs.readFileSync(source.path, 'utf-8');
const lines = content.split('\n');
lines.forEach(line => {
if (!line.trim()) return;
const parsed = parseLogLine(line);
if (!parsed || !parsed.ip) return;
// Create unique key for user
const userKey = parsed.email || parsed.ip;
if (!allUsers.has(userKey)) {
allUsers.set(userKey, {
userName: parsed.userName,
email: parsed.email,
ips: new Set(),
locations: new Set(),
firstSeen: parsed.timestamp,
lastSeen: parsed.timestamp,
messageCount: 0,
modes: new Set(),
sessions: []
});
}
const user = allUsers.get(userKey);
// Update user data
if (parsed.ip && parsed.ip !== 'Unknown') {
user.ips.add(parsed.ip);
}
if (parsed.location && parsed.location !== 'Unknown') {
user.locations.add(parsed.location);
}
if (parsed.mode && parsed.mode !== 'unknown') {
user.modes.add(parsed.mode);
}
if (parsed.message) {
user.messageCount++;
}
// Update timestamps
if (parsed.timestamp) {
if (!user.firstSeen || parsed.timestamp < user.firstSeen) {
user.firstSeen = parsed.timestamp;
}
if (!user.lastSeen || parsed.timestamp > user.lastSeen) {
user.lastSeen = parsed.timestamp;
}
// Track session
user.sessions.push({
timestamp: parsed.timestamp,
ip: parsed.ip,
location: parsed.location
});
}
// Update name if better one found
if (parsed.userName && parsed.userName !== 'Anonymous' && user.userName === 'Anonymous') {
user.userName = parsed.userName;
}
});
console.log(` ✓ Processed ${lines.length} lines\n`);
});
// Sort users by last activity
const sortedUsers = Array.from(allUsers.entries()).sort((a, b) => {
const timeA = new Date(a[1].lastSeen || 0).getTime();
const timeB = new Date(b[1].lastSeen || 0).getTime();
return timeB - timeA;
});
// Display results
console.log(`${colors.cyan}${'='.repeat(80)}${colors.reset}`);
console.log(`${colors.bright}${colors.magenta}👥 ALL USERS (${sortedUsers.length} Total)${colors.reset}`);
console.log(`${colors.cyan}${'='.repeat(80)}${colors.reset}\n`);
sortedUsers.forEach((([key, user], index) => {
const isActive = user.lastSeen &&
(new Date() - new Date(user.lastSeen)) < 3600000; // Active in last hour
const statusColor = isActive ? colors.green : colors.reset;
const status = isActive ? '🟢 ACTIVE' : '⚪ INACTIVE';
console.log(`${colors.bright}${index + 1}. ${user.userName}${colors.reset} ${statusColor}${status}${colors.reset}`);
if (user.email) {
console.log(` ${colors.blue}📧 Email:${colors.reset} ${user.email}`);
}
console.log(` ${colors.yellow}🌐 IPs:${colors.reset} ${Array.from(user.ips).join(', ') || 'None'}`);
console.log(` ${colors.cyan}📍 Locations:${colors.reset} ${Array.from(user.locations).join(', ') || 'Unknown'}`);
if (user.firstSeen) {
const first = new Date(user.firstSeen);
const last = new Date(user.lastSeen);
console.log(` ${colors.magenta}⏱️ First Seen:${colors.reset} ${first.toLocaleString()}`);
console.log(` ${colors.magenta}⏱️ Last Seen:${colors.reset} ${last.toLocaleString()}`);
}
console.log(` ${colors.green}💬 Messages:${colors.reset} ${user.messageCount}`);
if (user.modes.size > 0) {
console.log(` ${colors.blue}🎭 Modes Used:${colors.reset} ${Array.from(user.modes).join(', ')}`);
}
// Show recent activity
const recentSessions = user.sessions
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
.slice(0, 3);
if (recentSessions.length > 0) {
console.log(` ${colors.yellow}📅 Recent Activity:${colors.reset}`);
recentSessions.forEach(session => {
const time = new Date(session.timestamp).toLocaleString();
console.log(` • ${time} from ${session.ip}`);
});
}
console.log(` ${colors.cyan}${'-'.repeat(60)}${colors.reset}`);
}));
// Summary statistics
const activeCount = sortedUsers.filter(([k, u]) =>
u.lastSeen && (new Date() - new Date(u.lastSeen)) < 3600000
).length;
const todayCount = sortedUsers.filter(([k, u]) =>
u.lastSeen && (new Date() - new Date(u.lastSeen)) < 86400000
).length;
console.log(`\n${colors.cyan}${'='.repeat(80)}${colors.reset}`);
console.log(`${colors.bright}${colors.green}📈 SUMMARY STATISTICS${colors.reset}`);
console.log(`${colors.cyan}${'='.repeat(80)}${colors.reset}\n`);
console.log(`${colors.bright}Total Users:${colors.reset} ${sortedUsers.length}`);
console.log(`${colors.green}Active (Last Hour):${colors.reset} ${activeCount}`);
console.log(`${colors.yellow}Active Today:${colors.reset} ${todayCount}`);
console.log(`${colors.blue}With Email:${colors.reset} ${sortedUsers.filter(([k, u]) => u.email).length}`);
console.log(`${colors.magenta}Anonymous:${colors.reset} ${sortedUsers.filter(([k, u]) => u.userName === 'Anonymous').length}`);
// Geographic distribution
const locations = new Map();
sortedUsers.forEach(([k, user]) => {
user.locations.forEach(loc => {
if (loc && loc !== 'Unknown') {
locations.set(loc, (locations.get(loc) || 0) + 1);
}
});
});
if (locations.size > 0) {
console.log(`\n${colors.bright}${colors.cyan}🌍 GEOGRAPHIC DISTRIBUTION:${colors.reset}`);
Array.from(locations.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.forEach(([loc, count]) => {
console.log(` • ${loc}: ${count} users`);
});
}
// Save detailed report
const reportPath = '/root/Projects/dear-bubbe-admin/logs/user-report.json';
const report = {
generatedAt: new Date().toISOString(),
totalUsers: sortedUsers.length,
activeInLastHour: activeCount,
activeToday: todayCount,
users: sortedUsers.map(([key, user]) => ({
key,
...user,
ips: Array.from(user.ips),
locations: Array.from(user.locations),
modes: Array.from(user.modes)
}))
};
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
console.log(`\n${colors.green}✅ Detailed report saved to: ${reportPath}${colors.reset}`);
console.log(`${colors.cyan}${'='.repeat(80)}${colors.reset}\n`);