From 87bb34e0dd6b4de4469c907949ee263b4c471e40 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 28 Feb 2025 10:32:21 -0800 Subject: [PATCH] feat(logging): standardize logging across manual, scheduled, and api executions. persist manual executions to db --- app/api/scheduled/execute/route.ts | 10 +++-- app/api/workflow/[id]/log/route.ts | 40 +++++++++++++++++ app/w/hooks/use-workflow-execution.ts | 62 +++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 app/api/workflow/[id]/log/route.ts diff --git a/app/api/scheduled/execute/route.ts b/app/api/scheduled/execute/route.ts index c15bde4f23..71b24a8365 100644 --- a/app/api/scheduled/execute/route.ts +++ b/app/api/scheduled/execute/route.ts @@ -175,6 +175,8 @@ export async function GET(req: NextRequest) { cronExpression: schedule.cronExpression, }) + const executionId = uuidv4() + try { // Skip if this workflow is already running if (runningExecutions.has(schedule.workflowId)) { @@ -276,7 +278,6 @@ export async function GET(req: NextRequest) { // Serialize and execute the workflow const serializedWorkflow = new Serializer().serializeWorkflow(mergedStates, edges, loops) const executor = new Executor(serializedWorkflow, currentBlockStates, decryptedEnvVars) - const executionId = uuidv4() const result = await executor.execute(schedule.workflowId) // Log each execution step @@ -351,11 +352,12 @@ export async function GET(req: NextRequest) { await persistLog({ id: uuidv4(), workflowId: schedule.workflowId, - executionId: uuidv4(), + executionId, level: 'error', - message: error.message || 'Unknown error during scheduled workflow execution', - createdAt: new Date(), + message: `Scheduled workflow execution failed: ${error.message || 'Unknown error'}`, + duration: 'NA', trigger: 'schedule', + createdAt: new Date(), }) // On error, increment next_run_at by a small delay to prevent immediate retries diff --git a/app/api/workflow/[id]/log/route.ts b/app/api/workflow/[id]/log/route.ts new file mode 100644 index 0000000000..fdaa721c60 --- /dev/null +++ b/app/api/workflow/[id]/log/route.ts @@ -0,0 +1,40 @@ +import { NextRequest } from 'next/server' +import { v4 as uuidv4 } from 'uuid' +import { persistLog } from '@/lib/logging' +import { validateWorkflowAccess } from '../../middleware' +import { createErrorResponse, createSuccessResponse } from '../../utils' + +export const dynamic = 'force-dynamic' + +export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + const { id } = await params + + try { + const validation = await validateWorkflowAccess(request, id, false) + if (validation.error) { + return createErrorResponse(validation.error.message, validation.error.status) + } + + const body = await request.json() + const { logs, executionId } = body + + // Persist each log + for (const log of logs) { + await persistLog({ + id: uuidv4(), + workflowId: id, + executionId, + level: log.level, + message: log.message, + duration: log.duration, + trigger: 'manual', + createdAt: new Date(log.createdAt || new Date()), + }) + } + + return createSuccessResponse({ message: 'Logs persisted successfully' }) + } catch (error: any) { + console.error('Error persisting logs:', error) + return createErrorResponse(error.message || 'Failed to persist logs', 500) + } +} diff --git a/app/w/hooks/use-workflow-execution.ts b/app/w/hooks/use-workflow-execution.ts index 7aa0bb1ace..236ec7cef7 100644 --- a/app/w/hooks/use-workflow-execution.ts +++ b/app/w/hooks/use-workflow-execution.ts @@ -1,4 +1,5 @@ import { useCallback, useState } from 'react' +import { v4 as uuidv4 } from 'uuid' import { useConsoleStore } from '@/stores/console/store' import { useNotificationStore } from '@/stores/notifications/store' import { useEnvironmentStore } from '@/stores/settings/environment/store' @@ -19,6 +20,24 @@ export function useWorkflowExecution() { const { toggleConsole, isOpen } = useConsoleStore() const { getAllVariables } = useEnvironmentStore() + const persistLogs = async (logs: any[], executionId: string) => { + try { + const response = await fetch(`/api/workflow/${activeWorkflowId}/log`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ logs, executionId }), + }) + + if (!response.ok) { + throw new Error('Failed to persist logs') + } + } catch (error) { + console.error('Error persisting logs:', error) + } + } + const handleRunWorkflow = useCallback(async () => { if (!activeWorkflowId) return setIsExecuting(true) @@ -28,6 +47,8 @@ export function useWorkflowExecution() { toggleConsole() } + const executionId = uuidv4() + try { // Use the mergeSubblockState utility to get all block states const mergedStates = mergeSubblockState(blocks) @@ -70,6 +91,34 @@ export function useWorkflowExecution() { const executor = new Executor(workflow, currentBlockStates, envVarValues) const result = await executor.execute(activeWorkflowId) + // Prepare logs for persistence + const blockLogs = (result.logs || []).map((log) => ({ + level: log.success ? 'info' : 'error', + message: `Block ${log.blockName || log.blockId} (${log.blockType}): ${ + log.error || 'Completed successfully' + }`, + duration: log.success ? `${log.durationMs}ms` : 'NA', + createdAt: new Date(log.endedAt || log.startedAt).toISOString(), + })) + + // Calculate total duration from successful block logs + const totalDuration = (result.logs || []) + .filter((log) => log.success) + .reduce((sum, log) => sum + log.durationMs, 0) + + // Add final execution result log + blockLogs.push({ + level: result.success ? 'info' : 'error', + message: result.success + ? 'Manual workflow executed successfully' + : `Manual workflow execution failed: ${result.error}`, + duration: result.success ? `${totalDuration}ms` : 'NA', + createdAt: new Date().toISOString(), + }) + + // Persist all logs + await persistLogs(blockLogs, executionId) + setExecutionResult(result) // Show execution result notification @@ -90,6 +139,19 @@ export function useWorkflowExecution() { logs: [], }) + // Persist error log + await persistLogs( + [ + { + level: 'error', + message: `Manual workflow execution failed: ${errorMessage}`, + duration: 'NA', + createdAt: new Date().toISOString(), + }, + ], + executionId + ) + addNotification('error', `Workflow execution failed: ${errorMessage}`, activeWorkflowId) } finally { setIsExecuting(false)