Skip to content

Commit 17c05c4

Browse files
thomhurstclaude
andauthored
feat: Add Phase 2 Activity-based logging to FileSystem operations (#1694)
* feat: Enhance Folder.Clean() and CopyTo() methods with additional options - Add Clean(bool removeReadOnlyAttribute) overload that: - Handles read-only files/directories by removing the attribute before deletion - Adds try-catch with logging for resilience when individual items fail - Includes private helper RemoveReadOnlyAttributeRecursively(DirectoryInfo) - Default Clean() calls Clean(removeReadOnlyAttribute: true) - Add CopyTo(string targetPath, bool preserveTimestamps) overload that: - Preserves file and directory attributes - Optionally preserves CreationTimeUtc, LastWriteTimeUtc, LastAccessTimeUtc - Preserves root directory timestamps after all content is copied - Default CopyTo(targetPath) calls CopyTo(targetPath, preserveTimestamps: true) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Address PR review feedback for Folder enhancements - Fix RemoveReadOnlyAttributeRecursively to use TopDirectoryOnly with recursion instead of AllDirectories (performance improvement) - Change CopyTo log message to past tense "Copied Folder" (logging consistency) - Remove try-catch from Clean() to preserve exception-throwing behavior (backward compatibility) - Change CopyTo() default to preserveTimestamps: false (backward compatibility) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: Add Phase 2 Activity-based logging to FileSystem operations - Add GetCurrentModuleName() and GetCurrentActivityId() helper methods to ModuleActivityTracing for retrieving activity context - Update File.cs logging methods to include module name and activity ID in all file operations (read, write, copy, move, delete, create, append) - Update Folder.cs logging methods to include module name and activity ID in all folder operations (create, delete, clean, copy, move, get, search) - Add private helper methods LogFileOperation, LogFileOperationWithDestination, LogFolderOperation, LogFolderOperationWithDestination, LogFolderOperationWithExpression, and LogFolderWarning for consistent activity context logging This is Phase 2 of the Activity-based refactor, which uses Activity.Current for context alongside the existing AsyncLocal pattern for backward compatibility. The log messages now include the current module name and activity ID when available. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ec94d04 commit 17c05c4

3 files changed

Lines changed: 230 additions & 32 deletions

File tree

src/ModularPipelines/FileSystem/File.cs

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
using System.Diagnostics;
12
using System.Diagnostics.CodeAnalysis;
23
using System.Text.Json.Serialization;
34
using Microsoft.Extensions.Logging;
45
using ModularPipelines.Logging;
6+
using ModularPipelines.Tracing;
57

68
namespace ModularPipelines.FileSystem;
79

@@ -35,22 +37,22 @@ internal File(FileInfo fileInfo)
3537
/// <inheritdoc cref="System.IO.File.ReadAllTextAsync(string,System.Text.Encoding,System.Threading.CancellationToken)"/>>
3638
public Task<string> ReadAsync(CancellationToken cancellationToken = default)
3739
{
38-
ModuleLogger.Current.LogInformation("Reading File: {Path}", this);
40+
LogFileOperation("Reading File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);
3941

4042
return System.IO.File.ReadAllTextAsync(Path, cancellationToken);
4143
}
4244

4345
/// <inheritdoc cref="System.IO.File.ReadLinesAsync(string,System.Threading.CancellationToken)"/>
4446
public IAsyncEnumerable<string> ReadLinesAsync(CancellationToken cancellationToken = default)
4547
{
46-
ModuleLogger.Current.LogInformation("Reading File: {Path}", this);
48+
LogFileOperation("Reading File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);
4749

4850
return System.IO.File.ReadLinesAsync(Path, cancellationToken);
4951
}
5052

5153
public Task<byte[]> ReadBytesAsync(CancellationToken cancellationToken = default)
5254
{
53-
ModuleLogger.Current.LogInformation("Reading File: {Path}", this);
55+
LogFileOperation("Reading File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);
5456

5557
return System.IO.File.ReadAllBytesAsync(Path, cancellationToken);
5658
}
@@ -62,28 +64,28 @@ public FileStream GetStream(FileAccess fileAccess = FileAccess.ReadWrite)
6264

6365
public Task WriteAsync(string contents, CancellationToken cancellationToken = default)
6466
{
65-
ModuleLogger.Current.LogInformation("Writing to File: {Path}", this);
67+
LogFileOperation("Writing to File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);
6668

6769
return System.IO.File.WriteAllTextAsync(Path, contents, cancellationToken);
6870
}
6971

7072
public Task WriteAsync(byte[] contents, CancellationToken cancellationToken = default)
7173
{
72-
ModuleLogger.Current.LogInformation("Writing to File: {Path}", this);
74+
LogFileOperation("Writing to File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);
7375

7476
return System.IO.File.WriteAllBytesAsync(Path, contents, cancellationToken);
7577
}
7678

7779
public Task WriteAsync(IEnumerable<string> contents, CancellationToken cancellationToken = default)
7880
{
79-
ModuleLogger.Current.LogInformation("Writing to File: {Path}", this);
81+
LogFileOperation("Writing to File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);
8082

8183
return System.IO.File.WriteAllLinesAsync(Path, contents, cancellationToken);
8284
}
8385

8486
public async Task WriteAsync(ReadOnlyMemory<byte> contents, CancellationToken cancellationToken = default)
8587
{
86-
ModuleLogger.Current.LogInformation("Writing to File: {Path}", this);
88+
LogFileOperation("Writing to File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);
8789

8890
var fileStream = System.IO.File.Create(Path);
8991
await using (fileStream.ConfigureAwait(false))
@@ -94,7 +96,7 @@ public async Task WriteAsync(ReadOnlyMemory<byte> contents, CancellationToken ca
9496

9597
public async Task WriteAsync(Stream contents, CancellationToken cancellationToken = default)
9698
{
97-
ModuleLogger.Current.LogInformation("Writing to File: {Path}", this);
99+
LogFileOperation("Writing to File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);
98100

99101
var fileStream = System.IO.File.Create(Path);
100102
await using (fileStream.ConfigureAwait(false))
@@ -110,14 +112,14 @@ public async Task WriteAsync(Stream contents, CancellationToken cancellationToke
110112

111113
public Task AppendAsync(string contents, CancellationToken cancellationToken = default)
112114
{
113-
ModuleLogger.Current.LogInformation("Writing to File: {Path}", this);
115+
LogFileOperation("Appending to File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);
114116

115117
return System.IO.File.AppendAllTextAsync(Path, contents, cancellationToken);
116118
}
117119

118120
public Task AppendAsync(IEnumerable<string> contents, CancellationToken cancellationToken = default)
119121
{
120-
ModuleLogger.Current.LogInformation("Writing to File: {Path}", this);
122+
LogFileOperation("Appending to File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);
121123

122124
return System.IO.File.AppendAllLinesAsync(Path, contents, cancellationToken);
123125
}
@@ -143,7 +145,7 @@ public Task AppendAsync(IEnumerable<string> contents, CancellationToken cancella
143145

144146
public File Create()
145147
{
146-
ModuleLogger.Current.LogInformation("Creating File: {Path}", this);
148+
LogFileOperation("Creating File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);
147149

148150
var fileStream = System.IO.File.Create(Path);
149151
fileStream.Dispose();
@@ -174,15 +176,15 @@ public FileAttributes Attributes
174176
/// <inheritdoc cref="FileInfo.Delete"/>>
175177
public void Delete()
176178
{
177-
ModuleLogger.Current.LogInformation("Deleting File: {File}", this);
179+
LogFileOperation("Deleting File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);
178180

179181
FileInfo.Delete();
180182
}
181183

182184
/// <inheritdoc cref="FileInfo.MoveTo(string)"/>>
183185
public File MoveTo(string path)
184186
{
185-
ModuleLogger.Current.LogInformation("Moving File: {Source} > {Destination}", this, path);
187+
LogFileOperationWithDestination("Moving File: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, path);
186188

187189
FileInfo.MoveTo(path);
188190
return this;
@@ -191,7 +193,7 @@ public File MoveTo(string path)
191193
/// <inheritdoc cref="FileInfo.MoveTo(string)"/>>
192194
public File MoveTo(Folder folder)
193195
{
194-
ModuleLogger.Current.LogInformation("Moving File: {Source} > {Destination}", this, folder);
196+
LogFileOperationWithDestination("Moving File: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, folder);
195197

196198
folder.Create();
197199
return MoveTo(System.IO.Path.Combine(folder.Path, Name));
@@ -200,14 +202,14 @@ public File MoveTo(Folder folder)
200202
/// <inheritdoc cref="FileInfo.CopyTo(string)"/>>
201203
public File CopyTo(string path)
202204
{
203-
ModuleLogger.Current.LogInformation("Copying File: {Source} > {Destination}", this, path);
205+
LogFileOperationWithDestination("Copying File: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, path);
204206

205207
return FileInfo.CopyTo(path);
206208
}
207209

208210
public File CopyTo(Folder folder)
209211
{
210-
ModuleLogger.Current.LogInformation("Copying File: {Source} > {Destination}", this, folder);
212+
LogFileOperationWithDestination("Copying File: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, folder);
211213

212214
folder.Create();
213215
return CopyTo(System.IO.Path.Combine(folder.Path, Name));
@@ -217,7 +219,7 @@ public static File GetNewTemporaryFilePath()
217219
{
218220
var path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName());
219221

220-
ModuleLogger.Current.LogInformation("Temporary File Path: {Path}", path);
222+
LogFileOperation("Temporary File Path: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", path);
221223

222224
return path!;
223225
}
@@ -301,4 +303,30 @@ public override int GetHashCode()
301303
{
302304
return !Equals(left, right);
303305
}
304-
}
306+
307+
/// <summary>
308+
/// Logs a file operation with Activity context information.
309+
/// </summary>
310+
/// <remarks>
311+
/// Phase 2: Uses Activity.Current for context alongside AsyncLocal for backward compatibility.
312+
/// The log message includes the current module name and activity ID when available.
313+
/// </remarks>
314+
private static void LogFileOperation(string messageTemplate, object? arg1)
315+
{
316+
var moduleName = ModuleActivityTracing.GetCurrentModuleName() ?? "Unknown";
317+
var activityId = ModuleActivityTracing.GetCurrentActivityId();
318+
319+
ModuleLogger.Current.LogInformation(messageTemplate, arg1, moduleName, activityId);
320+
}
321+
322+
/// <summary>
323+
/// Logs a file operation with Activity context information for operations with source and destination.
324+
/// </summary>
325+
private static void LogFileOperationWithDestination(string messageTemplate, object? source, object? destination)
326+
{
327+
var moduleName = ModuleActivityTracing.GetCurrentModuleName() ?? "Unknown";
328+
var activityId = ModuleActivityTracing.GetCurrentActivityId();
329+
330+
ModuleLogger.Current.LogInformation(messageTemplate, source, destination, moduleName, activityId);
331+
}
332+
}

0 commit comments

Comments
 (0)