FlowBoxLogger API
The C# SDK provides a FlowMakerLogger class for logging capabilities. It sends log messages to the runtime over a Socket.IO connection, where they appear in the FlowMaker UI.
Overview
using Industream.FlowMaker.Sdk.Clients;
using Industream.FlowMaker.Sdk.Elements.Advanced;
using Microsoft.Extensions.Logging;
public class MyPipe : FlowBoxCore
{
private readonly ILogger _logger;
public MyPipe(FlowBoxInitParams initParams, IServiceProvider serviceProvider)
: base(initParams, serviceProvider)
{
// Create typed logger
_logger = flowMakerLogger.CreateILogger(GetType().Name);
}
protected override async Task OnInput(byte[] inputId, Header header, byte[] data)
{
_logger.LogInformation("Processing data: {Data}", data);
await ProcessData(data);
}
}
The logger integrates with Microsoft.Extensions.Logging, providing familiar logging patterns.
Class: IFlowMakerLogger
Interface Definition
public interface IFlowMakerLogger
{
ILogger<T> CreateILogger<T>();
ILogger CreateILogger(string category);
void Log(LogLevel level, RuntimeContext runtimeContext, string message);
}
Implementations:
FlowMakerLogger: Socket.IO backed implementation
Creating Loggers
Typed Logger (Recommended)
public class MyPipe : FlowBoxCore
{
private readonly ILogger _logger;
public MyPipe(FlowBoxInitParams initParams, IServiceProvider serviceProvider)
: base(initParams, serviceProvider)
{
_logger = flowMakerLogger.CreateILogger<MyPipe>();
}
}
Description:
Creates an ILogger<T> with the category name set to the type name. Integrates with Microsoft.Extensions.Logging abstraction.
String Category Logger
public class MyPipe : FlowBoxCore
{
private readonly ILogger _logger;
public MyPipe(FlowBoxInitParams initParams, IServiceProvider serviceProvider)
: base(initParams, serviceProvider)
{
_logger = flowMakerLogger.CreateILogger("MyPipe");
}
}
Description:
Creates an ILogger with a custom category name. Useful when you want a specific category that differs from the type name.
Direct Log Method
public class MyBox : FlowBoxRaw
{
public void ProcessData()
{
if (initParams.RuntimeContext == null)
throw new ArgumentNullException(nameof(initParams.RuntimeContext));
Log(LogLevel.Information, "Processing data");
}
}
Description: Directly logs a message with the specified log level. Includes runtime context (jobId, nodeId) automatically.
[INFO!info/RUNTIME CONTEXT] All log methods automatically include the
RuntimeContext(jobId, nodeId) in the payload. This allows the runtime to route logs to the correct job view in the UI.
Log Methods
Microsoft.Extensions.Logging Patterns
Once you have an ILogger, use standard patterns:
// Simple message
_logger.LogInformation("Processing started");
// Structured logging
_logger.LogInformation("Processed {Count} items in {DurationMs}ms", count, durationMs);
// Error with exception
try
{
await ProcessData(data);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing data");
}
// Different log levels
_logger.LogDebug("Debug information");
_logger.LogWarning("Warning message");
_logger.LogError("Error message");
_logger.LogCritical("Critical failure");
Direct Log Method
Log(LogLevel level, RuntimeContext runtimeContext, string message)
Parameters:
level:LogLevelenum (Debug, Information, Warning, Error, Critical)runtimeContext: ContainsJobId,NodeId,FlowBoxIdmessage: Log message string
Example:
public void ProcessData()
{
if (initParams.RuntimeContext == null)
throw new ArgumentNullException(nameof(initParams.RuntimeContext));
Log(LogLevel.Information, initParams.RuntimeContext, "Data processed");
}
[NOTE!info/LOG STRUCTURE] All log messages are emitted via Socket.IO to the
logchannel with this structure:{ "from": { "flowBoxId": "...", "jobId": "...", "nodeId": "...", "usedBy": "..." }, "data": "Your message" }
Socket.IO Connection Details
The logger uses Socket.IO to emit events:
- Channel:
log - Event payload:
{"from": {...}, "data": ...} - Connection URL: From
FlowMakerClientOptions.LoggerAddress - Namespace: Job ID (from
RuntimeContext.JobId)
Connection lifecycle:
- Created on first log emission
- Remains open for the FlowBox's lifetime
- Managed by
SocketIOFactory(singleton)
[INFO!info/CONNECTION SHARING] The
SocketIOFactorycreates sockets per job ID. Multiple FlowBoxes in the same worker process may share the same Socket.IO connection if they belong to the same job.
Differences from TypeScript/Python SDKs
| Feature | C# SDK | TypeScript SDK | Python SDK |
|---|---|---|---|
| Logger interface | IFlowMakerLogger |
FlowBoxLogger (singleton) |
FlowMakerLogger (per-instance) |
| Integration | Microsoft.Extensions.Logging | Custom implementation | Custom implementation |
| Log methods | ILogger<T> patterns |
log(message) |
log(message) |
| Creation | Via flowMakerLogger property |
Via flowBoxLogger singleton |
Via self.logger property |
| Disposal | Managed by factory | Manual (dispose()) |
Automatic (on_destroy()) |
[NOTE!lightbulb/MS EXT LOGGING] C# SDK integrates with Microsoft.Extensions.Logging, giving you access to the full ecosystem of logging providers, scopes, and formatters. TypeScript and Python use custom implementations.
Common Patterns
Structured Logging
// Instead of:
_logger.LogInformation($"Processed {count} items");
// Use:
_logger.LogInformation("Processed {Count} items", count);
[NOTE!lightbulb/STRUCTURED LOGS] Structured logging (with placeholders) allows the runtime to parse and index log fields for filtering and aggregation. String interpolation creates unparseable text.
Error Handling with Exceptions
try
{
await ProcessData(data);
}
catch (InvalidOperationException ex)
{
_logger.LogWarning(ex, "Invalid operation for data: {DataId}", dataId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error processing data");
throw; // Re-throw to fail the message
}
Log Scopes
using (_logger.BeginScope(new { JobId = initParams.RuntimeContext.JobId, NodeId = initParams.RuntimeContext.NodeId }))
{
_logger.LogInformation("Processing started");
await ProcessData();
_logger.LogInformation("Processing completed");
}
[INFO!info/LOG SCOPES] Microsoft.Extensions.Logging supports scopes for grouping related log entries. Use them to correlate logs with specific jobs, nodes, or operations.
Dependency Injection Setup
The logger is typically registered in DI:
using Industream.FlowMaker.Sdk.Clients;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
// Register logger
services.AddSingleton<IFlowMakerLogger, FlowMakerLogger>();
services.AddSingleton<SocketIOFactory>();
services.Configure<FlowMakerClientOptions>(options =>
{
options.LoggerAddress = "http://localhost:3040";
});
var serviceProvider = services.BuildServiceProvider();
[ERROR!error/DI REQUIRED] The
FlowMakerLoggerrequiresIFlowMakerLoggerandSocketIOFactoryto be registered in the service provider. Missing registrations causeInvalidOperationExceptionat runtime.
Troubleshooting
Logs Not Appearing
- Check LoggerAddress: Ensure
FlowMakerClientOptions.LoggerAddresspoints to the correct Socket.IO endpoint - Verify Job ID: Logs are scoped to
JobId—check you're viewing the right job in the UI - Connection errors: Check worker logs for Socket.IO connection failures
Duplicate Logs
If logs appear twice, check:
- Multiple worker instances processing the same job
- Logger being created multiple times (should use DI singleton)
Missing RuntimeContext
// Error: RuntimeContext cannot be null
if (initParams.RuntimeContext == null)
throw new ArgumentNullException(nameof(initParams.RuntimeContext));
[ERROR!error/RUNTIME CONTEXT NULL]
RuntimeContextis populated by the scheduler duringINIT_FLOW_ELEMENT. If it's null, the FlowBox wasn't properly initialized—check your registration and worker configuration.