Bug 906990 - Part 5: Allow logging related to a given candidate pair to be fetched. r=ekr
☠☠ backed out by 1cd17b9be3e1 ☠ ☠
authorByron Campen [:bwc] <docfaraday@gmail.com>
Tue, 29 Oct 2013 10:45:09 -0700
changeset 168735 ff8128d5a5d998b783f38653ca13c7496d64dc83
parent 168734 6fd8199adc93f64082ca7720b4db83925d4922ff
child 168736 325d718fec53de0dd1fcdc677bf2b3b6801a5bec
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersekr
bugs906990
milestone28.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 906990 - Part 5: Allow logging related to a given candidate pair to be fetched. r=ekr
media/mtransport/build/moz.build
media/mtransport/objs.mozbuild
media/mtransport/rlogringbuffer.cpp
media/mtransport/rlogringbuffer.h
media/mtransport/test/ice_unittest.cpp
media/mtransport/test/moz.build
media/mtransport/test/rlogringbuffer_unittest.cpp
media/mtransport/third_party/nrappkit/src/log/r_log.c
--- a/media/mtransport/build/moz.build
+++ b/media/mtransport/build/moz.build
@@ -7,16 +7,17 @@
 MODULE = 'mtransport'
 
 EXPORTS.mtransport += [
     '../dtlsidentity.h',
     '../m_cpp_utils.h',
     '../nricectx.h',
     '../nricemediastream.h',
     '../nriceresolverfake.h',
+    '../rlogringbuffer.h',
     '../runnable_utils.h',
     '../runnable_utils_generated.h',
     '../sigslot.h',
     '../simpletokenbucket.h',
     '../transportflow.h',
     '../transportlayer.h',
     '../transportlayerdtls.h',
     '../transportlayerice.h',
--- a/media/mtransport/objs.mozbuild
+++ b/media/mtransport/objs.mozbuild
@@ -8,16 +8,17 @@ mtransport_lcppsrcs = [
     'dtlsidentity.cpp',
     'nr_socket_prsock.cpp',
     'nr_timer.cpp',
     'nricectx.cpp',
     'nricemediastream.cpp',
     'nriceresolver.cpp',
     'nriceresolverfake.cpp',
     'nrinterfaceprioritizer.cpp',
+    'rlogringbuffer.cpp',
     'simpletokenbucket.cpp',
     'transportflow.cpp',
     'transportlayer.cpp',
     'transportlayerdtls.cpp',
     'transportlayerice.cpp',
     'transportlayerlog.cpp',
     'transportlayerloopback.cpp',
     'transportlayerprsock.cpp',
new file mode 100644
--- /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 <cstdarg>
+
+#include "rlogringbuffer.h"
+
+#include <deque>
+#include <string>
+#include "mozilla/Move.h" // Pinch hitting for <utility> and std::move
+#include <vector>
+
+extern "C" {
+#include <csi_platform.h>
+#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<std::string>* matching_logs) {
+  std::vector<std::string> substrings;
+  substrings.push_back(substring);
+  FilterAny(substrings, limit, matching_logs);
+}
+
+inline bool AnySubstringMatches(const std::vector<std::string>& 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<std::string>& substrings,
+                               uint32_t limit,
+                               std::deque<std::string>* 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
+
new file mode 100644
--- /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 <deque>
+#include <string>
+#include <vector>
+
+#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<std::string>* matching_logs);
+
+    void FilterAny(const std::vector<std::string>& substrings,
+                   uint32_t limit,
+                   std::deque<std::string>* matching_logs);
+
+    inline void GetAny(uint32_t limit,
+                       std::deque<std::string>* 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<std::string> 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__
+
+
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -2,16 +2,17 @@
 /* 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: ekr@rtfm.com
 
 #include <algorithm>
+#include <deque>
 #include <iostream>
 #include <limits>
 #include <map>
 #include <string>
 #include <vector>
 
 #include "sigslot.h"
 
@@ -25,16 +26,17 @@
 #include "nsXPCOM.h"
 
 #include "nricectx.h"
 #include "nricemediastream.h"
 #include "nriceresolverfake.h"
 #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
 // us a clean way to add object files to a single executable.
 #include "stunserver.cpp"
 
 #define GTEST_HAS_RTTI 0
 #include "gtest/gtest.h"
@@ -1217,16 +1219,49 @@ TEST_F(IceConnectTest, TestPollCandPairs
 
   WaitForComplete();
   p1_->UpdateAndValidateCandidatePairs(0, &pairs1);
   p2_->UpdateAndValidateCandidatePairs(0, &pairs2);
   ASSERT_TRUE(ContainsSucceededPair(pairs1));
   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<NrIceCandidatePair> pairs1;
+  std::vector<NrIceCandidatePair> 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<std::string> 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());
 
   AddInterface("0", NR_INTERFACE_TYPE_VPN, 100); // unknown vpn
   AddInterface("1", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_WIRED, 100); // wired vpn
   AddInterface("2", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_WIFI, 100); // wifi vpn
   AddInterface("3", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_MOBILE, 100); // wifi vpn
--- a/media/mtransport/test/moz.build
+++ b/media/mtransport/test/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 MODULE = 'test_mtransport'
 
 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',
         'simpletokenbucket_unittest.cpp',
         'sockettransportservice_unittest.cpp',
         'TestSyncRunnable.cpp',
         'transport_unittests.cpp',
         'turn_unittest.cpp',
     ]
 
new file mode 100644
--- /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 <deque>
+#include <string>
+#include <vector>
+
+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<std::string> 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<std::string> 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<std::string> 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<std::string> substrings;
+  substrings.push_back("foo");
+  substrings.push_back("Test");
+  std::deque<std::string> 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<std::string> substrings;
+  substrings.push_back("Test");
+  substrings.push_back("foo");
+  std::deque<std::string> 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<std::string> substrings;
+  substrings.push_back("Tes");
+  substrings.push_back("est");
+  std::deque<std::string> 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<std::string> substrings;
+  substrings.push_back("tes");
+  substrings.push_back("esT");
+  std::deque<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<char*>(facility), &NR_LOG_TEST);
+  ::testing::InitGoogleTest(&argc, argv);
+
+  int rv = RUN_ALL_TESTS();
+  return rv;
+}
+
--- a/media/mtransport/third_party/nrappkit/src/log/r_log.c
+++ b/media/mtransport/third_party/nrappkit/src/log/r_log.c
@@ -325,16 +325,25 @@ int r_dump(int facility,int level,char *
       r_log(facility,level,"%s[%d]=%s",name,len,hex);
     else
       r_log(facility,level,"%s",hex);
 
     RFREE(hex);
     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];
     char *level_str="unknown";
     char *facility_str="unknown";
     char *fmt_str=(char *)format;
     int i;
 
@@ -349,17 +358,22 @@ int r_vlog(int facility,int level,const 
         facility_str,level_str,format);
 
       log_fmt_buf[MAX_ERROR_STRING_SIZE-1]=0;
       fmt_str=log_fmt_buf;
     }
 
     for(i=0; i<LOG_NUM_DESTINATIONS; i++){
       if(r_logging_dest(i,facility,level)){
-        log_destinations[i].dest_vlog(facility,level,fmt_str,ap);
+        // Some platforms do not react well when you use a va_list more than
+        // once
+        va_list copy;
+        va_copy(copy, ap);
+        log_destinations[i].dest_vlog(facility,level,fmt_str,copy);
+        va_end(copy);
       }
     }
     return(0);
   }
 
 int stderr_vlog(int facility,int level,const char *format,va_list ap)
   {
 #if 0 /* remove time stamping, for now */