diff --git a/src/core/Akka.Tests/Loggers/StandardOutWriterSpec.cs b/src/core/Akka.Tests/Loggers/StandardOutWriterSpec.cs
new file mode 100644
index 00000000000..9475bdb466c
--- /dev/null
+++ b/src/core/Akka.Tests/Loggers/StandardOutWriterSpec.cs
@@ -0,0 +1,94 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (C) 2009-2022 Lightbend Inc.
+// Copyright (C) 2013-2025 .NET Foundation
+//
+//-----------------------------------------------------------------------
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Akka.TestKit;
+using Akka.Util;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Akka.Tests.Loggers
+{
+ ///
+ /// Tests for StandardOutWriter to ensure it handles IIS/Windows Service environments correctly
+ /// where Console.Out and Console.Error may be redirected to StreamWriter.Null
+ ///
+ public class StandardOutWriterSpec : AkkaSpec
+ {
+ public StandardOutWriterSpec(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public void StandardOutWriter_should_handle_concurrent_writes_without_race_conditions()
+ {
+ // This test simulates the concurrent access pattern that causes issues in IIS
+ // In normal test environments this won't reproduce the issue, but it ensures
+ // our fix doesn't break normal console operation
+
+ var tasks = new Task[100];
+
+ for (int i = 0; i < tasks.Length; i++)
+ {
+ var taskId = i;
+ tasks[i] = Task.Run(() =>
+ {
+ for (int j = 0; j < 10; j++)
+ {
+ // These calls should not throw even under concurrent access
+ StandardOutWriter.WriteLine($"Task {taskId} - Line {j}");
+ StandardOutWriter.Write($"Task {taskId} - Write {j} ");
+ }
+ });
+ }
+
+ // Should complete without throwing IndexOutOfRangeException
+ Assert.True(Task.WaitAll(tasks, TimeSpan.FromSeconds(5)));
+ }
+
+ [Fact]
+ public void StandardOutWriter_should_not_throw_when_console_is_redirected()
+ {
+ // Save original streams
+ var originalOut = Console.Out;
+ var originalError = Console.Error;
+
+ try
+ {
+ // Simulate IIS/Windows Service environment by redirecting to null
+ Console.SetOut(StreamWriter.Null);
+ Console.SetError(StreamWriter.Null);
+
+ // These should not throw even when console is redirected to null
+ StandardOutWriter.WriteLine("This should not throw");
+ StandardOutWriter.Write("Neither should this");
+
+ // Test with colors (which would normally fail in IIS)
+ StandardOutWriter.WriteLine("Colored output", ConsoleColor.Red);
+ StandardOutWriter.Write("Colored write", ConsoleColor.Blue, ConsoleColor.Yellow);
+ }
+ finally
+ {
+ // Restore original streams
+ Console.SetOut(originalOut);
+ Console.SetError(originalError);
+ }
+ }
+
+ [Fact]
+ public void StandardOutWriter_should_handle_null_and_empty_messages()
+ {
+ // Should not throw
+ StandardOutWriter.WriteLine(null);
+ StandardOutWriter.WriteLine("");
+ StandardOutWriter.Write(null);
+ StandardOutWriter.Write("");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/core/Akka/Event/DefaultLogger.cs b/src/core/Akka/Event/DefaultLogger.cs
index 129b844deae..3d87cec932e 100644
--- a/src/core/Akka/Event/DefaultLogger.cs
+++ b/src/core/Akka/Event/DefaultLogger.cs
@@ -46,7 +46,11 @@ protected override bool Receive(object message)
protected virtual void Print(LogEvent logEvent)
{
if (_stdoutLogger == null)
- throw new Exception("Logger has not been initialized yet.");
+ {
+ // Include context about the failed log event to help with debugging
+ var logDetails = $"[{logEvent.LogLevel()}] {logEvent.LogSource}: {logEvent.Message}";
+ throw new Exception($"Logger has not been initialized yet. Failed to log: {logDetails}");
+ }
_stdoutLogger.Tell(logEvent);
}
diff --git a/src/core/Akka/Util/StandardOutWriter.cs b/src/core/Akka/Util/StandardOutWriter.cs
index a049c308bbe..d0495dd9685 100644
--- a/src/core/Akka/Util/StandardOutWriter.cs
+++ b/src/core/Akka/Util/StandardOutWriter.cs
@@ -6,6 +6,7 @@
//-----------------------------------------------------------------------
using System;
+using System.IO;
namespace Akka.Util
{
@@ -16,6 +17,34 @@ namespace Akka.Util
public static class StandardOutWriter
{
private static readonly object _lock = new();
+ private static readonly bool _isConsoleAvailable = DetectConsoleAvailability();
+
+ ///
+ /// Detects whether a real console is available for output.
+ /// In environments like IIS and Windows Services, console output is redirected to StreamWriter.Null,
+ /// which is a singleton. When multiple threads write to both Console.Out and Console.Error
+ /// (which point to the same StreamWriter.Null instance), it causes race conditions.
+ ///
+ /// Since console output goes nowhere in these environments anyway, we skip it entirely
+ /// to prevent the race condition and improve performance.
+ ///
+ private static bool DetectConsoleAvailability()
+ {
+ // Specifically detect the IIS/Windows Service scenario where both Console.Out
+ // and Console.Error point to the SAME StreamWriter.Null singleton instance.
+ // This is the exact condition that causes the race condition.
+ // Note: We check both because in these environments, both are always set to the same instance
+ if (Console.Out == StreamWriter.Null && Console.Error == StreamWriter.Null)
+ return false;
+
+ // Also check Environment.UserInteractive for additional safety
+ // This returns false for Windows Services and IIS in .NET Framework
+ // (though less reliable in .NET Core, the StreamWriter.Null check above is the key)
+ if (!Environment.UserInteractive)
+ return false;
+
+ return true;
+ }
///
/// Writes the specified value to the standard output stream. Optionally
@@ -46,6 +75,16 @@ public static void WriteLine(string message, ConsoleColor? foregroundColor = nul
private static void WriteToConsole(string message, ConsoleColor? foregroundColor = null,
ConsoleColor? backgroundColor = null, bool line = true)
{
+ // Skip console output in IIS, Windows Services, and other non-console environments.
+ // In these environments:
+ // 1. Console output is redirected to StreamWriter.Null (goes nowhere anyway)
+ // 2. Both Console.Out and Console.Error point to the same StreamWriter.Null singleton
+ // 3. Concurrent writes to both streams cause race conditions and IndexOutOfRangeException
+ // 4. Skipping output entirely prevents the race condition and improves performance
+ // See: https://github.com/akkadotnet/akka.net/issues/7691
+ if (!_isConsoleAvailable)
+ return;
+
lock (_lock)
{
ConsoleColor? fg = null;