Skip to content

Commit 05c2826

Browse files
committed
Projektdateien hinzufügen.
1 parent 7c651af commit 05c2826

23 files changed

+1588
-0
lines changed

XKeys-dmxc3-Plugin.sln

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.5.33424.131
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XKeys-dmxc3-Plugin", "XKeys-dmxc3-Plugin\XKeys-dmxc3-Plugin.csproj", "{732410D5-E5AD-469F-B294-502141A8A26C}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{732410D5-E5AD-469F-B294-502141A8A26C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{732410D5-E5AD-469F-B294-502141A8A26C}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{732410D5-E5AD-469F-B294-502141A8A26C}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{732410D5-E5AD-469F-B294-502141A8A26C}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {D403F30F-1CD2-4B83-BA65-11FAC5E39D6D}
24+
EndGlobalSection
25+
EndGlobal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using Microsoft.Extensions.Logging;
2+
using System;
3+
4+
namespace XKeys_dmxc3_Plugin.Plugin.Logging
5+
{
6+
/// <summary>
7+
/// Represents a candidate for a log message that should be printed. This candidate will either be accepted or denied by the logger that is trying to print it.
8+
/// </summary>
9+
/// <remarks>
10+
/// <para>
11+
/// This is a readonly struct to reduce memory pressure, but because it is quite large (definitly larger than the recommended 16 bytes)
12+
/// it needs to be passed as a reference (with the in keyword) to make a difference.
13+
/// </para>
14+
/// <para>
15+
/// See <see href="https://devblogs.microsoft.com/premier-developer/the-in-modifier-and-the-readonly-structs-in-c/"/> for more information.
16+
/// </para>
17+
/// </remarks>
18+
/// <typeparam name="TState">Type of the state that is used to format the error message.</typeparam>
19+
public readonly struct MessageCandidate<TState>
20+
{
21+
public MessageCandidate(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
22+
{
23+
State = state;
24+
LogLevel = logLevel;
25+
EventId = eventId;
26+
Exception = exception;
27+
Formatter = formatter;
28+
}
29+
30+
/// <summary>
31+
/// The log level the message should be printed with.
32+
/// </summary>
33+
public LogLevel LogLevel { get; }
34+
35+
/// <summary>
36+
/// The event id of the message.
37+
/// </summary>
38+
public EventId EventId { get; }
39+
40+
/// <summary>
41+
/// The message state. Can be provided to the formatter to generate the string representation of the error message.
42+
/// </summary>
43+
public TState State { get; }
44+
45+
/// <summary>
46+
/// Exception that should be printed with the message. Null if the log message has no corrosponding exception.
47+
/// </summary>
48+
public Exception Exception { get; }
49+
50+
/// <summary>
51+
/// The message formatter. Can be called with the state and exception to generate the string representation of the error message.
52+
/// </summary>
53+
public Func<TState, Exception, string> Formatter { get; }
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
3+
namespace XKeys_dmxc3_Plugin.Plugin.Logging
4+
{
5+
/// <summary>
6+
/// A logger scope that does not save any information and does not need to be disposed.
7+
/// </summary>
8+
internal class NullScope : IDisposable
9+
{
10+
/// <summary>
11+
/// The singleton instance that represent every <see cref="NullScope"/>.
12+
/// </summary>
13+
internal static NullScope Instance { get; } = new NullScope();
14+
15+
/// <summary>
16+
/// Constructor that prevents external instantiation.
17+
/// </summary>
18+
private NullScope()
19+
{
20+
}
21+
22+
/// <inheritdoc/>
23+
public void Dispose()
24+
{
25+
// This is a null scope so we need to dispose nothing.
26+
}
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using Microsoft.Extensions.Logging;
2+
using System;
3+
4+
namespace XKeys_dmxc3_Plugin.Plugin.Logging
5+
{
6+
/// <summary>
7+
/// A <see cref="IExternalScopeProvider"/> that will not save nor return scopes.
8+
/// </summary>
9+
internal class NullScopeProvider : IExternalScopeProvider
10+
{
11+
/// <summary>
12+
/// The singleton instance that represents every <see cref="NullScopeProvider"/>.
13+
/// </summary>
14+
internal static NullScopeProvider Instance { get; } = new NullScopeProvider();
15+
16+
/// <summary>
17+
/// Constructor that prevents external instantiation.
18+
/// </summary>
19+
private NullScopeProvider()
20+
{
21+
}
22+
23+
/// <inheritdoc/>
24+
public void ForEachScope<TState>(Action<object, TState> callback, TState state)
25+
{
26+
// All scopes are null scopes so do nothing.
27+
}
28+
29+
/// <inheritdoc/>
30+
public IDisposable Push(object state)
31+
{
32+
return NullScope.Instance;
33+
}
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
using log4net.Core;
2+
using Microsoft.Extensions.Logging;
3+
using System.Collections;
4+
using System.Globalization;
5+
6+
namespace XKeys_dmxc3_Plugin.Plugin.Logging
7+
{
8+
public class LumosLogWrapperEventFactory
9+
{
10+
private static readonly LumosLogWrapperEventFactory instance = new();
11+
12+
public static LumosLogWrapperEventFactory getInstance()
13+
{
14+
return instance;
15+
}
16+
17+
/// <summary>
18+
/// The default property name for scopes that don't provide their own property name by implementing
19+
/// an <see cref="IEnumerable{T}"/> where T is <see cref="KeyValuePair{TKey,TValue}"/> and where TKey
20+
/// is <see cref="string"/>.
21+
/// </summary>
22+
protected const string DefaultScopeProperty = "scope";
23+
24+
/// <inheritdoc/>
25+
public LoggingEvent CreateLoggingEvent<TState>(
26+
in MessageCandidate<TState> messageCandidate,
27+
log4net.Core.ILogger logger,
28+
IExternalScopeProvider scopeProvider)
29+
{
30+
Type callerStackBoundaryDeclaringType = typeof(LoggerExtensions);
31+
string message = messageCandidate.Formatter(messageCandidate.State, messageCandidate.Exception);
32+
Level logLevel = LumosLogWrapperLogLevelTranslator.TranslateLogLevel(messageCandidate.LogLevel);
33+
34+
if (logLevel == null || (string.IsNullOrEmpty(message) && messageCandidate.Exception == null))
35+
return null;
36+
37+
var loggingEvent = new LoggingEvent(
38+
callerStackBoundaryDeclaringType: callerStackBoundaryDeclaringType,
39+
repository: logger.Repository,
40+
loggerName: logger.Name,
41+
level: logLevel,
42+
message: message,
43+
exception: messageCandidate.Exception);
44+
45+
EnrichWithScopes(loggingEvent, scopeProvider);
46+
47+
return loggingEvent;
48+
}
49+
50+
51+
/// <summary>
52+
/// Gets the scopes from the external scope provider and converts them to the properties on the logging event.
53+
/// This function will honor the convention that logging scopes can provide their own property name, by implementing
54+
/// an <see cref="IEnumerable{T}"/> where T is <see cref="KeyValuePair{TKey,TValue}"/> and where TKey is
55+
/// <see cref="string"/>.
56+
/// </summary>
57+
/// <remarks>
58+
/// The default implementation will call Convert.ToString(scope, CultureInfo.InvariantCulture) on all scope objects.
59+
/// If you want to do this conversion inside the Log4Net Pipeline, e. g. with a custom layout, you can override this
60+
/// method and change the behaviour.
61+
/// </remarks>
62+
/// <param name="loggingEvent">The <see cref="LoggingEvent"/> the scope information will be added to.</param>
63+
/// <param name="scopeProvider">The external provider for the current logging scope.</param>
64+
protected virtual void EnrichWithScopes(LoggingEvent loggingEvent, IExternalScopeProvider scopeProvider)
65+
{
66+
scopeProvider.ForEachScope((scope, @event) =>
67+
{
68+
// This function will add the scopes in the legacy way they were added before the IExternalScopeProvider was introduced,
69+
// to maintain backwards compatibility.
70+
// This pretty much means that we are emulating a LogicalThreadContextStack, which is a stack, that allows pushing
71+
// strings on to it, which will be concatenated with space as a separator.
72+
// See: https://github.com/apache/logging-log4net/blob/47aaf46d5f031ea29d781bac4617bd1bb9446215/src/log4net/Util/LogicalThreadContextStack.cs#L343
73+
74+
// Because string implements IEnumerable we first need to check for string.
75+
if (scope is string)
76+
{
77+
string previousValue = @event.Properties[DefaultScopeProperty] as string;
78+
79+
@event.Properties[DefaultScopeProperty] = JoinOldAndNewValue(previousValue, scope.ToString());
80+
return;
81+
}
82+
83+
if (scope is IEnumerable col)
84+
{
85+
foreach (var item in col)
86+
{
87+
if (item is KeyValuePair<string, string>)
88+
{
89+
var keyValuePair = (KeyValuePair<string, string>)item;
90+
string previousValue = @event.Properties[keyValuePair.Key] as string;
91+
@event.Properties[keyValuePair.Key] = JoinOldAndNewValue(previousValue, keyValuePair.Value);
92+
continue;
93+
}
94+
95+
if (item is KeyValuePair<string, object>)
96+
{
97+
var keyValuePair = (KeyValuePair<string, object>)item;
98+
string previousValue = @event.Properties[keyValuePair.Key] as string;
99+
100+
// The current culture should not influence how integers/floats/... are displayed in logging,
101+
// so we are using Convert.ToString which will convert IConvertible and IFormattable with
102+
// the specified IFormatProvider.
103+
string additionalValue = Convert.ToString(keyValuePair.Value, CultureInfo.InvariantCulture);
104+
@event.Properties[keyValuePair.Key] = JoinOldAndNewValue(previousValue, additionalValue);
105+
continue;
106+
}
107+
}
108+
return;
109+
}
110+
111+
if (scope is not null)
112+
{
113+
string previousValue = @event.Properties[DefaultScopeProperty] as string;
114+
string additionalValue = Convert.ToString(scope, CultureInfo.InvariantCulture);
115+
@event.Properties[DefaultScopeProperty] = JoinOldAndNewValue(previousValue, additionalValue);
116+
return;
117+
}
118+
119+
}, loggingEvent);
120+
}
121+
122+
private static string JoinOldAndNewValue(string previousValue, string newValue)
123+
{
124+
if (string.IsNullOrEmpty(previousValue))
125+
{
126+
return newValue;
127+
}
128+
129+
return previousValue + " " + newValue;
130+
}
131+
}
132+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using log4net.Core;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace XKeys_dmxc3_Plugin.Plugin.Logging
5+
{
6+
public sealed class LumosLogWrapperLogLevelTranslator
7+
{
8+
public static Level TranslateLogLevel(LogLevel logLevel, bool criticalEqualsFatal = false)
9+
{
10+
Level log4NetLevel = null;
11+
switch (logLevel)
12+
{
13+
case LogLevel.Critical:
14+
log4NetLevel = criticalEqualsFatal
15+
? Level.Fatal
16+
: Level.Critical;
17+
break;
18+
case LogLevel.Debug:
19+
log4NetLevel = Level.Debug;
20+
break;
21+
case LogLevel.Error:
22+
log4NetLevel = Level.Error;
23+
break;
24+
case LogLevel.Information:
25+
log4NetLevel = Level.Info;
26+
break;
27+
case LogLevel.Warning:
28+
log4NetLevel = Level.Warn;
29+
break;
30+
case LogLevel.Trace:
31+
log4NetLevel = Level.Trace;
32+
break;
33+
}
34+
35+
return log4NetLevel;
36+
}
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using log4net.Core;
2+
using LumosLIB.Kernel.Log;
3+
using Microsoft.Extensions.Logging;
4+
using IMELLogger = Microsoft.Extensions.Logging.ILogger;
5+
6+
namespace XKeys_dmxc3_Plugin.Plugin.Logging
7+
{
8+
public sealed class LumosLogWrapperLogger : IMELLogger
9+
{
10+
private readonly string _name;
11+
private readonly ILumosLog logger;
12+
private readonly IExternalScopeProvider externalScopeProvider;
13+
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="Log4NetLogger"/> class.
16+
/// </summary>
17+
/// <param name="options">The log4net provider options.</param>
18+
public LumosLogWrapperLogger(string name, IExternalScopeProvider externalScopeProvider)
19+
{
20+
_name = name;
21+
this.externalScopeProvider = externalScopeProvider ?? throw new ArgumentNullException(nameof(externalScopeProvider));
22+
this.logger = LumosLogger.getInstance(_name);
23+
}
24+
25+
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!;
26+
27+
public bool IsEnabled(LogLevel logLevel)
28+
{
29+
switch (logLevel)
30+
{
31+
case LogLevel.Trace:
32+
case LogLevel.Debug:
33+
case LogLevel.Information:
34+
case LogLevel.Warning:
35+
case LogLevel.Error:
36+
case LogLevel.Critical:
37+
return true;
38+
case LogLevel.None:
39+
default: return false;
40+
}
41+
}
42+
43+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
44+
{
45+
if (!IsEnabled(logLevel))
46+
{
47+
return;
48+
}
49+
50+
if (formatter == null)
51+
{
52+
throw new ArgumentNullException(nameof(formatter));
53+
}
54+
55+
var candidate = new MessageCandidate<TState>(logLevel, eventId, state, exception, formatter);
56+
LoggingEvent loggingEvent = LumosLogWrapperEventFactory.getInstance().CreateLoggingEvent(in candidate, logger.AsILog().Logger, externalScopeProvider);
57+
if (loggingEvent == null)
58+
return;
59+
logger.AsILog().Logger.Log(loggingEvent);
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)