Skip to content

Commit c01099a

Browse files
committed
Harden stderr tee logging (#2730)
1 parent b2f6646 commit c01099a

3 files changed

Lines changed: 84 additions & 8 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
category: fixed
3+
issues:
4+
- 2730
5+
affected:
6+
- src/CodeIndex/Cli/GlobalToolLog.cs
7+
- tests/CodeIndex.Tests/GlobalToolLogTests.cs
8+
---
9+
10+
## English
11+
12+
- **Persistent stderr mirroring no longer cascades disposed-writer failures (#2730)** — the global tool log tee now treats closed console/log writers as best-effort failures instead of throwing `ObjectDisposedException` back into test or CLI callers.
13+
14+
## 日本語
15+
16+
- **永続 stderr mirror が閉じた writer の失敗を連鎖させなくなりました (#2730)** — global tool log の tee は、閉じた console/log writer をベストエフォートの失敗として扱い、`ObjectDisposedException` をテストや CLI 呼び出し元へ投げ返さないようになりました。

src/CodeIndex/Cli/GlobalToolLog.cs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -638,26 +638,39 @@ private sealed class TeeTextWriter(TextWriter primary, TextWriter secondary) : T
638638

639639
public override void Flush()
640640
{
641-
primary.Flush();
642-
secondary.Flush();
641+
TryWrite(primary.Flush);
642+
TryWrite(secondary.Flush);
643643
}
644644

645645
public override void Write(char value)
646646
{
647-
primary.Write(value);
648-
secondary.Write(value);
647+
TryWrite(() => primary.Write(value));
648+
TryWrite(() => secondary.Write(value));
649649
}
650650

651651
public override void Write(string? value)
652652
{
653-
primary.Write(value);
654-
secondary.Write(value);
653+
TryWrite(() => primary.Write(value));
654+
TryWrite(() => secondary.Write(value));
655655
}
656656

657657
public override void WriteLine(string? value)
658658
{
659-
primary.WriteLine(value);
660-
secondary.WriteLine(value);
659+
TryWrite(() => primary.WriteLine(value));
660+
TryWrite(() => secondary.WriteLine(value));
661+
}
662+
663+
private static void TryWrite(Action write)
664+
{
665+
try
666+
{
667+
write();
668+
}
669+
catch (Exception ex) when (ex is IOException or ObjectDisposedException)
670+
{
671+
// Best-effort mirror: a disposed console writer must not cascade into callers.
672+
// mirror はベストエフォート。閉じた console writer が呼び出し側へ波及しないようにする。
673+
}
661674
}
662675
}
663676
}

tests/CodeIndex.Tests/GlobalToolLogTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Globalization;
2+
using System.Text;
23
using System.Text.RegularExpressions;
34
using CodeIndex.Cli;
45

@@ -117,6 +118,52 @@ public void TryStart_WritesInvariantUtcTimestampAndStackTrace()
117118
}
118119
}
119120

121+
[Fact]
122+
public void TryStart_ErrorMirrorIgnoresDisposedOriginalConsoleWriter()
123+
{
124+
var logRoot = Path.Combine(Path.GetTempPath(), $"cdidx_global_log_disposed_{Guid.NewGuid():N}");
125+
var originalError = Console.Error;
126+
try
127+
{
128+
using var env = EnvironmentVariableScope.Capture(
129+
"CDIDX_FORCE_GLOBAL_TOOL_LOG",
130+
"CDIDX_DISABLE_PERSISTENT_LOG",
131+
"CDIDX_GLOBAL_TOOL_LOG_DIR");
132+
env.Set("CDIDX_FORCE_GLOBAL_TOOL_LOG", "1");
133+
env.Set("CDIDX_DISABLE_PERSISTENT_LOG", null);
134+
env.Set("CDIDX_GLOBAL_TOOL_LOG_DIR", logRoot);
135+
136+
using var session = GlobalToolLog.TryStartForTesting(
137+
["status"],
138+
"test",
139+
afterWriterCreated: () => Console.SetError(new ThrowingTextWriter()));
140+
141+
var exception = Record.Exception(() => Console.Error.WriteLine("mirrored error"));
142+
143+
Assert.NotNull(session);
144+
Assert.Null(exception);
145+
}
146+
finally
147+
{
148+
Console.SetError(originalError);
149+
if (Directory.Exists(logRoot))
150+
Directory.Delete(logRoot, recursive: true);
151+
}
152+
}
153+
120154
private static void ThrowForGlobalToolLogTest() =>
121155
throw new InvalidOperationException("global log stack trace test");
156+
157+
private sealed class ThrowingTextWriter : TextWriter
158+
{
159+
public override Encoding Encoding => Encoding.UTF8;
160+
161+
public override void Flush() => throw new ObjectDisposedException(nameof(ThrowingTextWriter));
162+
163+
public override void Write(char value) => throw new ObjectDisposedException(nameof(ThrowingTextWriter));
164+
165+
public override void Write(string? value) => throw new ObjectDisposedException(nameof(ThrowingTextWriter));
166+
167+
public override void WriteLine(string? value) => throw new ObjectDisposedException(nameof(ThrowingTextWriter));
168+
}
122169
}

0 commit comments

Comments
 (0)