diff --git a/media/mtransport/build/moz.build b/media/mtransport/build/moz.build index ab39001e1363..17a7e108e83f 100644 --- a/media/mtransport/build/moz.build +++ b/media/mtransport/build/moz.build @@ -12,6 +12,7 @@ EXPORTS.mtransport += [ '../nricectx.h', '../nricemediastream.h', '../nriceresolverfake.h', + '../rlogringbuffer.h', '../runnable_utils.h', '../runnable_utils_generated.h', '../sigslot.h', diff --git a/media/mtransport/objs.mozbuild b/media/mtransport/objs.mozbuild index c95dd19709b3..b5ed8a7b7aa9 100644 --- a/media/mtransport/objs.mozbuild +++ b/media/mtransport/objs.mozbuild @@ -13,6 +13,7 @@ mtransport_lcppsrcs = [ 'nriceresolver.cpp', 'nriceresolverfake.cpp', 'nrinterfaceprioritizer.cpp', + 'rlogringbuffer.cpp', 'transportflow.cpp', 'transportlayer.cpp', 'transportlayerdtls.cpp', diff --git a/media/mtransport/rlogringbuffer.cpp b/media/mtransport/rlogringbuffer.cpp new file mode 100644 index 000000000000..5f0bbc824de4 --- /dev/null +++ b/media/mtransport/rlogringbuffer.cpp @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Original author: bcampen@mozilla.com */ + +#include + +#include "rlogringbuffer.h" + +#include +#include +#include "mozilla/Move.h" // Pinch hitting for and std::move +#include + +extern "C" { +#include +#include "r_log.h" +} + +/* Matches r_dest_vlog type defined in r_log.h */ +static int ringbuffer_vlog(int facility, + int level, + const char *format, + va_list ap) { + MOZ_ASSERT(mozilla::RLogRingBuffer::GetInstance()); + // I could be evil and printf right into a std::string, but unless this + // shows up in profiling, it is not worth doing. + char temp[4096]; + vsnprintf(temp, sizeof(temp), format, ap); + + mozilla::RLogRingBuffer::GetInstance()->Log(std::string(temp)); + return 0; +} + +namespace mozilla { + +RLogRingBuffer* RLogRingBuffer::instance; + +RLogRingBuffer::RLogRingBuffer() + : log_limit_(4096), + mutex_("RLogRingBuffer::mutex_") { +} + +RLogRingBuffer::~RLogRingBuffer() { +} + +void RLogRingBuffer::SetLogLimit(uint32_t new_limit) { + mozilla::MutexAutoLock lock(mutex_); + log_limit_ = new_limit; + RemoveOld(); +} + +void RLogRingBuffer::Log(std::string&& log) { + mozilla::MutexAutoLock lock(mutex_); + log_messages_.push_front(Move(log)); + RemoveOld(); +} + +inline void RLogRingBuffer::RemoveOld() { + if (log_messages_.size() > log_limit_) { + log_messages_.resize(log_limit_); + } +} + + +RLogRingBuffer* RLogRingBuffer::CreateInstance() { + if (!instance) { + instance = new RLogRingBuffer; + r_log_set_extra_destination(LOG_DEBUG, &ringbuffer_vlog); + } + return instance; +} + +RLogRingBuffer* RLogRingBuffer::GetInstance() { + return instance; +} + +void RLogRingBuffer::DestroyInstance() { + // First param is ignored when passing null + r_log_set_extra_destination(LOG_DEBUG, nullptr); + delete instance; + instance = nullptr; +} + +void RLogRingBuffer::Filter(const std::string& substring, + uint32_t limit, + std::deque* matching_logs) { + std::vector substrings; + substrings.push_back(substring); + FilterAny(substrings, limit, matching_logs); +} + +inline bool AnySubstringMatches(const std::vector& substrings, + const std::string& string) { + for (auto sub = substrings.begin(); sub != substrings.end(); ++sub) { + if (string.find(*sub) != std::string::npos) { + return true; + } + } + return false; +} + +void RLogRingBuffer::FilterAny(const std::vector& substrings, + uint32_t limit, + std::deque* matching_logs) { + mozilla::MutexAutoLock lock(mutex_); + if (limit == 0) { + // At a max, all of the log messages. + limit = log_limit_; + } + + for (auto log = log_messages_.begin(); + log != log_messages_.end() && matching_logs->size() < limit; + ++log) { + if (AnySubstringMatches(substrings, *log)) { + matching_logs->push_front(*log); + } + } +} + +} // namespace mozilla + diff --git a/media/mtransport/rlogringbuffer.h b/media/mtransport/rlogringbuffer.h new file mode 100644 index 000000000000..87e9ae94a607 --- /dev/null +++ b/media/mtransport/rlogringbuffer.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Some of this code is cut-and-pasted from nICEr. Copyright is: + +/* +Copyright (c) 2007, Adobe Systems, Incorporated +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +* Neither the name of Adobe Systems, Network Resonance nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Original author: bcampen@mozilla.com */ + +/* + This file defines an r_dest_vlog that can be used to accumulate log messages + for later inspection/filtering. The intent is to use this for interactive + debug purposes on an about:webrtc page or similar. +*/ + +#ifndef rlogringbuffer_h__ +#define rlogringbuffer_h__ + +#include +#include +#include + +#include "mozilla/Mutex.h" + +#include "m_cpp_utils.h" + +namespace mozilla { + +class RLogRingBuffer { + public: + /* + NB: These are not threadsafe, nor are they safe to call during static + init/deinit. + */ + static RLogRingBuffer* CreateInstance(); + static RLogRingBuffer* GetInstance(); + static void DestroyInstance(); + + /* + Retrieves log statements that match a given substring, subject to a + limit. |matching_logs| will be filled in chronological order (front() + is oldest, back() is newest). |limit| == 0 will be interpreted as no + limit. + */ + void Filter(const std::string& substring, + uint32_t limit, + std::deque* matching_logs); + + void FilterAny(const std::vector& substrings, + uint32_t limit, + std::deque* matching_logs); + + inline void GetAny(uint32_t limit, + std::deque* matching_logs) { + Filter("", limit, matching_logs); + } + + void SetLogLimit(uint32_t new_limit); + + void Log(std::string&& log); + + private: + RLogRingBuffer(); + ~RLogRingBuffer(); + void RemoveOld(); + static RLogRingBuffer* instance; + + /* + * Might be worthwhile making this a circular buffer, but I think it is + * preferable to take up as little space as possible if no logging is + * happening/the ringbuffer is not being used. + */ + std::deque log_messages_; + /* Max size of log buffer (should we use time-depth instead/also?) */ + uint32_t log_limit_; + mozilla::Mutex mutex_; + + DISALLOW_COPY_ASSIGN(RLogRingBuffer); +}; // class RLogRingBuffer + +} // namespace mozilla + +#endif // rlogringbuffer_h__ + + diff --git a/media/mtransport/test/ice_unittest.cpp b/media/mtransport/test/ice_unittest.cpp index 73904b991e34..44e7bfd456f9 100644 --- a/media/mtransport/test/ice_unittest.cpp +++ b/media/mtransport/test/ice_unittest.cpp @@ -7,6 +7,7 @@ // Original author: ekr@rtfm.com #include +#include #include #include #include @@ -30,6 +31,7 @@ #include "nriceresolver.h" #include "nrinterfaceprioritizer.h" #include "mtransport_test_utils.h" +#include "rlogringbuffer.h" #include "runnable_utils.h" #include "stunserver.h" // TODO(bcampen@mozilla.com): Big fat hack since the build system doesn't give @@ -1222,6 +1224,39 @@ TEST_F(IceConnectTest, TestPollCandPairsDuringConnect) { ASSERT_TRUE(ContainsSucceededPair(pairs2)); } +TEST_F(IceConnectTest, TestRLogRingBuffer) { + RLogRingBuffer::CreateInstance(); + AddStream("first", 1); + ASSERT_TRUE(Gather(true)); + + p1_->Connect(p2_, TRICKLE_NONE, false); + p2_->Connect(p1_, TRICKLE_NONE, false); + + std::vector pairs1; + std::vector pairs2; + + p1_->StartChecks(); + p1_->UpdateAndValidateCandidatePairs(0, &pairs1); + p2_->UpdateAndValidateCandidatePairs(0, &pairs2); + + p2_->StartChecks(); + p1_->UpdateAndValidateCandidatePairs(0, &pairs1); + p2_->UpdateAndValidateCandidatePairs(0, &pairs2); + + WaitForComplete(); + p1_->UpdateAndValidateCandidatePairs(0, &pairs1); + p2_->UpdateAndValidateCandidatePairs(0, &pairs2); + ASSERT_TRUE(ContainsSucceededPair(pairs1)); + ASSERT_TRUE(ContainsSucceededPair(pairs2)); + + std::deque logs; + RLogRingBuffer::GetInstance()->Filter("CAND-PAIR", 0, &logs); + std::cerr << "Dumping CAND-PAIR logging:" << std::endl; + for (auto i = logs.rbegin(); i != logs.rend(); ++i) { + std::cerr << *i << std::endl; + } + RLogRingBuffer::DestroyInstance(); +} TEST_F(PrioritizerTest, TestPrioritizer) { SetPriorizer(::mozilla::CreateInterfacePrioritizer()); diff --git a/media/mtransport/test/moz.build b/media/mtransport/test/moz.build index 11f48516b0d0..093d07aa8d99 100644 --- a/media/mtransport/test/moz.build +++ b/media/mtransport/test/moz.build @@ -10,6 +10,7 @@ if CONFIG['OS_TARGET'] != 'WINNT' and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk': CPP_UNIT_TESTS += [ 'ice_unittest.cpp', 'nrappkit_unittest.cpp', + 'rlogringbuffer_unittest.cpp', 'runnable_utils_unittest.cpp', 'sockettransportservice_unittest.cpp', 'TestSyncRunnable.cpp', diff --git a/media/mtransport/test/rlogringbuffer_unittest.cpp b/media/mtransport/test/rlogringbuffer_unittest.cpp new file mode 100644 index 000000000000..c50e04bc5432 --- /dev/null +++ b/media/mtransport/test/rlogringbuffer_unittest.cpp @@ -0,0 +1,269 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Original author: bcampen@mozilla.com */ + +#include "rlogringbuffer.h" + +extern "C" { +#include "registry.h" +#include "r_log.h" +} + +#define GTEST_HAS_RTTI 0 +#include "gtest/gtest.h" +#include "gtest_utils.h" + +#include +#include +#include + +using mozilla::RLogRingBuffer; + +int NR_LOG_TEST = 0; + +class RLogRingBufferTest : public ::testing::Test { + public: + RLogRingBufferTest() { + Init(); + } + + ~RLogRingBufferTest() { + Free(); + } + + void Init() { + RLogRingBuffer::CreateInstance(); + } + + void Free() { + RLogRingBuffer::DestroyInstance(); + } + + void ReInit() { + Free(); + Init(); + } +}; + +TEST_F(RLogRingBufferTest, TestGetFree) { + RLogRingBuffer* instance = RLogRingBuffer::GetInstance(); + ASSERT_NE(nullptr, instance); +} + +TEST_F(RLogRingBufferTest, TestFilterEmpty) { + std::deque logs; + RLogRingBuffer::GetInstance()->GetAny(0, &logs); + ASSERT_EQ(0U, logs.size()); +} + +TEST_F(RLogRingBufferTest, TestBasicFilter) { + r_log(NR_LOG_TEST, LOG_DEBUG, "Test"); + std::deque logs; + RLogRingBuffer::GetInstance()->Filter("Test", 0, &logs); + ASSERT_EQ(1U, logs.size()); +} + +TEST_F(RLogRingBufferTest, TestBasicFilterContent) { + r_log(NR_LOG_TEST, LOG_DEBUG, "Test"); + std::deque logs; + RLogRingBuffer::GetInstance()->Filter("Test", 0, &logs); + ASSERT_EQ("Test", logs.back()); +} + +TEST_F(RLogRingBufferTest, TestFilterAnyFrontMatch) { + r_log(NR_LOG_TEST, LOG_DEBUG, "Test"); + std::vector substrings; + substrings.push_back("foo"); + substrings.push_back("Test"); + std::deque logs; + RLogRingBuffer::GetInstance()->FilterAny(substrings, 0, &logs); + ASSERT_EQ("Test", logs.back()); +} + +TEST_F(RLogRingBufferTest, TestFilterAnyBackMatch) { + r_log(NR_LOG_TEST, LOG_DEBUG, "Test"); + std::vector substrings; + substrings.push_back("Test"); + substrings.push_back("foo"); + std::deque logs; + RLogRingBuffer::GetInstance()->FilterAny(substrings, 0, &logs); + ASSERT_EQ("Test", logs.back()); +} + +TEST_F(RLogRingBufferTest, TestFilterAnyBothMatch) { + r_log(NR_LOG_TEST, LOG_DEBUG, "Test"); + std::vector substrings; + substrings.push_back("Tes"); + substrings.push_back("est"); + std::deque logs; + RLogRingBuffer::GetInstance()->FilterAny(substrings, 0, &logs); + ASSERT_EQ("Test", logs.back()); +} + +TEST_F(RLogRingBufferTest, TestFilterAnyNeitherMatch) { + r_log(NR_LOG_TEST, LOG_DEBUG, "Test"); + std::vector substrings; + substrings.push_back("tes"); + substrings.push_back("esT"); + std::deque logs; + RLogRingBuffer::GetInstance()->FilterAny(substrings, 0, &logs); + ASSERT_EQ(0U, logs.size()); +} + +TEST_F(RLogRingBufferTest, TestAllMatch) { + r_log(NR_LOG_TEST, LOG_DEBUG, "Test1"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test2"); + std::deque logs; + RLogRingBuffer::GetInstance()->GetAny(0, &logs); + ASSERT_EQ(2U, logs.size()); +} + +TEST_F(RLogRingBufferTest, TestOrder) { + r_log(NR_LOG_TEST, LOG_DEBUG, "Test1"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test2"); + std::deque logs; + RLogRingBuffer::GetInstance()->GetAny(0, &logs); + ASSERT_EQ("Test2", logs.back()); + ASSERT_EQ("Test1", logs.front()); +} + +TEST_F(RLogRingBufferTest, TestNoMatch) { + r_log(NR_LOG_TEST, LOG_DEBUG, "Test1"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test2"); + std::deque logs; + RLogRingBuffer::GetInstance()->Filter("foo", 0, &logs); + ASSERT_EQ(0U, logs.size()); +} + +TEST_F(RLogRingBufferTest, TestSubstringFilter) { + r_log(NR_LOG_TEST, LOG_DEBUG, "Test1"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test2"); + std::deque logs; + RLogRingBuffer::GetInstance()->Filter("t1", 0, &logs); + ASSERT_EQ(1U, logs.size()); + ASSERT_EQ("Test1", logs.back()); +} + +TEST_F(RLogRingBufferTest, TestFilterLimit) { + r_log(NR_LOG_TEST, LOG_DEBUG, "Test1"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test2"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test3"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test4"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test5"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test6"); + std::deque logs; + RLogRingBuffer::GetInstance()->Filter("Test", 2, &logs); + ASSERT_EQ(2U, logs.size()); + ASSERT_EQ("Test6", logs.back()); + ASSERT_EQ("Test5", logs.front()); +} + +TEST_F(RLogRingBufferTest, TestFilterAnyLimit) { + r_log(NR_LOG_TEST, LOG_DEBUG, "TestOne"); + r_log(NR_LOG_TEST, LOG_DEBUG, "TestTwo"); + r_log(NR_LOG_TEST, LOG_DEBUG, "TestThree"); + r_log(NR_LOG_TEST, LOG_DEBUG, "TestFour"); + r_log(NR_LOG_TEST, LOG_DEBUG, "TestFive"); + r_log(NR_LOG_TEST, LOG_DEBUG, "TestSix"); + std::vector substrings; + // Matches Two, Three, Four, and Six + substrings.push_back("tT"); + substrings.push_back("o"); + substrings.push_back("r"); + substrings.push_back("S"); + std::deque logs; + RLogRingBuffer::GetInstance()->FilterAny(substrings, 2, &logs); + ASSERT_EQ(2U, logs.size()); + ASSERT_EQ("TestSix", logs.back()); + ASSERT_EQ("TestFour", logs.front()); +} + +TEST_F(RLogRingBufferTest, TestLimit) { + RLogRingBuffer::GetInstance()->SetLogLimit(3); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test1"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test2"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test3"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test4"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test5"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test6"); + std::deque logs; + RLogRingBuffer::GetInstance()->GetAny(0, &logs); + ASSERT_EQ(3U, logs.size()); + ASSERT_EQ("Test6", logs.back()); + ASSERT_EQ("Test4", logs.front()); +} + +TEST_F(RLogRingBufferTest, TestLimitBulkDiscard) { + r_log(NR_LOG_TEST, LOG_DEBUG, "Test1"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test2"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test3"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test4"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test5"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test6"); + RLogRingBuffer::GetInstance()->SetLogLimit(3); + std::deque logs; + RLogRingBuffer::GetInstance()->GetAny(0, &logs); + ASSERT_EQ(3U, logs.size()); + ASSERT_EQ("Test6", logs.back()); + ASSERT_EQ("Test4", logs.front()); +} + +TEST_F(RLogRingBufferTest, TestIncreaseLimit) { + RLogRingBuffer::GetInstance()->SetLogLimit(3); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test1"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test2"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test3"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test4"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test5"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test6"); + RLogRingBuffer::GetInstance()->SetLogLimit(300); + std::deque logs; + RLogRingBuffer::GetInstance()->GetAny(0, &logs); + ASSERT_EQ(3U, logs.size()); + ASSERT_EQ("Test6", logs.back()); + ASSERT_EQ("Test4", logs.front()); +} + +TEST_F(RLogRingBufferTest, TestClear) { + r_log(NR_LOG_TEST, LOG_DEBUG, "Test1"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test2"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test3"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test4"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test5"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test6"); + RLogRingBuffer::GetInstance()->SetLogLimit(0); + RLogRingBuffer::GetInstance()->SetLogLimit(4096); + std::deque logs; + RLogRingBuffer::GetInstance()->GetAny(0, &logs); + ASSERT_EQ(0U, logs.size()); +} + +TEST_F(RLogRingBufferTest, TestReInit) { + r_log(NR_LOG_TEST, LOG_DEBUG, "Test1"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test2"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test3"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test4"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test5"); + r_log(NR_LOG_TEST, LOG_DEBUG, "Test6"); + ReInit(); + std::deque logs; + RLogRingBuffer::GetInstance()->GetAny(0, &logs); + ASSERT_EQ(0U, logs.size()); +} + +int main(int argc, char** argv) { + NR_reg_init(NR_REG_MODE_LOCAL); + r_log_init(); + /* Would be nice to be able to register/unregister in the fixture */ + const char* facility = "rlogringbuffer_test"; + r_log_register(const_cast(facility), &NR_LOG_TEST); + ::testing::InitGoogleTest(&argc, argv); + + int rv = RUN_ALL_TESTS(); + return rv; +} + diff --git a/media/mtransport/third_party/nrappkit/src/log/r_log.c b/media/mtransport/third_party/nrappkit/src/log/r_log.c index aebf5782cc81..b1df10a2a0bf 100644 --- a/media/mtransport/third_party/nrappkit/src/log/r_log.c +++ b/media/mtransport/third_party/nrappkit/src/log/r_log.c @@ -330,6 +330,15 @@ int r_dump(int facility,int level,char *name,char *data,int len) return(0); } +// Some platforms (notably WIN32) do not have this +#ifndef va_copy + #ifdef WIN32 + #define va_copy(dest, src) ( (dest) = (src) ) + #else // WIN32 + #error va_copy undefined, and semantics of assignment on va_list unknown + #endif //WIN32 +#endif //va_copy + int r_vlog(int facility,int level,const char *format,va_list ap) { char log_fmt_buf[MAX_ERROR_STRING_SIZE]; @@ -354,7 +363,12 @@ int r_vlog(int facility,int level,const char *format,va_list ap) for(i=0; i