Portal Community

LogEntry Schema

// logEntry.types.ts
export interface LogEntry {
    logId     : string;              // Unique ID — used for deduplication
    nodeId    : string;              // Source node
    level     : LogLevel;            // 'Trace' | 'Debug' | 'Info' | 'Warning' | 'Error'
    message   : string;
    timestamp : string;              // ISO 8601
    fields    : Record<string, unknown>; // Structured log fields from INodeLogger
    traceId  ?: string;              // OTel TraceId — links to distributed trace
}

appendLog — Idempotent Append

appendLog: (entry: LogEntry) => set(state => {
    // Deduplication: skip if logId already exists
    if (state.logs.some(l => l.logId === entry.logId)) {
        return state;  // No change
    }
    return { logs: [...state.logs, entry] };
});

// Called by useExecutionSignalR on NodeLogEmitted events
connection.on("NodeLogEmitted", (entry: LogEntry) => {
    useExecutionStore.getState().appendLog(entry);
});

Log Level Color Mapping

LevelColorWhen to Expect It
TraceGrey (dim)Very detailed diagnostic entries — disabled in production by default
DebugGreyDiagnostic info for development
InfoWhiteNormal operational messages
WarningYellowUnexpected but recoverable conditions
ErrorRedNode threw an exception or returned a failure

Log Array Growth

The logs array grows unbounded during a run. For long-running workflows with verbose logging, the array can reach tens of thousands of entries. The Logs tab handles this with virtual scrolling — only visible rows are rendered. The store does not cap or truncate the array.

Historical logs come from the REST API, not SignalR. When viewing a past execution, logs are fetched via GET /api/executions/{id}/logs (paginated) and batch-appended with appendLog. The display path is identical — same array, same component.