Skip to content

Commit 38443f6

Browse files
thomhurstclaude
andcommitted
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>
1 parent bf06434 commit 38443f6

3 files changed

Lines changed: 127 additions & 31 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+
}

src/ModularPipelines/FileSystem/Folder.cs

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Diagnostics;
12
using System.Diagnostics.CodeAnalysis;
23
using System.Runtime.CompilerServices;
34
using System.Text.Json.Serialization;
@@ -6,6 +7,7 @@
67
using Microsoft.Extensions.Logging;
78
using ModularPipelines.JsonUtils;
89
using ModularPipelines.Logging;
10+
using ModularPipelines.Tracing;
911

1012
namespace ModularPipelines.FileSystem;
1113

@@ -77,15 +79,15 @@ public Folder Root
7779

7880
public Folder Create()
7981
{
80-
ModuleLogger.Current.LogInformation("Creating Folder: {Path}", this);
82+
LogFolderOperation("Creating Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);
8183

8284
Directory.CreateDirectory(Path);
8385
return this;
8486
}
8587

8688
public void Delete()
8789
{
88-
ModuleLogger.Current.LogInformation("Deleting Folder: {Path}", this);
90+
LogFolderOperation("Deleting Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);
8991

9092
DirectoryInfo.Delete(true);
9193
}
@@ -107,7 +109,7 @@ public void Clean()
107109
/// </param>
108110
public void Clean(bool removeReadOnlyAttribute)
109111
{
110-
ModuleLogger.Current.LogInformation("Cleaning Folder: {Path}", this);
112+
LogFolderOperation("Cleaning Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);
111113

112114
foreach (var directory in DirectoryInfo.EnumerateDirectories("*", SearchOption.TopDirectoryOnly))
113115
{
@@ -204,14 +206,14 @@ public Folder CopyTo(string targetPath, bool preserveTimestamps)
204206
targetRootDir.LastAccessTimeUtc = DirectoryInfo.LastAccessTimeUtc;
205207
}
206208

207-
ModuleLogger.Current.LogInformation("Copied Folder: {Source} > {Destination}", this, targetPath);
209+
LogFolderOperationWithDestination("Copied Folder: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, targetPath);
208210

209211
return new Folder(targetPath);
210212
}
211213

212214
public Folder MoveTo(string path)
213215
{
214-
ModuleLogger.Current.LogInformation("Moving Folder: {Source} > {Destination}", this, path);
216+
LogFolderOperationWithDestination("Moving Folder: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, path);
215217

216218
DirectoryInfo.MoveTo(path);
217219
return this;
@@ -221,7 +223,7 @@ public Folder GetFolder(string name)
221223
{
222224
var directoryInfo = new DirectoryInfo(System.IO.Path.Combine(Path, name));
223225

224-
ModuleLogger.Current.LogInformation("Getting Folder: {Path}", directoryInfo.FullName);
226+
LogFolderOperation("Getting Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", directoryInfo.FullName);
225227

226228
return directoryInfo;
227229
}
@@ -230,7 +232,7 @@ public Folder CreateFolder(string name)
230232
{
231233
var folder = GetFolder(name).Create();
232234

233-
ModuleLogger.Current.LogInformation("Creating Folder: {Path}", folder);
235+
LogFolderOperation("Creating Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", folder);
234236

235237
return folder;
236238
}
@@ -239,7 +241,7 @@ public File GetFile(string name)
239241
{
240242
var fileInfo = new FileInfo(System.IO.Path.Combine(Path, name));
241243

242-
ModuleLogger.Current.LogInformation("Getting File: {Path}", fileInfo.FullName);
244+
LogFolderOperation("Getting File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", fileInfo.FullName);
243245

244246
return fileInfo;
245247
}
@@ -255,7 +257,7 @@ public File CreateFile(string name)
255257

256258
public IEnumerable<Folder> GetFolders(Func<Folder, bool> predicate, Func<Folder, bool> exclusionFilters, [CallerArgumentExpression("predicate")] string predicateExpression = "")
257259
{
258-
ModuleLogger.Current.LogInformation("Searching Folders in: {Path} > {Expression}", this, predicateExpression);
260+
LogFolderOperationWithExpression("Searching Folders in: {Path} > {Expression} [Module: {ModuleName}, Activity: {ActivityId}]", this, predicateExpression);
259261

260262
return SafeWalk.EnumerateFolders(this, exclusionFilters)
261263
.Select(x => new Folder(x))
@@ -265,7 +267,7 @@ public IEnumerable<Folder> GetFolders(Func<Folder, bool> predicate, Func<Folder,
265267

266268
public IEnumerable<File> GetFiles(Func<File, bool> predicate, Func<Folder, bool> directoryExclusionFilters, [CallerArgumentExpression("predicate")] string predicateExpression = "")
267269
{
268-
ModuleLogger.Current.LogInformation("Searching Files in: {Path} > {Expression}", this, predicateExpression);
270+
LogFolderOperationWithExpression("Searching Files in: {Path} > {Expression} [Module: {ModuleName}, Activity: {ActivityId}]", this, predicateExpression);
269271

270272
return SafeWalk.EnumerateFiles(this, directoryExclusionFilters)
271273
.Select(x => new File(x))
@@ -275,7 +277,7 @@ public IEnumerable<File> GetFiles(Func<File, bool> predicate, Func<Folder, bool>
275277

276278
public IEnumerable<File> GetFiles(string globPattern)
277279
{
278-
ModuleLogger.Current.LogInformation("Searching Files in: {Path} > {Glob}", this, globPattern);
280+
LogFolderOperationWithExpression("Searching Files in: {Path} > {Glob} [Module: {ModuleName}, Activity: {ActivityId}]", this, globPattern);
279281

280282
return new Matcher(StringComparison.OrdinalIgnoreCase)
281283
.AddInclude(globPattern)
@@ -312,7 +314,7 @@ public static Folder CreateTemporaryFolder()
312314
var tempDirectory = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName().Replace(".", string.Empty));
313315
Directory.CreateDirectory(tempDirectory);
314316

315-
ModuleLogger.Current.LogInformation("Creating Temporary Folder: {Path}", tempDirectory);
317+
LogFolderOperation("Creating Temporary Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", tempDirectory);
316318

317319
return tempDirectory!;
318320
}
@@ -417,4 +419,52 @@ private static void RemoveReadOnlyAttributeRecursively(DirectoryInfo directory)
417419
RemoveReadOnlyAttributeRecursively(subDirectory);
418420
}
419421
}
420-
}
422+
423+
/// <summary>
424+
/// Logs a folder operation with Activity context information.
425+
/// </summary>
426+
/// <remarks>
427+
/// Phase 2: Uses Activity.Current for context alongside AsyncLocal for backward compatibility.
428+
/// The log message includes the current module name and activity ID when available.
429+
/// </remarks>
430+
private static void LogFolderOperation(string messageTemplate, object? arg1)
431+
{
432+
var moduleName = ModuleActivityTracing.GetCurrentModuleName() ?? "Unknown";
433+
var activityId = ModuleActivityTracing.GetCurrentActivityId();
434+
435+
ModuleLogger.Current.LogInformation(messageTemplate, arg1, moduleName, activityId);
436+
}
437+
438+
/// <summary>
439+
/// Logs a folder operation with Activity context information for operations with source and destination.
440+
/// </summary>
441+
private static void LogFolderOperationWithDestination(string messageTemplate, object? source, object? destination)
442+
{
443+
var moduleName = ModuleActivityTracing.GetCurrentModuleName() ?? "Unknown";
444+
var activityId = ModuleActivityTracing.GetCurrentActivityId();
445+
446+
ModuleLogger.Current.LogInformation(messageTemplate, source, destination, moduleName, activityId);
447+
}
448+
449+
/// <summary>
450+
/// Logs a folder operation with Activity context information for operations with path and expression/glob.
451+
/// </summary>
452+
private static void LogFolderOperationWithExpression(string messageTemplate, object? path, object? expression)
453+
{
454+
var moduleName = ModuleActivityTracing.GetCurrentModuleName() ?? "Unknown";
455+
var activityId = ModuleActivityTracing.GetCurrentActivityId();
456+
457+
ModuleLogger.Current.LogInformation(messageTemplate, path, expression, moduleName, activityId);
458+
}
459+
460+
/// <summary>
461+
/// Logs a folder warning with Activity context information.
462+
/// </summary>
463+
private static void LogFolderWarning(Exception ex, string messageTemplate, object? arg1)
464+
{
465+
var moduleName = ModuleActivityTracing.GetCurrentModuleName() ?? "Unknown";
466+
var activityId = ModuleActivityTracing.GetCurrentActivityId();
467+
468+
ModuleLogger.Current.LogWarning(ex, messageTemplate, arg1, moduleName, activityId);
469+
}
470+
}

src/ModularPipelines/Tracing/ModuleActivityTracing.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,22 @@ public static void RecordFailure(Activity? activity, Exception exception)
109109
activity.SetTag(ExceptionMessageTag, exception.Message);
110110
activity.SetStatus(ActivityStatusCode.Error, exception.Message);
111111
}
112+
113+
/// <summary>
114+
/// Gets the current module name from the current Activity, if available.
115+
/// </summary>
116+
/// <returns>The module name, or null if no module activity is active.</returns>
117+
public static string? GetCurrentModuleName()
118+
{
119+
return Activity.Current?.GetTagItem(ModuleTypeTag) as string;
120+
}
121+
122+
/// <summary>
123+
/// Gets the current Activity ID, if available.
124+
/// </summary>
125+
/// <returns>The activity ID, or null if no activity is active.</returns>
126+
public static string? GetCurrentActivityId()
127+
{
128+
return Activity.Current?.Id;
129+
}
112130
}

0 commit comments

Comments
 (0)