Describe the Bug
In BeamableMicroService.ResolveCustomInitializationHook(), the catch block that handles a failed [InitializeServices] method constructs its log message using C# string interpolation, then passes the result to BeamableLogger.LogError as a format string:
BeamableLogger.LogError(
$"Custom service initializer [{initializationMethod.DeclaringType?.FullName}.{initializationMethod.Name}] failed.\n" +
$"{ex.Message}\n" +
$"{{stacktrace}}",
ex.StackTrace);
$"{ex.Message}" embeds the exception message directly into the format string via interpolation. If the exception message contains curly braces (e.g. from a JSON fragment, a C# format string, or any {...} text), the logging framework interprets them as additional placeholders. With only one argument supplied (ex.StackTrace), this causes a secondary IndexOutOfRangeException inside the logger, which propagates as:
An error occurred while writing to logger(s). (Index (zero based) must be greater than or equal to zero and less than the size of the argument list.)
This secondary exception completely masks the original [InitializeServices] failure, making the root cause invisible to the developer.
To Reproduce
Add an [InitializeServices] method that throws an exception whose message contains curly braces:
[InitializeServices]
public static async Task ReproLoggerFormatStringBug(IServiceInitializer initializer)
{
throw new Exception("Simulated init failure with {curly} {braces} in message");
}
Run the microservice locally (beam project run). The displayed error will be the logger's secondary crash, not the thrown exception.
Expected Behavior
The original [InitializeServices] exception (message and stack trace) is logged clearly. The developer can see what actually went wrong.
Actual Behavior
A secondary IndexOutOfRangeException from the logging framework is displayed instead. The original exception is lost. The error repeats multiple times in the output:
Service failed to provide services. An error occurred while writing to logger(s).
(Index (zero based) must be greater than or equal to zero and less than the size of the argument list.)
(Index (zero based) must be greater than or equal to zero and less than the size of the argument list.)
(Index (zero based) must be greater than or equal to zero and less than the size of the argument list.)
at Microsoft.Extensions.Logging.Logger.ThrowLoggingError(List`1 exceptions)
...
at Beamable.Common.BeamableLogger.LogError(String error, Object[] args)
at Beamable.Server.BeamableMicroService.ResolveCustomInitializationHook()
at Beamable.Server.BeamableMicroService.SetupWebsocket(IConnection socket, Boolean initContent)
Suggested Fix
Pass ex.Message as a separate argument rather than interpolating it into the format string, and use a literal placeholder:
BeamableLogger.LogError(
"Custom service initializer [{typeName}.{methodName}] failed.\n{message}\n{stacktrace}",
initializationMethod.DeclaringType?.FullName,
initializationMethod.Name,
ex.Message,
ex.StackTrace);
Metadata
- Affected file:
microservice/microservice/dbmicroservice/BeamableMicroService.cs, ResolveCustomInitializationHook()
- Observed in: CLI 7.0.1 (
cli-7.0.1 tag)
- Operating System: macOS (arm64), confirmed also present in a Linux container environment
Additional Context
This bug was discovered during a CLI 7.x upgrade support case. An [InitializeServices] hook threw an exception whose message contained curly braces, triggering the secondary logging crash. The misleading error message significantly delayed diagnosis - the real failure was only found after commenting out the init hook entirely.
Describe the Bug
In
BeamableMicroService.ResolveCustomInitializationHook(), the catch block that handles a failed[InitializeServices]method constructs its log message using C# string interpolation, then passes the result toBeamableLogger.LogErroras a format string:$"{ex.Message}"embeds the exception message directly into the format string via interpolation. If the exception message contains curly braces (e.g. from a JSON fragment, a C# format string, or any{...}text), the logging framework interprets them as additional placeholders. With only one argument supplied (ex.StackTrace), this causes a secondaryIndexOutOfRangeExceptioninside the logger, which propagates as:This secondary exception completely masks the original
[InitializeServices]failure, making the root cause invisible to the developer.To Reproduce
Add an
[InitializeServices]method that throws an exception whose message contains curly braces:Run the microservice locally (
beam project run). The displayed error will be the logger's secondary crash, not the thrown exception.Expected Behavior
The original
[InitializeServices]exception (message and stack trace) is logged clearly. The developer can see what actually went wrong.Actual Behavior
A secondary
IndexOutOfRangeExceptionfrom the logging framework is displayed instead. The original exception is lost. The error repeats multiple times in the output:Suggested Fix
Pass
ex.Messageas a separate argument rather than interpolating it into the format string, and use a literal placeholder:Metadata
microservice/microservice/dbmicroservice/BeamableMicroService.cs,ResolveCustomInitializationHook()cli-7.0.1tag)Additional Context
This bug was discovered during a CLI 7.x upgrade support case. An
[InitializeServices]hook threw an exception whose message contained curly braces, triggering the secondary logging crash. The misleading error message significantly delayed diagnosis - the real failure was only found after commenting out the init hook entirely.