Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions library/src/Core/Common/Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="NLog" Version="6.1.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.3" />
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
</ItemGroup>
Expand Down
113 changes: 113 additions & 0 deletions library/src/Core/Common/Util/LogUtilities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using NLog;

namespace ReFlex.Core.Common.Util
{
public static class LogUtilities
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private static readonly ConcurrentDictionary<string, byte> LoggedErrors = new ConcurrentDictionary<string, byte>();

/// <summary>
/// Logs an exception only once for the same source, method and exception signature.
/// </summary>
/// <param name="exception">Exception to log.</param>
/// <param name="sourceName">Logical source (for example class name) used for scoping deduplication.</param>
/// <param name="methodName">Method name used for scoping deduplication.</param>
/// <param name="message">Optional custom message for the log entry.</param>
/// <exception cref="ArgumentNullException">Thrown when required arguments are null.</exception>
public static void LogErrorOnce(
Exception exception,
string sourceName,
string methodName,
string message = null)
{
if (sourceName == null)
throw new ArgumentNullException(nameof(sourceName));
if (methodName == null)
throw new ArgumentNullException(nameof(methodName));

if (exception == null)
{
return;
}

var errorKey = BuildErrorKey(sourceName, methodName, exception);
if (!LoggedErrors.TryAdd(errorKey, 0))
{
return;
}

if (string.IsNullOrWhiteSpace(message))
{
Log.Error(exception);
return;
}

Log.Error(exception, message);
}

/// <summary>
/// Clears deduplication entries.
/// If <paramref name="sourceName"/> is provided, only entries for this source are removed.
/// If it is null or whitespace, all entries are removed.
/// </summary>
/// <param name="sourceName">Optional source name filter.</param>
public static void ClearLoggedErrors(string sourceName = null)
{
if (string.IsNullOrWhiteSpace(sourceName))
{
LoggedErrors.Clear();
return;
}

var sourcePrefix = $"{sourceName}:";
var keys = LoggedErrors.Keys
.Where(key => key.StartsWith(sourcePrefix, StringComparison.Ordinal))
.ToList();

foreach (var key in keys)
{
LoggedErrors.TryRemove(key, out _);
}
}

/// <summary>
/// Creates a key that uniquely identifies an error context for deduplicated logging.
/// </summary>
/// <param name="sourceName">Logical source (for example class name).</param>
/// <param name="methodName">Method name where the exception occurred.</param>
/// <param name="exception">Exception to create the key from.</param>
/// <returns>Deterministic key based on source, method and exception signature.</returns>
private static string BuildErrorKey(string sourceName, string methodName, Exception exception)
{
return $"{sourceName}:{methodName}:{BuildExceptionSignature(exception)}";
}

/// <summary>
/// Builds a deterministic signature from the exception chain.
/// This includes each exception type and message, including inner exceptions.
/// </summary>
/// <param name="exception">Exception to convert to a signature.</param>
/// <returns>Signature string that can be used for deduplication.</returns>
private static string BuildExceptionSignature(Exception exception)
{
var builder = new StringBuilder();
var current = exception;

while (current != null)
{
builder.Append(current.GetType().FullName);
builder.Append(':');
builder.Append(current.Message);
builder.Append('|');
current = current.InnerException;
}

return builder.ToString();
}
}
}
28 changes: 19 additions & 9 deletions library/src/Core/Tuio/Components/TuioSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using NLog;
using ReFlex.Core.Common.Adapter;
using ReFlex.Core.Common.Interfaces;
using ReFlex.Core.Common.Util;
using ReFlex.Core.Tuio.Interfaces;
using ReFlex.Core.Tuio.Util;

Expand All @@ -23,12 +24,12 @@
/// Client for sending TUIO Messages to Server using UDP
/// </summary>
protected UdpClient UdpClient;

/// <summary>
/// Client Interface for sending TUIO Messages to Server using TCP. (use <see cref="TcpClientAdapter"/> for better test abilities)
/// </summary>
protected ITcpClient TcpClient;

/// <summary>
/// Client Interface for sending TUIO Messages to Server using WebSocket protocol. (use <see cref="ClientWebSocketAdapter"/> for better test abilities)
/// </summary>
Expand Down Expand Up @@ -78,6 +79,7 @@

_serverAddress = config.ServerAddress;
_serverPort = config.ServerPort;
LogUtilities.ClearLoggedErrors(nameof(TuioSender));
IsInitialized = true;
}

Expand All @@ -96,7 +98,7 @@
}
catch (Exception exc)
{
Log.Error(exc, $"Error sending osc message via UDP");
LogUtilities.LogErrorOnce(exc, nameof(TuioSender), nameof(SendUdp), "Error sending osc message via UDP");
}
}

Expand All @@ -115,7 +117,7 @@
}
catch (Exception exc)
{
Log.Error(exc);
LogUtilities.LogErrorOnce(exc, nameof(TuioSender), nameof(SendTcp));
}
}

Expand Down Expand Up @@ -146,7 +148,7 @@
}
catch (Exception exc)
{
Log.Error(exc);
LogUtilities.LogErrorOnce(exc, nameof(TuioSender), nameof(SendWebSocket));
}
}

Expand All @@ -165,7 +167,14 @@
/// <param name="msg"><see cref="OscMessage"/> to be transmitted</param>
protected virtual async Task SendOscMessageUdp(OscMessage msg)
{
await UdpClient.SendMessageAsync(msg);
try
{
await UdpClient.SendMessageAsync(msg);
}
catch (Exception exc)
{
LogUtilities.LogErrorOnce(exc, nameof(TuioSender), nameof(SendOscMessageUdp));
}
}

/// <summary>
Expand All @@ -175,7 +184,7 @@
/// <param name="msg"><see cref="OscMessage"/> to be transmitted</param>
protected virtual async Task SendOscMessageTcp(OscMessage msg)
{
var format = new BinaryFormatter();

Check warning on line 187 in library/src/Core/Tuio/Components/TuioSender.cs

View workflow job for this annotation

GitHub Actions / build and test .NET Solution

'BinaryFormatter' is obsolete: 'BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.' (https://aka.ms/dotnet-warnings/SYSLIB0011)

Check warning on line 187 in library/src/Core/Tuio/Components/TuioSender.cs

View workflow job for this annotation

GitHub Actions / build and test .NET Solution

'BinaryFormatter' is obsolete: 'BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.' (https://aka.ms/dotnet-warnings/SYSLIB0011)
try
{

Expand All @@ -190,7 +199,7 @@
}
catch (Exception exc)
{
Log.Error(exc);
LogUtilities.LogErrorOnce(exc, nameof(TuioSender), nameof(SendOscMessageTcp));
}
}

Expand All @@ -203,7 +212,7 @@
protected virtual async Task SendOscMessageWebSocket(OscMessage msg)
{
var mStream = new MemoryStream();
var format = new BinaryFormatter();

Check warning on line 215 in library/src/Core/Tuio/Components/TuioSender.cs

View workflow job for this annotation

GitHub Actions / build and test .NET Solution

'BinaryFormatter' is obsolete: 'BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.' (https://aka.ms/dotnet-warnings/SYSLIB0011)

Check warning on line 215 in library/src/Core/Tuio/Components/TuioSender.cs

View workflow job for this annotation

GitHub Actions / build and test .NET Solution

'BinaryFormatter' is obsolete: 'BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.' (https://aka.ms/dotnet-warnings/SYSLIB0011)
format.Serialize(mStream, msg.Address.Value);
foreach (var arg in msg.Arguments)
{
Expand All @@ -218,7 +227,7 @@
}
catch (Exception exc)
{
Log.Error(exc);
LogUtilities.LogErrorOnce(exc, nameof(TuioSender), nameof(SendOscMessageWebSocket));
}
}

Expand All @@ -237,8 +246,9 @@
UdpClient?.Dispose();
TcpClient?.Dispose();
WsClient?.Dispose();
LogUtilities.ClearLoggedErrors(nameof(TuioSender));

IsInitialized = false;
}
}
}
}
Loading
Loading