@aerocorp/cli
Version:
AeroCorp CLI 5.1.0 - Future-Proofed Enterprise Infrastructure with Live Preview, Tunneling & Advanced DevOps
232 lines (192 loc) ⢠6.89 kB
text/typescript
import axios from 'axios';
import chalk from 'chalk';
import ora from 'ora';
import { AuthService } from './auth';
export class LogService {
private authService = new AuthService();
async getLogs(appName: string, options: any) {
if (!this.authService.isAuthenticated()) {
throw new Error('Not authenticated. Run "aerocorp login" first.');
}
const spinner = ora(`Fetching logs for ${appName}...`).start();
try {
const coolifyUrl = this.authService.getCoolifyUrl();
const headers = this.authService.getAuthHeaders();
// Find the application
const app = await this.findApplication(coolifyUrl, headers, appName);
if (!app) {
spinner.fail(`Application '${appName}' not found`);
throw new Error(`Application '${appName}' not found`);
}
// Get logs
const logsResponse = await axios.get(
`${coolifyUrl}/api/v1/applications/${app.id}/logs`,
{
headers,
params: {
lines: options.tail || 100,
follow: options.follow || false
}
}
);
spinner.succeed(`Logs fetched for ${appName}`);
// Display logs
this.displayLogs(appName, logsResponse.data, options);
// If follow mode, set up streaming
if (options.follow) {
await this.followLogs(coolifyUrl, headers, app.id, appName);
}
} catch (error) {
spinner.fail(`Failed to fetch logs for ${appName}`);
if (error.response?.status === 404) {
throw new Error(`Application '${appName}' not found or no logs available`);
} else {
throw new Error(`API Error: ${error.response?.data?.message || error.message}`);
}
}
}
private async findApplication(coolifyUrl: string, headers: any, appName: string): Promise<any> {
try {
// Get all projects
const projectsResponse = await axios.get(`${coolifyUrl}/api/v1/projects`, { headers });
const projects = projectsResponse.data;
// Search for application in all projects
for (const project of projects) {
try {
const appsResponse = await axios.get(
`${coolifyUrl}/api/v1/projects/${project.id}/applications`,
{ headers }
);
const app = appsResponse.data.find((app: any) =>
app.name === appName || app.name.includes(appName)
);
if (app) {
return app;
}
} catch (error) {
// Continue searching in other projects
continue;
}
}
return null;
} catch (error) {
throw new Error(`Failed to search for application: ${error.message}`);
}
}
private displayLogs(appName: string, logs: any, options: any) {
console.log(chalk.cyan(`\nš Logs for ${appName}`));
console.log(chalk.cyan('='.repeat(50)));
if (!logs || (Array.isArray(logs) && logs.length === 0)) {
console.log(chalk.yellow('ā No logs available'));
return;
}
// Handle different log formats
let logEntries: any[] = [];
if (typeof logs === 'string') {
logEntries = logs.split('\n').filter(line => line.trim());
} else if (Array.isArray(logs)) {
logEntries = logs;
} else if (logs.logs) {
logEntries = Array.isArray(logs.logs) ? logs.logs : logs.logs.split('\n');
}
logEntries.forEach((logEntry, index) => {
let logLine: string;
let timestamp: string = '';
let level: string = '';
let message: string = '';
if (typeof logEntry === 'string') {
logLine = logEntry;
// Try to parse timestamp and level
const timestampMatch = logLine.match(/^(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2})/);
if (timestampMatch) {
timestamp = timestampMatch[1];
logLine = logLine.substring(timestampMatch[0].length).trim();
}
const levelMatch = logLine.match(/^(ERROR|WARN|INFO|DEBUG|TRACE)/i);
if (levelMatch) {
level = levelMatch[1].toUpperCase();
message = logLine.substring(levelMatch[0].length).trim();
} else {
message = logLine;
}
} else {
timestamp = logEntry.timestamp || logEntry.time || '';
level = logEntry.level || logEntry.severity || '';
message = logEntry.message || logEntry.msg || JSON.stringify(logEntry);
}
// Format output
let output = '';
if (timestamp) {
output += chalk.gray(`[${timestamp}] `);
}
if (level) {
const levelColor = this.getLevelColor(level);
output += (chalk as any)[levelColor](`${level.padEnd(5)} `);
}
output += message;
console.log(output);
});
console.log(chalk.cyan(`\nš Showing ${logEntries.length} log entries`));
}
private getLevelColor(level: string): string {
switch (level.toUpperCase()) {
case 'ERROR':
return 'red';
case 'WARN':
case 'WARNING':
return 'yellow';
case 'INFO':
return 'blue';
case 'DEBUG':
return 'magenta';
case 'TRACE':
return 'gray';
default:
return 'white';
}
}
private async followLogs(coolifyUrl: string, headers: any, appId: string, appName: string) {
console.log(chalk.blue(`\nšļø Following logs for ${appName} (Press Ctrl+C to stop)...`));
let lastLogCount = 0;
const followInterval = setInterval(async () => {
try {
const logsResponse = await axios.get(
`${coolifyUrl}/api/v1/applications/${appId}/logs`,
{
headers,
params: {
lines: 50
}
}
);
let logEntries: any[] = [];
const logs = logsResponse.data;
if (typeof logs === 'string') {
logEntries = logs.split('\n').filter(line => line.trim());
} else if (Array.isArray(logs)) {
logEntries = logs;
} else if (logs.logs) {
logEntries = Array.isArray(logs.logs) ? logs.logs : logs.logs.split('\n');
}
// Only show new logs
if (logEntries.length > lastLogCount) {
const newLogs = logEntries.slice(lastLogCount);
newLogs.forEach(logEntry => {
let message = typeof logEntry === 'string' ? logEntry :
logEntry.message || JSON.stringify(logEntry);
console.log(chalk.gray(`[${new Date().toISOString()}] `) + message);
});
lastLogCount = logEntries.length;
}
} catch (error) {
console.error(chalk.red(`Error fetching logs: ${error.message}`));
}
}, 2000); // Poll every 2 seconds
// Handle Ctrl+C
process.on('SIGINT', () => {
clearInterval(followInterval);
console.log(chalk.yellow('\nš Stopped following logs'));
process.exit(0);
});
}
}