diff --git a/dotnet/DotNetStandardClasses.sln b/dotnet/DotNetStandardClasses.sln index 6b41cd91d..037774d6b 100644 --- a/dotnet/DotNetStandardClasses.sln +++ b/dotnet/DotNetStandardClasses.sln @@ -104,6 +104,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GXAzureStorage", "src\dotnetcore\Providers\Storage\GXAzureStorage\GXAzureStorage.csproj", "{BF72FAF5-3A7C-41B6-A27F-9BE049290356}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1D6F1776-FF4B-46C2-9B3D-BC46CCF049DC}" + ProjectSection(SolutionItems) = preProject + src\dotnetcore\GxNetCoreStartup\Startup.cs = src\dotnetcore\GxNetCoreStartup\Startup.cs + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GxEncryptCMD", "src\dotnetframework\GxEncryptCMD\GxEncryptCMD.csproj", "{E2CC404A-3AE4-4CE9-84DC-0EA6078F69E3}" EndProject @@ -255,6 +258,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GXAzureEventGrid", "src\dot EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCoreAttackMitigationTest", "test\DotNetCoreAttackMitigationTest\DotNetCoreAttackMitigationTest.csproj", "{2D615969-53E2-4B77-9A9A-75C33865CF76}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gxlogging", "gxlogging", "{AA04A8A9-3CBD-431D-9711-C4D481E4A1E5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeneXus.Log.Azure.AzureAppInsights", "src\dotnetcore\Providers\Logging\GeneXus.Log.Azure.AzureAppInsights\GeneXus.Log.Azure.AzureAppInsights.csproj", "{6B28F942-D355-44A2-9C5D-92AF2D256A0D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -613,6 +620,10 @@ Global {2D615969-53E2-4B77-9A9A-75C33865CF76}.Debug|Any CPU.Build.0 = Debug|Any CPU {2D615969-53E2-4B77-9A9A-75C33865CF76}.Release|Any CPU.ActiveCfg = Release|Any CPU {2D615969-53E2-4B77-9A9A-75C33865CF76}.Release|Any CPU.Build.0 = Release|Any CPU + {6B28F942-D355-44A2-9C5D-92AF2D256A0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B28F942-D355-44A2-9C5D-92AF2D256A0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B28F942-D355-44A2-9C5D-92AF2D256A0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B28F942-D355-44A2-9C5D-92AF2D256A0D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -729,6 +740,8 @@ Global {5BBC75F0-E51A-4EBD-A628-92498D319B1D} = {4C43F2DA-59E5-46F5-B691-195449498555} {7250CDB1-95C4-4822-B01B-3CBD73324CC9} = {30159B0F-BE61-4DB7-AC02-02851426BE4B} {2D615969-53E2-4B77-9A9A-75C33865CF76} = {1D6F1776-FF4B-46C2-9B3D-BC46CCF049DC} + {AA04A8A9-3CBD-431D-9711-C4D481E4A1E5} = {2261B65E-3757-4E5B-9DCD-EAE8D1E236A3} + {6B28F942-D355-44A2-9C5D-92AF2D256A0D} = {AA04A8A9-3CBD-431D-9711-C4D481E4A1E5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E18684C9-7D76-45CD-BF24-E3944B7F174C} diff --git a/dotnet/src/dotnetcore/GxClasses.Web/GxClasses.Web.csproj b/dotnet/src/dotnetcore/GxClasses.Web/GxClasses.Web.csproj index 3227ecb70..9cc233113 100644 --- a/dotnet/src/dotnetcore/GxClasses.Web/GxClasses.Web.csproj +++ b/dotnet/src/dotnetcore/GxClasses.Web/GxClasses.Web.csproj @@ -30,6 +30,8 @@ + + diff --git a/dotnet/src/dotnetcore/GxClasses/GxClasses.csproj b/dotnet/src/dotnetcore/GxClasses/GxClasses.csproj index 41c7ff2ad..bb08809b2 100644 --- a/dotnet/src/dotnetcore/GxClasses/GxClasses.csproj +++ b/dotnet/src/dotnetcore/GxClasses/GxClasses.csproj @@ -158,6 +158,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/dotnet/src/dotnetcore/GxClasses/Services/LogService/GXLogService.cs b/dotnet/src/dotnetcore/GxClasses/Services/LogService/GXLogService.cs new file mode 100644 index 000000000..774047c67 --- /dev/null +++ b/dotnet/src/dotnetcore/GxClasses/Services/LogService/GXLogService.cs @@ -0,0 +1,48 @@ +using System; +using GeneXus.Application; +using GxClasses.Helpers; +using Microsoft.Extensions.Logging; + +namespace GeneXus.Services.Log +{ + public interface IGXLogProvider : ILoggerFactory + { + ILoggerFactory GetLoggerFactory(); + } + + public static class GXLogService + { + private static string LOG_SERVICE = "Log"; + + public static ILoggerFactory GetLogFactory() + { + IGXLogProvider gxLogProvider = null; + GXService providerService = GXServices.Instance?.Get(LOG_SERVICE); + + if (providerService != null) + { + try + { +#if !NETCORE + Type type = Type.GetType(providerService.ClassName, true, true); +#else + Type type = AssemblyLoader.GetType(providerService.ClassName); +#endif + gxLogProvider = (IGXLogProvider)Activator.CreateInstance(type, new object[] { providerService }); + return gxLogProvider.GetLoggerFactory(); + } + catch (Exception e) + { + throw e; + } + } + else + { + string log4net_config = GxContext.IsHttpContext ? "log.config" : "log.console.config"; + Log4NetProvider log4NetProvider = new Log4NetProvider(log4net_config); + return LoggerFactory.Create(builder => builder.AddProvider(log4NetProvider)); + } + + } + } +} diff --git a/dotnet/src/dotnetcore/Providers/Logging/GeneXus.Log.Azure.AzureAppInsights/AzureAppInsightsLogProvider.cs b/dotnet/src/dotnetcore/Providers/Logging/GeneXus.Log.Azure.AzureAppInsights/AzureAppInsightsLogProvider.cs new file mode 100644 index 000000000..7bc3de514 --- /dev/null +++ b/dotnet/src/dotnetcore/Providers/Logging/GeneXus.Log.Azure.AzureAppInsights/AzureAppInsightsLogProvider.cs @@ -0,0 +1,51 @@ +using System; +using GeneXus.Services; +using GeneXus.Services.Log; +using Microsoft.Extensions.Logging; + +namespace GeneXus.Log.Azure +{ + public class AzureAppInsightsLogProvider : IGXLogProvider + { + private static string APPLICATIONINSIGHTS_CONNECTION_STRING = "APPLICATIONINSIGHTS_CONNECTION_STRING"; + + public static ILoggerFactory loggerFactory; + + public AzureAppInsightsLogProvider(GXService s) { } + public ILoggerFactory GetLoggerFactory() + { + string appInsightsConnection = Environment.GetEnvironmentVariable(APPLICATIONINSIGHTS_CONNECTION_STRING); + if (appInsightsConnection != null) { + loggerFactory = LoggerFactory.Create(builder => builder.AddApplicationInsights( + + configureTelemetryConfiguration: (config) => + config.ConnectionString = appInsightsConnection, + configureApplicationInsightsLoggerOptions: (options) => { } + ) + ); + + } + else + { + throw new ArgumentNullException("APPLICATIONINSIGHTS_CONNECTION_STRING","Application Insight Log could not be initialized due to missing APPLICATIONINSIGHTS_CONNECTION_STRING environment variable."); + } + return loggerFactory; + } + + public void AddProvider(ILoggerProvider provider) + { + loggerFactory.AddProvider(provider); + } + + public void Dispose() + { + loggerFactory.Dispose(); + } + + public ILogger CreateLogger(string name) + { + return loggerFactory.CreateLogger(name); + } + + } +} \ No newline at end of file diff --git a/dotnet/src/dotnetcore/Providers/Logging/GeneXus.Log.Azure.AzureAppInsights/GeneXus.Log.Azure.AzureAppInsights.csproj b/dotnet/src/dotnetcore/Providers/Logging/GeneXus.Log.Azure.AzureAppInsights/GeneXus.Log.Azure.AzureAppInsights.csproj new file mode 100644 index 000000000..06128b5db --- /dev/null +++ b/dotnet/src/dotnetcore/Providers/Logging/GeneXus.Log.Azure.AzureAppInsights/GeneXus.Log.Azure.AzureAppInsights.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + NETCORE; + Properties + false + GeneXus.Log.Azure.AzureAppInsights + Azure Application Insights Log GeneXus + GeneXus.Log.Azure.AzureAppInsights + + + + + + + + + + + + + diff --git a/dotnet/src/dotnetframework/GxClasses/GxClasses.csproj b/dotnet/src/dotnetframework/GxClasses/GxClasses.csproj index 1e437fe5a..a719b8d79 100644 --- a/dotnet/src/dotnetframework/GxClasses/GxClasses.csproj +++ b/dotnet/src/dotnetframework/GxClasses/GxClasses.csproj @@ -8,8 +8,13 @@ Data Access GeneXus.Classes + + + + + diff --git a/dotnet/src/dotnetframework/GxClasses/Helpers/GXLogging.cs b/dotnet/src/dotnetframework/GxClasses/Helpers/GXLogging.cs index d4a68765e..9b6f1e4cd 100644 --- a/dotnet/src/dotnetframework/GxClasses/Helpers/GXLogging.cs +++ b/dotnet/src/dotnetframework/GxClasses/Helpers/GXLogging.cs @@ -1,22 +1,41 @@ using System; +using System.Collections.Concurrent; +using System.IO; using System.Reflection; using System.Text; +using GeneXus.Services.Log; using log4net; +using log4net.Config; using log4net.Core; +using Microsoft.Extensions.Logging; +using ILogger = Microsoft.Extensions.Logging.ILogger; namespace GeneXus { - - public static class GXLogging + public static class GXLogging { + + private static Microsoft.Extensions.Logging.ILoggerFactory _instance = GXLogService.GetLogFactory(); + + public static ILogger GetLogger() where Type : class + { + return _instance.CreateLogger(); + } + public static void Trace(this ILog log, params string[] list) { if (log.Logger.IsEnabledFor(Level.Trace)) { - log.Logger.Log(MethodBase.GetCurrentMethod().DeclaringType, Level.Trace, String.Join(" ", list), null); + log.Logger.Log(MethodBase.GetCurrentMethod().DeclaringType, Level.Trace, string.Join(" ", list), null); } } - public static void Error(ILog log, string msg, Exception ex) + + public static void Trace(ILogger log, params string[] list) + { + if (log.IsEnabled(LogLevel.Trace)) + log.LogTrace(string.Join(" ", list)); + } + public static void Error(ILog log, string msg, Exception ex) { if (log.IsErrorEnabled) { @@ -24,6 +43,14 @@ public static void Error(ILog log, string msg, Exception ex) } } + public static void Error(ILogger log, string msg, Exception ex) + { + if (log.IsEnabled(LogLevel.Error)) + { + log.LogError(msg, ex); + } + } + public static void ErrorSanitized(ILog log, string msg, Exception ex) { if (log.IsErrorEnabled) @@ -32,24 +59,54 @@ public static void ErrorSanitized(ILog log, string msg, Exception ex) } } + public static void ErrorSanitized(ILogger log, string msg, Exception ex) + { + if (log.IsEnabled(LogLevel.Error)) + { + log.LogError(Utils.StringUtil.Sanitize(msg, Utils.StringUtil.LogUserEntryWhiteList), ex); + } + } + public static void Error(ILog log, string msg1, string msg2, Exception ex) { Error(log, msg1 + msg2, ex); } - public static void Error(ILog log, Exception ex, params string[] list) + + public static void Error(ILogger log, string msg1, string msg2, Exception ex) + { + Error(log, msg1 + msg2, ex); + } + public static void Error(ILog log, Exception ex, params string[] list) { - if (log.IsErrorEnabled){ - foreach (string parm in list){ + if (log.IsErrorEnabled) + { + foreach (string parm in list) + { log.Error(parm); } } } - public static void Error(ILog log, params string[] list) + public static void Error(ILogger log, Exception ex, params string[] list) + { + if (log.IsEnabled(LogLevel.Error)) + { + foreach (string parm in list) + { + log.LogError(parm); + } + } + } + public static void Error(ILog log, params string[] list) + { + Error(log, null, list); + } + + public static void Error(ILogger log, params string[] list) { Error(log, null, list); } - public static void Warn(ILog log, Exception ex, params string[] list) + public static void Warn(ILog log, Exception ex, params string[] list) { if (log.IsWarnEnabled) { @@ -64,18 +121,48 @@ public static void Warn(ILog log, Exception ex, params string[] list) log.Warn(msg); } } - public static void Warn(ILog log, params string[] list) + + public static void Warn(ILogger log, Exception ex, params string[] list) + { + if (log.IsEnabled(LogLevel.Warning)) + { + StringBuilder msg = new StringBuilder(); + foreach (string parm in list) + { + msg.Append(parm); + } + if (ex != null) + log.LogWarning(exception:ex, message:msg.ToString()); + else + log.LogWarning(message:msg.ToString()); + } + } + public static void Warn(ILog log, params string[] list) + { + Warn(log, null, list); + } + + public static void Warn(ILogger log, params string[] list) { Warn(log, null, list); } - public static void Warn(ILog log, string msg, Exception ex) + + public static void Warn(ILog log, string msg, Exception ex) { if (log.IsWarnEnabled) { log.Warn(msg, ex); } } - public static void Debug(ILog log, Exception ex, params string[] list) + public static void Warn(ILogger log, string msg, Exception ex) + { + if (log.IsEnabled(LogLevel.Warning)) + { + log.LogWarning + (exception:ex,msg); + } + } + public static void Debug(ILog log, Exception ex, params string[] list) { if (log.IsDebugEnabled) { @@ -90,6 +177,21 @@ public static void Debug(ILog log, Exception ex, params string[] list) log.Debug(msg); } } + public static void Debug(ILogger log, Exception ex, params string[] list) + { + if (log.IsEnabled(LogLevel.Debug)) + { + StringBuilder msg = new StringBuilder(); + foreach (string parm in list) + { + msg.Append(parm); + } + if (ex != null) + log.LogDebug(exception :ex, message: msg.ToString()); + else + log.LogDebug(message: msg.ToString()); + } + } public static void DebugSanitized(ILog log, Exception ex, params string[] list) { @@ -107,17 +209,40 @@ public static void DebugSanitized(ILog log, Exception ex, params string[] list) } } - + public static void DebugSanitized(ILogger log, Exception ex, params string[] list) + { + if (log.IsEnabled(LogLevel.Debug)) + { + StringBuilder msg = new StringBuilder(); + foreach (string parm in list) + { + msg.Append(Utils.StringUtil.Sanitize(parm, Utils.StringUtil.LogUserEntryWhiteList)); + } + if (ex != null) + log.LogDebug(exception:ex,message:msg.ToString()); + else + log.LogDebug(msg.ToString()); + } + } public static void Debug(ILog log, params string[] list) { Debug(log, null, list); } + public static void Debug(ILogger log, params string[] list) + { + Debug(log, null, list); + } + public static void DebugSanitized(ILog log, params string[] list) { DebugSanitized(log, null, list); } + public static void DebugSanitized(ILogger log, params string[] list) + { + DebugSanitized(log, null, list); + } public static void Debug(ILog log, string startMsg, Func buildMsg) { @@ -127,18 +252,38 @@ public static void Debug(ILog log, string startMsg, Func buildMsg) log.Debug(startMsg + msg); } } - public static void Debug(ILog log, string msg1, string msg2, Exception ex) + public static void Debug(ILogger log, string startMsg, Func buildMsg) + { + if (log.IsEnabled(LogLevel.Debug)) + { + string msg = buildMsg(); + log.LogDebug(startMsg + msg); + } + } + public static void Debug(ILog log, string msg1, string msg2, Exception ex) + { + Debug(log, msg1 + msg2, ex); + } + + public static void Debug(ILogger log, string msg1, string msg2, Exception ex) { Debug(log, msg1 + msg2, ex); } - public static void Debug(ILog log, string msg, Exception ex) + public static void Debug(ILog log, string msg, Exception ex) { if (log.IsDebugEnabled) { log.Debug(msg, ex); } } - public static void Info(ILog log, params string[] list) + public static void Debug(ILogger log, string msg, Exception ex) + { + if (log.IsEnabled(LogLevel.Debug)) + { + log.LogDebug(exception:ex,msg); + } + } + public static void Info(ILog log, params string[] list) { if (log.IsInfoEnabled) { @@ -150,6 +295,147 @@ public static void Info(ILog log, params string[] list) log.Info(msg); } } + public static void Info(ILogger log, params string[] list) + { + if (log.IsEnabled(LogLevel.Information)) + { + StringBuilder msg = new StringBuilder(); + foreach (string parm in list) + { + msg.Append(parm); + } + log.LogInformation(msg.ToString()); + } + } + public static void Info(ILogger log, string msg, params string[] list) + { + if (log.IsEnabled(LogLevel.Information)) + { + log.LogInformation(msg.ToString(),list); + } + } + } + internal class Log4NetProvider : ILoggerProvider + { + private readonly string _log4NetConfigFile; + + private readonly ConcurrentDictionary _loggers = + new ConcurrentDictionary(); + + internal Log4NetProvider(string log4NetConfigFile) + { + _log4NetConfigFile = log4NetConfigFile; + } + + public ILogger CreateLogger(string categoryName) + { + return _loggers.GetOrAdd(categoryName, CreateLoggerImplementation); + } + + public void Dispose() + { + _loggers.Clear(); + } + + private ILogger CreateLoggerImplementation(string name) + { + return new Log4NetLogger(name, new FileInfo(_log4NetConfigFile)); + } + } + internal class Log4NetLogger : ILogger + { + private readonly string _name; + + private readonly ILog _log; + + private log4net.Repository.ILoggerRepository _loggerRepository; + + public Log4NetLogger(string name, FileInfo fileInfo) + { + _name = name; + _loggerRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); + + _log = LogManager.GetLogger(_loggerRepository.Name, name); + XmlConfigurator.ConfigureAndWatch(_loggerRepository, fileInfo); + + if (_log.IsDebugEnabled) { + _log.Debug($"log4net configured with {fileInfo.FullName}"); + } + + } + + public IDisposable BeginScope(TState state) + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Critical: + return _log.IsFatalEnabled; + case LogLevel.Debug: + case LogLevel.Trace: + return _log.IsDebugEnabled; + case LogLevel.Error: + return _log.IsErrorEnabled; + case LogLevel.Information: + return _log.IsInfoEnabled; + case LogLevel.Warning: + return _log.IsWarnEnabled; + default: + throw new ArgumentOutOfRangeException(nameof(logLevel)); + } + } + + public void Log( + LogLevel logLevel, + EventId eventId, + TState state, + Exception exception, + Func formatter) + { + if (!IsEnabled(logLevel)) + { + return; + } + + if (formatter == null) + { + throw new ArgumentNullException(nameof(formatter)); + } + + string message = $"{formatter(state, exception)} {exception}"; + if (!string.IsNullOrEmpty(message) || exception != null) + { + switch (logLevel) + { + case LogLevel.Critical: + _log.Fatal(message); + break; + case LogLevel.Debug: + case LogLevel.Trace: + _log.Debug(message); + break; + case LogLevel.Error: + _log.Error(message); + break; + case LogLevel.Information: + _log.Info(message); + break; + case LogLevel.Warning: + _log.Warn(message); + break; + default: + _log.Warn($"Encountered unknown log level {logLevel}, writing out as Info."); + _log.Info(message, exception); + break; + } + } + } } + } +