diff --git a/Snowflake.Data.Tests/Snowflake.Data.Tests.csproj b/Snowflake.Data.Tests/Snowflake.Data.Tests.csproj
index 89d295439..791cf75a6 100644
--- a/Snowflake.Data.Tests/Snowflake.Data.Tests.csproj
+++ b/Snowflake.Data.Tests/Snowflake.Data.Tests.csproj
@@ -13,10 +13,16 @@
+
+
+
+
+
+
@@ -36,6 +42,21 @@
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+
full
True
diff --git a/Snowflake.Data.Tests/TestLog4Net.config b/Snowflake.Data.Tests/TestLog4Net.config
new file mode 100644
index 000000000..72de5b815
--- /dev/null
+++ b/Snowflake.Data.Tests/TestLog4Net.config
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Snowflake.Data.Tests/TestNLog.config b/Snowflake.Data.Tests/TestNLog.config
new file mode 100644
index 000000000..6dd1b6ce8
--- /dev/null
+++ b/Snowflake.Data.Tests/TestNLog.config
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Snowflake.Data.Tests/TestSerilog.config b/Snowflake.Data.Tests/TestSerilog.config
new file mode 100644
index 000000000..65bf5c82c
--- /dev/null
+++ b/Snowflake.Data.Tests/TestSerilog.config
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigParserTest.cs b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigParserTest.cs
index 847d57c51..60a3ab272 100644
--- a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigParserTest.cs
+++ b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigParserTest.cs
@@ -33,7 +33,7 @@ public static void AfterAll()
{
Directory.Delete(s_workingDirectory, true);
}
-
+
[Test]
public void TestThatParsesConfigFile()
{
@@ -59,12 +59,12 @@ public void TestThatParsesConfigFileWithNullValues(string filePath)
// act
var config = parser.Parse(filePath);
-
+
// assert
Assert.IsNotNull(config);
Assert.IsNotNull(config.CommonProps);
Assert.IsNull(config.CommonProps.LogLevel);
- Assert.IsNull(config.CommonProps.LogPath);
+ Assert.IsNull(config.CommonProps.LogPath);
}
[Test]
@@ -77,20 +77,20 @@ public void TestThatReturnsNullWhenNothingToParse(string noFilePath)
// act
var config = parser.Parse(noFilePath);
-
+
// assert
Assert.IsNull(config);
}
-
+
[Test]
public void TestThatFailsWhenTheFileDoesNotExist()
{
// arrange
var parser = new EasyLoggingConfigParser();
-
+
// act
var thrown = Assert.Throws(() => parser.Parse(NotExistingFilePath));
-
+
// assert
Assert.IsNotNull(thrown);
Assert.AreEqual("Finding easy logging configuration failed", thrown.Message);
diff --git a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigProviderTest.cs b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigProviderTest.cs
index f61d51503..6e971ff9e 100644
--- a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigProviderTest.cs
+++ b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigProviderTest.cs
@@ -13,7 +13,7 @@ public class EasyLoggingConfigProviderTest
{
private const string FilePathFromConnectionString = "/Users/dotnet/config.json";
private const string FilePathToUse = "/home/config.json";
-
+
[Test]
public void TestThatProvidesConfiguration()
{
@@ -31,7 +31,7 @@ public void TestThatProvidesConfiguration()
// act
var result = configProvider.ProvideConfig(FilePathFromConnectionString);
-
+
// assert
Assert.AreSame(config, result);
}
@@ -45,11 +45,11 @@ public void TestThatReturnsNullWhenNoConfigurationFound()
var configProvider = new EasyLoggingConfigProvider(configFinder.Object, configParser.Object);
configFinder
.Setup(finder => finder.FindConfigFilePath(FilePathFromConnectionString))
- .Returns((string) null);
-
+ .Returns((string)null);
+
// act
var result = configProvider.ProvideConfig(FilePathFromConnectionString);
-
+
// assert
Assert.IsNull(result);
configParser.Verify(parser => parser.Parse(It.IsAny()), Times.Never);
diff --git a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingLogLevelTest.cs b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingLogLevelTest.cs
index d61cfaeec..7b6e827db 100644
--- a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingLogLevelTest.cs
+++ b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingLogLevelTest.cs
@@ -19,7 +19,7 @@ public void TestThatGetsLogLevelValueIgnoringLetterCase(string loglevelString, E
{
// act
var logLevel = EasyLoggingLogLevelExtensions.From(loglevelString);
-
+
// assert
Assert.AreEqual(expectedLogLevel, logLevel);
}
@@ -29,7 +29,7 @@ public void TestThatFailsToParseLogLevelFromUnknownValue()
{
// act
var thrown = Assert.Throws(() => EasyLoggingLogLevelExtensions.From("unknown"));
-
+
// assert
Assert.IsNotNull(thrown);
Assert.AreEqual("Requested value 'unknown' was not found.", thrown.Message);
diff --git a/Snowflake.Data.Tests/UnitTests/Logger/EasyLoggerManagerTest.cs b/Snowflake.Data.Tests/UnitTests/Logger/EasyLoggerManagerTest.cs
index 4dfba8c83..b3aaab92e 100644
--- a/Snowflake.Data.Tests/UnitTests/Logger/EasyLoggerManagerTest.cs
+++ b/Snowflake.Data.Tests/UnitTests/Logger/EasyLoggerManagerTest.cs
@@ -16,7 +16,7 @@ namespace Snowflake.Data.Tests.UnitTests.Logger
[TestFixture, NonParallelizable]
public class EasyLoggerManagerTest
{
-
+
private const string InfoMessage = "Easy logging Info message";
private const string DebugMessage = "Easy logging Debug message";
private const string WarnMessage = "Easy logging Warn message";
@@ -26,7 +26,7 @@ public class EasyLoggerManagerTest
[ThreadStatic]
private static string t_directoryLogPath;
-
+
[OneTimeTearDown]
public static void CleanUp()
{
@@ -44,14 +44,46 @@ public void AfterEach()
{
EasyLoggerManager.Instance.ReconfigureEasyLogging(EasyLoggingLogLevel.Warn, t_directoryLogPath);
}
-
+
[Test]
public void TestThatChangesLogLevel()
{
// arrange
- var logger = SFLoggerFactory.GetLogger();
+ var logger = SFLoggerFactory.GetSFLogger();
+
+ // act
+ EasyLoggerManager.Instance.ReconfigureEasyLogging(EasyLoggingLogLevel.Off, t_directoryLogPath);
+
+ // assert
+ Assert.IsFalse(logger.IsDebugEnabled());
+ Assert.IsFalse(logger.IsInfoEnabled());
+ Assert.IsFalse(logger.IsWarnEnabled());
+ Assert.IsFalse(logger.IsErrorEnabled());
+ Assert.IsFalse(logger.IsFatalEnabled());
+
+ // act
+ EasyLoggerManager.Instance.ReconfigureEasyLogging(EasyLoggingLogLevel.Fatal, t_directoryLogPath);
+
+ // assert
+ Assert.IsFalse(logger.IsDebugEnabled());
+ Assert.IsFalse(logger.IsInfoEnabled());
+ Assert.IsFalse(logger.IsWarnEnabled());
+ Assert.IsFalse(logger.IsErrorEnabled());
+ Assert.IsTrue(logger.IsFatalEnabled());
+
+ // act
+ EasyLoggerManager.Instance.ReconfigureEasyLogging(EasyLoggingLogLevel.Error, t_directoryLogPath);
+
+ // assert
+ Assert.IsFalse(logger.IsDebugEnabled());
+ Assert.IsFalse(logger.IsInfoEnabled());
+ Assert.IsFalse(logger.IsWarnEnabled());
+ Assert.IsTrue(logger.IsErrorEnabled());
+ Assert.IsTrue(logger.IsFatalEnabled());
+
+ // act
EasyLoggerManager.Instance.ReconfigureEasyLogging(EasyLoggingLogLevel.Warn, t_directoryLogPath);
-
+
// assert
Assert.IsFalse(logger.IsDebugEnabled());
Assert.IsFalse(logger.IsInfoEnabled());
@@ -74,9 +106,9 @@ public void TestThatChangesLogLevel()
public void TestThatLogsToProperFileWithProperLogLevelOnly()
{
// arrange
- var logger = SFLoggerFactory.GetLogger();
+ var logger = SFLoggerFactory.GetSFLogger();
EasyLoggerManager.Instance.ReconfigureEasyLogging(EasyLoggingLogLevel.Info, t_directoryLogPath);
-
+
// act
logger.Debug(DebugMessage);
logger.Info(InfoMessage);
@@ -91,8 +123,52 @@ public void TestThatLogsToProperFileWithProperLogLevelOnly()
Assert.That(logLines, Has.Exactly(1).Matches(s => s.Contains(WarnMessage)));
Assert.That(logLines, Has.Exactly(1).Matches(s => s.Contains(ErrorMessage)));
Assert.That(logLines, Has.Exactly(1).Matches(s => s.Contains(FatalMessage)));
+
+ // arrange
+ File.Delete(FindLogFilePath(t_directoryLogPath));
+ EasyLoggerManager.Instance.ReconfigureEasyLogging(EasyLoggingLogLevel.Debug, t_directoryLogPath);
+
+ // act
+ logger.Debug(DebugMessage);
+
+ // assert
+ logLines = File.ReadLines(FindLogFilePath(t_directoryLogPath));
+ Assert.That(logLines, Has.Exactly(1).Matches(s => s.Contains(DebugMessage)));
}
-
+
+ [Test]
+ public void TestThatRollsLogIfSizeIsTooBig()
+ {
+ // arrange
+ const int expecetedBackupLogCount = 2;
+ var logger = SFLoggerFactory.GetSFLogger();
+ EasyLoggerManager.Instance.ReconfigureEasyLogging(EasyLoggingLogLevel.Trace, t_directoryLogPath);
+
+ var appenders = SFLogRepository.s_rootLogger.GetAppenders();
+ appenders.Remove(appenders[0]);
+ var randomFileName = $"snowflake_dotnet_{Path.GetRandomFileName()}";
+ var logFileName = randomFileName.Substring(0, randomFileName.Length - 4) + ".log";
+ appenders.Add(new SFRollingFileAppender()
+ {
+ _name = "RollingFileAppender",
+ _logFilePath = Path.Combine(t_directoryLogPath, logFileName),
+ _maximumFileSizeInBytes = 1,
+ _maxSizeRollBackups = expecetedBackupLogCount,
+ _patternLayout = EasyLoggerManager.PatternLayout()
+ });
+
+ // act
+ for (int i = 0; i < 5; i++)
+ {
+ logger.Debug(DebugMessage);
+ System.Threading.Thread.Sleep(1000);
+ }
+ var backupLogs = Directory.GetFiles(t_directoryLogPath, $"{logFileName}.*.bak");
+
+ // assert
+ Assert.AreEqual(expecetedBackupLogCount, backupLogs.Length);
+ }
+
[Test]
public void TestThatOnlyUnknownFieldsAreLogged()
{
@@ -138,7 +214,7 @@ private static string FindLogFilePath(string directoryLogPath)
Assert.AreEqual(1, files.Length);
return files.First();
}
-
+
private static void RemoveEasyLoggingLogFiles()
{
Directory.GetFiles(s_logsDirectory)
diff --git a/Snowflake.Data.Tests/UnitTests/Logger/ILoggerTest.cs b/Snowflake.Data.Tests/UnitTests/Logger/ILoggerTest.cs
new file mode 100644
index 000000000..d0e3959b5
--- /dev/null
+++ b/Snowflake.Data.Tests/UnitTests/Logger/ILoggerTest.cs
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2023 Snowflake Computing Inc. All rights reserved.
+ */
+
+using NUnit.Framework;
+using Snowflake.Data.Log;
+using Microsoft.Extensions.Logging;
+using ILogger = Microsoft.Extensions.Logging.ILogger;
+using NLog.Extensions.Logging;
+using Serilog;
+using Serilog.Extensions.Logging;
+using System.IO;
+using System;
+
+namespace Snowflake.Data.Tests.UnitTests
+{
+ [TestFixture, NonParallelizable]
+ class ILoggerTest
+ {
+ private const string InfoMessage = "Info message";
+ private const string DebugMessage = "Debug message";
+ private const string WarnMessage = "Warn message";
+ private const string ErrorMessage = "Error message";
+ private const string CriticalMessage = "critical message";
+
+ private const string Log4NetFileName = "test_log4net.log";
+ private const string SerilogFileName = "test_serilog.log";
+ private const string NlogFileName = "test_nlog.log";
+
+ public abstract class ILoggerBaseTest
+ {
+ protected ILogger _logger;
+ protected string _logFile;
+
+ [OneTimeSetUp]
+ public void BeforeTest()
+ {
+ SFLoggerFactory.EnableCustomLogger();
+ }
+
+ [OneTimeTearDown]
+ public void AfterTest()
+ {
+ // Return to default setting
+ SFLoggerFactory.UseDefaultLogger();
+ SFLoggerFactory.DisableCustomLogger();
+ if (_logFile != null)
+ {
+ File.Delete(_logFile);
+ _logFile = null;
+ }
+ }
+
+ [Test]
+ public void TestUsingDefaultLogger()
+ {
+ var originalLogger = SFLoggerFactory.GetCustomLogger();
+ SFLoggerFactory.UseDefaultLogger();
+ _logger = SFLoggerFactory.GetCustomLogger();
+ Assert.IsInstanceOf(_logger);
+ SFLoggerFactory.SetCustomLogger(originalLogger);
+ }
+
+ [Test]
+ public void TestSettingCustomLogger()
+ {
+ var originalLogger = SFLoggerFactory.GetCustomLogger();
+ SFLoggerFactory.SetCustomLogger(new ILoggerEmptyImpl());
+ _logger = SFLoggerFactory.GetCustomLogger();
+ Assert.IsInstanceOf(_logger);
+ SFLoggerFactory.SetCustomLogger(originalLogger);
+ }
+
+ [Test]
+ public void TestBeginScope(
+ [Values(false, true)] bool isEnabled)
+ {
+ _logger = GetLogger(isEnabled);
+
+ if (_logger is ILoggerEmptyImpl)
+ {
+ Assert.Throws(() => _logger.BeginScope("Test"));
+ }
+ }
+
+ [Test]
+ public void TestIsDebugEnabled(
+ [Values(false, true)] bool isEnabled)
+ {
+ _logger = GetLogger(isEnabled);
+ Assert.AreEqual(isEnabled, _logger.IsEnabled(LogLevel.Debug));
+ }
+
+ [Test]
+ public void TestIsInfoEnabled(
+ [Values(false, true)] bool isEnabled)
+ {
+ _logger = GetLogger(isEnabled);
+ Assert.AreEqual(isEnabled, _logger.IsEnabled(LogLevel.Information));
+ }
+
+ [Test]
+ public void TestIsWarnEnabled(
+ [Values(false, true)] bool isEnabled)
+ {
+ _logger = GetLogger(isEnabled);
+ Assert.AreEqual(isEnabled, _logger.IsEnabled(LogLevel.Warning));
+ }
+
+ [Test]
+ public void TestIsErrorEnabled(
+ [Values(false, true)] bool isEnabled)
+ {
+ _logger = GetLogger(isEnabled);
+ Assert.AreEqual(isEnabled, _logger.IsEnabled(LogLevel.Error));
+ }
+
+ [Test]
+ public void TestIsFatalEnabled(
+ [Values(false, true)] bool isEnabled)
+ {
+ _logger = GetLogger(isEnabled);
+ Assert.AreEqual(isEnabled, _logger.IsEnabled(LogLevel.Critical));
+ }
+
+ private ILogger GetLogger(bool isEnabled)
+ {
+ if (isEnabled)
+ {
+ SFLoggerFactory.EnableCustomLogger();
+ }
+ else
+ {
+ SFLoggerFactory.DisableCustomLogger();
+ }
+
+ return SFLoggerFactory.GetCustomLogger();
+ }
+
+ [Test]
+ public void TestThatLogsToProperFileWithProperLogLevelOnly()
+ {
+ _logger = SFLoggerFactory.GetCustomLogger();
+
+ // act
+ _logger.LogDebug(DebugMessage);
+ _logger.LogInformation(InfoMessage);
+ _logger.LogWarning(WarnMessage);
+ _logger.LogError(ErrorMessage);
+ _logger.LogCritical(CriticalMessage);
+
+ // assert
+ using (FileStream logFileStream = new FileStream(_logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+ {
+ using (StreamReader logFileReader = new StreamReader(logFileStream))
+ {
+ string logLines = logFileReader.ReadToEnd();
+ Assert.IsTrue(logLines.Contains(DebugMessage));
+ Assert.IsTrue(logLines.Contains(InfoMessage));
+ Assert.IsTrue(logLines.Contains(WarnMessage));
+ Assert.IsTrue(logLines.Contains(ErrorMessage));
+ Assert.IsTrue(logLines.Contains(CriticalMessage));
+ }
+ }
+ }
+ }
+
+ [TestFixture]
+ public class Log4NetTest : ILoggerBaseTest
+ {
+ [OneTimeSetUp]
+ public void SetUp()
+ {
+ Environment.SetEnvironmentVariable("TEST_LOG4NET_FILE_NAME", Log4NetFileName);
+ var factory = LoggerFactory.Create(
+ builder => builder
+ .AddLog4Net("TestLog4Net.config")
+ .SetMinimumLevel(LogLevel.Trace));
+
+ var log4netLogger = factory.CreateLogger("Log4NetTest");
+ SFLoggerFactory.SetCustomLogger(log4netLogger);
+ _logFile = Log4NetFileName;
+ }
+ }
+
+ [TestFixture]
+ public class SerilogTest : ILoggerBaseTest
+ {
+ [OneTimeSetUp]
+ public void SetUp()
+ {
+ var loggerSerilog = new LoggerConfiguration()
+ //.ReadFrom.Xml("TestSerilog.Config")
+ .MinimumLevel.Verbose()
+ .WriteTo.File(SerilogFileName)
+ .CreateLogger();
+
+ var serilogLogger = new SerilogLoggerFactory(loggerSerilog).CreateLogger("SerilogTest");
+ SFLoggerFactory.SetCustomLogger(serilogLogger);
+ _logFile = SerilogFileName;
+ }
+ }
+
+ [TestFixture]
+ public class NlogTest : ILoggerBaseTest
+ {
+ [OneTimeSetUp]
+ public void SetUp()
+ {
+ Environment.SetEnvironmentVariable("TEST_NLOG_FILE_NAME", NlogFileName);
+ var factory = LoggerFactory.Create(
+ builder => builder
+ .AddNLog("TestNLog.config")
+ .SetMinimumLevel(LogLevel.Trace));
+
+ var nlogLogger = factory.CreateLogger("NlogTest");
+ SFLoggerFactory.SetCustomLogger(nlogLogger);
+ _logFile = NlogFileName;
+ }
+ }
+ }
+}
diff --git a/Snowflake.Data.Tests/UnitTests/Logger/SFLoggerPairTest.cs b/Snowflake.Data.Tests/UnitTests/Logger/SFLoggerPairTest.cs
new file mode 100644
index 000000000..8555b0fcd
--- /dev/null
+++ b/Snowflake.Data.Tests/UnitTests/Logger/SFLoggerPairTest.cs
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved.
+ */
+
+using NUnit.Framework;
+using Snowflake.Data.Configuration;
+using Snowflake.Data.Log;
+using System;
+
+namespace Snowflake.Data.Tests.UnitTests
+{
+ [TestFixture, NonParallelizable]
+ class SFLoggerPairTest
+ {
+ SFLogger _loggerPair;
+
+ [OneTimeSetUp]
+ public static void BeforeAll()
+ {
+ // Log level defaults to Warn on net6.0 builds in github actions
+ // Set the root level to Debug
+ EasyLoggerManager.Instance.ReconfigureEasyLogging(EasyLoggingLogLevel.Debug, "STDOUT");
+ }
+
+ [OneTimeTearDown]
+ public static void AfterAll()
+ {
+ EasyLoggerManager.Instance.ReconfigureEasyLogging(EasyLoggingLogLevel.Warn, "STDOUT");
+ }
+
+ [SetUp]
+ public void BeforeTest()
+ {
+ _loggerPair = SFLoggerFactory.GetLogger();
+ }
+
+ [TearDown]
+ public void AfterTest()
+ {
+ // Return to default setting
+ SFLoggerFactory.UseDefaultSFLogger();
+ }
+
+ [Test]
+ public void TestUsingSFLogger()
+ {
+ SFLoggerFactory.UseDefaultSFLogger();
+ _loggerPair = SFLoggerFactory.GetLogger();
+ Assert.IsInstanceOf(_loggerPair);
+ }
+
+ [Test]
+ public void TestIsDebugEnabled(
+ [Values(false, true)] bool isEnabled)
+ {
+ _loggerPair = GetLogger(isEnabled);
+
+ Assert.AreEqual(isEnabled, _loggerPair.IsDebugEnabled());
+ _loggerPair.Debug("debug log message", new Exception("test exception"));
+ }
+
+ [Test]
+ public void TestIsInfoEnabled(
+ [Values(false, true)] bool isEnabled)
+ {
+ _loggerPair = GetLogger(isEnabled);
+
+ Assert.AreEqual(isEnabled, _loggerPair.IsInfoEnabled());
+ _loggerPair.Info("info log message", new Exception("test exception"));
+ }
+
+ [Test]
+ public void TestIsWarnEnabled(
+ [Values(false, true)] bool isEnabled)
+ {
+ _loggerPair = GetLogger(isEnabled);
+
+ Assert.AreEqual(isEnabled, _loggerPair.IsWarnEnabled());
+ _loggerPair.Warn("warn log message", new Exception("test exception"));
+ }
+
+ [Test]
+ public void TestIsErrorEnabled(
+ [Values(false, true)] bool isEnabled)
+ {
+ _loggerPair = GetLogger(isEnabled);
+
+ Assert.AreEqual(isEnabled, _loggerPair.IsErrorEnabled());
+ _loggerPair.Error("error log message", new Exception("test exception"));
+ }
+
+ [Test]
+ public void TestIsFatalEnabled(
+ [Values(false, true)] bool isEnabled)
+ {
+ _loggerPair = GetLogger(isEnabled);
+
+ Assert.AreEqual(isEnabled, _loggerPair.IsFatalEnabled());
+ _loggerPair.Fatal("fatal log message", new Exception("test exception"));
+ }
+
+ [Test]
+ public void TestGetAppenders()
+ {
+ Assert.Throws(() => _loggerPair.GetAppenders());
+ }
+
+ [Test]
+ public void TestAddAppender()
+ {
+ Assert.Throws(() => _loggerPair.AddAppender(new SFConsoleAppender()));
+ }
+
+ [Test]
+ public void TestRemoveAppender()
+ {
+ Assert.Throws(() => _loggerPair.RemoveAppender(new SFConsoleAppender()));
+ }
+
+ [Test]
+ public void TestSetLevel()
+ {
+ Assert.Throws(() => _loggerPair.SetLevel(LoggingEvent.DEBUG));
+ }
+
+ private SFLogger GetLogger(bool isEnabled)
+ {
+ if (isEnabled)
+ {
+ SFLoggerFactory.UseDefaultSFLogger();
+ }
+ else
+ {
+ SFLoggerFactory.UseEmptySFLogger();
+ }
+
+ return SFLoggerFactory.GetLogger();
+ }
+ }
+}
diff --git a/Snowflake.Data.Tests/UnitTests/Logger/SFLoggerTest.cs b/Snowflake.Data.Tests/UnitTests/Logger/SFLoggerTest.cs
index 710a8d645..f0a2da54a 100644
--- a/Snowflake.Data.Tests/UnitTests/Logger/SFLoggerTest.cs
+++ b/Snowflake.Data.Tests/UnitTests/Logger/SFLoggerTest.cs
@@ -1,14 +1,14 @@
-/*
- * Copyright (c) 2023 Snowflake Computing Inc. All rights reserved.
+/*
+ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved.
*/
+using NUnit.Framework;
using Snowflake.Data.Configuration;
+using Snowflake.Data.Log;
+using System;
namespace Snowflake.Data.Tests.UnitTests
{
- using NUnit.Framework;
- using Snowflake.Data.Log;
-
[TestFixture, NonParallelizable]
class SFLoggerTest
{
@@ -27,27 +27,27 @@ public static void AfterAll()
{
EasyLoggerManager.Instance.ReconfigureEasyLogging(EasyLoggingLogLevel.Warn, "STDOUT");
}
-
- [TearDown] public void AfterTest()
+
+ [TearDown]
+ public void AfterTest()
{
// Return to default setting
- SFLoggerFactory.useDefaultLogger();
- SFLoggerFactory.enableLogger();
+ SFLoggerFactory.UseDefaultSFLogger();
}
[Test]
- public void TestUsingDefaultLogger()
+ public void TestUsingSFLogger()
{
- SFLoggerFactory.useDefaultLogger();
- _logger = SFLoggerFactory.GetLogger();
- Assert.IsInstanceOf(_logger);
+ SFLoggerFactory.UseDefaultSFLogger();
+ _logger = SFLoggerFactory.GetSFLogger();
+ Assert.IsInstanceOf(_logger);
}
[Test]
- public void TestSettingCustomLogger()
+ public void TestUsingEmptyLogger()
{
- SFLoggerFactory.Instance(new SFLoggerEmptyImpl());
- _logger = SFLoggerFactory.GetLogger();
+ SFLoggerFactory.UseEmptySFLogger();
+ _logger = SFLoggerFactory.GetSFLogger();
Assert.IsInstanceOf(_logger);
}
@@ -58,6 +58,7 @@ public void TestIsDebugEnabled(
_logger = GetLogger(isEnabled);
Assert.AreEqual(isEnabled, _logger.IsDebugEnabled());
+ _logger.Debug("debug log message", new Exception("test exception"));
}
[Test]
@@ -67,6 +68,7 @@ public void TestIsInfoEnabled(
_logger = GetLogger(isEnabled);
Assert.AreEqual(isEnabled, _logger.IsInfoEnabled());
+ _logger.Info("info log message", new Exception("test exception"));
}
[Test]
@@ -76,6 +78,7 @@ public void TestIsWarnEnabled(
_logger = GetLogger(isEnabled);
Assert.AreEqual(isEnabled, _logger.IsWarnEnabled());
+ _logger.Warn("warn log message", new Exception("test exception"));
}
[Test]
@@ -85,6 +88,7 @@ public void TestIsErrorEnabled(
_logger = GetLogger(isEnabled);
Assert.AreEqual(isEnabled, _logger.IsErrorEnabled());
+ _logger.Error("error log message", new Exception("test exception"));
}
[Test]
@@ -94,20 +98,89 @@ public void TestIsFatalEnabled(
_logger = GetLogger(isEnabled);
Assert.AreEqual(isEnabled, _logger.IsFatalEnabled());
+ _logger.Fatal("fatal log message", new Exception("test exception"));
+ }
+
+ [Test]
+ public void TestGetAppenders(
+ [Values(false, true)] bool isEnabled)
+ {
+ _logger = GetLogger(isEnabled);
+ if (isEnabled)
+ {
+ var appenders = _logger.GetAppenders();
+ Assert.IsInstanceOf(appenders[0]);
+ }
+ else
+ {
+ Assert.Throws(() => _logger.GetAppenders());
+ }
+ }
+
+ [Test]
+ public void TestAddAppender(
+ [Values(false, true)] bool isEnabled)
+ {
+ _logger = GetLogger(isEnabled);
+ if (isEnabled)
+ {
+ var appenders = _logger.GetAppenders();
+ Assert.AreEqual(1, appenders.Count);
+ _logger.AddAppender(new SFConsoleAppender());
+ Assert.AreEqual(2, appenders.Count);
+ }
+ else
+ {
+ Assert.Throws(() => _logger.AddAppender(new SFConsoleAppender()));
+ }
+ }
+
+ [Test]
+ public void TestRemoveAppender(
+ [Values(false, true)] bool isEnabled)
+ {
+ _logger = GetLogger(isEnabled);
+ if (isEnabled)
+ {
+ var appenders = _logger.GetAppenders();
+ Assert.AreEqual(1, appenders.Count);
+ _logger.RemoveAppender(appenders[0]);
+ Assert.AreEqual(0, appenders.Count);
+ }
+ else
+ {
+ Assert.Throws(() => _logger.RemoveAppender(new SFConsoleAppender()));
+ }
+ }
+
+ [Test]
+ public void TestSetLevel(
+ [Values(false, true)] bool isEnabled)
+ {
+ _logger = GetLogger(isEnabled);
+ if (isEnabled)
+ {
+ _logger.SetLevel(LoggingEvent.DEBUG);
+ Assert.AreEqual(LoggingEvent.DEBUG, ((SFLoggerImpl)_logger)._level);
+ }
+ else
+ {
+ Assert.Throws(() => _logger.SetLevel(LoggingEvent.DEBUG));
+ }
}
private SFLogger GetLogger(bool isEnabled)
{
if (isEnabled)
{
- SFLoggerFactory.enableLogger();
+ SFLoggerFactory.UseDefaultSFLogger();
}
else
{
- SFLoggerFactory.disableLogger();
+ SFLoggerFactory.UseEmptySFLogger();
}
- return SFLoggerFactory.GetLogger();
+ return SFLoggerFactory.GetSFLogger(false);
}
}
}
diff --git a/Snowflake.Data.Tests/log4net.config b/Snowflake.Data.Tests/log4net.config
new file mode 100644
index 000000000..e461d54b5
--- /dev/null
+++ b/Snowflake.Data.Tests/log4net.config
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Snowflake.Data/Configuration/EasyLoggingConfigFinder.cs b/Snowflake.Data/Configuration/EasyLoggingConfigFinder.cs
index d417eb4d8..d42c2573a 100644
--- a/Snowflake.Data/Configuration/EasyLoggingConfigFinder.cs
+++ b/Snowflake.Data/Configuration/EasyLoggingConfigFinder.cs
@@ -13,15 +13,15 @@ namespace Snowflake.Data.Configuration
{
internal class EasyLoggingConfigFinder
{
- private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger();
-
+ private static readonly SFLogger s_logger = SFLoggerFactory.GetSFLogger();
+
internal const string ClientConfigFileName = "sf_client_config.json";
internal const string ClientConfigEnvironmentName = "SF_CLIENT_CONFIG_FILE";
private readonly FileOperations _fileOperations;
private readonly UnixOperations _unixOperations;
private readonly EnvironmentOperations _environmentOperations;
-
+
public static readonly EasyLoggingConfigFinder Instance = new EasyLoggingConfigFinder(FileOperations.Instance, UnixOperations.Instance, EnvironmentOperations.Instance);
internal EasyLoggingConfigFinder(FileOperations fileOperations, UnixOperations unixFileOperations, EnvironmentOperations environmentOperations)
@@ -47,7 +47,7 @@ public virtual string FindConfigFilePath(string configFilePathFromConnectionStri
}
return configFilePath;
}
-
+
private string GetFilePathEnvironmentVariable()
{
var filePath = _environmentOperations.GetEnvironmentVariable(ClientConfigEnvironmentName);
@@ -69,7 +69,7 @@ private string GetFilePathFromInputParameter(string filePath, string inputDescri
private string GetHomeDirectory() => HomeDirectoryProvider.HomeDirectory(_environmentOperations);
private string GetFilePathFromDriverLocation() => SearchForConfigInDirectory(() => _environmentOperations.GetExecutionDirectory(), "driver");
-
+
private string SearchForConfigInDirectory(Func directoryProvider, string directoryDescription)
{
try
diff --git a/Snowflake.Data/Configuration/EasyLoggingConfigParser.cs b/Snowflake.Data/Configuration/EasyLoggingConfigParser.cs
index dbc6820b4..48b244b57 100644
--- a/Snowflake.Data/Configuration/EasyLoggingConfigParser.cs
+++ b/Snowflake.Data/Configuration/EasyLoggingConfigParser.cs
@@ -15,7 +15,7 @@ namespace Snowflake.Data.Configuration
{
internal class EasyLoggingConfigParser
{
- private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger();
+ private static readonly SFLogger s_logger = SFLoggerFactory.GetSFLogger();
public static readonly EasyLoggingConfigParser Instance = new EasyLoggingConfigParser();
@@ -45,7 +45,8 @@ private string TryToReadFile(string filePath)
private ClientConfig TryToParseFile(string fileContent)
{
- try {
+ try
+ {
var config = JsonConvert.DeserializeObject(fileContent);
Validate(config);
CheckForUnknownFields(fileContent);
diff --git a/Snowflake.Data/Configuration/EasyLoggingConfigProvider.cs b/Snowflake.Data/Configuration/EasyLoggingConfigProvider.cs
index 32788fae0..027d6b695 100644
--- a/Snowflake.Data/Configuration/EasyLoggingConfigProvider.cs
+++ b/Snowflake.Data/Configuration/EasyLoggingConfigProvider.cs
@@ -15,7 +15,7 @@ internal class EasyLoggingConfigProvider
internal EasyLoggingConfigProvider() : this(EasyLoggingConfigFinder.Instance, EasyLoggingConfigParser.Instance)
{
}
-
+
internal EasyLoggingConfigProvider(EasyLoggingConfigFinder finder, EasyLoggingConfigParser configParser)
{
_finder = finder;
diff --git a/Snowflake.Data/Configuration/EasyLoggingLogLevel.cs b/Snowflake.Data/Configuration/EasyLoggingLogLevel.cs
index 450a9bcf1..0b6ebe31c 100644
--- a/Snowflake.Data/Configuration/EasyLoggingLogLevel.cs
+++ b/Snowflake.Data/Configuration/EasyLoggingLogLevel.cs
@@ -6,9 +6,10 @@
namespace Snowflake.Data.Configuration
{
- internal enum EasyLoggingLogLevel
+ public enum EasyLoggingLogLevel
{
Off,
+ Fatal,
Error,
Warn,
Info,
diff --git a/Snowflake.Data/Core/ArrowResultSet.cs b/Snowflake.Data/Core/ArrowResultSet.cs
old mode 100755
new mode 100644
diff --git a/Snowflake.Data/Core/HttpUtil.cs b/Snowflake.Data/Core/HttpUtil.cs
old mode 100755
new mode 100644
diff --git a/Snowflake.Data/Core/SFBlockingChunkDownloaderV3.cs b/Snowflake.Data/Core/SFBlockingChunkDownloaderV3.cs
index 2e19146aa..955af16a4 100755
--- a/Snowflake.Data/Core/SFBlockingChunkDownloaderV3.cs
+++ b/Snowflake.Data/Core/SFBlockingChunkDownloaderV3.cs
@@ -1,239 +1,239 @@
-/*
- * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
- */
-
-using System;
-using System.IO.Compression;
-using System.IO;
-using System.Collections;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Net.Http;
-using Newtonsoft.Json;
-using System.Diagnostics;
-using Newtonsoft.Json.Serialization;
-using Snowflake.Data.Log;
-
-namespace Snowflake.Data.Core
-{
- class SFBlockingChunkDownloaderV3 : IChunkDownloader
- {
- static private SFLogger logger = SFLoggerFactory.GetLogger();
-
- private List chunkDatas = new List();
-
- private string qrmk;
-
- private int nextChunkToDownloadIndex;
-
- private int nextChunkToConsumeIndex;
-
- // External cancellation token, used to stop donwload
- private CancellationToken externalCancellationToken;
-
- private readonly int prefetchSlot;
-
- private readonly IRestRequester _RestRequester;
-
- private readonly SFSessionProperties sessionProperies;
-
- private Dictionary chunkHeaders;
-
- private readonly SFBaseResultSet ResultSet;
-
- private readonly List chunkInfos;
-
- private readonly List> taskQueues;
-
- public SFBlockingChunkDownloaderV3(int colCount,
- List chunkInfos, string qrmk,
- Dictionary chunkHeaders,
- CancellationToken cancellationToken,
- SFBaseResultSet ResultSet,
- ResultFormat resultFormat)
- {
- this.qrmk = qrmk;
- this.chunkHeaders = chunkHeaders;
- this.nextChunkToDownloadIndex = 0;
- this.ResultSet = ResultSet;
- this._RestRequester = ResultSet.sfStatement.SfSession.restRequester;
- this.sessionProperies = ResultSet.sfStatement.SfSession.properties;
- this.prefetchSlot = Math.Min(chunkInfos.Count, GetPrefetchThreads(ResultSet));
- this.chunkInfos = chunkInfos;
- this.nextChunkToConsumeIndex = 0;
- this.taskQueues = new List>();
- externalCancellationToken = cancellationToken;
-
- for (int i=0; i sessionParameters = resultSet.sfStatement.SfSession.ParameterMap;
- String val = (String)sessionParameters[SFSessionParameter.CLIENT_PREFETCH_THREADS];
- return Int32.Parse(val);
- }
-
- public async Task GetNextChunkAsync()
- {
- logger.Info($"NextChunkToConsume: {nextChunkToConsumeIndex}, NextChunkToDownload: {nextChunkToDownloadIndex}");
- if (nextChunkToConsumeIndex < chunkInfos.Count)
- {
- Task chunk = taskQueues[nextChunkToConsumeIndex % prefetchSlot];
-
- if (nextChunkToDownloadIndex < chunkInfos.Count && nextChunkToConsumeIndex > 0)
- {
- BaseResultChunk reusableChunk = chunkDatas[nextChunkToDownloadIndex % prefetchSlot];
- reusableChunk.Reset(chunkInfos[nextChunkToDownloadIndex], nextChunkToDownloadIndex);
-
- taskQueues[nextChunkToDownloadIndex % prefetchSlot] = DownloadChunkAsync(new DownloadContextV3()
- {
- chunk = reusableChunk,
- qrmk = this.qrmk,
- chunkHeaders = this.chunkHeaders,
- cancellationToken = externalCancellationToken
- });
- nextChunkToDownloadIndex++;
-
- // in case of one slot we need to return the chunk already downloaded
- if (prefetchSlot == 1)
- {
- chunk = taskQueues[0];
- }
- }
- nextChunkToConsumeIndex++;
- return await chunk;
- }
- else
- {
- return await Task.FromResult(null);
- }
- }
-
- private async Task DownloadChunkAsync(DownloadContextV3 downloadContext)
- {
- BaseResultChunk chunk = downloadContext.chunk;
- int backOffInSec = 1;
- bool retry = false;
- int retryCount = 0;
- int maxRetry = int.Parse(sessionProperies[SFSessionProperty.MAXHTTPRETRIES]);
-
- do
- {
- retry = false;
-
- S3DownloadRequest downloadRequest =
- new S3DownloadRequest()
- {
- Url = new UriBuilder(chunk.Url).Uri,
- qrmk = downloadContext.qrmk,
- // s3 download request timeout to one hour
- RestTimeout = TimeSpan.FromHours(1),
- HttpTimeout = Timeout.InfiniteTimeSpan, // Disable timeout for each request
- chunkHeaders = downloadContext.chunkHeaders,
- sid = ResultSet.sfStatement.SfSession.sessionId
- };
-
- using (var httpResponse = await _RestRequester.GetAsync(downloadRequest, downloadContext.cancellationToken)
- .ConfigureAwait(continueOnCapturedContext: false))
- using (Stream stream = await httpResponse.Content.ReadAsStreamAsync()
- .ConfigureAwait(continueOnCapturedContext: false))
- {
- // retry on chunk downloading since the retry logic in HttpClient.RetryHandler
- // doesn't cover this. The GET request could be succeeded but network error
- // still could happen during reading chunk data from stream and that needs
- // retry as well.
- try
- {
- IEnumerable encoding;
- if (httpResponse.Content.Headers.TryGetValues("Content-Encoding", out encoding))
- {
- if (String.Compare(encoding.First(), "gzip", true) == 0)
- {
- using (Stream streamGzip = new GZipStream(stream, CompressionMode.Decompress))
- {
- await ParseStreamIntoChunk(streamGzip, chunk).ConfigureAwait(false);
- }
- }
- else
- {
- await ParseStreamIntoChunk(stream, chunk).ConfigureAwait(false);
- }
- }
- else
- {
- await ParseStreamIntoChunk(stream, chunk).ConfigureAwait(false);
- }
- }
- catch (Exception e)
- {
- if ((maxRetry <= 0) || (retryCount < maxRetry))
- {
- logger.Debug($"Retry {retryCount}/{maxRetry} of parse stream to chunk error: " + e.Message);
- retry = true;
- // reset the chunk before retry in case there could be garbage
- // data left from last attempt
- chunk.ResetForRetry();
- await Task.Delay(TimeSpan.FromSeconds(backOffInSec), downloadContext.cancellationToken).ConfigureAwait(false);
- ++retryCount;
- // Set next backoff time
- backOffInSec = backOffInSec * 2;
- if (backOffInSec > HttpUtil.MAX_BACKOFF)
- {
- backOffInSec = HttpUtil.MAX_BACKOFF;
- }
- }
- else
- {
- //parse error
- logger.Error("Failed retries of parse stream to chunk error: " + e.Message);
- throw new Exception("Parse stream to chunk error: " + e.Message);
- }
- }
- }
- } while (retry);
- logger.Info($"Succeed downloading chunk #{chunk.ChunkIndex}");
- return chunk;
- }
-
- private async Task ParseStreamIntoChunk(Stream content, BaseResultChunk resultChunk)
- {
- IChunkParser parser = ChunkParserFactory.Instance.GetParser(resultChunk.ResultFormat, content);
- await parser.ParseChunk(resultChunk);
- }
- }
-
- class DownloadContextV3
- {
- public BaseResultChunk chunk { get; set; }
-
- public string qrmk { get; set; }
-
- public Dictionary chunkHeaders { get; set; }
-
- public CancellationToken cancellationToken { get; set; }
- }
-}
+/*
+ * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
+ */
+
+using System;
+using System.IO.Compression;
+using System.IO;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Net.Http;
+using Newtonsoft.Json;
+using System.Diagnostics;
+using Newtonsoft.Json.Serialization;
+using Snowflake.Data.Log;
+
+namespace Snowflake.Data.Core
+{
+ class SFBlockingChunkDownloaderV3 : IChunkDownloader
+ {
+ static private SFLogger logger = SFLoggerFactory.GetLogger();
+
+ private List chunkDatas = new List();
+
+ private string qrmk;
+
+ private int nextChunkToDownloadIndex;
+
+ private int nextChunkToConsumeIndex;
+
+ // External cancellation token, used to stop donwload
+ private CancellationToken externalCancellationToken;
+
+ private readonly int prefetchSlot;
+
+ private readonly IRestRequester _RestRequester;
+
+ private readonly SFSessionProperties sessionProperies;
+
+ private Dictionary chunkHeaders;
+
+ private readonly SFBaseResultSet ResultSet;
+
+ private readonly List chunkInfos;
+
+ private readonly List> taskQueues;
+
+ public SFBlockingChunkDownloaderV3(int colCount,
+ List chunkInfos, string qrmk,
+ Dictionary chunkHeaders,
+ CancellationToken cancellationToken,
+ SFBaseResultSet ResultSet,
+ ResultFormat resultFormat)
+ {
+ this.qrmk = qrmk;
+ this.chunkHeaders = chunkHeaders;
+ this.nextChunkToDownloadIndex = 0;
+ this.ResultSet = ResultSet;
+ this._RestRequester = ResultSet.sfStatement.SfSession.restRequester;
+ this.sessionProperies = ResultSet.sfStatement.SfSession.properties;
+ this.prefetchSlot = Math.Min(chunkInfos.Count, GetPrefetchThreads(ResultSet));
+ this.chunkInfos = chunkInfos;
+ this.nextChunkToConsumeIndex = 0;
+ this.taskQueues = new List>();
+ externalCancellationToken = cancellationToken;
+
+ for (int i=0; i sessionParameters = resultSet.sfStatement.SfSession.ParameterMap;
+ String val = (String)sessionParameters[SFSessionParameter.CLIENT_PREFETCH_THREADS];
+ return Int32.Parse(val);
+ }
+
+ public async Task GetNextChunkAsync()
+ {
+ logger.Info($"NextChunkToConsume: {nextChunkToConsumeIndex}, NextChunkToDownload: {nextChunkToDownloadIndex}");
+ if (nextChunkToConsumeIndex < chunkInfos.Count)
+ {
+ Task chunk = taskQueues[nextChunkToConsumeIndex % prefetchSlot];
+
+ if (nextChunkToDownloadIndex < chunkInfos.Count && nextChunkToConsumeIndex > 0)
+ {
+ BaseResultChunk reusableChunk = chunkDatas[nextChunkToDownloadIndex % prefetchSlot];
+ reusableChunk.Reset(chunkInfos[nextChunkToDownloadIndex], nextChunkToDownloadIndex);
+
+ taskQueues[nextChunkToDownloadIndex % prefetchSlot] = DownloadChunkAsync(new DownloadContextV3()
+ {
+ chunk = reusableChunk,
+ qrmk = this.qrmk,
+ chunkHeaders = this.chunkHeaders,
+ cancellationToken = externalCancellationToken
+ });
+ nextChunkToDownloadIndex++;
+
+ // in case of one slot we need to return the chunk already downloaded
+ if (prefetchSlot == 1)
+ {
+ chunk = taskQueues[0];
+ }
+ }
+ nextChunkToConsumeIndex++;
+ return await chunk;
+ }
+ else
+ {
+ return await Task.FromResult(null);
+ }
+ }
+
+ private async Task DownloadChunkAsync(DownloadContextV3 downloadContext)
+ {
+ BaseResultChunk chunk = downloadContext.chunk;
+ int backOffInSec = 1;
+ bool retry = false;
+ int retryCount = 0;
+ int maxRetry = int.Parse(sessionProperies[SFSessionProperty.MAXHTTPRETRIES]);
+
+ do
+ {
+ retry = false;
+
+ S3DownloadRequest downloadRequest =
+ new S3DownloadRequest()
+ {
+ Url = new UriBuilder(chunk.Url).Uri,
+ qrmk = downloadContext.qrmk,
+ // s3 download request timeout to one hour
+ RestTimeout = TimeSpan.FromHours(1),
+ HttpTimeout = Timeout.InfiniteTimeSpan, // Disable timeout for each request
+ chunkHeaders = downloadContext.chunkHeaders,
+ sid = ResultSet.sfStatement.SfSession.sessionId
+ };
+
+ using (var httpResponse = await _RestRequester.GetAsync(downloadRequest, downloadContext.cancellationToken)
+ .ConfigureAwait(continueOnCapturedContext: false))
+ using (Stream stream = await httpResponse.Content.ReadAsStreamAsync()
+ .ConfigureAwait(continueOnCapturedContext: false))
+ {
+ // retry on chunk downloading since the retry logic in HttpClient.RetryHandler
+ // doesn't cover this. The GET request could be succeeded but network error
+ // still could happen during reading chunk data from stream and that needs
+ // retry as well.
+ try
+ {
+ IEnumerable encoding;
+ if (httpResponse.Content.Headers.TryGetValues("Content-Encoding", out encoding))
+ {
+ if (String.Compare(encoding.First(), "gzip", true) == 0)
+ {
+ using (Stream streamGzip = new GZipStream(stream, CompressionMode.Decompress))
+ {
+ await ParseStreamIntoChunk(streamGzip, chunk).ConfigureAwait(false);
+ }
+ }
+ else
+ {
+ await ParseStreamIntoChunk(stream, chunk).ConfigureAwait(false);
+ }
+ }
+ else
+ {
+ await ParseStreamIntoChunk(stream, chunk).ConfigureAwait(false);
+ }
+ }
+ catch (Exception e)
+ {
+ if ((maxRetry <= 0) || (retryCount < maxRetry))
+ {
+ logger.Debug($"Retry {retryCount}/{maxRetry} of parse stream to chunk error: " + e.Message);
+ retry = true;
+ // reset the chunk before retry in case there could be garbage
+ // data left from last attempt
+ chunk.ResetForRetry();
+ await Task.Delay(TimeSpan.FromSeconds(backOffInSec), downloadContext.cancellationToken).ConfigureAwait(false);
+ ++retryCount;
+ // Set next backoff time
+ backOffInSec = backOffInSec * 2;
+ if (backOffInSec > HttpUtil.MAX_BACKOFF)
+ {
+ backOffInSec = HttpUtil.MAX_BACKOFF;
+ }
+ }
+ else
+ {
+ //parse error
+ logger.Error("Failed retries of parse stream to chunk error: " + e.Message);
+ throw new Exception("Parse stream to chunk error: " + e.Message);
+ }
+ }
+ }
+ } while (retry);
+ logger.Info($"Succeed downloading chunk #{chunk.ChunkIndex}");
+ return chunk;
+ }
+
+ private async Task ParseStreamIntoChunk(Stream content, BaseResultChunk resultChunk)
+ {
+ IChunkParser parser = ChunkParserFactory.Instance.GetParser(resultChunk.ResultFormat, content);
+ await parser.ParseChunk(resultChunk);
+ }
+ }
+
+ class DownloadContextV3
+ {
+ public BaseResultChunk chunk { get; set; }
+
+ public string qrmk { get; set; }
+
+ public Dictionary chunkHeaders { get; set; }
+
+ public CancellationToken cancellationToken { get; set; }
+ }
+}
diff --git a/Snowflake.Data/Core/Session/EasyLoggingStarter.cs b/Snowflake.Data/Core/Session/EasyLoggingStarter.cs
index 3b07d349c..2358d8441 100644
--- a/Snowflake.Data/Core/Session/EasyLoggingStarter.cs
+++ b/Snowflake.Data/Core/Session/EasyLoggingStarter.cs
@@ -14,7 +14,7 @@ namespace Snowflake.Data.Core
{
internal class EasyLoggingStarter
{
- private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger();
+ private static readonly SFLogger s_logger = SFLoggerFactory.GetSFLogger();
private readonly EasyLoggingConfigProvider _easyLoggingConfigProvider;
diff --git a/Snowflake.Data/Logger/EasyLoggerManager.cs b/Snowflake.Data/Logger/EasyLoggerManager.cs
index 18ffe818b..8e364fc73 100644
--- a/Snowflake.Data/Logger/EasyLoggerManager.cs
+++ b/Snowflake.Data/Logger/EasyLoggerManager.cs
@@ -5,9 +5,6 @@
using System;
using System.IO;
using System.Linq;
-using log4net;
-using log4net.Appender;
-using log4net.Layout;
using Snowflake.Data.Configuration;
namespace Snowflake.Data.Log
@@ -18,23 +15,21 @@ internal class EasyLoggerManager
private readonly object _lockForExclusiveConfigure = new object();
- private const string AppenderPrefix = "SFEasyLogging";
+ internal const string AppenderPrefix = "SFEasyLogging";
private readonly EasyLoggingLevelMapper _levelMapper = EasyLoggingLevelMapper.Instance;
public virtual void ReconfigureEasyLogging(EasyLoggingLogLevel easyLoggingLogLevel, string logsPath)
{
- var log4netLevel = _levelMapper.ToLog4NetLevel(easyLoggingLogLevel);
+ var sfLoggerLevel = _levelMapper.ToLoggingEventLevel(easyLoggingLogLevel);
lock (_lockForExclusiveConfigure)
{
- var repository = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
- var rootLogger = (log4net.Repository.Hierarchy.Logger)repository.GetLogger("Snowflake.Data");
- rootLogger.Level = log4netLevel;
+ var rootLogger = SFLogRepository.GetRootLogger();
+ rootLogger.SetLevel(sfLoggerLevel);
var appender = IsStdout(logsPath)
? AddConsoleAppender(rootLogger)
: AddRollingFileAppender(rootLogger, logsPath);
RemoveOtherEasyLoggingAppenders(rootLogger, appender);
- repository.RaiseConfigurationChanged(EventArgs.Empty);
}
}
@@ -45,27 +40,24 @@ internal static bool IsStdout(string logsPath)
internal void ResetEasyLogging(EasyLoggingLogLevel easyLoggingLogLevel)
{
- var log4netLevel = _levelMapper.ToLog4NetLevel(easyLoggingLogLevel);
+ var sfLoggerLevel = _levelMapper.ToLoggingEventLevel(easyLoggingLogLevel);
lock (_lockForExclusiveConfigure)
{
- var repository = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
- var rootLogger = (log4net.Repository.Hierarchy.Logger)repository.GetLogger("Snowflake.Data");
- rootLogger.Level = log4netLevel;
+ var rootLogger = SFLogRepository.GetRootLogger();
+ rootLogger.SetLevel(sfLoggerLevel);
RemoveOtherEasyLoggingAppenders(rootLogger, null);
- repository.RaiseConfigurationChanged(EventArgs.Empty);
}
}
internal static bool HasEasyLoggingAppender()
{
- var repository = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
- var rootLogger = (log4net.Repository.Hierarchy.Logger)repository.GetLogger("Snowflake.Data");
- return rootLogger.Appenders.ToArray().Any(IsEasyLoggingAppender);
+ var rootLogger = SFLogRepository.GetRootLogger();
+ return rootLogger.GetAppenders().ToArray().Any(IsEasyLoggingAppender);
}
- private static void RemoveOtherEasyLoggingAppenders(log4net.Repository.Hierarchy.Logger logger, IAppender appender)
+ private static void RemoveOtherEasyLoggingAppenders(SFLogger logger, SFAppender appender)
{
- var existingAppenders = logger.Appenders.ToArray();
+ var existingAppenders = logger.GetAppenders().ToArray();
foreach (var existingAppender in existingAppenders)
{
if (IsEasyLoggingAppender(existingAppender) && existingAppender != appender)
@@ -75,67 +67,60 @@ private static void RemoveOtherEasyLoggingAppenders(log4net.Repository.Hierarchy
}
}
- private static IAppender AddRollingFileAppender(log4net.Repository.Hierarchy.Logger logger,
+ private static SFAppender AddRollingFileAppender(SFLogger logger,
string directoryPath)
{
var patternLayout = PatternLayout();
var randomFileName = $"snowflake_dotnet_{Path.GetRandomFileName()}";
var logFileName = randomFileName.Substring(0, randomFileName.Length - 4) + ".log";
- var appender = new RollingFileAppender
+ var appender = new SFRollingFileAppender
{
- Layout = patternLayout,
- AppendToFile = true,
- File = Path.Combine(directoryPath, logFileName),
- Name = $"{AppenderPrefix}RollingFileAppender",
- StaticLogFileName = true,
- RollingStyle = RollingFileAppender.RollingMode.Size,
- MaximumFileSize = "1GB",
- MaxSizeRollBackups = 2,
- PreserveLogFileNameExtension = true,
- LockingModel = new FileAppender.MinimalLock()
+ _patternLayout = patternLayout,
+ _logFilePath = Path.Combine(directoryPath, logFileName),
+ _name = $"{AppenderPrefix}RollingFileAppender",
+ _maximumFileSizeInBytes = 1000000000, // "1GB"
+ _maxSizeRollBackups = 2,
};
appender.ActivateOptions();
logger.AddAppender(appender);
return appender;
}
- private static bool IsEasyLoggingAppender(IAppender appender)
+ private static bool IsEasyLoggingAppender(SFAppender appender)
{
- if (appender.GetType() == typeof(ConsoleAppender))
+ if (appender.GetType() == typeof(SFConsoleAppender))
{
- var consoleAppender = (ConsoleAppender)appender;
- return consoleAppender.Name != null && consoleAppender.Name.StartsWith(AppenderPrefix);
+ var consoleAppender = (SFConsoleAppender)appender;
+ return consoleAppender._name != null && consoleAppender._name.StartsWith(AppenderPrefix);
}
- if (appender.GetType() == typeof(RollingFileAppender))
+ if (appender.GetType() == typeof(SFRollingFileAppender))
{
- var rollingFileAppender = (RollingFileAppender)appender;
- return rollingFileAppender.Name != null && rollingFileAppender.Name.StartsWith(AppenderPrefix);
+ var rollingFileAppender = (SFRollingFileAppender)appender;
+ return rollingFileAppender._name != null && rollingFileAppender._name.StartsWith(AppenderPrefix);
}
return false;
}
- private static IAppender AddConsoleAppender(log4net.Repository.Hierarchy.Logger logger)
+ private static SFAppender AddConsoleAppender(SFLogger logger)
{
var patternLayout = PatternLayout();
- var appender = new ConsoleAppender()
+ var appender = new SFConsoleAppender()
{
- Layout = patternLayout,
- Name = $"{AppenderPrefix}ConsoleAppender"
+ _patternLayout = patternLayout,
+ _name = $"{AppenderPrefix}ConsoleAppender"
};
- appender.ActivateOptions();
logger.AddAppender(appender);
return appender;
}
- private static PatternLayout PatternLayout()
+ internal static PatternLayout PatternLayout()
{
var patternLayout = new PatternLayout
{
- ConversionPattern = "[%date] [%t] [%-5level] [%logger] %message%newline"
+ _conversionPattern = "[%date] [%t] [%-5level] [%logger] %message%newline"
};
- patternLayout.ActivateOptions();
return patternLayout;
}
}
diff --git a/Snowflake.Data/Logger/EasyLoggingLevelMapper.cs b/Snowflake.Data/Logger/EasyLoggingLevelMapper.cs
index 609265e30..6364321f2 100644
--- a/Snowflake.Data/Logger/EasyLoggingLevelMapper.cs
+++ b/Snowflake.Data/Logger/EasyLoggingLevelMapper.cs
@@ -3,7 +3,6 @@
*/
using System;
-using log4net.Core;
using Snowflake.Data.Configuration;
namespace Snowflake.Data.Log
@@ -12,18 +11,19 @@ internal class EasyLoggingLevelMapper
{
public static readonly EasyLoggingLevelMapper Instance = new EasyLoggingLevelMapper();
- public Level ToLog4NetLevel(EasyLoggingLogLevel level)
+ public LoggingEvent ToLoggingEventLevel(EasyLoggingLogLevel level)
{
switch (level)
{
- case EasyLoggingLogLevel.Off: return Level.Off;
- case EasyLoggingLogLevel.Error: return Level.Error;
- case EasyLoggingLogLevel.Warn: return Level.Warn;
- case EasyLoggingLogLevel.Info: return Level.Info;
- case EasyLoggingLogLevel.Debug: return Level.Debug;
- case EasyLoggingLogLevel.Trace: return Level.Trace;
+ case EasyLoggingLogLevel.Off: return LoggingEvent.OFF;
+ case EasyLoggingLogLevel.Fatal: return LoggingEvent.FATAL;
+ case EasyLoggingLogLevel.Error: return LoggingEvent.ERROR;
+ case EasyLoggingLogLevel.Warn: return LoggingEvent.WARN;
+ case EasyLoggingLogLevel.Info: return LoggingEvent.INFO;
+ case EasyLoggingLogLevel.Debug: return LoggingEvent.DEBUG;
+ case EasyLoggingLogLevel.Trace: return LoggingEvent.TRACE;
default: throw new Exception("Unknown log level");
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Snowflake.Data/Logger/ILoggerEmptyImpl.cs b/Snowflake.Data/Logger/ILoggerEmptyImpl.cs
new file mode 100644
index 000000000..4a0d83374
--- /dev/null
+++ b/Snowflake.Data/Logger/ILoggerEmptyImpl.cs
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
+ */
+
+using Microsoft.Extensions.Logging;
+using System;
+
+namespace Snowflake.Data.Log
+{
+ // Empty implementation of SFLogger
+ // Used when SFLoggerFactory.disableLogger() is called.
+
+ class ILoggerEmptyImpl : ILogger
+ {
+ IDisposable ILogger.BeginScope(TState state)
+ {
+ throw new NotImplementedException();
+ }
+
+ bool ILogger.IsEnabled(LogLevel logLevel)
+ {
+ return false;
+ }
+
+ void ILogger.Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ {
+ return;
+ }
+ }
+
+}
diff --git a/Snowflake.Data/Logger/Log4netImpl.cs b/Snowflake.Data/Logger/Log4netImpl.cs
deleted file mode 100755
index 4a9ec00d6..000000000
--- a/Snowflake.Data/Logger/Log4netImpl.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
- */
-
-using log4net;
-using System;
-
-namespace Snowflake.Data.Log
-{
- // Default implementation for SFLogger
-
- class Log4NetImpl : SFLogger
- {
- private readonly ILog logger;
-
- public Log4NetImpl(ILog logger)
- {
- this.logger = logger;
- }
-
- public bool IsDebugEnabled()
- {
- return logger.IsDebugEnabled;
- }
-
- public bool IsInfoEnabled()
- {
- return logger.IsInfoEnabled;
- }
-
- public bool IsWarnEnabled()
- {
- return logger.IsWarnEnabled;
- }
-
- public bool IsErrorEnabled()
- {
- return logger.IsErrorEnabled;
- }
-
- public bool IsFatalEnabled()
- {
- return logger.IsFatalEnabled;
- }
-
- public void Debug(string msg, Exception ex = null)
- {
- msg = SecretDetector.MaskSecrets(msg).maskedText;
- logger.Debug(msg, ex);
- }
-
- public void Info(string msg, Exception ex = null)
- {
- msg = SecretDetector.MaskSecrets(msg).maskedText;
- logger.Info(msg, ex);
- }
-
- public void Warn(string msg, Exception ex = null)
- {
- msg = SecretDetector.MaskSecrets(msg).maskedText;
- logger.Warn(msg, ex);
- }
-
-
- public void Error(string msg, Exception ex = null)
- {
- msg = SecretDetector.MaskSecrets(msg).maskedText;
- logger.Error(msg, ex);
- }
-
- public void Fatal(string msg, Exception ex = null)
- {
- msg = SecretDetector.MaskSecrets(msg).maskedText;
- logger.Fatal(msg, ex);
- }
- }
-
-}
diff --git a/Snowflake.Data/Logger/SFAppender.cs b/Snowflake.Data/Logger/SFAppender.cs
new file mode 100644
index 000000000..becfebc7f
--- /dev/null
+++ b/Snowflake.Data/Logger/SFAppender.cs
@@ -0,0 +1,13 @@
+/*
+ //* Copyright (c) 2024 Snowflake Computing Inc. All rights reserved.
+ */
+
+using System;
+
+namespace Snowflake.Data.Log
+{
+ public interface SFAppender
+ {
+ void Append(string logLevel, string message, Type type, Exception ex = null);
+ }
+}
diff --git a/Snowflake.Data/Logger/SFAppenderImpl.cs b/Snowflake.Data/Logger/SFAppenderImpl.cs
new file mode 100644
index 000000000..38ee5089b
--- /dev/null
+++ b/Snowflake.Data/Logger/SFAppenderImpl.cs
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved.
+ */
+
+using Snowflake.Data.Log;
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading;
+
+internal class PatternLayout
+{
+ internal string _conversionPattern;
+
+ public PatternLayout() { }
+
+ public string Format(string logLevel, string message, Type type)
+ {
+ var formattedMessage = _conversionPattern
+ .Replace("%date", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))
+ .Replace("%t", Thread.CurrentThread.ManagedThreadId.ToString())
+ .Replace("%-5level", logLevel)
+ .Replace("%logger", type.ToString())
+ .Replace("%message", message)
+ .Replace("%newline", "\n");
+
+ return formattedMessage;
+ }
+}
+
+internal class SFConsoleAppender : SFAppender
+{
+ internal string _name;
+ internal PatternLayout _patternLayout;
+
+ public SFConsoleAppender() { }
+
+ public void Append(string logLevel, string message, Type type, Exception ex = null)
+ {
+ var formattedMessage = _patternLayout.Format(logLevel, message, type);
+ Console.Write(formattedMessage);
+ if (ex != null)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ }
+}
+
+internal class SFRollingFileAppender : SFAppender
+{
+ internal string _name;
+ internal string _logFilePath;
+ internal long _maximumFileSizeInBytes;
+ internal int _maxSizeRollBackups;
+ internal PatternLayout _patternLayout;
+
+ public SFRollingFileAppender() { }
+
+ public void Append(string logLevel, string message, Type type, Exception ex = null)
+ {
+ var formattedMessage = _patternLayout.Format(logLevel, message, type);
+ try
+ {
+ if (LogFileIsTooLarge())
+ {
+ RollLogFile();
+ }
+
+ using (FileStream fs = new FileStream(_logFilePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
+ {
+ using (var writer = new StreamWriter(fs))
+ {
+ writer.Write(formattedMessage);
+ if (ex != null)
+ {
+ writer.WriteLine(ex.Message);
+ }
+ }
+ }
+ }
+ catch (Exception logEx)
+ {
+ Console.WriteLine($"Unable to log the following message:\n{formattedMessage}" +
+ $"Due to the error: {logEx.Message}\n");
+ }
+ }
+
+ public void ActivateOptions()
+ {
+ var logDir = Path.GetDirectoryName(_logFilePath);
+ if (!Directory.Exists(logDir))
+ {
+ Directory.CreateDirectory(logDir);
+ }
+ if (!File.Exists(_logFilePath))
+ {
+ File.Create(_logFilePath).Dispose();
+ }
+ }
+
+ private bool LogFileIsTooLarge()
+ {
+ FileInfo fileInfo = new FileInfo(_logFilePath);
+ return fileInfo.Exists && fileInfo.Length > _maximumFileSizeInBytes;
+ }
+
+ private void RollLogFile()
+ {
+ string rollFilePath = $"{_logFilePath}.{DateTime.Now:yyyyMMddHHmmss}.bak";
+ File.Move(_logFilePath, rollFilePath);
+
+ var logDirectory = Path.GetDirectoryName(_logFilePath);
+ var logFileName = Path.GetFileName(_logFilePath);
+ var rollFiles = Directory.GetFiles(logDirectory, $"{logFileName}.*.bak")
+ .OrderByDescending(f => f)
+ .Skip(_maxSizeRollBackups);
+ foreach (var oldRollFile in rollFiles)
+ {
+ File.Delete(oldRollFile);
+ }
+ }
+}
diff --git a/Snowflake.Data/Logger/SFLogger.cs b/Snowflake.Data/Logger/SFLogger.cs
old mode 100755
new mode 100644
index 28a8a5b3c..167a171f7
--- a/Snowflake.Data/Logger/SFLogger.cs
+++ b/Snowflake.Data/Logger/SFLogger.cs
@@ -1,39 +1,45 @@
-/*
- * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
- */
-
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Snowflake.Data.Log
-{
- interface SFLogger
- {
- bool IsDebugEnabled();
-
- bool IsInfoEnabled();
-
- bool IsWarnEnabled();
-
- bool IsErrorEnabled();
-
- bool IsFatalEnabled();
-
- void Debug(string msg, Exception ex = null);
-
- void Info(string msg, Exception ex = null);
-
- void Warn(string msg, Exception ex = null);
-
- void Error(string msg, Exception ex = null);
-
- void Fatal(string msg, Exception ex = null);
- }
-
- enum LoggingEvent
- {
- DEBUG, INFO, WARN, ERROR, FATAL
- }
-
-}
\ No newline at end of file
+/*
+ * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
+ */
+
+using System;
+using System.Collections.Generic;
+
+namespace Snowflake.Data.Log
+{
+ public interface SFLogger
+ {
+ bool IsDebugEnabled();
+
+ bool IsInfoEnabled();
+
+ bool IsWarnEnabled();
+
+ bool IsErrorEnabled();
+
+ bool IsFatalEnabled();
+
+ void Debug(string msg, Exception ex = null);
+
+ void Info(string msg, Exception ex = null);
+
+ void Warn(string msg, Exception ex = null);
+
+ void Error(string msg, Exception ex = null);
+
+ void Fatal(string msg, Exception ex = null);
+
+ List GetAppenders();
+
+ void AddAppender(SFAppender appender);
+
+ void RemoveAppender(SFAppender appender);
+
+ void SetLevel(LoggingEvent level);
+ }
+
+ public enum LoggingEvent
+ {
+ OFF, TRACE, DEBUG, INFO, WARN, ERROR, FATAL
+ }
+}
diff --git a/Snowflake.Data/Logger/SFLoggerEmptyImpl.cs b/Snowflake.Data/Logger/SFLoggerEmptyImpl.cs
index 2508c4309..1e6eb2e68 100644
--- a/Snowflake.Data/Logger/SFLoggerEmptyImpl.cs
+++ b/Snowflake.Data/Logger/SFLoggerEmptyImpl.cs
@@ -1,8 +1,9 @@
-/*
+/*
* Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
*/
-
- using System;
+
+using System;
+using System.Collections.Generic;
namespace Snowflake.Data.Log
{
@@ -60,6 +61,26 @@ public void Fatal(string msg, Exception ex)
{
return;
}
+
+ List SFLogger.GetAppenders()
+ {
+ throw new NotImplementedException();
+ }
+
+ void SFLogger.AddAppender(SFAppender appender)
+ {
+ throw new NotImplementedException();
+ }
+
+ void SFLogger.RemoveAppender(SFAppender appender)
+ {
+ throw new NotImplementedException();
+ }
+
+ void SFLogger.SetLevel(LoggingEvent level)
+ {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/Snowflake.Data/Logger/SFLoggerFactory.cs b/Snowflake.Data/Logger/SFLoggerFactory.cs
index e38440167..4e212ab7e 100755
--- a/Snowflake.Data/Logger/SFLoggerFactory.cs
+++ b/Snowflake.Data/Logger/SFLoggerFactory.cs
@@ -1,51 +1,88 @@
-/*
+/*
* Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
*/
-using log4net;
+using Microsoft.Extensions.Logging;
+using System.IO;
namespace Snowflake.Data.Log
{
- class SFLoggerFactory
+ public class SFLoggerFactory
{
- private static bool isLoggerEnabled = true;
+ private static bool s_isCustomLoggerEnabled = false;
- private static SFLogger logger = null;
+ private static bool s_isSFLoggerEnabled = false;
- private SFLoggerFactory()
+ private static bool s_useDefaultSFLogger = true;
+
+ private static ILogger s_customLogger = null;
+
+ public static void UseEmptySFLogger()
+ {
+ s_useDefaultSFLogger = false;
+ }
+
+ public static void UseDefaultSFLogger()
+ {
+ s_useDefaultSFLogger = true;
+ }
+
+ public static void DisableCustomLogger()
{
+ s_isCustomLoggerEnabled = false;
}
- public static void disableLogger()
+ public static void EnableCustomLogger()
{
- isLoggerEnabled = false;
+ s_isCustomLoggerEnabled = true;
}
- public static void enableLogger()
+ public static void UseDefaultLogger()
{
- isLoggerEnabled = true;
+ s_customLogger = null;
}
- public static void useDefaultLogger()
+ public static void SetCustomLogger(ILogger customLogger)
{
- logger = null;
+ s_customLogger = customLogger;
+ SFLoggerPair.s_customLogger = s_customLogger;
}
- public static void Instance(SFLogger customLogger)
- {
- logger = customLogger;
+ internal static SFLogger GetLogger()
+ {
+ return new SFLoggerPair(GetSFLogger(), GetCustomLogger());
}
- public static SFLogger GetLogger()
+ internal static SFLogger GetSFLogger(bool useFileAppender = true)
{
// If true, return the default/specified logger
- if (isLoggerEnabled)
+ if (s_useDefaultSFLogger)
{
- // If no logger specified, use the default logger: log4net
- if (logger == null)
+ var logger = new SFLoggerImpl(typeof(T));
+ if (!s_isSFLoggerEnabled)
+ {
+ logger.SetLevel(LoggingEvent.OFF); // Logger is disabled by default and can be enabled by the EasyLogging feature
+ }
+ if (useFileAppender)
{
- ILog loggerL = LogManager.GetLogger(typeof(T));
- return new Log4NetImpl(loggerL);
+ var fileAppender = new SFRollingFileAppender()
+ {
+ _name = "RollingFileAppender",
+ _logFilePath = Path.Combine(Directory.GetCurrentDirectory(), "test_snowflake_log.log"),
+ _maximumFileSizeInBytes = 1000000000, // "1GB"
+ _maxSizeRollBackups = 0,
+ _patternLayout = EasyLoggerManager.PatternLayout()
+ };
+ logger.AddAppender(fileAppender);
+ }
+ else
+ {
+ var consoleAppender = new SFConsoleAppender()
+ {
+ _name = "ConsoleAppender",
+ _patternLayout = EasyLoggerManager.PatternLayout()
+ };
+ logger.AddAppender(consoleAppender);
}
return logger;
}
@@ -55,6 +92,30 @@ public static SFLogger GetLogger()
return new SFLoggerEmptyImpl();
}
}
- }
+ internal static ILogger GetCustomLogger()
+ {
+ // If true, return the default/specified logger
+ if (s_isCustomLoggerEnabled)
+ {
+ // If no logger specified, use the default logger: Microsoft's console logger
+ if (s_customLogger == null)
+ {
+ ILoggerFactory factory = LoggerFactory.Create(
+ builder => builder
+ .AddConsole()
+ .SetMinimumLevel(LogLevel.Trace)
+ );
+
+ return factory.CreateLogger();
+ }
+ return s_customLogger;
+ }
+ // Else, return the empty logger implementation which outputs nothing
+ else
+ {
+ return new ILoggerEmptyImpl();
+ }
+ }
+ }
}
diff --git a/Snowflake.Data/Logger/SFLoggerImpl.cs b/Snowflake.Data/Logger/SFLoggerImpl.cs
new file mode 100644
index 000000000..0c09dfdc3
--- /dev/null
+++ b/Snowflake.Data/Logger/SFLoggerImpl.cs
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved.
+ */
+
+using Snowflake.Data.Log;
+using System;
+using System.Collections.Generic;
+
+public static class SFLogRepository
+{
+ internal static SFLogger s_rootLogger = new SFLoggerImpl(typeof(SFLogRepository));
+
+ internal static SFLogger GetRootLogger()
+ {
+ return s_rootLogger;
+ }
+}
+
+public class SFLoggerImpl : SFLogger
+{
+ private readonly Type _type;
+ internal readonly List _appenders;
+ internal LoggingEvent _level;
+
+ private bool _isDebugEnabled;
+ private bool _isInfoEnabled;
+ private bool _isWarnEnabled;
+ private bool _isErrorEnabled;
+ private bool _isFatalEnabled;
+
+ internal SFLoggerImpl(Type type, LoggingEvent level = LoggingEvent.DEBUG)
+ {
+ _appenders = new List();
+ _type = type;
+ SetLevel(level);
+ }
+
+ public void SetLevel(LoggingEvent level)
+ {
+ _level = level;
+ SetEnableValues();
+ }
+
+ private void SetEnableValues()
+ {
+ var enabled = _level != LoggingEvent.OFF;
+ _isDebugEnabled = enabled;
+ _isInfoEnabled = enabled;
+ _isWarnEnabled = enabled;
+ _isErrorEnabled = enabled;
+ _isFatalEnabled = enabled;
+
+ if (enabled)
+ {
+ switch (_level)
+ {
+ case LoggingEvent.TRACE:
+ case LoggingEvent.DEBUG:
+ break;
+ case LoggingEvent.FATAL:
+ _isErrorEnabled = false;
+ goto case LoggingEvent.ERROR;
+ case LoggingEvent.ERROR:
+ _isWarnEnabled = false;
+ goto case LoggingEvent.WARN;
+ case LoggingEvent.WARN:
+ _isInfoEnabled = false;
+ goto case LoggingEvent.INFO;
+ case LoggingEvent.INFO:
+ _isDebugEnabled = false;
+ break;
+ }
+ }
+ }
+
+ public List GetAppenders()
+ {
+ return _appenders;
+ }
+
+ public void AddAppender(SFAppender appender)
+ {
+ _appenders.Add(appender);
+ }
+
+ public void RemoveAppender(SFAppender appender)
+ {
+ _appenders.Remove(appender);
+ }
+
+ public bool IsDebugEnabled()
+ {
+ return SFLogRepository.s_rootLogger == this ?
+ _isDebugEnabled :
+ SFLogRepository.s_rootLogger.IsDebugEnabled();
+ }
+
+ public bool IsInfoEnabled()
+ {
+ return SFLogRepository.s_rootLogger == this ?
+ _isInfoEnabled :
+ SFLogRepository.s_rootLogger.IsInfoEnabled();
+ }
+
+ public bool IsWarnEnabled()
+ {
+ return SFLogRepository.s_rootLogger == this ?
+ _isWarnEnabled :
+ SFLogRepository.s_rootLogger.IsWarnEnabled();
+ }
+
+ public bool IsErrorEnabled()
+ {
+ return SFLogRepository.s_rootLogger == this ?
+ _isErrorEnabled :
+ SFLogRepository.s_rootLogger.IsErrorEnabled();
+ }
+
+ public bool IsFatalEnabled()
+ {
+ return SFLogRepository.s_rootLogger == this ?
+ _isFatalEnabled :
+ SFLogRepository.s_rootLogger.IsFatalEnabled();
+ }
+
+ public void Debug(string msg, Exception ex = null)
+ {
+ if (IsDebugEnabled())
+ {
+ Log(LoggingEvent.DEBUG.ToString(), msg, ex);
+ }
+ }
+
+ public void Info(string msg, Exception ex = null)
+ {
+ if (IsInfoEnabled())
+ {
+ Log(LoggingEvent.INFO.ToString(), msg, ex);
+ }
+ }
+
+ public void Warn(string msg, Exception ex = null)
+ {
+ if (IsWarnEnabled())
+ {
+ Log(LoggingEvent.WARN.ToString(), msg, ex);
+ }
+ }
+
+
+ public void Error(string msg, Exception ex = null)
+ {
+ if (IsErrorEnabled())
+ {
+ Log(LoggingEvent.ERROR.ToString(), msg, ex);
+ }
+ }
+
+ public void Fatal(string msg, Exception ex = null)
+ {
+ if (IsFatalEnabled())
+ {
+ Log(LoggingEvent.FATAL.ToString(), msg, ex);
+ }
+ }
+
+ private void Log(string logLevel, string logMessage, Exception ex = null)
+ {
+ var rootAppenders = SFLogRepository.s_rootLogger.GetAppenders();
+ var appenders = rootAppenders.Count > 0 ? rootAppenders : _appenders;
+ foreach (var appender in appenders)
+ {
+ appender.Append(logLevel, logMessage, _type, ex);
+ }
+ }
+}
diff --git a/Snowflake.Data/Logger/SFLoggerPair.cs b/Snowflake.Data/Logger/SFLoggerPair.cs
new file mode 100644
index 000000000..92b176e00
--- /dev/null
+++ b/Snowflake.Data/Logger/SFLoggerPair.cs
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved.
+ */
+
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Snowflake.Data.Log
+{
+ public class SFLoggerPair : SFLogger
+ {
+ private static SFLogger s_snowflakeLogger;
+ internal static ILogger s_customLogger;
+
+ public SFLoggerPair(SFLogger snowflakeLogger, ILogger customLogger)
+ {
+ s_snowflakeLogger = snowflakeLogger;
+ s_customLogger = customLogger;
+ }
+
+ public void Debug(string message, Exception ex = null)
+ {
+ message = SecretDetector.MaskSecrets(message).maskedText;
+ s_snowflakeLogger.Debug(message, ex);
+ s_customLogger.LogDebug(FormatBrackets(message), ex);
+ }
+
+ public void Info(string message, Exception ex = null)
+ {
+ message = SecretDetector.MaskSecrets(message).maskedText;
+ s_snowflakeLogger.Info(message, ex);
+ s_customLogger.LogInformation(FormatBrackets(message), ex);
+ }
+
+ public void Warn(string message, Exception ex = null)
+ {
+ message = SecretDetector.MaskSecrets(message).maskedText;
+ s_snowflakeLogger.Warn(message, ex);
+ s_customLogger.LogWarning(FormatBrackets(message), ex);
+ }
+
+ public void Error(string message, Exception ex = null)
+ {
+ message = SecretDetector.MaskSecrets(message).maskedText;
+ s_snowflakeLogger.Error(message, ex);
+ s_customLogger.LogError(FormatBrackets(message), ex);
+ }
+
+ public void Fatal(string message, Exception ex = null)
+ {
+ message = SecretDetector.MaskSecrets(message).maskedText;
+ s_snowflakeLogger.Fatal(message, ex);
+ s_customLogger.LogCritical(FormatBrackets(message), ex);
+ }
+
+ public bool IsDebugEnabled()
+ {
+ return s_snowflakeLogger.IsDebugEnabled() ||
+ s_customLogger.IsEnabled(LogLevel.Debug);
+ }
+
+ public bool IsInfoEnabled()
+ {
+ return s_snowflakeLogger.IsInfoEnabled() ||
+ s_customLogger.IsEnabled(LogLevel.Information);
+ }
+
+ public bool IsWarnEnabled()
+ {
+ return s_snowflakeLogger.IsWarnEnabled() ||
+ s_customLogger.IsEnabled(LogLevel.Warning);
+ }
+
+ public bool IsErrorEnabled()
+ {
+ return s_snowflakeLogger.IsErrorEnabled() ||
+ s_customLogger.IsEnabled(LogLevel.Error);
+ }
+
+ public bool IsFatalEnabled()
+ {
+ return s_snowflakeLogger.IsFatalEnabled() ||
+ s_customLogger.IsEnabled(LogLevel.Critical);
+ }
+
+ public List GetAppenders()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void AddAppender(SFAppender appender)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void RemoveAppender(SFAppender appender)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void SetLevel(LoggingEvent level)
+ {
+ throw new NotImplementedException();
+ }
+
+ private string FormatBrackets(string message)
+ {
+ var sb = new StringBuilder(message).Replace("{", "{{").Replace("}", "}}");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/Snowflake.Data/Snowflake.Data.csproj b/Snowflake.Data/Snowflake.Data.csproj
index d38c0b0e1..79e1c0899 100644
--- a/Snowflake.Data/Snowflake.Data.csproj
+++ b/Snowflake.Data/Snowflake.Data.csproj
@@ -22,10 +22,12 @@
+
+
-
+