Skip to content

Commit a3e699e

Browse files
Merge pull request #60 from General-Fault/feature/56-logging-infrastructure
Add structured logging infrastructure. Closes #56.
2 parents 136e02b + 17a3d3f commit a3e699e

32 files changed

Lines changed: 1252 additions & 95 deletions

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,26 @@ dotnet msbuild WebRtcInterop.UnitTests\WebRtcInterop.UnitTests.vcxproj /p:Config
7777
.\WebRtcInterop.UnitTests\x64\Debug\WebRtcInterop.UnitTests.exe
7878
```
7979

80+
## Logging configuration
81+
82+
`Host.SetLoggerFactory(...)` configures logging for managed API code, C++/CLI interop code, and WebRTC native logs.
83+
84+
```csharp
85+
using Microsoft.Extensions.Logging;
86+
using WebRtcNet;
87+
88+
var loggerFactory = LoggerFactory.Create(builder =>
89+
{
90+
builder
91+
.SetMinimumLevel(LogLevel.Information)
92+
.AddConsole();
93+
});
94+
95+
Host.SetLoggerFactory(loggerFactory);
96+
```
97+
98+
If `SetLoggerFactory` is not called, Debug builds default to console logging and Release builds are silent.
99+
80100
## Docker pipeline
81101

82102
The build pipeline uses individual-stage Dockerfiles rather than a single monolithic file. `docker buildx` is not used — Windows containers require classic `docker build`.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include "pch.h"
2+
3+
#include <winerror.h>
4+
#include "gtest/gtest.h"
5+
6+
using namespace WebRtcInterop;
7+
8+
class interop_hresult_tests
9+
{};
10+
11+
TEST(interop_hresult_tests, log_if_failed_returns_false_for_success)
12+
{
13+
EXPECT_FALSE(InteropHResult::LogIfFailed(S_OK, "success", "Interop.Tests"));
14+
}
15+
16+
TEST(interop_hresult_tests, log_if_failed_returns_true_for_failure)
17+
{
18+
EXPECT_TRUE(InteropHResult::LogIfFailed(E_FAIL, "failure", "Interop.Tests"));
19+
}

WebRtcInterop.UnitTests/WebRtcInterop.UnitTests.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
<ClInclude Include="include\TestUtils.h" />
5252
</ItemGroup>
5353
<ItemGroup>
54+
<ClCompile Include="InteropHResultTests.cpp" />
5455
<ClCompile Include="ManagedScopedRefPtrTests.cpp" />
5556
<ClCompile Include="Marshaling\MarshalCollectionsTests.cpp" />
5657
<ClCompile Include="Marshaling\MarshalMediaTests.cpp" />

WebRtcInterop/InteropHResult.cpp

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#include "pch.h"
2+
3+
#include "InteropHResult.h"
4+
5+
#include <windows.h>
6+
7+
using namespace System::ComponentModel;
8+
using namespace System::Runtime::InteropServices;
9+
using namespace System::Threading;
10+
using namespace System;
11+
using namespace WebRtcNet::Logging;
12+
13+
namespace WebRtcInterop
14+
{
15+
namespace
16+
{
17+
String^ TrimSystemMessage(String^ message)
18+
{
19+
if (String::IsNullOrWhiteSpace(message))
20+
return "Unknown system error";
21+
22+
return message->Trim();
23+
}
24+
25+
String^ FormatSystemMessage(const HRESULT hr)
26+
{
27+
LPWSTR rawMessage = nullptr;
28+
const auto flags =
29+
FORMAT_MESSAGE_ALLOCATE_BUFFER |
30+
FORMAT_MESSAGE_FROM_SYSTEM |
31+
FORMAT_MESSAGE_IGNORE_INSERTS;
32+
auto messageId = static_cast<DWORD>(hr);
33+
auto length = FormatMessageW(
34+
flags,
35+
nullptr,
36+
messageId,
37+
0,
38+
reinterpret_cast<LPWSTR>(&rawMessage),
39+
0,
40+
nullptr);
41+
42+
if (length == 0 && HRESULT_FACILITY(hr) == FACILITY_WIN32)
43+
{
44+
messageId = HRESULT_CODE(hr);
45+
length = FormatMessageW(
46+
flags,
47+
nullptr,
48+
messageId,
49+
0,
50+
reinterpret_cast<LPWSTR>(&rawMessage),
51+
0,
52+
nullptr);
53+
}
54+
55+
if (length == 0 || rawMessage == nullptr)
56+
return "Unknown system error";
57+
58+
try
59+
{
60+
return TrimSystemMessage(gcnew String(rawMessage));
61+
}
62+
finally
63+
{
64+
LocalFree(rawMessage);
65+
}
66+
}
67+
}
68+
69+
void InteropHResult::ThrowIfFailed(HRESULT hr, String^ message)
70+
{
71+
if (SUCCEEDED(hr))
72+
return;
73+
74+
if (HRESULT_FACILITY(hr) == FACILITY_WIN32)
75+
throw gcnew Win32Exception(HRESULT_CODE(hr), message);
76+
77+
throw gcnew COMException(message, hr);
78+
}
79+
80+
bool InteropHResult::LogIfFailed(HRESULT hr, String^ operation, String^ category)
81+
{
82+
if (SUCCEEDED(hr))
83+
return false;
84+
85+
if (String::IsNullOrWhiteSpace(operation))
86+
operation = "Interop operation";
87+
if (String::IsNullOrWhiteSpace(category))
88+
category = "Interop.HResult";
89+
90+
try
91+
{
92+
const auto formatted = String::Format(
93+
"{0} failed. HRESULT=0x{1:X8} ({2}), Facility={3}, Code={4}, SystemMessage=\"{5}\"",
94+
operation,
95+
static_cast<UInt32>(hr),
96+
hr,
97+
HRESULT_FACILITY(hr),
98+
HRESULT_CODE(hr),
99+
FormatSystemMessage(hr));
100+
101+
WebRtcLogWriterBridge::WriteInteropLog(
102+
4,
103+
static_cast<int>(WebRtcLogEventId::InteropHResultFailure),
104+
category,
105+
Thread::CurrentThread->ManagedThreadId,
106+
formatted);
107+
}
108+
catch (...)
109+
{
110+
}
111+
112+
return true;
113+
}
114+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ namespace WebRtcInterop
88
{
99
public:
1010
static void ThrowIfFailed(HRESULT hr, System::String^ message);
11+
static bool LogIfFailed(HRESULT hr, System::String^ operation, System::String^ category);
1112
};
1213
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
3+
#include <rtc_base/logging.h>
4+
#include <msclr/marshal.h>
5+
#include <map>
6+
#include "MarshalEnums.h"
7+
8+
// Static map for rtc::LoggingSeverity to LogLevel conversion
9+
static const std::map<const rtc::LoggingSeverity, const Microsoft::Extensions::Logging::LogLevel> rtc_logging_severity_map{
10+
{ rtc::LS_VERBOSE, Microsoft::Extensions::Logging::LogLevel::Debug },
11+
{ rtc::LS_INFO, Microsoft::Extensions::Logging::LogLevel::Information },
12+
{ rtc::LS_WARNING, Microsoft::Extensions::Logging::LogLevel::Warning },
13+
{ rtc::LS_ERROR, Microsoft::Extensions::Logging::LogLevel::Error },
14+
{ rtc::LS_NONE, Microsoft::Extensions::Logging::LogLevel::None }
15+
};
16+
17+
// Marshal rtc::LoggingSeverity to Microsoft.Extensions.Logging.LogLevel
18+
template <>
19+
Microsoft::Extensions::Logging::LogLevel marshal_as<Microsoft::Extensions::Logging::LogLevel>(const rtc::LoggingSeverity& from)
20+
{
21+
return marshal_mapped_native_type(rtc_logging_severity_map, from);
22+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#include "pch.h"
2+
3+
#include "WebRtcLogSink.h"
4+
5+
using namespace System;
6+
using namespace WebRtcNet::Logging;
7+
8+
namespace WebRtcInterop::Logging
9+
{
10+
WebRtcLogSink::WebRtcLogSink()
11+
{
12+
}
13+
14+
WebRtcLogSink::~WebRtcLogSink()
15+
{
16+
}
17+
18+
void WebRtcLogSink::OnLogMessage(const webrtc::LogLineRef& msg)
19+
{
20+
try
21+
{
22+
// Extract tag, message, and convert severity
23+
auto tag = marshal_as<String^>(std::string(msg.tag()));
24+
auto message = marshal_as<String^>(std::string(msg.message()));
25+
auto severity = ConvertSeverity(msg.severity());
26+
27+
// Resolve category and EventId base
28+
String^ category = String::Empty;
29+
int eventIdBase = 0;
30+
ResolveCategoryAndEventId(tag, category, eventIdBase);
31+
32+
// Get current thread ID
33+
int threadId = Threading::Thread::CurrentThread->ManagedThreadId;
34+
35+
// Create log event
36+
WebRtcLogWriterBridge::WriteInteropLog(
37+
severity,
38+
eventIdBase,
39+
category,
40+
threadId,
41+
message);
42+
}
43+
catch (...)
44+
{
45+
// Suppress exceptions; do not disrupt native logging
46+
}
47+
}
48+
49+
int WebRtcLogSink::ConvertSeverity(webrtc::LoggingSeverity severity)
50+
{
51+
switch (severity)
52+
{
53+
case webrtc::LS_VERBOSE:
54+
return 1;
55+
case webrtc::LS_INFO:
56+
return 2;
57+
case webrtc::LS_WARNING:
58+
return 3;
59+
case webrtc::LS_ERROR:
60+
return 4;
61+
case webrtc::LS_NONE:
62+
return 6;
63+
default:
64+
return 2;
65+
}
66+
}
67+
68+
void WebRtcLogSink::ResolveCategoryAndEventId(
69+
String^ tag,
70+
String^% category,
71+
int% eventIdBase)
72+
{
73+
try
74+
{
75+
WebRtcLogWriterBridge::ResolveWebRtcCategory(tag, category, eventIdBase);
76+
}
77+
catch (...)
78+
{
79+
// Fallback: use WebRTC.Other
80+
category = "WebRTC.Other";
81+
eventIdBase = 1900;
82+
}
83+
}
84+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#pragma once
2+
3+
#include <rtc_base/logging.h>
4+
#include <memory>
5+
6+
namespace WebRtcInterop::Logging
7+
{
8+
/// <summary>
9+
/// Custom WebRTC log sink that forwards rtc::LogMessage events to managed IWebRtcLogWriter.
10+
/// Registers with WebRTC's logging system to capture all diagnostic output.
11+
/// </summary>
12+
class WebRtcLogSink : public webrtc::LogSink
13+
{
14+
public:
15+
WebRtcLogSink();
16+
~WebRtcLogSink() override;
17+
18+
/// <summary>
19+
/// Called by WebRTC for each log message.
20+
/// Extracts severity, tag, and message, then forwards to managed writer.
21+
/// </summary>
22+
void OnLogMessage(const webrtc::LogLineRef& msg) override;
23+
24+
/// <summary>
25+
/// Called by WebRTC for each log message (alternate interface).
26+
/// </summary>
27+
void OnLogMessage(const std::string& message) override { }
28+
29+
private:
30+
/// <summary>
31+
/// Converts WebRTC severity level to Microsoft.Extensions.Logging.LogLevel numeric values.
32+
/// </summary>
33+
static int ConvertSeverity(webrtc::LoggingSeverity severity);
34+
35+
/// <summary>
36+
/// Extracts category and EventId base from WebRTC tag via managed mapping.
37+
/// </summary>
38+
static void ResolveCategoryAndEventId(
39+
System::String^ tag,
40+
System::String^% category,
41+
System::Int32% eventIdBase);
42+
};
43+
}

0 commit comments

Comments
 (0)