For my console application, I have implemented a simple file logger. The logger uses StringBuilder to keep appending log entries and writes the LogText data to the LogFile at the end. This results in having only one file I/O operation. The application execution must be extremely fast and hence, I have implemented Parallel.ForEach along with async-await and reduced I/O operations wherever possible.
The problem is that Logger is not thread safe. Using lock or Monitor to synchronize the shared resource logger inside Parallel.ForEach loop slows down the performance. Is there any optimal way to synchronize the shared resource which does not affect execution speed much?
I am open to alternative approaches or suggestions.
Logger.cs
public class Logger
{
    private readonly string LogFile;
    private readonly StringBuilder LogText;
    public Logger(string logFile)
    {
        LogFile = logFile;
        LogText = new StringBuilder();
    }
    public void Write(string message)
    {
        LogText.AppendLine(message);
    }
    
    public void WriteToFile()
    {
        File.AppendAllText(LogFile, LogText.ToString());
    }
}
Program.cs
public class Program
{
    public static void Main(string[] args)
    {
        string logFile = args[0];
        string workingDirectory = args[1];
        Logger logger = new Logger(logFile);
        logger.Write($"INFO | Execution Started");
        try
        {
            List<string> files = Directory.EnumerateFiles(workingDirectory, "*", SearchOption.AllDirectories).ToList();
            Parallel.ForEach(files, async file =>
            {
                List<string> results = await PerformCPUBoundComputationAsync();
                foreach(string result in results)
                {
                    logger.Write($"INFO | Item: {result}");
                }
                string response = await MakePostRequestAsync(results);
                logger.Write($"INFO | Response: {response}");
            });
        }
        catch (Exception ex)
        {
            logger.Write($"ERROR | {ex.Message}");
        }
        finally
        {
            logger.Write($"INFO | Execution Ended");
            logger.WriteToFile();
        }
    }
}
 
     
    