@hivetechs/hive-ai
Version:
Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API
228 lines • 7.14 kB
JavaScript
/**
* Activity Logger for Dashboard Real-Time Tracking
*
* Provides event logging for all consensus pipeline activities,
* enabling real-time dashboards and analytics.
*/
import { getDatabase } from './unified-database.js';
import { structuredLogger } from '../tools/structured-logger.js';
/**
* Log an activity event to the database
*/
export async function logActivity(entry) {
const logId = Math.random().toString(36).substr(2, 9);
console.log(`[ACTIVITY-LOG-DEBUG] Attempting to log: ${entry.eventType} for conversation: ${entry.conversationId}`);
try {
const db = await getDatabase();
// Debug: log what we're trying to insert
structuredLogger.debug('Attempting to log activity', {
logId,
eventType: entry.eventType,
conversationId: entry.conversationId,
stage: entry.stage,
modelUsed: entry.modelUsed
});
await db.run(`
INSERT INTO activity_log (
event_type, conversation_id, user_id, stage, model_used,
provider_name, cost, duration_ms, tokens_used, error_message, metadata
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [
entry.eventType,
entry.conversationId || null,
entry.userId || null,
entry.stage || null,
entry.modelUsed || null,
entry.providerName || null,
entry.cost || null,
entry.durationMs || null,
entry.tokensUsed || null,
entry.errorMessage || null,
entry.metadata ? JSON.stringify(entry.metadata) : null
]);
structuredLogger.debug('Activity logged successfully', {
logId,
eventType: entry.eventType,
conversationId: entry.conversationId,
stage: entry.stage
});
}
catch (error) {
console.log(`[ACTIVITY-LOG-DEBUG] ERROR: ${error.message} for ${entry.eventType}`);
// Don't throw - activity logging should not break the main flow
structuredLogger.warn('Failed to log activity', {
logId,
eventType: entry.eventType,
conversationId: entry.conversationId,
stage: entry.stage,
error: error.message,
errorType: error.constructor.name,
sqliteCode: error.code,
stackTrace: error.stack?.split('\n')[0]
});
}
}
/**
* Get recent activity for dashboard display
*/
export async function getRecentActivity(limit = 10) {
try {
const db = await getDatabase();
const activities = await db.all(`
SELECT
datetime(created_at, 'localtime') as time,
event_type,
stage,
model_used,
cost,
duration_ms,
error_message,
metadata
FROM activity_log
WHERE created_at > datetime('now', '-1 hour')
ORDER BY created_at DESC
LIMIT ?
`, [limit]);
return activities.map(activity => ({
...activity,
metadata: activity.metadata ? JSON.parse(activity.metadata) : null
}));
}
catch (error) {
structuredLogger.error('Failed to get recent activity', {}, error);
return [];
}
}
/**
* Get count of active queries (started but not completed)
*/
export async function getActiveQueryCount() {
try {
const db = await getDatabase();
const result = await db.get(`
SELECT COUNT(DISTINCT conversation_id) as active_count
FROM activity_log
WHERE event_type = 'query_start'
AND conversation_id NOT IN (
SELECT conversation_id
FROM activity_log
WHERE event_type IN ('query_complete', 'query_error')
AND conversation_id IS NOT NULL
)
AND created_at > datetime('now', '-5 minutes')
`);
return result?.active_count || 0;
}
catch (error) {
structuredLogger.error('Failed to get active query count', {}, error);
return 0;
}
}
/**
* Get throughput (queries completed per hour)
*/
export async function getThroughput(hours = 1) {
try {
const db = await getDatabase();
const result = await db.get(`
SELECT COUNT(DISTINCT conversation_id) as throughput
FROM activity_log
WHERE event_type = 'query_complete'
AND created_at > datetime('now', '-${hours} hour')
AND conversation_id IS NOT NULL
`);
return result?.throughput || 0;
}
catch (error) {
structuredLogger.error('Failed to get throughput', {}, error);
return 0;
}
}
/**
* Get model performance stats for leaderboard
*/
export async function getModelPerformanceStats(hours = 24) {
try {
const db = await getDatabase();
const stats = await db.all(`
SELECT
al.model_used,
al.provider_name,
COUNT(DISTINCT al.conversation_id) as usage_count,
AVG(al.cost) as avg_cost,
AVG(al.duration_ms) as avg_duration,
SUM(CASE WHEN al.error_message IS NULL THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as success_rate,
AVG(al.tokens_used) as avg_tokens
FROM activity_log al
WHERE al.event_type IN ('stage_complete', 'stage_error')
AND al.created_at > datetime('now', '-${hours} hour')
AND al.model_used IS NOT NULL
GROUP BY al.model_used, al.provider_name
ORDER BY success_rate DESC, avg_duration ASC
LIMIT 10
`);
return stats;
}
catch (error) {
structuredLogger.error('Failed to get model performance stats', {}, error);
return [];
}
}
/**
* Log query start event
*/
export async function logQueryStart(conversationId, userId, metadata) {
await logActivity({
eventType: 'query_start',
conversationId,
userId,
metadata: { ...metadata, source: 'logQueryStart' }
});
}
/**
* Log query completion event
*/
export async function logQueryComplete(conversationId, totalCost, durationMs, tokensUsed, userId, metadata) {
await logActivity({
eventType: 'query_complete',
conversationId,
userId,
cost: totalCost,
durationMs,
tokensUsed,
metadata
});
}
/**
* Log stage event
*/
export async function logStageEvent(eventType, conversationId, stage, model, provider, userId, options) {
await logActivity({
eventType,
conversationId,
userId,
stage,
modelUsed: model,
providerName: provider,
...options
});
}
/**
* Clean up old activity logs (retention policy)
*/
export async function cleanupOldActivities(daysToKeep = 30) {
try {
const db = await getDatabase();
const result = await db.run(`
DELETE FROM activity_log
WHERE created_at < datetime('now', '-${daysToKeep} days')
`);
structuredLogger.info('Cleaned up old activity logs', {
deletedCount: result.changes
});
}
catch (error) {
structuredLogger.error('Failed to cleanup old activities', {}, error);
}
}
//# sourceMappingURL=activity-logger.js.map