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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,26 @@ dotnet msbuild WebRtcInterop.UnitTests\WebRtcInterop.UnitTests.vcxproj /p:Config
.\WebRtcInterop.UnitTests\x64\Debug\WebRtcInterop.UnitTests.exe
```

## Logging configuration

`Host.SetLoggerFactory(...)` configures logging for managed API code, C++/CLI interop code, and WebRTC native logs.

```csharp
using Microsoft.Extensions.Logging;
using WebRtcNet;

var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.SetMinimumLevel(LogLevel.Information)
.AddConsole();
});

Host.SetLoggerFactory(loggerFactory);
```

If `SetLoggerFactory` is not called, Debug builds default to console logging and Release builds are silent.

## Docker pipeline

The build pipeline uses individual-stage Dockerfiles rather than a single monolithic file. `docker buildx` is not used — Windows containers require classic `docker build`.
Expand Down
19 changes: 19 additions & 0 deletions WebRtcInterop.UnitTests/InteropHResultTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include "pch.h"

#include <winerror.h>
#include "gtest/gtest.h"

using namespace WebRtcInterop;

class interop_hresult_tests
{};

TEST(interop_hresult_tests, log_if_failed_returns_false_for_success)
{
EXPECT_FALSE(InteropHResult::LogIfFailed(S_OK, "success", "Interop.Tests"));
}

TEST(interop_hresult_tests, log_if_failed_returns_true_for_failure)
{
EXPECT_TRUE(InteropHResult::LogIfFailed(E_FAIL, "failure", "Interop.Tests"));
}
1 change: 1 addition & 0 deletions WebRtcInterop.UnitTests/WebRtcInterop.UnitTests.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<ClInclude Include="include\TestUtils.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="InteropHResultTests.cpp" />
<ClCompile Include="ManagedScopedRefPtrTests.cpp" />
<ClCompile Include="Marshaling\MarshalCollectionsTests.cpp" />
<ClCompile Include="Marshaling\MarshalMediaTests.cpp" />
Expand Down
20 changes: 0 additions & 20 deletions WebRtcInterop/InteropHResult.cpp

This file was deleted.

114 changes: 114 additions & 0 deletions WebRtcInterop/Logging/InteropHResult.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#include "pch.h"

#include "InteropHResult.h"

#include <windows.h>

using namespace System::ComponentModel;
using namespace System::Runtime::InteropServices;
using namespace System::Threading;
using namespace System;
using namespace WebRtcNet::Logging;

namespace WebRtcInterop
{
namespace
{
String^ TrimSystemMessage(String^ message)
{
if (String::IsNullOrWhiteSpace(message))
return "Unknown system error";

return message->Trim();
}

String^ FormatSystemMessage(const HRESULT hr)
{
LPWSTR rawMessage = nullptr;
const auto flags =
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS;
auto messageId = static_cast<DWORD>(hr);
auto length = FormatMessageW(
flags,
nullptr,
messageId,
0,
reinterpret_cast<LPWSTR>(&rawMessage),
0,
nullptr);

if (length == 0 && HRESULT_FACILITY(hr) == FACILITY_WIN32)
{
messageId = HRESULT_CODE(hr);
length = FormatMessageW(
flags,
nullptr,
messageId,
0,
reinterpret_cast<LPWSTR>(&rawMessage),
0,
nullptr);
}

if (length == 0 || rawMessage == nullptr)
return "Unknown system error";

try
{
return TrimSystemMessage(gcnew String(rawMessage));
}
finally
{
LocalFree(rawMessage);
}
}
}

void InteropHResult::ThrowIfFailed(HRESULT hr, String^ message)
{
if (SUCCEEDED(hr))
return;

if (HRESULT_FACILITY(hr) == FACILITY_WIN32)
throw gcnew Win32Exception(HRESULT_CODE(hr), message);

throw gcnew COMException(message, hr);
}

bool InteropHResult::LogIfFailed(HRESULT hr, String^ operation, String^ category)
{
if (SUCCEEDED(hr))
return false;

if (String::IsNullOrWhiteSpace(operation))
operation = "Interop operation";
if (String::IsNullOrWhiteSpace(category))
category = "Interop.HResult";

try
{
const auto formatted = String::Format(
"{0} failed. HRESULT=0x{1:X8} ({2}), Facility={3}, Code={4}, SystemMessage=\"{5}\"",
operation,
static_cast<UInt32>(hr),
hr,
HRESULT_FACILITY(hr),
HRESULT_CODE(hr),
FormatSystemMessage(hr));

WebRtcLogWriterBridge::WriteInteropLog(
4,
static_cast<int>(WebRtcLogEventId::InteropHResultFailure),
category,
Thread::CurrentThread->ManagedThreadId,
formatted);
}
catch (...)
{
}

return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ namespace WebRtcInterop
{
public:
static void ThrowIfFailed(HRESULT hr, System::String^ message);
static bool LogIfFailed(HRESULT hr, System::String^ operation, System::String^ category);
};
}
22 changes: 22 additions & 0 deletions WebRtcInterop/Logging/Marshaling/MarshalLogging.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include <rtc_base/logging.h>
#include <msclr/marshal.h>
#include <map>
#include "MarshalEnums.h"

// Static map for rtc::LoggingSeverity to LogLevel conversion
static const std::map<const rtc::LoggingSeverity, const Microsoft::Extensions::Logging::LogLevel> rtc_logging_severity_map{
{ rtc::LS_VERBOSE, Microsoft::Extensions::Logging::LogLevel::Debug },
{ rtc::LS_INFO, Microsoft::Extensions::Logging::LogLevel::Information },
{ rtc::LS_WARNING, Microsoft::Extensions::Logging::LogLevel::Warning },
{ rtc::LS_ERROR, Microsoft::Extensions::Logging::LogLevel::Error },
{ rtc::LS_NONE, Microsoft::Extensions::Logging::LogLevel::None }
};

// Marshal rtc::LoggingSeverity to Microsoft.Extensions.Logging.LogLevel
template <>
Microsoft::Extensions::Logging::LogLevel marshal_as<Microsoft::Extensions::Logging::LogLevel>(const rtc::LoggingSeverity& from)
{
return marshal_mapped_native_type(rtc_logging_severity_map, from);
}
84 changes: 84 additions & 0 deletions WebRtcInterop/Logging/WebRtcLogSink.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#include "pch.h"

#include "WebRtcLogSink.h"

using namespace System;
using namespace WebRtcNet::Logging;

namespace WebRtcInterop::Logging
{
WebRtcLogSink::WebRtcLogSink()
{
}

WebRtcLogSink::~WebRtcLogSink()
{
}

void WebRtcLogSink::OnLogMessage(const webrtc::LogLineRef& msg)
{
try
{
// Extract tag, message, and convert severity
auto tag = marshal_as<String^>(std::string(msg.tag()));
auto message = marshal_as<String^>(std::string(msg.message()));
auto severity = ConvertSeverity(msg.severity());

// Resolve category and EventId base
String^ category = String::Empty;
int eventIdBase = 0;
ResolveCategoryAndEventId(tag, category, eventIdBase);

// Get current thread ID
int threadId = Threading::Thread::CurrentThread->ManagedThreadId;

// Create log event
WebRtcLogWriterBridge::WriteInteropLog(
severity,
eventIdBase,
category,
threadId,
message);
}
catch (...)
{
// Suppress exceptions; do not disrupt native logging
}
}

int WebRtcLogSink::ConvertSeverity(webrtc::LoggingSeverity severity)
{
switch (severity)
{
case webrtc::LS_VERBOSE:
return 1;
case webrtc::LS_INFO:
return 2;
case webrtc::LS_WARNING:
return 3;
case webrtc::LS_ERROR:
return 4;
case webrtc::LS_NONE:
return 6;
default:
return 2;
}
}

void WebRtcLogSink::ResolveCategoryAndEventId(
String^ tag,
String^% category,
int% eventIdBase)
{
try
{
WebRtcLogWriterBridge::ResolveWebRtcCategory(tag, category, eventIdBase);
}
catch (...)
{
// Fallback: use WebRTC.Other
category = "WebRTC.Other";
eventIdBase = 1900;
}
}
}
43 changes: 43 additions & 0 deletions WebRtcInterop/Logging/WebRtcLogSink.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#pragma once

#include <rtc_base/logging.h>
#include <memory>

namespace WebRtcInterop::Logging
{
/// <summary>
/// Custom WebRTC log sink that forwards rtc::LogMessage events to managed IWebRtcLogWriter.
/// Registers with WebRTC's logging system to capture all diagnostic output.
/// </summary>
class WebRtcLogSink : public webrtc::LogSink
{
public:
WebRtcLogSink();
~WebRtcLogSink() override;

/// <summary>
/// Called by WebRTC for each log message.
/// Extracts severity, tag, and message, then forwards to managed writer.
/// </summary>
void OnLogMessage(const webrtc::LogLineRef& msg) override;

/// <summary>
/// Called by WebRTC for each log message (alternate interface).
/// </summary>
void OnLogMessage(const std::string& message) override { }

private:
/// <summary>
/// Converts WebRTC severity level to Microsoft.Extensions.Logging.LogLevel numeric values.
/// </summary>
static int ConvertSeverity(webrtc::LoggingSeverity severity);

/// <summary>
/// Extracts category and EventId base from WebRTC tag via managed mapping.
/// </summary>
static void ResolveCategoryAndEventId(
System::String^ tag,
System::String^% category,
System::Int32% eventIdBase);
};
}
Loading
Loading