Flow Studio
Backend Log Emission
When an executor calls ctx.Observability.Logger.LogXxx(), the INodeLogger implementation writes to the SignalRLogSink. The sink enriches the entry with pre-seeded fields, assigns a unique logId, and hands it to the server-side buffer for batching before transmission.
SignalRLogSink
// SignalRLogSink.cs — INodeLogger implementation that routes to SignalR
public class SignalRLogSink : INodeLogger
{
private readonly ILogBuffer _buffer;
private readonly NodeExecutionContext _ctx;
public void LogInformation(string message, params object[] args)
{
var entry = new LogEntry
{
LogId = Guid.NewGuid().ToString(), // Unique ID for deduplication
NodeId = _ctx.NodeId,
Level = "Information",
Message = string.Format(message, args),
Timestamp = DateTimeOffset.UtcNow.ToString("o"),
Fields = ExtractStructuredFields(message, args),
TraceId = Activity.Current?.TraceId.ToString()
};
// Add pre-seeded context fields
entry.Fields["executionId"] = _ctx.ExecutionId;
entry.Fields["tenantId"] = _ctx.TenantId;
entry.Fields["processId"] = _ctx.ProcessId;
entry.Fields["nodeType"] = _ctx.NodeType;
_buffer.Add(_ctx.ExecutionId, entry);
}
}
LogEntry Schema (Backend)
public class LogEntry
{
public string LogId { get; init; } // GUID — for client-side deduplication
public string NodeId { get; init; }
public string Level { get; init; } // Trace|Debug|Information|Warning|Error
public string Message { get; init; }
public string Timestamp { get; init; } // ISO 8601
public Dictionary<string, string> Fields { get; init; }
public string? TraceId { get; init; }
}
Dual Routing
Log entries go to two destinations simultaneously:
// 1. Buffer → SignalR → Client (Observer Panel, real-time)
_buffer.Add(_ctx.ExecutionId, entry);
// 2. Serilog pipeline → Loki (long-term storage)
// This happens via the normal Serilog sink — the INodeLogger also delegates to Serilog
_serilogLogger.Information(message, args);
Logs are stored durably in Loki regardless of whether any client is connected via SignalR. If no Observer Panel is open when a workflow runs, logs are still captured — they are available for historical viewing via the REST API later.