From 5127a460883ef2d5946e47fae57a473c9bba6c7b Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Tue, 30 Dec 2025 19:50:04 +0000 Subject: [PATCH 1/3] feat: Enhance Folder.Clean() and CopyTo() methods with additional options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/ModularPipelines/FileSystem/Folder.cs | 125 +++++++++++++++++++++- 1 file changed, 122 insertions(+), 3 deletions(-) diff --git a/src/ModularPipelines/FileSystem/Folder.cs b/src/ModularPipelines/FileSystem/Folder.cs index 5c1dd201b0..09e78631f3 100644 --- a/src/ModularPipelines/FileSystem/Folder.cs +++ b/src/ModularPipelines/FileSystem/Folder.cs @@ -90,37 +90,132 @@ public void Delete() DirectoryInfo.Delete(true); } + /// + /// Removes all files and subdirectories within the folder. + /// public void Clean() + { + Clean(removeReadOnlyAttribute: true); + } + + /// + /// Removes all files and subdirectories within the folder. + /// + /// + /// When true, removes the read-only attribute from files and directories before deletion. + /// This helps handle read-only items that would otherwise fail to delete. + /// + public void Clean(bool removeReadOnlyAttribute) { ModuleLogger.Current.LogInformation("Cleaning Folder: {Path}", this); foreach (var directory in DirectoryInfo.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)) { - directory.Delete(true); + try + { + if (removeReadOnlyAttribute) + { + RemoveReadOnlyAttributeRecursively(directory); + } + + directory.Delete(true); + } + catch (Exception ex) + { + ModuleLogger.Current.LogWarning(ex, "Failed to delete directory: {Path}", directory.FullName); + } } foreach (var file in DirectoryInfo.EnumerateFiles("*", SearchOption.TopDirectoryOnly)) { - file.Delete(); + try + { + if (removeReadOnlyAttribute && (file.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) + { + file.Attributes &= ~FileAttributes.ReadOnly; + } + + file.Delete(); + } + catch (Exception ex) + { + ModuleLogger.Current.LogWarning(ex, "Failed to delete file: {Path}", file.FullName); + } } } + /// + /// Copies the folder and its contents to the specified target path. + /// + /// The destination path for the copied folder. + /// A new instance representing the copied folder. public Folder CopyTo(string targetPath) + { + return CopyTo(targetPath, preserveTimestamps: true); + } + + /// + /// Copies the folder and its contents to the specified target path. + /// + /// The destination path for the copied folder. + /// + /// When true, preserves CreationTimeUtc, LastWriteTimeUtc, and LastAccessTimeUtc + /// for all files and directories. + /// + /// A new instance representing the copied folder. + public Folder CopyTo(string targetPath, bool preserveTimestamps) { Directory.CreateDirectory(targetPath); + // Copy all subdirectories first foreach (var dirPath in Directory.EnumerateDirectories(this, "*", SearchOption.AllDirectories)) { + var sourceDir = new DirectoryInfo(dirPath); var relativePath = System.IO.Path.GetRelativePath(this, dirPath); var newPath = System.IO.Path.Combine(targetPath, relativePath); - Directory.CreateDirectory(newPath); + var targetDir = Directory.CreateDirectory(newPath); + + // Preserve directory attributes + targetDir.Attributes = sourceDir.Attributes; + + if (preserveTimestamps) + { + targetDir.CreationTimeUtc = sourceDir.CreationTimeUtc; + targetDir.LastWriteTimeUtc = sourceDir.LastWriteTimeUtc; + targetDir.LastAccessTimeUtc = sourceDir.LastAccessTimeUtc; + } } + // Copy all files foreach (var filePath in Directory.EnumerateFiles(this, "*", SearchOption.AllDirectories)) { + var sourceFile = new FileInfo(filePath); var relativePath = System.IO.Path.GetRelativePath(this, filePath); var newPath = System.IO.Path.Combine(targetPath, relativePath); System.IO.File.Copy(filePath, newPath, true); + + var targetFile = new FileInfo(newPath); + + // Preserve file attributes + targetFile.Attributes = sourceFile.Attributes; + + if (preserveTimestamps) + { + targetFile.CreationTimeUtc = sourceFile.CreationTimeUtc; + targetFile.LastWriteTimeUtc = sourceFile.LastWriteTimeUtc; + targetFile.LastAccessTimeUtc = sourceFile.LastAccessTimeUtc; + } + } + + // Preserve root directory attributes and timestamps after all content is copied + var targetRootDir = new DirectoryInfo(targetPath); + targetRootDir.Attributes = DirectoryInfo.Attributes; + + if (preserveTimestamps) + { + targetRootDir.CreationTimeUtc = DirectoryInfo.CreationTimeUtc; + targetRootDir.LastWriteTimeUtc = DirectoryInfo.LastWriteTimeUtc; + targetRootDir.LastAccessTimeUtc = DirectoryInfo.LastAccessTimeUtc; } ModuleLogger.Current.LogInformation("Copying Folder: {Source} > {Destination}", this, targetPath); @@ -315,4 +410,28 @@ public override int GetHashCode() { return !Equals(left, right); } + + private static void RemoveReadOnlyAttributeRecursively(DirectoryInfo directory) + { + if ((directory.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) + { + directory.Attributes &= ~FileAttributes.ReadOnly; + } + + foreach (var file in directory.EnumerateFiles("*", SearchOption.AllDirectories)) + { + if ((file.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) + { + file.Attributes &= ~FileAttributes.ReadOnly; + } + } + + foreach (var subDirectory in directory.EnumerateDirectories("*", SearchOption.AllDirectories)) + { + if ((subDirectory.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) + { + subDirectory.Attributes &= ~FileAttributes.ReadOnly; + } + } + } } \ No newline at end of file From bf064346a934c36cf74869036c883d39867dc3af Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Tue, 30 Dec 2025 20:14:45 +0000 Subject: [PATCH 2/3] fix: Address PR review feedback for Folder enhancements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/ModularPipelines/FileSystem/Folder.cs | 43 +++++++---------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/src/ModularPipelines/FileSystem/Folder.cs b/src/ModularPipelines/FileSystem/Folder.cs index 09e78631f3..7033189e2e 100644 --- a/src/ModularPipelines/FileSystem/Folder.cs +++ b/src/ModularPipelines/FileSystem/Folder.cs @@ -111,36 +111,22 @@ public void Clean(bool removeReadOnlyAttribute) foreach (var directory in DirectoryInfo.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)) { - try + if (removeReadOnlyAttribute) { - if (removeReadOnlyAttribute) - { - RemoveReadOnlyAttributeRecursively(directory); - } - - directory.Delete(true); - } - catch (Exception ex) - { - ModuleLogger.Current.LogWarning(ex, "Failed to delete directory: {Path}", directory.FullName); + RemoveReadOnlyAttributeRecursively(directory); } + + directory.Delete(true); } foreach (var file in DirectoryInfo.EnumerateFiles("*", SearchOption.TopDirectoryOnly)) { - try + if (removeReadOnlyAttribute && (file.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) { - if (removeReadOnlyAttribute && (file.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) - { - file.Attributes &= ~FileAttributes.ReadOnly; - } - - file.Delete(); - } - catch (Exception ex) - { - ModuleLogger.Current.LogWarning(ex, "Failed to delete file: {Path}", file.FullName); + file.Attributes &= ~FileAttributes.ReadOnly; } + + file.Delete(); } } @@ -151,7 +137,7 @@ public void Clean(bool removeReadOnlyAttribute) /// A new instance representing the copied folder. public Folder CopyTo(string targetPath) { - return CopyTo(targetPath, preserveTimestamps: true); + return CopyTo(targetPath, preserveTimestamps: false); } /// @@ -218,7 +204,7 @@ public Folder CopyTo(string targetPath, bool preserveTimestamps) targetRootDir.LastAccessTimeUtc = DirectoryInfo.LastAccessTimeUtc; } - ModuleLogger.Current.LogInformation("Copying Folder: {Source} > {Destination}", this, targetPath); + ModuleLogger.Current.LogInformation("Copied Folder: {Source} > {Destination}", this, targetPath); return new Folder(targetPath); } @@ -418,7 +404,7 @@ private static void RemoveReadOnlyAttributeRecursively(DirectoryInfo directory) directory.Attributes &= ~FileAttributes.ReadOnly; } - foreach (var file in directory.EnumerateFiles("*", SearchOption.AllDirectories)) + foreach (var file in directory.EnumerateFiles("*", SearchOption.TopDirectoryOnly)) { if ((file.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) { @@ -426,12 +412,9 @@ private static void RemoveReadOnlyAttributeRecursively(DirectoryInfo directory) } } - foreach (var subDirectory in directory.EnumerateDirectories("*", SearchOption.AllDirectories)) + foreach (var subDirectory in directory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)) { - if ((subDirectory.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) - { - subDirectory.Attributes &= ~FileAttributes.ReadOnly; - } + RemoveReadOnlyAttributeRecursively(subDirectory); } } } \ No newline at end of file From 38443f64bdbe2dbd858726713845a37970e8b8ca Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Tue, 30 Dec 2025 20:00:13 +0000 Subject: [PATCH 3/3] feat: Add Phase 2 Activity-based logging to FileSystem operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/ModularPipelines/FileSystem/File.cs | 64 +++++++++++----- src/ModularPipelines/FileSystem/Folder.cs | 76 +++++++++++++++---- .../Tracing/ModuleActivityTracing.cs | 18 +++++ 3 files changed, 127 insertions(+), 31 deletions(-) diff --git a/src/ModularPipelines/FileSystem/File.cs b/src/ModularPipelines/FileSystem/File.cs index 8ca2a2c5ab..ffade0eb56 100644 --- a/src/ModularPipelines/FileSystem/File.cs +++ b/src/ModularPipelines/FileSystem/File.cs @@ -1,7 +1,9 @@ +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using Microsoft.Extensions.Logging; using ModularPipelines.Logging; +using ModularPipelines.Tracing; namespace ModularPipelines.FileSystem; @@ -35,7 +37,7 @@ internal File(FileInfo fileInfo) /// > public Task ReadAsync(CancellationToken cancellationToken = default) { - ModuleLogger.Current.LogInformation("Reading File: {Path}", this); + LogFileOperation("Reading File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); return System.IO.File.ReadAllTextAsync(Path, cancellationToken); } @@ -43,14 +45,14 @@ public Task ReadAsync(CancellationToken cancellationToken = default) /// public IAsyncEnumerable ReadLinesAsync(CancellationToken cancellationToken = default) { - ModuleLogger.Current.LogInformation("Reading File: {Path}", this); + LogFileOperation("Reading File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); return System.IO.File.ReadLinesAsync(Path, cancellationToken); } public Task ReadBytesAsync(CancellationToken cancellationToken = default) { - ModuleLogger.Current.LogInformation("Reading File: {Path}", this); + LogFileOperation("Reading File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); return System.IO.File.ReadAllBytesAsync(Path, cancellationToken); } @@ -62,28 +64,28 @@ public FileStream GetStream(FileAccess fileAccess = FileAccess.ReadWrite) public Task WriteAsync(string contents, CancellationToken cancellationToken = default) { - ModuleLogger.Current.LogInformation("Writing to File: {Path}", this); + LogFileOperation("Writing to File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); return System.IO.File.WriteAllTextAsync(Path, contents, cancellationToken); } public Task WriteAsync(byte[] contents, CancellationToken cancellationToken = default) { - ModuleLogger.Current.LogInformation("Writing to File: {Path}", this); + LogFileOperation("Writing to File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); return System.IO.File.WriteAllBytesAsync(Path, contents, cancellationToken); } public Task WriteAsync(IEnumerable contents, CancellationToken cancellationToken = default) { - ModuleLogger.Current.LogInformation("Writing to File: {Path}", this); + LogFileOperation("Writing to File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); return System.IO.File.WriteAllLinesAsync(Path, contents, cancellationToken); } public async Task WriteAsync(ReadOnlyMemory contents, CancellationToken cancellationToken = default) { - ModuleLogger.Current.LogInformation("Writing to File: {Path}", this); + LogFileOperation("Writing to File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); var fileStream = System.IO.File.Create(Path); await using (fileStream.ConfigureAwait(false)) @@ -94,7 +96,7 @@ public async Task WriteAsync(ReadOnlyMemory contents, CancellationToken ca public async Task WriteAsync(Stream contents, CancellationToken cancellationToken = default) { - ModuleLogger.Current.LogInformation("Writing to File: {Path}", this); + LogFileOperation("Writing to File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); var fileStream = System.IO.File.Create(Path); await using (fileStream.ConfigureAwait(false)) @@ -110,14 +112,14 @@ public async Task WriteAsync(Stream contents, CancellationToken cancellationToke public Task AppendAsync(string contents, CancellationToken cancellationToken = default) { - ModuleLogger.Current.LogInformation("Writing to File: {Path}", this); + LogFileOperation("Appending to File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); return System.IO.File.AppendAllTextAsync(Path, contents, cancellationToken); } public Task AppendAsync(IEnumerable contents, CancellationToken cancellationToken = default) { - ModuleLogger.Current.LogInformation("Writing to File: {Path}", this); + LogFileOperation("Appending to File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); return System.IO.File.AppendAllLinesAsync(Path, contents, cancellationToken); } @@ -143,7 +145,7 @@ public Task AppendAsync(IEnumerable contents, CancellationToken cancella public File Create() { - ModuleLogger.Current.LogInformation("Creating File: {Path}", this); + LogFileOperation("Creating File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); var fileStream = System.IO.File.Create(Path); fileStream.Dispose(); @@ -174,7 +176,7 @@ public FileAttributes Attributes /// > public void Delete() { - ModuleLogger.Current.LogInformation("Deleting File: {File}", this); + LogFileOperation("Deleting File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); FileInfo.Delete(); } @@ -182,7 +184,7 @@ public void Delete() /// > public File MoveTo(string path) { - ModuleLogger.Current.LogInformation("Moving File: {Source} > {Destination}", this, path); + LogFileOperationWithDestination("Moving File: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, path); FileInfo.MoveTo(path); return this; @@ -191,7 +193,7 @@ public File MoveTo(string path) /// > public File MoveTo(Folder folder) { - ModuleLogger.Current.LogInformation("Moving File: {Source} > {Destination}", this, folder); + LogFileOperationWithDestination("Moving File: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, folder); folder.Create(); return MoveTo(System.IO.Path.Combine(folder.Path, Name)); @@ -200,14 +202,14 @@ public File MoveTo(Folder folder) /// > public File CopyTo(string path) { - ModuleLogger.Current.LogInformation("Copying File: {Source} > {Destination}", this, path); + LogFileOperationWithDestination("Copying File: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, path); return FileInfo.CopyTo(path); } public File CopyTo(Folder folder) { - ModuleLogger.Current.LogInformation("Copying File: {Source} > {Destination}", this, folder); + LogFileOperationWithDestination("Copying File: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, folder); folder.Create(); return CopyTo(System.IO.Path.Combine(folder.Path, Name)); @@ -217,7 +219,7 @@ public static File GetNewTemporaryFilePath() { var path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName()); - ModuleLogger.Current.LogInformation("Temporary File Path: {Path}", path); + LogFileOperation("Temporary File Path: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", path); return path!; } @@ -301,4 +303,30 @@ public override int GetHashCode() { return !Equals(left, right); } -} \ No newline at end of file + + /// + /// Logs a file operation with Activity context information. + /// + /// + /// Phase 2: Uses Activity.Current for context alongside AsyncLocal for backward compatibility. + /// The log message includes the current module name and activity ID when available. + /// + private static void LogFileOperation(string messageTemplate, object? arg1) + { + var moduleName = ModuleActivityTracing.GetCurrentModuleName() ?? "Unknown"; + var activityId = ModuleActivityTracing.GetCurrentActivityId(); + + ModuleLogger.Current.LogInformation(messageTemplate, arg1, moduleName, activityId); + } + + /// + /// Logs a file operation with Activity context information for operations with source and destination. + /// + private static void LogFileOperationWithDestination(string messageTemplate, object? source, object? destination) + { + var moduleName = ModuleActivityTracing.GetCurrentModuleName() ?? "Unknown"; + var activityId = ModuleActivityTracing.GetCurrentActivityId(); + + ModuleLogger.Current.LogInformation(messageTemplate, source, destination, moduleName, activityId); + } +} diff --git a/src/ModularPipelines/FileSystem/Folder.cs b/src/ModularPipelines/FileSystem/Folder.cs index 7033189e2e..4e3f290fb8 100644 --- a/src/ModularPipelines/FileSystem/Folder.cs +++ b/src/ModularPipelines/FileSystem/Folder.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Text.Json.Serialization; @@ -6,6 +7,7 @@ using Microsoft.Extensions.Logging; using ModularPipelines.JsonUtils; using ModularPipelines.Logging; +using ModularPipelines.Tracing; namespace ModularPipelines.FileSystem; @@ -77,7 +79,7 @@ public Folder Root public Folder Create() { - ModuleLogger.Current.LogInformation("Creating Folder: {Path}", this); + LogFolderOperation("Creating Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); Directory.CreateDirectory(Path); return this; @@ -85,7 +87,7 @@ public Folder Create() public void Delete() { - ModuleLogger.Current.LogInformation("Deleting Folder: {Path}", this); + LogFolderOperation("Deleting Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); DirectoryInfo.Delete(true); } @@ -107,7 +109,7 @@ public void Clean() /// public void Clean(bool removeReadOnlyAttribute) { - ModuleLogger.Current.LogInformation("Cleaning Folder: {Path}", this); + LogFolderOperation("Cleaning Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); foreach (var directory in DirectoryInfo.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)) { @@ -204,14 +206,14 @@ public Folder CopyTo(string targetPath, bool preserveTimestamps) targetRootDir.LastAccessTimeUtc = DirectoryInfo.LastAccessTimeUtc; } - ModuleLogger.Current.LogInformation("Copied Folder: {Source} > {Destination}", this, targetPath); + LogFolderOperationWithDestination("Copied Folder: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, targetPath); return new Folder(targetPath); } public Folder MoveTo(string path) { - ModuleLogger.Current.LogInformation("Moving Folder: {Source} > {Destination}", this, path); + LogFolderOperationWithDestination("Moving Folder: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, path); DirectoryInfo.MoveTo(path); return this; @@ -221,7 +223,7 @@ public Folder GetFolder(string name) { var directoryInfo = new DirectoryInfo(System.IO.Path.Combine(Path, name)); - ModuleLogger.Current.LogInformation("Getting Folder: {Path}", directoryInfo.FullName); + LogFolderOperation("Getting Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", directoryInfo.FullName); return directoryInfo; } @@ -230,7 +232,7 @@ public Folder CreateFolder(string name) { var folder = GetFolder(name).Create(); - ModuleLogger.Current.LogInformation("Creating Folder: {Path}", folder); + LogFolderOperation("Creating Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", folder); return folder; } @@ -239,7 +241,7 @@ public File GetFile(string name) { var fileInfo = new FileInfo(System.IO.Path.Combine(Path, name)); - ModuleLogger.Current.LogInformation("Getting File: {Path}", fileInfo.FullName); + LogFolderOperation("Getting File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", fileInfo.FullName); return fileInfo; } @@ -255,7 +257,7 @@ public File CreateFile(string name) public IEnumerable GetFolders(Func predicate, Func exclusionFilters, [CallerArgumentExpression("predicate")] string predicateExpression = "") { - ModuleLogger.Current.LogInformation("Searching Folders in: {Path} > {Expression}", this, predicateExpression); + LogFolderOperationWithExpression("Searching Folders in: {Path} > {Expression} [Module: {ModuleName}, Activity: {ActivityId}]", this, predicateExpression); return SafeWalk.EnumerateFolders(this, exclusionFilters) .Select(x => new Folder(x)) @@ -265,7 +267,7 @@ public IEnumerable GetFolders(Func predicate, Func GetFiles(Func predicate, Func directoryExclusionFilters, [CallerArgumentExpression("predicate")] string predicateExpression = "") { - ModuleLogger.Current.LogInformation("Searching Files in: {Path} > {Expression}", this, predicateExpression); + LogFolderOperationWithExpression("Searching Files in: {Path} > {Expression} [Module: {ModuleName}, Activity: {ActivityId}]", this, predicateExpression); return SafeWalk.EnumerateFiles(this, directoryExclusionFilters) .Select(x => new File(x)) @@ -275,7 +277,7 @@ public IEnumerable GetFiles(Func predicate, Func public IEnumerable GetFiles(string globPattern) { - ModuleLogger.Current.LogInformation("Searching Files in: {Path} > {Glob}", this, globPattern); + LogFolderOperationWithExpression("Searching Files in: {Path} > {Glob} [Module: {ModuleName}, Activity: {ActivityId}]", this, globPattern); return new Matcher(StringComparison.OrdinalIgnoreCase) .AddInclude(globPattern) @@ -312,7 +314,7 @@ public static Folder CreateTemporaryFolder() var tempDirectory = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName().Replace(".", string.Empty)); Directory.CreateDirectory(tempDirectory); - ModuleLogger.Current.LogInformation("Creating Temporary Folder: {Path}", tempDirectory); + LogFolderOperation("Creating Temporary Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", tempDirectory); return tempDirectory!; } @@ -417,4 +419,52 @@ private static void RemoveReadOnlyAttributeRecursively(DirectoryInfo directory) RemoveReadOnlyAttributeRecursively(subDirectory); } } -} \ No newline at end of file + + /// + /// Logs a folder operation with Activity context information. + /// + /// + /// Phase 2: Uses Activity.Current for context alongside AsyncLocal for backward compatibility. + /// The log message includes the current module name and activity ID when available. + /// + private static void LogFolderOperation(string messageTemplate, object? arg1) + { + var moduleName = ModuleActivityTracing.GetCurrentModuleName() ?? "Unknown"; + var activityId = ModuleActivityTracing.GetCurrentActivityId(); + + ModuleLogger.Current.LogInformation(messageTemplate, arg1, moduleName, activityId); + } + + /// + /// Logs a folder operation with Activity context information for operations with source and destination. + /// + private static void LogFolderOperationWithDestination(string messageTemplate, object? source, object? destination) + { + var moduleName = ModuleActivityTracing.GetCurrentModuleName() ?? "Unknown"; + var activityId = ModuleActivityTracing.GetCurrentActivityId(); + + ModuleLogger.Current.LogInformation(messageTemplate, source, destination, moduleName, activityId); + } + + /// + /// Logs a folder operation with Activity context information for operations with path and expression/glob. + /// + private static void LogFolderOperationWithExpression(string messageTemplate, object? path, object? expression) + { + var moduleName = ModuleActivityTracing.GetCurrentModuleName() ?? "Unknown"; + var activityId = ModuleActivityTracing.GetCurrentActivityId(); + + ModuleLogger.Current.LogInformation(messageTemplate, path, expression, moduleName, activityId); + } + + /// + /// Logs a folder warning with Activity context information. + /// + private static void LogFolderWarning(Exception ex, string messageTemplate, object? arg1) + { + var moduleName = ModuleActivityTracing.GetCurrentModuleName() ?? "Unknown"; + var activityId = ModuleActivityTracing.GetCurrentActivityId(); + + ModuleLogger.Current.LogWarning(ex, messageTemplate, arg1, moduleName, activityId); + } +} diff --git a/src/ModularPipelines/Tracing/ModuleActivityTracing.cs b/src/ModularPipelines/Tracing/ModuleActivityTracing.cs index c53ff6f250..79723b7f17 100644 --- a/src/ModularPipelines/Tracing/ModuleActivityTracing.cs +++ b/src/ModularPipelines/Tracing/ModuleActivityTracing.cs @@ -109,4 +109,22 @@ public static void RecordFailure(Activity? activity, Exception exception) activity.SetTag(ExceptionMessageTag, exception.Message); activity.SetStatus(ActivityStatusCode.Error, exception.Message); } + + /// + /// Gets the current module name from the current Activity, if available. + /// + /// The module name, or null if no module activity is active. + public static string? GetCurrentModuleName() + { + return Activity.Current?.GetTagItem(ModuleTypeTag) as string; + } + + /// + /// Gets the current Activity ID, if available. + /// + /// The activity ID, or null if no activity is active. + public static string? GetCurrentActivityId() + { + return Activity.Current?.Id; + } }