diff --git a/src/Core.UnitTests/Logging/LoggerBaseTests.cs b/src/Core.UnitTests/Logging/LoggerBaseTests.cs
new file mode 100644
index 0000000000..24df863fbf
--- /dev/null
+++ b/src/Core.UnitTests/Logging/LoggerBaseTests.cs
@@ -0,0 +1,333 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+using SonarLint.VisualStudio.Core.Logging;
+
+namespace SonarLint.VisualStudio.Core.UnitTests.Logging;
+
+[TestClass]
+public class LoggerBaseTests
+{
+    private ILoggerContextManager contextManager;
+    private ILoggerWriter writer;
+    private ILoggerSettingsProvider settingsProvider;
+    private LoggerBase testSubject;
+
+    [TestInitialize]
+    public void TestInitialize()
+    {
+        contextManager = Substitute.For<ILoggerContextManager>();
+        writer = Substitute.For<ILoggerWriter>();
+        settingsProvider = Substitute.For<ILoggerSettingsProvider>();
+        testSubject = new LoggerBase(contextManager, writer, settingsProvider);
+    }
+
+    [TestMethod]
+    public void ForContext_CreatesNewLoggerWithUpdatedContextManager()
+    {
+        var newContextManager = Substitute.For<ILoggerContextManager>();
+        contextManager.CreateAugmentedContext(Arg.Any<IEnumerable<string>>()).Returns(newContextManager);
+
+        var newLogger = testSubject.ForContext("ctx");
+
+        contextManager.Received(1).CreateAugmentedContext(Arg.Is<IEnumerable<string>>(x => x.SequenceEqual(new[] { "ctx" })));
+        newLogger.Should().NotBeSameAs(testSubject);
+        newLogger.WriteLine("msg");
+        contextManager.DidNotReceiveWithAnyArgs().GetFormattedContextOrNull(default);
+        newContextManager.ReceivedWithAnyArgs().GetFormattedContextOrNull(default);
+        writer.Received().WriteLine(Arg.Any<string>());
+        _ = settingsProvider.Received().IsVerboseEnabled;
+    }
+
+    [TestMethod]
+    public void ForVerboseContext_CreatesNewLoggerWithUpdatedContextManager()
+    {
+        var newContextManager = Substitute.For<ILoggerContextManager>();
+        contextManager.CreateAugmentedVerboseContext(Arg.Any<IEnumerable<string>>()).Returns(newContextManager);
+
+        var newLogger = testSubject.ForVerboseContext("ctx");
+
+        contextManager.Received(1).CreateAugmentedVerboseContext(Arg.Is<IEnumerable<string>>(x => x.SequenceEqual(new[] { "ctx" })));
+        newLogger.Should().NotBeSameAs(testSubject);
+        newLogger.WriteLine("msg");
+        contextManager.DidNotReceiveWithAnyArgs().GetFormattedContextOrNull(default);
+        newContextManager.ReceivedWithAnyArgs().GetFormattedContextOrNull(default);
+        writer.Received().WriteLine(Arg.Any<string>());
+        _ = settingsProvider.Received().IsVerboseEnabled;
+    }
+
+    [TestMethod]
+    public void LogVerbose_VerboseDisabled_DoesNothing()
+    {
+        settingsProvider.IsVerboseEnabled.Returns(false);
+
+        testSubject.LogVerbose("msg {0}", "sent");
+
+        writer.DidNotReceiveWithAnyArgs().WriteLine(default);
+    }
+
+    [TestMethod]
+    public void LogVerbose_VerboseEnabled_AddsDebugProperty()
+    {
+        settingsProvider.IsVerboseEnabled.Returns(true);
+
+        testSubject.LogVerbose("msg {0}", "sent");
+
+        writer.Received().WriteLine("[DEBUG] msg sent");
+    }
+
+    [TestMethod]
+    public void LogVerbose_ThreadIdLoggingEnabled_AddsThreadIdProperty()
+    {
+        settingsProvider.IsThreadIdEnabled.Returns(true);
+        settingsProvider.IsVerboseEnabled.Returns(true);
+
+        testSubject.LogVerbose("msg {0}", "sent");
+
+        writer.Received().WriteLine($"[DEBUG] [ThreadId {Thread.CurrentThread.ManagedThreadId}] msg sent");
+    }
+
+    [TestMethod]
+    public void LogVerbose_Context_AddsContextProperty()
+    {
+        settingsProvider.IsVerboseEnabled.Returns(true);
+        contextManager.GetFormattedContextOrNull(default).Returns("context");
+
+        testSubject.LogVerbose("msg {0}", "sent");
+
+        writer.Received().WriteLine("[DEBUG] [context] msg sent");
+    }
+
+    [TestMethod]
+    public void LogVerbose_VerboseContext_AddsVerboseContextProperty()
+    {
+        settingsProvider.IsVerboseEnabled.Returns(true);
+        contextManager.GetFormattedVerboseContextOrNull(default).Returns("verbose context");
+
+        testSubject.LogVerbose("msg {0}", "sent");
+
+        writer.Received().WriteLine("[DEBUG] [verbose context] msg sent");
+    }
+
+    [TestMethod]
+    public void LogVerbose_AllContextsEnabled_AddsInCorrectOrder()
+    {
+        settingsProvider.IsThreadIdEnabled.Returns(true);
+        settingsProvider.IsVerboseEnabled.Returns(true);
+        contextManager.GetFormattedContextOrNull(default).Returns("context");
+        contextManager.GetFormattedVerboseContextOrNull(default).Returns("verbose context");
+
+        testSubject.LogVerbose("msg {0}", "sent");
+
+        writer.Received().WriteLine($"[DEBUG] [ThreadId {Thread.CurrentThread.ManagedThreadId}] [context] [verbose context] msg sent");
+    }
+
+    [TestMethod]
+    public void LogVerboseWithContext_AllContextsEnabled_AddsInCorrectOrder()
+    {
+        var messageLevelContext = new MessageLevelContext
+        {
+            Context = Substitute.For<IReadOnlyCollection<string>>(),
+            VerboseContext = Substitute.For<IReadOnlyCollection<string>>()
+        };
+        settingsProvider.IsThreadIdEnabled.Returns(true);
+        settingsProvider.IsVerboseEnabled.Returns(true);
+        contextManager.GetFormattedContextOrNull(messageLevelContext).Returns("context with message level");
+        contextManager.GetFormattedVerboseContextOrNull(messageLevelContext).Returns("verbose context with message level");
+
+        testSubject.LogVerbose(messageLevelContext, "msg {0}", "sent");
+
+        writer.Received().WriteLine($"[DEBUG] [ThreadId {Thread.CurrentThread.ManagedThreadId}] [context with message level] [verbose context with message level] msg sent");
+    }
+
+    [TestMethod]
+    public void WriteLine_VerboseDisabled_Writes()
+    {
+        settingsProvider.IsVerboseEnabled.Returns(false);
+
+        testSubject.WriteLine("msg sent");
+
+        writer.Received().WriteLine("msg sent");
+    }
+
+    [TestMethod]
+    public void WriteLineFormatted_VerboseDisabled_Writes()
+    {
+        settingsProvider.IsVerboseEnabled.Returns(false);
+
+        testSubject.WriteLine("msg {0}", "sent");
+
+        writer.Received().WriteLine("msg sent");
+    }
+
+    [TestMethod]
+    public void WriteLine_VerboseEnabled_DoesNotAddDebugProperty()
+    {
+        settingsProvider.IsVerboseEnabled.Returns(true);
+
+        testSubject.WriteLine("msg sent");
+
+        writer.Received().WriteLine("msg sent");
+    }
+
+    [TestMethod]
+    public void WriteLineFormatted_VerboseEnabled_DoesNotAddDebugProperty()
+    {
+        settingsProvider.IsVerboseEnabled.Returns(true);
+
+        testSubject.WriteLine("msg {0}", "sent");
+
+        writer.Received().WriteLine("msg sent");
+    }
+
+    [TestMethod]
+    public void WriteLine_ThreadIdLoggingEnabled_AddsThreadIdProperty()
+    {
+        settingsProvider.IsThreadIdEnabled.Returns(true);
+
+        testSubject.WriteLine("msg sent");
+
+        writer.Received().WriteLine($"[ThreadId {Thread.CurrentThread.ManagedThreadId}] msg sent");
+    }
+
+    [TestMethod]
+    public void WriteLineFormatted_ThreadIdLoggingEnabled_AddsThreadIdProperty()
+    {
+        settingsProvider.IsThreadIdEnabled.Returns(true);
+
+        testSubject.WriteLine("msg {0}", "sent");
+
+        writer.Received().WriteLine($"[ThreadId {Thread.CurrentThread.ManagedThreadId}] msg sent");
+    }
+
+    [DataRow(true)]
+    [DataRow(false)]
+    [DataTestMethod]
+    public void WriteLine_Context_AddsContextProperty(bool isVerboseEnabled)
+    {
+        settingsProvider.IsVerboseEnabled.Returns(isVerboseEnabled);
+        contextManager.GetFormattedContextOrNull(default).Returns("context");
+
+        testSubject.WriteLine("msg sent");
+
+        writer.Received().WriteLine("[context] msg sent");
+    }
+
+    [DataRow(true)]
+    [DataRow(false)]
+    [DataTestMethod]
+    public void WriteLineFormatted_Context_AddsContextProperty(bool isVerboseEnabled)
+    {
+        settingsProvider.IsVerboseEnabled.Returns(isVerboseEnabled);
+        contextManager.GetFormattedContextOrNull(default).Returns("context");
+
+        testSubject.WriteLine("msg {0}", "sent");
+
+        writer.Received().WriteLine("[context] msg sent");
+    }
+
+    [TestMethod]
+    public void WriteLine_VerboseContext_VerboseLoggingDisabled_DoesNotAddVerboseContextProperty()
+    {
+        settingsProvider.IsVerboseEnabled.Returns(false);
+        contextManager.GetFormattedVerboseContextOrNull(default).Returns("verbose context");
+
+        testSubject.WriteLine("msg sent");
+
+        writer.Received().WriteLine("msg sent");
+    }
+
+    [TestMethod]
+    public void WriteLineFormatted_VerboseContext_VerboseLoggingDisabled_DoesNotAddVerboseContextProperty()
+    {
+        settingsProvider.IsVerboseEnabled.Returns(false);
+        contextManager.GetFormattedVerboseContextOrNull(default).Returns("verbose context");
+
+        testSubject.WriteLine("msg {0}", "sent");
+
+        writer.Received().WriteLine("msg sent");
+    }
+
+    [TestMethod]
+    public void WriteLine_VerboseContext_VerboseLoggingEnabled_AddsVerboseContextProperty()
+    {
+        settingsProvider.IsVerboseEnabled.Returns(true);
+        contextManager.GetFormattedVerboseContextOrNull(default).Returns("verbose context");
+
+        testSubject.WriteLine("msg sent");
+
+        writer.Received().WriteLine("[verbose context] msg sent");
+    }
+
+    [TestMethod]
+    public void WriteLineFormatted_VerboseContext_VerboseLoggingEnabled_AddsVerboseContextProperty()
+    {
+        settingsProvider.IsVerboseEnabled.Returns(true);
+        contextManager.GetFormattedVerboseContextOrNull(default).Returns("verbose context");
+
+        testSubject.WriteLine("msg {0}", "sent");
+
+        writer.Received().WriteLine("[verbose context] msg sent");
+    }
+
+    [TestMethod]
+    public void WriteLine_AllContextsEnabled_AddsInCorrectOrder()
+    {
+        settingsProvider.IsThreadIdEnabled.Returns(true);
+        settingsProvider.IsVerboseEnabled.Returns(true);
+        contextManager.GetFormattedContextOrNull(default).Returns("context");
+        contextManager.GetFormattedVerboseContextOrNull(default).Returns("verbose context");
+
+        testSubject.WriteLine("msg sent");
+
+        writer.Received().WriteLine($"[ThreadId {Thread.CurrentThread.ManagedThreadId}] [context] [verbose context] msg sent");
+    }
+
+    [TestMethod]
+    public void WriteLineFormatted_AllContextsEnabled_AddsInCorrectOrder()
+    {
+        settingsProvider.IsThreadIdEnabled.Returns(true);
+        settingsProvider.IsVerboseEnabled.Returns(true);
+        contextManager.GetFormattedContextOrNull(default).Returns("context");
+        contextManager.GetFormattedVerboseContextOrNull(default).Returns("verbose context");
+
+        testSubject.WriteLine("msg {0}", "sent");
+
+        writer.Received().WriteLine($"[ThreadId {Thread.CurrentThread.ManagedThreadId}] [context] [verbose context] msg sent");
+    }
+
+    [TestMethod]
+    public void WriteLineFormattedWithContext_AllContextsEnabled_AddsInCorrectOrder()
+    {
+        var messageLevelContext = new MessageLevelContext
+        {
+            Context = Substitute.For<IReadOnlyCollection<string>>(),
+            VerboseContext = Substitute.For<IReadOnlyCollection<string>>()
+        };
+        settingsProvider.IsThreadIdEnabled.Returns(true);
+        settingsProvider.IsVerboseEnabled.Returns(true);
+        contextManager.GetFormattedContextOrNull(messageLevelContext).Returns("context with message level");
+        contextManager.GetFormattedVerboseContextOrNull(messageLevelContext).Returns("verbose context with message level");
+
+        testSubject.WriteLine(messageLevelContext, "msg {0}", "sent");
+
+        writer.Received().WriteLine($"[ThreadId {Thread.CurrentThread.ManagedThreadId}] [context with message level] [verbose context with message level] msg sent");
+    }
+}
diff --git a/src/Core.UnitTests/Logging/LoggerContextManagerTests.cs b/src/Core.UnitTests/Logging/LoggerContextManagerTests.cs
new file mode 100644
index 0000000000..a691e56054
--- /dev/null
+++ b/src/Core.UnitTests/Logging/LoggerContextManagerTests.cs
@@ -0,0 +1,172 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+using SonarLint.VisualStudio.Core.Logging;
+using SonarLint.VisualStudio.TestInfrastructure;
+
+namespace SonarLint.VisualStudio.Core.UnitTests.Logging;
+
+[TestClass]
+public class LoggerContextManagerTests
+{
+    private LoggerContextManager testSubject;
+
+    [TestInitialize]
+    public void TestInitialize()
+    {
+        testSubject = new LoggerContextManager();
+    }
+
+    [TestMethod]
+    public void MefCtor_CheckIsExported() =>
+        MefTestHelpers.CheckTypeCanBeImported<LoggerContextManager, ILoggerContextManager>();
+
+    [TestMethod]
+    public void MefCtor_CheckIsTransient() =>
+        MefTestHelpers.CheckIsNonSharedMefComponent<LoggerContextManager>();
+
+    [TestMethod]
+    public void DefaultCtor_EmptyContext()
+    {
+        testSubject.GetFormattedContextOrNull(default).Should().BeNull();
+        testSubject.GetFormattedVerboseContextOrNull(default).Should().BeNull();
+    }
+
+    [TestMethod]
+    public void Augmented_Immutable()
+    {
+        var contextualized= testSubject.CreateAugmentedContext(["a"]);
+        var verboseContextualized = testSubject.CreateAugmentedVerboseContext(["b"]);
+        var doubleContextualized = testSubject.CreateAugmentedContext(["c"]).CreateAugmentedVerboseContext(["d"]);
+
+        testSubject.GetFormattedContextOrNull(default).Should().BeNull();
+        testSubject.GetFormattedVerboseContextOrNull(default).Should().BeNull();
+        contextualized.GetFormattedContextOrNull(default).Should().Be("a");
+        contextualized.GetFormattedVerboseContextOrNull(default).Should().BeNull();
+        verboseContextualized.GetFormattedContextOrNull(default).Should().BeNull();
+        verboseContextualized.GetFormattedVerboseContextOrNull(default).Should().Be("b");
+        doubleContextualized.GetFormattedContextOrNull(default).Should().Be("c");
+        doubleContextualized.GetFormattedVerboseContextOrNull(default).Should().Be("d");
+    }
+
+    [TestMethod]
+    public void Augmented_MultipleAtOnce_Combines() =>
+        testSubject
+            .CreateAugmentedContext(["a", "b"])
+            .GetFormattedContextOrNull(default).Should().Be("a > b");
+
+    [TestMethod]
+    public void Augmented_MultipleInSequence_Combines() =>
+        testSubject
+            .CreateAugmentedContext(["a"])
+            .CreateAugmentedContext(["b"])
+            .GetFormattedContextOrNull(default).Should().Be("a > b");
+
+    [TestMethod]
+    public void Augmented_AtOnceAndInSequence_CombinesInCorrectOrder() =>
+        testSubject
+            .CreateAugmentedContext(["a"])
+            .CreateAugmentedContext(["b", "c"])
+            .CreateAugmentedContext(["d"])
+            .GetFormattedContextOrNull(default).Should().Be("a > b > c > d");
+
+    [TestMethod]
+    public void AugmentedVerbose_MultipleAtOnce_Combines() =>
+        testSubject
+            .CreateAugmentedVerboseContext(["a", "b"])
+            .GetFormattedVerboseContextOrNull(default).Should().Be("a > b");
+
+    [TestMethod]
+    public void AugmentedVerbose_MultipleInSequence_Combines() =>
+        testSubject
+            .CreateAugmentedVerboseContext(["a"])
+            .CreateAugmentedVerboseContext(["b"])
+            .GetFormattedVerboseContextOrNull(default).Should().Be("a > b");
+
+    [TestMethod]
+    public void AugmentedVerbose_AtOnceAndInSequence_CombinesInCorrectOrder() =>
+        testSubject
+            .CreateAugmentedVerboseContext(["a"])
+            .CreateAugmentedVerboseContext(["b", "c"])
+            .CreateAugmentedVerboseContext(["d"])
+            .GetFormattedVerboseContextOrNull(default).Should().Be("a > b > c > d");
+
+    [TestMethod]
+    public void GetFormattedContextOrNull_NoContext_ReturnsNull() =>
+        testSubject.GetFormattedContextOrNull(new MessageLevelContext{Context = null, VerboseContext = ["c", "d"]}).Should().BeNull();
+
+    [TestMethod]
+    public void GetFormattedContextOrNull_NoBaseContext_ReturnsMessageLevelContextOnly() =>
+        testSubject.GetFormattedContextOrNull(new MessageLevelContext{Context = ["a", "b"], VerboseContext = ["c", "d"]}).Should().Be("a > b");
+
+    [TestMethod]
+    public void GetFormattedContextOrNull_NullAndEmptyItems_ReturnsNonNullMessageLevelContextOnly() =>
+        testSubject.GetFormattedContextOrNull(new MessageLevelContext{Context = ["a", null, "", "b"], VerboseContext = ["c", "d"]}).Should().Be("a > b");
+
+    [TestMethod]
+    public void GetFormattedContextOrNull_NullAndEmptyItemsOnly_ReturnsNull() =>
+        testSubject.GetFormattedContextOrNull(new MessageLevelContext{Context = [null, ""], VerboseContext = ["c", "d"]}).Should().BeNull();
+
+    [TestMethod]
+    public void GetFormattedContextOrNull_MessageLevelContextNotCached()
+    {
+        testSubject.GetFormattedContextOrNull(new MessageLevelContext{Context = ["a", "b"], VerboseContext = ["c", "d"]}).Should().Be("a > b");
+        testSubject.GetFormattedContextOrNull(new MessageLevelContext{Context = ["a2", "b2"], VerboseContext = ["c", "d"]}).Should().Be("a2 > b2");
+    }
+
+    [TestMethod]
+    public void GetFormattedContextOrNull_NoMessageLevelContext_ReturnsBaseContextOnly() =>
+        testSubject.CreateAugmentedContext(["x", "y"]).GetFormattedContextOrNull(new MessageLevelContext{Context = null, VerboseContext = ["c", "d"]}).Should().Be("x > y");
+
+    [TestMethod]
+    public void GetFormattedContextOrNull_BothContexts_CombinesInOrder() =>
+        testSubject.CreateAugmentedContext(["x", "y"]).GetFormattedContextOrNull(new MessageLevelContext{Context = ["a", "b"], VerboseContext = ["c", "d"]}).Should().Be("x > y > a > b");
+
+    [TestMethod]
+    public void GetFormattedVerboseContextOrNull_NoContext_ReturnsNull() =>
+        testSubject.GetFormattedVerboseContextOrNull(new MessageLevelContext{Context = ["a", "b"], VerboseContext = null}).Should().BeNull();
+
+    [TestMethod]
+    public void GetFormattedVerboseContextOrNull_NoBaseContext_ReturnsMessageLevelContextOnly() =>
+        testSubject.GetFormattedVerboseContextOrNull(new MessageLevelContext{Context = ["a", "b"], VerboseContext = ["c", "d"]}).Should().Be("c > d");
+
+    [TestMethod]
+    public void GetFormattedVerboseContextOrNull_NullAndEmptyItems_ReturnsNonNullMessageLevelContextOnly() =>
+        testSubject.GetFormattedVerboseContextOrNull(new MessageLevelContext{Context = ["a", "b"], VerboseContext = ["c", null, "", "d"]}).Should().Be("c > d");
+
+    [TestMethod]
+    public void GetFormattedVerboseContextOrNull_NullAndEmptyItemsOnly_ReturnsNull() =>
+        testSubject.GetFormattedVerboseContextOrNull(new MessageLevelContext{Context = ["a", "b"], VerboseContext = [null, ""]}).Should().BeNull();
+
+    [TestMethod]
+    public void GetFormattedVerboseContextOrNull_MessageLevelContextNotCached()
+    {
+        testSubject.GetFormattedVerboseContextOrNull(new MessageLevelContext { Context = ["a", "b"], VerboseContext = ["c", "d"] }).Should().Be("c > d");
+        testSubject.GetFormattedVerboseContextOrNull(new MessageLevelContext { Context = ["a", "b"], VerboseContext = ["c2", "d2"] }).Should().Be("c2 > d2");
+    }
+
+    [TestMethod]
+    public void GetFormattedVerboseContextOrNull_NoMessageLevelContext_ReturnsBaseContextOnly() =>
+        testSubject.CreateAugmentedVerboseContext(["v", "w"]).GetFormattedVerboseContextOrNull(new MessageLevelContext{Context = ["a", "b"], VerboseContext = null}).Should().Be("v > w");
+
+    [TestMethod]
+    public void GetFormattedVerboseContextOrNull_BothContexts_CombinesInOrder() =>
+        testSubject.CreateAugmentedVerboseContext(["v", "w"]).GetFormattedVerboseContextOrNull(new MessageLevelContext{Context = ["a", "b"], VerboseContext = ["c", "d"]}).Should().Be("v > w > c > d");
+}
diff --git a/src/Core.UnitTests/Logging/LoggerFactoryTests.cs b/src/Core.UnitTests/Logging/LoggerFactoryTests.cs
new file mode 100644
index 0000000000..a150547d7e
--- /dev/null
+++ b/src/Core.UnitTests/Logging/LoggerFactoryTests.cs
@@ -0,0 +1,63 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+using SonarLint.VisualStudio.Core.Logging;
+using SonarLint.VisualStudio.TestInfrastructure;
+
+namespace SonarLint.VisualStudio.Core.UnitTests.Logging;
+
+[TestClass]
+public class LoggerFactoryTests
+{
+    private ILoggerContextManager logContextManager;
+    private ILoggerWriter logWriter;
+    private ILoggerSettingsProvider logVerbosityIndicator;
+    private LoggerFactory testSubject;
+
+    [TestInitialize]
+    public void TestInitialize()
+    {
+        logContextManager = Substitute.For<ILoggerContextManager>();
+        logWriter = Substitute.For<ILoggerWriter>();
+        logVerbosityIndicator = Substitute.For<ILoggerSettingsProvider>();
+        testSubject = new LoggerFactory(logContextManager);
+    }
+
+    [TestMethod]
+    public void MefCtor_CheckIsExported() =>
+        MefTestHelpers.CheckTypeCanBeImported<LoggerFactory, ILoggerFactory>(
+            MefTestHelpers.CreateExport<ILoggerContextManager>());
+
+    [TestMethod]
+    public void MefCtor_CheckIsSingleton() =>
+        MefTestHelpers.CheckIsNonSharedMefComponent<LoggerFactory>();
+
+    [TestMethod]
+    public void Create_ReturnsLoggerConfiguredWithCorrectDependencies()
+    {
+        var logger = testSubject.Create(logWriter, logVerbosityIndicator);
+
+        logger.Should().NotBeNull();
+        logger.WriteLine("msg");
+        logContextManager.Received().GetFormattedContextOrNull(default);
+        _ = logVerbosityIndicator.Received().IsVerboseEnabled;
+        logWriter.Received().WriteLine(Arg.Is<string>(x => x.Contains("msg")));
+    }
+}
diff --git a/src/Core.UnitTests/Logging/StringBuilderLoggingExtensionsTests.cs b/src/Core.UnitTests/Logging/StringBuilderLoggingExtensionsTests.cs
new file mode 100644
index 0000000000..e4322b546e
--- /dev/null
+++ b/src/Core.UnitTests/Logging/StringBuilderLoggingExtensionsTests.cs
@@ -0,0 +1,60 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+using System.Text;
+using SonarLint.VisualStudio.Core.Logging;
+
+namespace SonarLint.VisualStudio.Core.UnitTests.Logging;
+
+[TestClass]
+public class StringBuilderLoggingExtensionsTests
+{
+    [DataTestMethod]
+    [DataRow(null, "", "[] ")]
+    [DataRow(null, null, "[] ")]
+    [DataRow(null, "a", "[a] ")]
+    [DataRow("", "a", "[a] ")]
+    [DataRow("", "a {0}", "[a {0}] ")]
+    [DataRow("abc", "def", "abc[def] ")]
+    [DataRow("abc ", "def", "abc [def] ")]
+    public void AppendProperty_AddsPlainPropertyValueToTheEnd(string original, string property, string expected) =>
+        new StringBuilder(original).AppendProperty(property).ToString().Should().Be(expected);
+
+    [DataTestMethod]
+    [DataRow(null, "", "[] ")]
+    [DataRow(null, "a", "[a] ")]
+    [DataRow("", "a", "[a] ")]
+    [DataRow("abc", "def", "abc[def] ")]
+    [DataRow("abc ", "def", "abc [def] ")]
+    public void AppendPropertyFormat_NonFormattedProperty_AddsPlainValueToTheEnd(string original, string property, string expected) =>
+        new StringBuilder(original).AppendPropertyFormat(property).ToString().Should().Be(expected);
+
+    [TestMethod]
+    public void AppendPropertyFormat_FormattedString_CorrectlyAppliesStringFormat() =>
+        new StringBuilder().AppendPropertyFormat("for{0}ted", "mat").ToString().Should().Be("[formatted] ");
+
+    [TestMethod]
+    public void AppendPropertyFormat_IncorrectNumberOfParameters_Throws()
+    {
+        var act =()=> new StringBuilder().AppendPropertyFormat("for{0}t{1}", "mat");
+
+        act.Should().Throw<FormatException>();
+    }
+}
diff --git a/src/Core/Logging/ILogger.cs b/src/Core/Logging/ILogger.cs
new file mode 100644
index 0000000000..16d2231f00
--- /dev/null
+++ b/src/Core/Logging/ILogger.cs
@@ -0,0 +1,47 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+namespace SonarLint.VisualStudio.Core;
+
+public interface ILogger
+{
+    /// <summary>
+    /// Logs a message and appends a new line.
+    /// </summary>
+    void WriteLine(string message);
+    void WriteLine(string messageFormat, params object[] args);
+    void WriteLine(MessageLevelContext context, string messageFormat, params object[] args);
+
+    /// <summary>
+    /// Logs a message and appends a new line if logging is set to verbose. Otherwise does nothing.
+    /// </summary>
+    void LogVerbose(string messageFormat, params object[] args);
+    void LogVerbose(MessageLevelContext context, string messageFormat, params object[] args);
+
+    ILogger ForContext(params string[] context);
+
+    ILogger ForVerboseContext(params string[] context);
+}
+
+public readonly struct MessageLevelContext
+{
+    public IReadOnlyCollection<string> Context { get; init; }
+    public IReadOnlyCollection<string> VerboseContext { get; init; }
+}
diff --git a/src/Core/Logging/ILoggerContextManager.cs b/src/Core/Logging/ILoggerContextManager.cs
new file mode 100644
index 0000000000..93c35de52a
--- /dev/null
+++ b/src/Core/Logging/ILoggerContextManager.cs
@@ -0,0 +1,47 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+namespace SonarLint.VisualStudio.Core.Logging;
+
+public interface ILoggerContextManager
+{
+    /// <summary>
+    /// Returns a combination of logger-level context and <see cref="messageLevelContext"/>.
+    /// If one of the contexts is null, only returns the other.
+    /// If both contexts are null, returns null.
+    /// </summary>
+    public string GetFormattedContextOrNull(MessageLevelContext messageLevelContext);
+    /// <summary>
+    /// Returns a combination of logger-level verbose context and verbose <see cref="messageLevelContext"/>.
+    /// If one of the contexts is null, only returns the other.
+    /// If both contexts are null, returns null.
+    /// </summary>
+    public string GetFormattedVerboseContextOrNull(MessageLevelContext messageLevelContext);
+
+    /// <summary>
+    /// Creates a new instance of logger-level context with appended <see cref="additionalContexts"/>
+    /// </summary>
+    ILoggerContextManager CreateAugmentedContext(IEnumerable<string> additionalContexts);
+
+    /// <summary>
+    /// Creates a new instance of logger-level context with appended <see cref="additionalVerboseContexts"/>
+    /// </summary>
+    ILoggerContextManager CreateAugmentedVerboseContext(IEnumerable<string> additionalVerboseContexts);
+}
diff --git a/src/Core/ILogger.cs b/src/Core/Logging/ILoggerFactory.cs
similarity index 63%
rename from src/Core/ILogger.cs
rename to src/Core/Logging/ILoggerFactory.cs
index 0fbfaae299..7e2f857a61 100644
--- a/src/Core/ILogger.cs
+++ b/src/Core/Logging/ILoggerFactory.cs
@@ -18,19 +18,9 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-namespace SonarLint.VisualStudio.Core
-{
-    public interface ILogger
-    {
-        /// <summary>
-        /// Logs a message and appends a new line.
-        /// </summary>
-        void WriteLine(string message);
-        void WriteLine(string messageFormat, params object[] args);
+namespace SonarLint.VisualStudio.Core.Logging;
 
-        /// <summary>
-        /// Logs a message and appends a new line if logging is set to verbose. Otherwise does nothing.
-        /// </summary>
-        void LogVerbose(string messageFormat, params object[] args);
-    }
+public interface ILoggerFactory
+{
+    ILogger Create(ILoggerWriter writer, ILoggerSettingsProvider settingsProvider);
 }
diff --git a/src/Core/Logging/ILoggerSettingsProvider.cs b/src/Core/Logging/ILoggerSettingsProvider.cs
new file mode 100644
index 0000000000..65ba4d8c55
--- /dev/null
+++ b/src/Core/Logging/ILoggerSettingsProvider.cs
@@ -0,0 +1,27 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+namespace SonarLint.VisualStudio.Core.Logging;
+
+public interface ILoggerSettingsProvider
+{
+    bool IsVerboseEnabled { get; }
+    bool IsThreadIdEnabled { get; }
+}
diff --git a/src/Core/Logging/ILoggerWriter.cs b/src/Core/Logging/ILoggerWriter.cs
new file mode 100644
index 0000000000..af4b19f1ec
--- /dev/null
+++ b/src/Core/Logging/ILoggerWriter.cs
@@ -0,0 +1,26 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+namespace SonarLint.VisualStudio.Core.Logging;
+
+public interface ILoggerWriter
+{
+    void WriteLine(string message);
+}
diff --git a/src/Core/Logging/LoggerBase.cs b/src/Core/Logging/LoggerBase.cs
new file mode 100644
index 0000000000..51fa2c6f27
--- /dev/null
+++ b/src/Core/Logging/LoggerBase.cs
@@ -0,0 +1,95 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+using System.Globalization;
+using System.Text;
+
+namespace SonarLint.VisualStudio.Core.Logging;
+
+internal class LoggerBase(
+    ILoggerContextManager contextManager,
+    ILoggerWriter writer,
+    ILoggerSettingsProvider settingsProvider) : ILogger
+{
+
+    public ILogger ForContext(params string[] context) =>
+        new LoggerBase(
+            contextManager.CreateAugmentedContext(context),
+            writer,
+            settingsProvider);
+
+    public ILogger ForVerboseContext(params string[] context) =>
+        new LoggerBase(
+            contextManager.CreateAugmentedVerboseContext(context),
+            writer,
+            settingsProvider);
+
+    public void WriteLine(string message) =>
+        writer.WriteLine(CreateStandardLogPrefix().Append(message).ToString());
+
+    public void WriteLine(string messageFormat, params object[] args) =>
+        WriteLine(default, messageFormat, args);
+
+    public void WriteLine(MessageLevelContext context, string messageFormat, params object[] args) =>
+        writer.WriteLine(CreateStandardLogPrefix(context).AppendFormat(CultureInfo.CurrentCulture, messageFormat, args).ToString());
+
+    public void LogVerbose(string messageFormat, params object[] args) =>
+        LogVerbose(default, messageFormat, args);
+
+    public void LogVerbose(MessageLevelContext context, string messageFormat, params object[] args)
+    {
+        if (!settingsProvider.IsVerboseEnabled)
+        {
+            return;
+        }
+
+        var debugLogPrefix = CreateDebugLogPrefix(context);
+        var logLine = args.Length > 0
+            ? debugLogPrefix.AppendFormat(CultureInfo.CurrentCulture, messageFormat, args)
+            : debugLogPrefix.Append(messageFormat);
+        writer.WriteLine(logLine.ToString());
+    }
+
+    private StringBuilder CreateStandardLogPrefix(MessageLevelContext context = default) =>
+        AddStandardProperties(new StringBuilder(), context);
+
+    private StringBuilder CreateDebugLogPrefix(MessageLevelContext context = default) =>
+        AddStandardProperties(new StringBuilder().AppendProperty("DEBUG"), context);
+
+    private StringBuilder AddStandardProperties(StringBuilder builder, MessageLevelContext context)
+    {
+        if (settingsProvider.IsThreadIdEnabled)
+        {
+            builder.AppendPropertyFormat("ThreadId {0}", Thread.CurrentThread.ManagedThreadId);
+        }
+
+        if (contextManager.GetFormattedContextOrNull(context) is var formatedContext && !string.IsNullOrEmpty(formatedContext))
+        {
+            builder.AppendProperty(formatedContext);
+        }
+
+        if (settingsProvider.IsVerboseEnabled && contextManager.GetFormattedVerboseContextOrNull(context) is var formattedVerboseContext && !string.IsNullOrEmpty(formattedVerboseContext))
+        {
+            builder.AppendProperty(formattedVerboseContext);
+        }
+
+        return builder;
+    }
+}
diff --git a/src/Core/Logging/LoggerContextManager.cs b/src/Core/Logging/LoggerContextManager.cs
new file mode 100644
index 0000000000..42d2553a53
--- /dev/null
+++ b/src/Core/Logging/LoggerContextManager.cs
@@ -0,0 +1,80 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+
+using System.Collections.Immutable;
+using System.ComponentModel.Composition;
+
+namespace SonarLint.VisualStudio.Core.Logging;
+
+[Export(typeof(ILoggerContextManager))]
+[PartCreationPolicy(CreationPolicy.NonShared)]
+internal class LoggerContextManager : ILoggerContextManager
+{
+    private const string Separator = " > ";
+    private readonly ImmutableList<string> contexts;
+    private readonly ImmutableList<string> verboseContexts;
+    private readonly Lazy<string> formatedContext;
+    private readonly Lazy<string> formatedVerboseContext;
+
+    [ImportingConstructor]
+    public LoggerContextManager() : this(ImmutableList<string>.Empty, ImmutableList<string>.Empty) { }
+
+    private LoggerContextManager(ImmutableList<string> contexts, ImmutableList<string> verboseContexts)
+    {
+        this.contexts = contexts;
+        this.verboseContexts = verboseContexts;
+        formatedContext = new Lazy<string>(() => MergeContextsIntoSingleProperty(contexts), LazyThreadSafetyMode.PublicationOnly);
+        formatedVerboseContext = new Lazy<string>(() => MergeContextsIntoSingleProperty(verboseContexts), LazyThreadSafetyMode.PublicationOnly);
+    }
+
+    public ILoggerContextManager CreateAugmentedContext(IEnumerable<string> additionalContexts) => new LoggerContextManager(contexts.AddRange(FilterContexts(additionalContexts)), verboseContexts);
+
+    public ILoggerContextManager CreateAugmentedVerboseContext(IEnumerable<string> additionalVerboseContexts) => new LoggerContextManager(contexts, verboseContexts.AddRange(FilterContexts(additionalVerboseContexts)));
+
+    public string GetFormattedContextOrNull(MessageLevelContext messageLevelContext) =>
+        GetContextInternal(formatedContext.Value, messageLevelContext.Context);
+    public string GetFormattedVerboseContextOrNull(MessageLevelContext messageLevelContext) =>
+        GetContextInternal(formatedVerboseContext.Value, messageLevelContext.VerboseContext);
+
+    private static IEnumerable<string> FilterContexts(IEnumerable<string> contexts) => contexts.Where(context => !string.IsNullOrEmpty(context));
+
+    private static string GetContextInternal(string baseContext, IReadOnlyCollection<string> messageLevelContext)
+    {
+        if (messageLevelContext is not { Count: > 0 })
+        {
+            return baseContext;
+        }
+
+        IEnumerable<string> resultingContext = FilterContexts(messageLevelContext);
+        if (baseContext != null)
+        {
+            resultingContext = resultingContext.Prepend(baseContext);
+        }
+
+        return MergeContextsIntoSingleProperty(resultingContext);
+    }
+
+    private static string MergeContextsIntoSingleProperty(IEnumerable<string> contexts)
+    {
+        var joinResult = string.Join(Separator, contexts);
+        return string.IsNullOrEmpty(joinResult) ? null : joinResult;
+    }
+}
diff --git a/src/Core/Logging/LoggerFactory.cs b/src/Core/Logging/LoggerFactory.cs
new file mode 100644
index 0000000000..d280442b4d
--- /dev/null
+++ b/src/Core/Logging/LoggerFactory.cs
@@ -0,0 +1,33 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+using System.ComponentModel.Composition;
+
+namespace SonarLint.VisualStudio.Core.Logging;
+
+[Export(typeof(ILoggerFactory))]
+[PartCreationPolicy(CreationPolicy.NonShared)]
+[method: ImportingConstructor]
+public class LoggerFactory(ILoggerContextManager loggerContextManager) : ILoggerFactory
+{
+    public static ILoggerFactory Default { get; } = new LoggerFactory(new LoggerContextManager());
+    public ILogger Create(ILoggerWriter writer, ILoggerSettingsProvider settingsProvider) =>
+        new LoggerBase(loggerContextManager, writer, settingsProvider);
+}
diff --git a/src/Roslyn.Suppressions/Roslyn.Suppressions/Logger.cs b/src/Core/Logging/StringBuilderLoggingExtensions.cs
similarity index 61%
rename from src/Roslyn.Suppressions/Roslyn.Suppressions/Logger.cs
rename to src/Core/Logging/StringBuilderLoggingExtensions.cs
index 7632e0f9f5..7c94eae543 100644
--- a/src/Roslyn.Suppressions/Roslyn.Suppressions/Logger.cs
+++ b/src/Core/Logging/StringBuilderLoggingExtensions.cs
@@ -18,27 +18,15 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-using System.Diagnostics;
-using SonarLint.VisualStudio.Core;
+using System.Text;
 
-namespace SonarLint.VisualStudio.Roslyn.Suppressions
-{
-    internal class Logger : ILogger
-    {
-        public void WriteLine(string message)
-        {
-            Debug.WriteLine(message);
-        }
+namespace SonarLint.VisualStudio.Core.Logging;
 
-        public void WriteLine(string messageFormat, params object[] args)
-        {
-            Debug.WriteLine(messageFormat, args);
-        }
+internal static class StringBuilderLoggingExtensions
+{
+    public static StringBuilder AppendProperty(this StringBuilder builder, string property) =>
+        builder.Append('[').Append(property).Append(']').Append(' ');
 
-        public void LogVerbose(string messageFormat, params object[] args)
-        {
-            WriteLine(messageFormat, args);
-        }
-    }
+    public static StringBuilder AppendPropertyFormat(this StringBuilder builder, string property, params object[] args) =>
+        builder.Append('[').AppendFormat(property, args).Append(']').Append(' ');
 }
-
diff --git a/src/Integration.UnitTests/Helpers/SonarLintOutputLoggerFactory.cs b/src/Integration.UnitTests/Helpers/SonarLintOutputLoggerFactory.cs
new file mode 100644
index 0000000000..dd7c5e5194
--- /dev/null
+++ b/src/Integration.UnitTests/Helpers/SonarLintOutputLoggerFactory.cs
@@ -0,0 +1,51 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+using SonarLint.VisualStudio.Core.Logging;
+using SonarLint.VisualStudio.Integration.Helpers;
+
+namespace SonarLint.VisualStudio.Integration.UnitTests.Helpers
+{
+    [TestClass]
+    public class SonarLintOutputLoggerFactory
+    {
+        // the normal check for export does not work here because this is a special Property export instead of the normal Class export
+        // [TestMethod]
+        // public void MefCtor_CheckIsExported()
+        // {
+        //     MefTestHelpers.CheckTypeCanBeImported<SonarLintOutputLoggerFactory, ILogger>(
+        //         MefTestHelpers.CreateExport<ILoggerFactory>(),
+        //         MefTestHelpers.CreateExport<SVsServiceProvider>(),
+        //         MefTestHelpers.CreateExport<ISonarLintSettings>());
+        // }
+
+        [TestMethod]
+        public void Ctor_CreatesLoggerWithExpectedParameters()
+        {
+            var loggerFactory = Substitute.For<ILoggerFactory>();
+
+            var testSubject = new Integration.Helpers.SonarLintOutputLoggerFactory(loggerFactory, Substitute.For<IServiceProvider>(), Substitute.For<ISonarLintSettings>());
+
+            testSubject.Instance.Should().NotBeNull();
+            loggerFactory.Create(Arg.Any<SonarLintOutputWindowLoggerWriter>(), Arg.Any<SonarLintSettingsLoggerSettingsProvider>());
+        }
+
+    }
+}
diff --git a/src/Integration.UnitTests/Helpers/SonarLintOutputTests.cs b/src/Integration.UnitTests/Helpers/SonarLintOutputTests.cs
deleted file mode 100644
index 3002d7a252..0000000000
--- a/src/Integration.UnitTests/Helpers/SonarLintOutputTests.cs
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * SonarLint for Visual Studio
- * Copyright (C) 2016-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-using System;
-using Microsoft.VisualStudio.Shell;
-using Microsoft.VisualStudio.Shell.Interop;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Moq;
-using SonarLint.VisualStudio.Core;
-using SonarLint.VisualStudio.TestInfrastructure;
-
-namespace SonarLint.VisualStudio.Integration.UnitTests.Helpers
-{
-    [TestClass]
-    public class SonarLintOutputTests
-    {
-        [TestMethod]
-        public void MefCtor_CheckIsExported()
-        {
-            MefTestHelpers.CheckTypeCanBeImported<SonarLintOutputLogger, ILogger>(
-                MefTestHelpers.CreateExport<SVsServiceProvider>(),
-                MefTestHelpers.CreateExport<ISonarLintSettings>());
-        }
-
-        [TestMethod]
-        public void Write_OutputsToWindow()
-        {
-            // Arrange
-            var windowMock = new ConfigurableVsOutputWindow();
-            var sonarLintSettings = CreateSonarLintSettings(DaemonLogLevel.Info);
-            var serviceProviderMock = CreateConfiguredServiceProvider(windowMock);
-
-            var testSubject = CreateTestSubject(serviceProviderMock, sonarLintSettings);
-
-            // Act
-            testSubject.WriteLine("123");
-            testSubject.WriteLine("abc");
-
-            // Assert
-            var outputPane = windowMock.AssertPaneExists(VsShellUtils.SonarLintOutputPaneGuid);
-            outputPane.AssertOutputStrings("123", "abc");
-        }
-
-        [TestMethod]
-        [DataRow(DaemonLogLevel.Info)]
-        [DataRow(DaemonLogLevel.Minimal)]
-        [DataRow(DaemonLogLevel.Verbose)]
-        public void LogVerbose_OnlyOutputsToWindowIfLogLevelIsVerbose(DaemonLogLevel logLevel)
-        {
-            // Arrange
-            var windowMock = new ConfigurableVsOutputWindow();
-            var serviceProviderMock = CreateConfiguredServiceProvider(windowMock);
-
-            var sonarLintSettings = CreateSonarLintSettings(logLevel);
-
-            var testSubject = CreateTestSubject(serviceProviderMock, sonarLintSettings);
-
-            testSubject.WriteLine("create window pane");
-            var outputPane = windowMock.AssertPaneExists(VsShellUtils.SonarLintOutputPaneGuid);
-            outputPane.Reset();
-
-            // Act
-            testSubject.LogVerbose("123 {0} {1}", "param 1", 2);
-            testSubject.LogVerbose("{0} {1} abc", 1, "param 2");
-
-            // Assert
-            if (logLevel == DaemonLogLevel.Verbose)
-            {
-                var currentThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId;
-
-                outputPane.AssertOutputStrings(
-                    $"[ThreadId {currentThreadId}] [DEBUG] 123 param 1 2",
-                    $"[ThreadId {currentThreadId}] [DEBUG] 1 param 2 abc");
-                outputPane.AssertOutputStrings(2);
-            }
-            else
-            {
-                outputPane.AssertOutputStrings(0);
-            }
-        }
-
-        [TestMethod]
-        [DataRow(DaemonLogLevel.Info)]
-        [DataRow(DaemonLogLevel.Minimal)]
-        [DataRow(DaemonLogLevel.Verbose)]
-        public void WriteLine_ThreadIdIsAddedIfLogLevelIsVerbose(DaemonLogLevel logLevel)
-        {
-            // Arrange
-            var windowMock = new ConfigurableVsOutputWindow();
-            var serviceProviderMock = CreateConfiguredServiceProvider(windowMock);
-
-            var sonarLintSettings = CreateSonarLintSettings(logLevel);
-
-            var testSubject = CreateTestSubject(serviceProviderMock, sonarLintSettings);
-
-            testSubject.WriteLine("create window pane");
-            var outputPane = windowMock.AssertPaneExists(VsShellUtils.SonarLintOutputPaneGuid);
-            outputPane.Reset();
-
-            // Act
-            testSubject.WriteLine("writeline, no params");
-            testSubject.WriteLine("writeline, with params: {0}", "zzz");
-
-            outputPane.AssertOutputStrings(2);
-
-            string expectedPrefix;
-            if (logLevel == DaemonLogLevel.Verbose)
-            {
-                var currentThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId;
-                expectedPrefix = $"[ThreadId {currentThreadId}] ";
-            }
-            else
-            {
-                expectedPrefix = string.Empty;
-            }
-
-            outputPane.AssertOutputStrings(
-                $"{expectedPrefix}writeline, no params",
-                $"{expectedPrefix}writeline, with params: zzz");
-        }
-
-        private static IServiceProvider CreateConfiguredServiceProvider(IVsOutputWindow outputWindow)
-        {
-            var serviceProvider = new ConfigurableServiceProvider(assertOnUnexpectedServiceRequest: true);
-            serviceProvider.RegisterService(typeof(SVsOutputWindow), outputWindow);
-            return serviceProvider;
-        }
-
-        private static ISonarLintSettings CreateSonarLintSettings(DaemonLogLevel logLevel)
-        {
-            var sonarLintSettings = new Mock<ISonarLintSettings>();
-            sonarLintSettings.Setup(x => x.DaemonLogLevel).Returns(logLevel);
-            return sonarLintSettings.Object;
-        }
-
-        private static SonarLintOutputLogger CreateTestSubject(IServiceProvider serviceProvider,
-            ISonarLintSettings sonarLintSettings = null)
-        {
-            sonarLintSettings ??= Mock.Of<ISonarLintSettings>();
-            return new SonarLintOutputLogger(serviceProvider, sonarLintSettings);
-        }
-    }
-}
diff --git a/src/Integration.UnitTests/Helpers/SonarLintOutputWindowLoggerWriterTests.cs b/src/Integration.UnitTests/Helpers/SonarLintOutputWindowLoggerWriterTests.cs
new file mode 100644
index 0000000000..fc72384941
--- /dev/null
+++ b/src/Integration.UnitTests/Helpers/SonarLintOutputWindowLoggerWriterTests.cs
@@ -0,0 +1,86 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+using Microsoft.VisualStudio.Shell.Interop;
+using SonarLint.VisualStudio.Integration.Helpers;
+using SonarLint.VisualStudio.TestInfrastructure;
+
+namespace SonarLint.VisualStudio.Integration.UnitTests.Helpers;
+
+[TestClass]
+public class SonarLintOutputWindowLoggerWriterTests
+{
+    private ConfigurableVsOutputWindow windowMock;
+    private IServiceProvider serviceProviderMock;
+    private SonarLintOutputWindowLoggerWriter testSubject;
+
+    [TestInitialize]
+    public void TestInitialize()
+    {
+        windowMock = new ConfigurableVsOutputWindow();
+        serviceProviderMock = CreateConfiguredServiceProvider(windowMock);
+        testSubject = new SonarLintOutputWindowLoggerWriter(serviceProviderMock);
+    }
+
+    [TestMethod]
+    public void WriteLine_Empty_PutsEmptyLineToCorrectPane()
+    {
+        var message = string.Empty;
+
+        testSubject.WriteLine(message);
+
+        var outputPane = windowMock.AssertPaneExists(VsShellUtils.SonarLintOutputPaneGuid);
+        outputPane.AssertOutputStrings(message);
+    }
+
+    [TestMethod]
+    public void WriteLine_Simple_PutsSingleLineToCorrectPane()
+    {
+        var message = "ABOBA";
+
+        testSubject.WriteLine(message);
+
+        var outputPane = windowMock.AssertPaneExists(VsShellUtils.SonarLintOutputPaneGuid);
+        outputPane.AssertOutputStrings(message);
+    }
+
+    [TestMethod]
+    public void WriteLine_Multiline_PutsMultiLineToCorrectPane()
+    {
+        var message =
+            """
+            A
+            B
+            OBA
+            """;
+
+        testSubject.WriteLine(message);
+
+        var outputPane = windowMock.AssertPaneExists(VsShellUtils.SonarLintOutputPaneGuid);
+        outputPane.AssertOutputStrings(message);
+    }
+
+    private static IServiceProvider CreateConfiguredServiceProvider(IVsOutputWindow outputWindow)
+    {
+        var serviceProvider = new ConfigurableServiceProvider(assertOnUnexpectedServiceRequest: true);
+        serviceProvider.RegisterService(typeof(SVsOutputWindow), outputWindow);
+        return serviceProvider;
+    }
+}
diff --git a/src/Integration.UnitTests/Helpers/SonarLintSettingsLoggerSettingsProviderTests.cs b/src/Integration.UnitTests/Helpers/SonarLintSettingsLoggerSettingsProviderTests.cs
new file mode 100644
index 0000000000..325e3622e2
--- /dev/null
+++ b/src/Integration.UnitTests/Helpers/SonarLintSettingsLoggerSettingsProviderTests.cs
@@ -0,0 +1,49 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+using SonarLint.VisualStudio.Integration.Helpers;
+
+namespace SonarLint.VisualStudio.Integration.UnitTests.Helpers;
+
+[TestClass]
+public class SonarLintSettingsLoggerSettingsProviderTests
+{
+    private ISonarLintSettings sonarLintSettings;
+    private SonarLintSettingsLoggerSettingsProvider testSubject;
+
+    [TestInitialize]
+    public void TestInitialize()
+    {
+        sonarLintSettings = Substitute.For<ISonarLintSettings>();
+        testSubject = new SonarLintSettingsLoggerSettingsProvider(sonarLintSettings);
+    }
+
+    [DataRow(DaemonLogLevel.Verbose, true)]
+    [DataRow(DaemonLogLevel.Info, false)]
+    [DataRow(DaemonLogLevel.Minimal, false)]
+    [DataTestMethod]
+    public void IsVerboseEnabled_IsThreadIdEnabled_ReturnsFor(DaemonLogLevel logLevel, bool isVerbose)
+    {
+        sonarLintSettings.DaemonLogLevel.Returns(logLevel);
+
+        testSubject.IsVerboseEnabled.Should().Be(isVerbose);
+        testSubject.IsThreadIdEnabled.Should().Be(isVerbose);
+    }
+}
diff --git a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt
index dbf76edd16..5b93271c68 100644
--- a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt
+++ b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt
@@ -123,6 +123,7 @@ Referenced assemblies:
 - 'PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
 - 'SonarQube.Client, Version=8.10.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244'
 - 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
+- 'System.Collections.Immutable, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
 - 'System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
 - 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
 - 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59'
@@ -131,7 +132,7 @@ Referenced assemblies:
 - 'System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
 - 'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
 - 'WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
-# Number of references: 14
+# Number of references: 15
 
 ---
 Assembly: 'SonarLint.VisualStudio.Education, Version=8.10.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244'
diff --git a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt
index c782ad8848..43872d3369 100644
--- a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt
+++ b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt
@@ -123,6 +123,7 @@ Referenced assemblies:
 - 'PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
 - 'SonarQube.Client, Version=8.10.0.0, Culture=neutral, PublicKeyToken=null'
 - 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
+- 'System.Collections.Immutable, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
 - 'System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
 - 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
 - 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59'
@@ -131,7 +132,7 @@ Referenced assemblies:
 - 'System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
 - 'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
 - 'WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
-# Number of references: 14
+# Number of references: 15
 
 ---
 Assembly: 'SonarLint.VisualStudio.Education, Version=8.10.0.0, Culture=neutral, PublicKeyToken=null'
diff --git a/src/Integration/Helpers/SonarLintOutputLogger.cs b/src/Integration/Helpers/SonarLintOutputLogger.cs
deleted file mode 100644
index 6ba83084e0..0000000000
--- a/src/Integration/Helpers/SonarLintOutputLogger.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * SonarLint for Visual Studio
- * Copyright (C) 2016-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-using System;
-using System.ComponentModel.Composition;
-using Microsoft.VisualStudio.Shell;
-using SonarLint.VisualStudio.Core;
-
-namespace SonarLint.VisualStudio.Integration
-{
-    [Export(typeof(ILogger))]
-    [PartCreationPolicy(CreationPolicy.Shared)]
-    public class SonarLintOutputLogger : ILogger
-    {
-        private readonly IServiceProvider serviceProvider;
-        private readonly ISonarLintSettings sonarLintSettings;
-
-        [ImportingConstructor]
-        public SonarLintOutputLogger([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider,
-            ISonarLintSettings sonarLintSettings)
-        {
-            this.serviceProvider = serviceProvider;
-            this.sonarLintSettings = sonarLintSettings;
-        }
-
-        public void WriteLine(string message)
-        {
-            var prefixedMessage = AddPrefixIfVerboseLogging(message);
-            VsShellUtils.WriteToSonarLintOutputPane(this.serviceProvider, prefixedMessage);
-        }
-
-        public void WriteLine(string messageFormat, params object[] args)
-        {
-            var prefixedMessageFormat = AddPrefixIfVerboseLogging(messageFormat);
-            VsShellUtils.WriteToSonarLintOutputPane(this.serviceProvider, prefixedMessageFormat, args);
-        }
-
-        public void LogVerbose(string messageFormat, params object[] args)
-        {
-            if (sonarLintSettings.DaemonLogLevel == DaemonLogLevel.Verbose)
-            {
-                var text = args.Length == 0 ? messageFormat : string.Format(messageFormat, args);
-                WriteLine("[DEBUG] " + text);
-            }
-        }
-
-        private string AddPrefixIfVerboseLogging(string message)
-        {
-            if (sonarLintSettings.DaemonLogLevel == DaemonLogLevel.Verbose)
-            {
-                message = $"[ThreadId {System.Threading.Thread.CurrentThread.ManagedThreadId}] " + message;
-            }
-            return message;
-        }
-    }
-}
diff --git a/src/Integration/Helpers/SonarLintOutputLoggerFactory.cs b/src/Integration/Helpers/SonarLintOutputLoggerFactory.cs
new file mode 100644
index 0000000000..fcd53d5399
--- /dev/null
+++ b/src/Integration/Helpers/SonarLintOutputLoggerFactory.cs
@@ -0,0 +1,51 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+using System.ComponentModel.Composition;
+using Microsoft.VisualStudio.Shell;
+using SonarLint.VisualStudio.Core;
+using SonarLint.VisualStudio.Core.Logging;
+
+namespace SonarLint.VisualStudio.Integration.Helpers;
+
+internal class SonarLintOutputWindowLoggerWriter(IServiceProvider serviceProvider) : ILoggerWriter
+{
+    public void WriteLine(string message) => VsShellUtils.WriteToSonarLintOutputPane(serviceProvider, message);
+}
+
+internal class SonarLintSettingsLoggerSettingsProvider(ISonarLintSettings sonarLintSettings) : ILoggerSettingsProvider
+{
+    public bool IsVerboseEnabled => sonarLintSettings.DaemonLogLevel == DaemonLogLevel.Verbose;
+    public bool IsThreadIdEnabled => IsVerboseEnabled;
+}
+
+[PartCreationPolicy(CreationPolicy.Shared)]
+[method: ImportingConstructor]
+internal class SonarLintOutputLoggerFactory(
+    ILoggerFactory logFactory,
+    [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider,
+    ISonarLintSettings sonarLintSettings)
+{
+    [Export(typeof(ILogger))]
+    public ILogger Instance { get; } =
+        logFactory.Create(
+            new SonarLintOutputWindowLoggerWriter(serviceProvider),
+            new SonarLintSettingsLoggerSettingsProvider(sonarLintSettings));
+}
diff --git a/src/Integration/Helpers/VsShellUtils.cs b/src/Integration/Helpers/VsShellUtils.cs
index 3433d0d174..dcb2cec893 100644
--- a/src/Integration/Helpers/VsShellUtils.cs
+++ b/src/Integration/Helpers/VsShellUtils.cs
@@ -41,28 +41,6 @@ public static class VsShellUtils
             Target = "~F:SonarLint.VisualStudio.Integration.VsShellUtils.SonarLintOutputPaneGuid")]
         internal static Guid SonarLintOutputPaneGuid = new Guid("EB476B82-D73A-44A6-AFEF-830F7BBA73DB");
 
-        /// <summary>
-        /// Writes a message to the SonarLint output pane. Will append a new line after the message.
-        /// </summary>
-        public static void WriteToSonarLintOutputPane(IServiceProvider serviceProvider, string messageFormat, params object[] args)
-        {
-            if (serviceProvider == null)
-            {
-                throw new ArgumentNullException(nameof(serviceProvider));
-            }
-
-            if (messageFormat == null)
-            {
-                throw new ArgumentNullException(nameof(messageFormat));
-            }
-
-            IVsOutputWindowPane sonarLintPane = GetOrCreateSonarLintOutputPane(serviceProvider);
-            if (sonarLintPane != null)
-            {
-                WriteLineToPane(sonarLintPane, messageFormat, args);
-            }
-        }
-
         /// <summary>
         /// Writes a message to the SonarLint output pane. Will append a new line after the message.
         /// </summary>
@@ -193,12 +171,6 @@ public static IVsOutputWindowPane GetOrCreateSonarLintOutputPane(IServiceProvide
             return pane;
         }
 
-        private static void WriteLineToPane(IVsOutputWindowPane pane, string messageFormat, params object[] args)
-        {
-            int hr = pane.OutputStringThreadSafe(string.Format(CultureInfo.CurrentCulture, messageFormat, args: args) + Environment.NewLine);
-            Debug.Assert(ErrorHandler.Succeeded(hr), "Failed in OutputStringThreadSafe: " + hr.ToString());
-        }
-
         private static void WriteLineToPane(IVsOutputWindowPane pane, string message)
         {
             int hr = pane.OutputStringThreadSafe(message + Environment.NewLine);
diff --git a/src/Roslyn.Suppressions/Roslyn.Suppressions/Container.cs b/src/Roslyn.Suppressions/Roslyn.Suppressions/Container.cs
index 737d408a66..ac8ad1f701 100644
--- a/src/Roslyn.Suppressions/Roslyn.Suppressions/Container.cs
+++ b/src/Roslyn.Suppressions/Roslyn.Suppressions/Container.cs
@@ -22,6 +22,7 @@
 using System.IO;
 using System.Threading;
 using SonarLint.VisualStudio.Core;
+using SonarLint.VisualStudio.Core.Logging;
 using SonarLint.VisualStudio.Roslyn.Suppressions.Settings.Cache;
 using SonarLint.VisualStudio.Roslyn.Suppressions.SettingsFile;
 
@@ -66,7 +67,7 @@ public static IContainer Instance
         public Container()
         {
             Directory.CreateDirectory(RoslynSettingsFileInfo.Directory);
-            Logger = new Logger();
+            Logger = LoggerFactory.Default.Create(new SystemDebugLoggerWriter(), new EnableAllLoggerSettingsProvider());
 
             var settingsCache = new SettingsCache(Logger);
             fileWatcher = new SuppressedIssuesFileWatcher(settingsCache, Logger);
diff --git a/src/Roslyn.Suppressions/Roslyn.Suppressions/Logging/EnableAllLoggerSettingsProvider.cs b/src/Roslyn.Suppressions/Roslyn.Suppressions/Logging/EnableAllLoggerSettingsProvider.cs
new file mode 100644
index 0000000000..718cf1f92a
--- /dev/null
+++ b/src/Roslyn.Suppressions/Roslyn.Suppressions/Logging/EnableAllLoggerSettingsProvider.cs
@@ -0,0 +1,31 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+using System.Diagnostics.CodeAnalysis;
+using SonarLint.VisualStudio.Core.Logging;
+
+namespace SonarLint.VisualStudio.Roslyn.Suppressions;
+
+[ExcludeFromCodeCoverage]
+internal class EnableAllLoggerSettingsProvider : ILoggerSettingsProvider
+{
+    public bool IsVerboseEnabled => true;
+    public bool IsThreadIdEnabled => true;
+}
diff --git a/src/Roslyn.Suppressions/Roslyn.Suppressions/Logging/SystemDebugLoggerWriter.cs b/src/Roslyn.Suppressions/Roslyn.Suppressions/Logging/SystemDebugLoggerWriter.cs
new file mode 100644
index 0000000000..cd74c901e7
--- /dev/null
+++ b/src/Roslyn.Suppressions/Roslyn.Suppressions/Logging/SystemDebugLoggerWriter.cs
@@ -0,0 +1,30 @@
+/*
+ * SonarLint for Visual Studio
+ * Copyright (C) 2016-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+using System.Diagnostics.CodeAnalysis;
+using SonarLint.VisualStudio.Core.Logging;
+
+namespace SonarLint.VisualStudio.Roslyn.Suppressions;
+
+[ExcludeFromCodeCoverage]
+internal class SystemDebugLoggerWriter : ILoggerWriter
+{
+    public void WriteLine(string message) => Debug.WriteLine(message);
+}
diff --git a/src/SLCore.Listeners.UnitTests/Logging/LoggerListenerTests.cs b/src/SLCore.Listeners.UnitTests/Logging/LoggerListenerTests.cs
index dd45816a37..ee2db968e9 100644
--- a/src/SLCore.Listeners.UnitTests/Logging/LoggerListenerTests.cs
+++ b/src/SLCore.Listeners.UnitTests/Logging/LoggerListenerTests.cs
@@ -57,7 +57,7 @@ public void Log_LogInfoTraceAndDebugAsVerbose(LogLevel logLevel, bool verboseLog
 
             if (verboseLogs)
             {
-                logger.AssertOutputStringExists("[Verbose] [SLCORE] some Message");
+                logger.AssertOutputStringExists("[DEBUG] [SLCORE] some Message");
             }
             else
             {
@@ -65,4 +65,4 @@ public void Log_LogInfoTraceAndDebugAsVerbose(LogLevel logLevel, bool verboseLog
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/SLCore.UnitTests/SLCoreInstanceHandlerTests.cs b/src/SLCore.UnitTests/SLCoreInstanceHandlerTests.cs
index fcc7c8a58e..f9ff84706f 100644
--- a/src/SLCore.UnitTests/SLCoreInstanceHandlerTests.cs
+++ b/src/SLCore.UnitTests/SLCoreInstanceHandlerTests.cs
@@ -89,7 +89,7 @@ public void StartInstance_InstanceCreationFailed_LogsAndExits()
         slCoreHandler.currentInstanceHandle.Should().BeNull();
         logger.AssertOutputStringExists(SLCoreStrings.SLCoreHandler_CreatingInstance);
         logger.AssertOutputStringExists(SLCoreStrings.SLCoreHandler_CreatingInstanceError);
-        logger.AssertPartialOutputStringExists("[Verbose] System.Exception");
+        logger.AssertPartialOutputStringExists("[DEBUG] System.Exception");
     }
 
     [TestMethod]
@@ -152,7 +152,7 @@ public void StartInstance_InstanceInitializationThrows_RaisesEventAndResets()
         logger.AssertOutputStringExists(SLCoreStrings.SLCoreHandler_StartingInstance);
         logger.AssertOutputStringExists(SLCoreStrings.SLCoreHandler_StartingInstanceError);
         logger.AssertOutputStringExists(SLCoreStrings.SLCoreHandler_InstanceDied);
-        logger.AssertPartialOutputStringExists("[Verbose] System.Exception");
+        logger.AssertPartialOutputStringExists("[DEBUG] System.Exception");
         Received.InOrder(() =>
         {
             threadHandling.ThrowIfOnUIThread();
diff --git a/src/TestInfrastructure/Framework/TestLogger.cs b/src/TestInfrastructure/Framework/TestLogger.cs
index 299dad4f5b..ad21b614fe 100644
--- a/src/TestInfrastructure/Framework/TestLogger.cs
+++ b/src/TestInfrastructure/Framework/TestLogger.cs
@@ -18,15 +18,13 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-using System;
 using System.Collections.Concurrent;
-using System.Linq;
-using FluentAssertions;
 using SonarLint.VisualStudio.Core;
+using SonarLint.VisualStudio.Core.Logging;
 
 namespace SonarLint.VisualStudio.TestInfrastructure
 {
-    public class TestLogger : ILogger
+    public class TestLogger : ILogger, ILoggerWriter, ILoggerSettingsProvider
     {
         public BlockingCollection<string> OutputStrings { get; private set; } = new();
 
@@ -34,6 +32,7 @@ public class TestLogger : ILogger
 
         private readonly bool logToConsole;
         private readonly bool logThreadId;
+        private readonly ILogger logger;
 
         public TestLogger(bool logToConsole = false, bool logThreadId = false)
         {
@@ -43,6 +42,7 @@ public TestLogger(bool logToConsole = false, bool logThreadId = false)
             this.logToConsole = logToConsole;
 
             this.logThreadId = logThreadId;
+            logger = LoggerFactory.Default.Create(this, this);
         }
 
         public void AssertOutputStrings(int expectedOutputMessages)
@@ -98,46 +98,34 @@ public void Reset()
 
         #region ILogger methods
 
-        public void WriteLine(string message)
-        {
-            var messageToLog = GetFormattedMessage(message);
-            OutputStrings.Add(messageToLog);
-            if (logToConsole)
-            {
-                Console.WriteLine(messageToLog);
-            }
+        public void WriteLine(string message) => logger.WriteLine(message);
 
-            LogMessageAdded?.Invoke(this, EventArgs.Empty);
-        }
+        public void WriteLine(string messageFormat, params object[] args) => logger.WriteLine(messageFormat, args);
 
-        public void WriteLine(string messageFormat, params object[] args)
-        {
-            WriteLine(string.Format(System.Globalization.CultureInfo.CurrentCulture, messageFormat, args));
-        }
+        public void WriteLine(MessageLevelContext context, string messageFormat, params object[] args) => logger.WriteLine(context, messageFormat, args);
 
-        public void LogVerbose(string message, params object[] args)
-        {
-            var verboseMessage = $"[Verbose] {message}";
-            if (args.Length == 0)
-            {
-                WriteLine(verboseMessage);
-            }
-            else
-            {
-                WriteLine(verboseMessage, args);
-            }
-        }
+        public void LogVerbose(string message, params object[] args) => logger.LogVerbose(message, args);
+
+        public void LogVerbose(MessageLevelContext context, string messageFormat, params object[] args) => logger.WriteLine(context, messageFormat, args);
+
+        public ILogger ForContext(params string[] context) => logger.ForContext(context);
+
+        public ILogger ForVerboseContext(params string[] context) => logger.ForVerboseContext();
+
+        #endregion
 
-        private string GetFormattedMessage(string message)
+        void ILoggerWriter.WriteLine(string message)
         {
             var messageToLog = message + Environment.NewLine;
-            if (logThreadId)
+            OutputStrings.Add(messageToLog);
+            if (logToConsole)
             {
-                messageToLog = $"[Thread {System.Threading.Thread.CurrentThread.ManagedThreadId}] {messageToLog}";
+                Console.WriteLine(messageToLog);
             }
-            return messageToLog;
-        }
 
-        #endregion
+            LogMessageAdded?.Invoke(this, EventArgs.Empty);
+        }
+        bool ILoggerSettingsProvider.IsVerboseEnabled => true;
+        bool ILoggerSettingsProvider.IsThreadIdEnabled => logThreadId;
     }
 }