AppRTCDemo file logging.

Adds logging macros to log logs to a file. Undeletes CircularFileStream
for that purpose.

BUG=
R=jiayl@webrtc.org, pbos@webrtc.org

Review URL: https://codereview.webrtc.org/1217473011 .

Cr-Commit-Position: refs/heads/master@{#9582}
diff --git a/talk/app/webrtc/objc/RTCFileLogger.mm b/talk/app/webrtc/objc/RTCFileLogger.mm
new file mode 100644
index 0000000..b474d7a
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCFileLogger.mm
@@ -0,0 +1,230 @@
+/*
+ * libjingle
+ * Copyright 2015 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+#import "RTCFileLogger.h"
+
+#include "webrtc/base/checks.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/stream.h"
+
+NSString *const kDefaultLogFileName = @"webrtc.log";
+NSUInteger const kDefaultMaxFileSize = 10 * 1024 * 1024; // 10MB.
+
+namespace rtc {
+
+class CircularFileStreamLogSink : public LogSink {
+ public:
+  // Creates a log sink that writes to the given stream. This log sink takes
+  // ownership of |stream|.
+  CircularFileStreamLogSink(CircularFileStream *stream) {
+    DCHECK(stream);
+    _stream.reset(stream);
+  }
+
+  ~CircularFileStreamLogSink() override {}
+
+  void OnLogMessage(const std::string &message) override {
+    if (_stream) {
+      _stream->WriteAll(message.data(), message.size(), nullptr, nullptr);
+    }
+  }
+
+  CircularFileStream *GetStream() { return _stream.get(); }
+
+ private:
+  scoped_ptr<CircularFileStream> _stream;
+};
+
+} // namespace rtc
+
+@implementation RTCFileLogger {
+  BOOL _hasStarted;
+  NSString *_filePath;
+  NSUInteger _maxFileSize;
+  rtc::scoped_ptr<rtc::CircularFileStreamLogSink> _logSink;
+}
+
+@synthesize severity = _severity;
+
+- (instancetype)init {
+  NSArray *paths = NSSearchPathForDirectoriesInDomains(
+      NSDocumentDirectory, NSUserDomainMask, YES);
+  NSString *documentsDirPath = [paths firstObject];
+  NSString *defaultFilePath =
+      [documentsDirPath stringByAppendingPathComponent:kDefaultLogFileName];
+  return [self initWithFilePath:defaultFilePath
+                    maxFileSize:kDefaultMaxFileSize];
+}
+
+- (instancetype)initWithFilePath:(NSString *)filePath
+                     maxFileSize:(NSUInteger)maxFileSize {
+  NSParameterAssert(filePath.length);
+  NSParameterAssert(maxFileSize);
+  if (self = [super init]) {
+    _filePath = filePath;
+    _maxFileSize = maxFileSize;
+    _severity = kRTCFileLoggerSeverityInfo;
+  }
+  return self;
+}
+
+- (void)dealloc {
+  [self stop];
+}
+
+- (void)start {
+  if (_hasStarted) {
+    return;
+  }
+  rtc::scoped_ptr<rtc::CircularFileStream> stream;
+  stream.reset(new rtc::CircularFileStream(_maxFileSize));
+  _logSink.reset(new rtc::CircularFileStreamLogSink(stream.release()));
+  int error = 0;
+  if (!_logSink->GetStream()->Open(_filePath.UTF8String, "wb", &error)) {
+    LOG(LS_ERROR) << "Failed to open log file at path: "
+                  << _filePath.UTF8String
+                  << " Error: "
+                  << error;
+    _logSink.reset();
+    return;
+  }
+  // TODO(tkchin): Log thead info on iOS, currently this doesn't do anything.
+  rtc::LogMessage::LogThreads(true);
+  rtc::LogMessage::LogTimestamps(true);
+  rtc::LogMessage::AddLogToStream(_logSink.get(), [self rtcSeverity]);
+  _hasStarted = YES;
+}
+
+- (void)stop {
+  if (!_hasStarted) {
+    return;
+  }
+  DCHECK(_logSink);
+  rtc::LogMessage::RemoveLogToStream(_logSink.get());
+  _hasStarted = NO;
+
+  // Read the ordered version of the log.
+  NSData *logData = [self reorderedLogData];
+  NSError *error = nil;
+  // Write the ordered version back to disk.
+  if (![logData writeToFile:_filePath
+                    options:NSDataWritingAtomic
+                      error:&error]) {
+    LOG(LS_ERROR) << "Failed to rewrite log to disk at path: "
+                  << _filePath.UTF8String;
+    if (error) {
+      LOG(LS_ERROR) << "Error: " << error.localizedDescription.UTF8String;
+    }
+  } else {
+    // If we succeeded in writing to disk we don't need to hold on to the
+    // stream anymore.
+    _logSink.reset();
+  }
+}
+
+- (NSData *)logData {
+  if (_hasStarted) {
+    return nil;
+  }
+  if (!_logSink.get()) {
+    // If there isn't a previously used stream just return contents of file.
+    return [[self class] contentsOfFileAtPath:_filePath];
+  }
+  return [self reorderedLogData];
+}
+
+#pragma mark - Private
+
++ (NSData *)contentsOfFileAtPath:(NSString *)path {
+  NSError *error = nil;
+  NSData *contents = [NSData dataWithContentsOfFile:path
+                                            options:0
+                                              error:&error];
+  if (error) {
+    LOG(LS_ERROR) << "Failed to read contents of file at path: "
+                  << path.UTF8String
+                  << " Error: "
+                  << error.localizedDescription.UTF8String;
+    return nil;
+  }
+  return contents;
+}
+
+- (NSData *)reorderedLogData {
+  if (_hasStarted || !_logSink.get()) {
+    return nil;
+  }
+  // We have a stream we used for writing in memory and we're not writing. The
+  // stream has a pointer to where the log boundary is so it can reorder the
+  // log correctly. We just need to reopen the file in read mode.
+  int error = 0;
+  rtc::CircularFileStream *stream = _logSink->GetStream();
+  if (!stream->Open(_filePath.UTF8String, "r", &error)) {
+    LOG(LS_ERROR) << "Failed to open log file at path: "
+                  << _filePath.UTF8String
+                  << " Error: "
+                  << error;
+    return nil;
+  }
+  size_t logSize = 0;
+  size_t bytesRead = 0;
+  error = 0;
+  if (!stream->GetSize(&logSize)) {
+    LOG(LS_ERROR) << "Failed to get log file size.";
+    return nil;
+  }
+  // Allocate memory using malloc so we can pass it direcly to NSData without
+  // copying.
+  rtc::scoped_ptr<uint8_t[]> buffer(static_cast<uint8_t*>(malloc(logSize)));
+  if (stream->ReadAll(buffer.get(), logSize, &bytesRead, &error)
+      != rtc::SR_SUCCESS) {
+    LOG(LS_ERROR) << "Failed to read log file at path: "
+                  << _filePath.UTF8String
+                  << " Error: "
+                  << error;
+  }
+  DCHECK_LE(bytesRead, logSize);
+  // NSData takes ownership of the bytes and frees it on dealloc.
+  return [NSData dataWithBytesNoCopy:buffer.release()
+                              length:bytesRead];
+}
+
+- (rtc::LoggingSeverity)rtcSeverity {
+  switch (_severity) {
+    case kRTCFileLoggerSeverityVerbose:
+      return rtc::LS_VERBOSE;
+    case kRTCFileLoggerSeverityInfo:
+      return rtc::LS_INFO;
+    case kRTCFileLoggerSeverityWarning:
+      return rtc::LS_WARNING;
+    case kRTCFileLoggerSeverityError:
+      return rtc::LS_ERROR;
+  }
+}
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCFileLogger.h b/talk/app/webrtc/objc/public/RTCFileLogger.h
new file mode 100644
index 0000000..5c311b7
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCFileLogger.h
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2015 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+// TODO(tkchin): Move this to a common location.
+#ifndef NS_DESIGNATED_INITIALIZER
+#define NS_DESIGNATED_INITIALIZER
+#endif
+
+typedef NS_ENUM(NSUInteger, RTCFileLoggerSeverity) {
+  kRTCFileLoggerSeverityVerbose,
+  kRTCFileLoggerSeverityInfo,
+  kRTCFileLoggerSeverityWarning,
+  kRTCFileLoggerSeverityError
+};
+
+// This class intercepts WebRTC logs and saves them to a file. The file size
+// will not exceed the given maximum bytesize. When the maximum bytesize is
+// reached logs from the beginning and the end are preserved while the middle
+// section is overwritten instead.
+// This class is not threadsafe.
+@interface RTCFileLogger : NSObject
+
+// The severity level to capture. The default is kRTCFileLoggerSeverityInfo.
+@property(nonatomic, assign) RTCFileLoggerSeverity severity;
+
+// Default constructor provides default settings for file path and file size.
+- (instancetype)init;
+
+- (instancetype)initWithFilePath:(NSString *)filePath
+                     maxFileSize:(NSUInteger)maxFileSize
+    NS_DESIGNATED_INITIALIZER;
+
+// Starts writing WebRTC logs to file if not already started. Overwrites any
+// existing file.
+- (void)start;
+
+// Stops writing WebRTC logs to file. Rewrites the log file as required to
+// reorder logs because logs may be disordered due to use of
+// rtc::CircularFileStream. This method is also called on dealloc.
+- (void)stop;
+
+// Returns the current contents of the log file. Returns nil if start has been
+// called without a stop, or if there is no data.
+- (NSData *)logData;
+
+@end
diff --git a/talk/examples/objc/.clang-format b/talk/examples/objc/.clang-format
new file mode 120000
index 0000000..ce43d52
--- /dev/null
+++ b/talk/examples/objc/.clang-format
@@ -0,0 +1 @@
+../../app/webrtc/objc/.clang-format
\ No newline at end of file
diff --git a/talk/examples/objc/AppRTCDemo/ARDAppClient.m b/talk/examples/objc/AppRTCDemo/ARDAppClient.m
index ac99ca2..5b905c6 100644
--- a/talk/examples/objc/AppRTCDemo/ARDAppClient.m
+++ b/talk/examples/objc/AppRTCDemo/ARDAppClient.m
@@ -30,6 +30,7 @@
 #if defined(WEBRTC_IOS)
 #import "RTCAVFoundationVideoSource.h"
 #endif
+#import "RTCFileLogger.h"
 #import "RTCICEServer.h"
 #import "RTCMediaConstraints.h"
 #import "RTCMediaStream.h"
@@ -41,6 +42,7 @@
 #import "ARDAppEngineClient.h"
 #import "ARDCEODTURNClient.h"
 #import "ARDJoinResponse.h"
+#import "ARDLogging.h"
 #import "ARDMessageResponse.h"
 #import "ARDSDPUtils.h"
 #import "ARDSignalingMessage.h"
@@ -65,7 +67,9 @@
 static NSInteger const kARDAppClientErrorInvalidClient = -5;
 static NSInteger const kARDAppClientErrorInvalidRoom = -6;
 
-@implementation ARDAppClient
+@implementation ARDAppClient {
+  RTCFileLogger *_fileLogger;
+}
 
 @synthesize delegate = _delegate;
 @synthesize state = _state;
@@ -131,6 +135,8 @@
   _factory = [[RTCPeerConnectionFactory alloc] init];
   _messageQueue = [NSMutableArray array];
   _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]];
+  _fileLogger = [[RTCFileLogger alloc] init];
+  [_fileLogger start];
 }
 
 - (void)dealloc {
@@ -156,7 +162,7 @@
   [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
                                                      NSError *error) {
     if (error) {
-      NSLog(@"Error retrieving TURN servers: %@", error);
+      ARDLog("Error retrieving TURN servers: %@", error.localizedDescription);
     }
     ARDAppClient *strongSelf = weakSelf;
     [strongSelf.iceServers addObjectsFromArray:turnServers];
@@ -175,12 +181,12 @@
     NSError *joinError =
         [[strongSelf class] errorForJoinResultType:response.result];
     if (joinError) {
-      NSLog(@"Failed to join room:%@ on room server.", roomId);
+      ARDLog(@"Failed to join room:%@ on room server.", roomId);
       [strongSelf disconnect];
       [strongSelf.delegate appClient:strongSelf didError:joinError];
       return;
     }
-    NSLog(@"Joined room:%@ on room server.", roomId);
+    ARDLog(@"Joined room:%@ on room server.", roomId);
     strongSelf.roomId = response.roomId;
     strongSelf.clientId = response.clientId;
     strongSelf.isInitiator = response.isInitiator;
@@ -272,13 +278,13 @@
 
 - (void)peerConnection:(RTCPeerConnection *)peerConnection
     signalingStateChanged:(RTCSignalingState)stateChanged {
-  NSLog(@"Signaling state changed: %d", stateChanged);
+  ARDLog(@"Signaling state changed: %d", stateChanged);
 }
 
 - (void)peerConnection:(RTCPeerConnection *)peerConnection
            addedStream:(RTCMediaStream *)stream {
   dispatch_async(dispatch_get_main_queue(), ^{
-    NSLog(@"Received %lu video tracks and %lu audio tracks",
+    ARDLog(@"Received %lu video tracks and %lu audio tracks",
         (unsigned long)stream.videoTracks.count,
         (unsigned long)stream.audioTracks.count);
     if (stream.videoTracks.count) {
@@ -290,17 +296,17 @@
 
 - (void)peerConnection:(RTCPeerConnection *)peerConnection
         removedStream:(RTCMediaStream *)stream {
-  NSLog(@"Stream was removed.");
+  ARDLog(@"Stream was removed.");
 }
 
 - (void)peerConnectionOnRenegotiationNeeded:
     (RTCPeerConnection *)peerConnection {
-  NSLog(@"WARNING: Renegotiation needed but unimplemented.");
+  ARDLog(@"WARNING: Renegotiation needed but unimplemented.");
 }
 
 - (void)peerConnection:(RTCPeerConnection *)peerConnection
     iceConnectionChanged:(RTCICEConnectionState)newState {
-  NSLog(@"ICE state changed: %d", newState);
+  ARDLog(@"ICE state changed: %d", newState);
   dispatch_async(dispatch_get_main_queue(), ^{
     [_delegate appClient:self didChangeConnectionState:newState];
   });
@@ -308,7 +314,7 @@
 
 - (void)peerConnection:(RTCPeerConnection *)peerConnection
     iceGatheringChanged:(RTCICEGatheringState)newState {
-  NSLog(@"ICE gathering state changed: %d", newState);
+  ARDLog(@"ICE gathering state changed: %d", newState);
 }
 
 - (void)peerConnection:(RTCPeerConnection *)peerConnection
@@ -333,7 +339,7 @@
                           error:(NSError *)error {
   dispatch_async(dispatch_get_main_queue(), ^{
     if (error) {
-      NSLog(@"Failed to create session description. Error: %@", error);
+      ARDLog(@"Failed to create session description. Error: %@", error);
       [self disconnect];
       NSDictionary *userInfo = @{
         NSLocalizedDescriptionKey: @"Failed to create session description.",
@@ -362,7 +368,7 @@
     didSetSessionDescriptionWithError:(NSError *)error {
   dispatch_async(dispatch_get_main_queue(), ^{
     if (error) {
-      NSLog(@"Failed to set session description. Error: %@", error);
+      ARDLog(@"Failed to set session description. Error: %@", error);
       [self disconnect];
       NSDictionary *userInfo = @{
         NSLocalizedDescriptionKey: @"Failed to set session description.",
diff --git a/talk/examples/objc/AppRTCDemo/ARDAppEngineClient.m b/talk/examples/objc/AppRTCDemo/ARDAppEngineClient.m
index 760a171..0b33325 100644
--- a/talk/examples/objc/AppRTCDemo/ARDAppEngineClient.m
+++ b/talk/examples/objc/AppRTCDemo/ARDAppEngineClient.m
@@ -28,6 +28,7 @@
 #import "ARDAppEngineClient.h"
 
 #import "ARDJoinResponse.h"
+#import "ARDLogging.h"
 #import "ARDMessageResponse.h"
 #import "ARDSignalingMessage.h"
 #import "ARDUtilities.h"
@@ -57,7 +58,7 @@
   NSString *urlString =
       [NSString stringWithFormat:kARDRoomServerJoinFormat, roomId];
   NSURL *roomURL = [NSURL URLWithString:urlString];
-  NSLog(@"Joining room:%@ on room server.", roomId);
+  ARDLog(@"Joining room:%@ on room server.", roomId);
   NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:roomURL];
   request.HTTPMethod = @"POST";
   __weak ARDAppEngineClient *weakSelf = self;
@@ -101,7 +102,7 @@
       [NSString stringWithFormat:
           kARDRoomServerMessageFormat, roomId, clientId];
   NSURL *url = [NSURL URLWithString:urlString];
-  NSLog(@"C->RS POST: %@", message);
+  ARDLog(@"C->RS POST: %@", message);
   NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
   request.HTTPMethod = @"POST";
   request.HTTPBody = data;
@@ -147,19 +148,19 @@
   NSError *error = nil;
   // We want a synchronous request so that we know that we've left the room on
   // room server before we do any further work.
-  NSLog(@"C->RS: BYE");
+  ARDLog(@"C->RS: BYE");
   [NSURLConnection sendSynchronousRequest:request
                         returningResponse:&response
                                     error:&error];
   if (error) {
-    NSLog(@"Error leaving room %@ on room server: %@",
+    ARDLog(@"Error leaving room %@ on room server: %@",
           roomId, error.localizedDescription);
     if (completionHandler) {
       completionHandler(error);
     }
     return;
   }
-  NSLog(@"Left room:%@ on room server.", roomId);
+  ARDLog(@"Left room:%@ on room server.", roomId);
   if (completionHandler) {
     completionHandler(nil);
   }
diff --git a/talk/examples/objc/AppRTCDemo/ARDSDPUtils.m b/talk/examples/objc/AppRTCDemo/ARDSDPUtils.m
index 157d6fc..481b6ce 100644
--- a/talk/examples/objc/AppRTCDemo/ARDSDPUtils.m
+++ b/talk/examples/objc/AppRTCDemo/ARDSDPUtils.m
@@ -27,6 +27,7 @@
 
 #import "ARDSDPUtils.h"
 
+#import "ARDLogging.h"
 #import "RTCSessionDescription.h"
 
 @implementation ARDSDPUtils
@@ -42,7 +43,7 @@
   NSMutableArray *lines =
       [NSMutableArray arrayWithArray:
           [sdpString componentsSeparatedByString:lineSeparator]];
-  int mLineIndex = -1;
+  NSInteger mLineIndex = -1;
   NSString *codecRtpMap = nil;
   // a=rtpmap:<payload type> <encoding name>/<clock rate>
   // [/<encoding parameters>]
@@ -70,11 +71,11 @@
     }
   }
   if (mLineIndex == -1) {
-    NSLog(@"No m=video line, so can't prefer %@", codec);
+    ARDLog(@"No m=video line, so can't prefer %@", codec);
     return description;
   }
   if (!codecRtpMap) {
-    NSLog(@"No rtpmap for %@", codec);
+    ARDLog(@"No rtpmap for %@", codec);
     return description;
   }
   NSArray *origMLineParts =
@@ -98,7 +99,7 @@
     [lines replaceObjectAtIndex:mLineIndex
                      withObject:newMLine];
   } else {
-    NSLog(@"Wrong SDP media description format: %@", lines[mLineIndex]);
+    ARDLog(@"Wrong SDP media description format: %@", lines[mLineIndex]);
   }
   NSString *mangledSdpString = [lines componentsJoinedByString:lineSeparator];
   return [[RTCSessionDescription alloc] initWithType:description.type
diff --git a/talk/examples/objc/AppRTCDemo/ARDSignalingMessage.m b/talk/examples/objc/AppRTCDemo/ARDSignalingMessage.m
index e9fb09a..dc00e8f 100644
--- a/talk/examples/objc/AppRTCDemo/ARDSignalingMessage.m
+++ b/talk/examples/objc/AppRTCDemo/ARDSignalingMessage.m
@@ -27,6 +27,7 @@
 
 #import "ARDSignalingMessage.h"
 
+#import "ARDLogging.h"
 #import "ARDUtilities.h"
 #import "RTCICECandidate+JSON.h"
 #import "RTCSessionDescription+JSON.h"
@@ -52,7 +53,7 @@
 + (ARDSignalingMessage *)messageFromJSONString:(NSString *)jsonString {
   NSDictionary *values = [NSDictionary dictionaryWithJSONString:jsonString];
   if (!values) {
-    NSLog(@"Error parsing signaling message JSON.");
+    ARDLog(@"Error parsing signaling message JSON.");
     return nil;
   }
 
@@ -71,7 +72,7 @@
   } else if ([typeString isEqualToString:@"bye"]) {
     message = [[ARDByeMessage alloc] init];
   } else {
-    NSLog(@"Unexpected type: %@", typeString);
+    ARDLog(@"Unexpected type: %@", typeString);
   }
   return message;
 }
diff --git a/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.m b/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.m
index 27dd25d..f25e9d0 100644
--- a/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.m
+++ b/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.m
@@ -27,6 +27,7 @@
 
 #import "ARDWebSocketChannel.h"
 
+#import "ARDLogging.h"
 #import "ARDUtilities.h"
 #import "SRWebSocket.h"
 
@@ -57,7 +58,7 @@
     _delegate = delegate;
     _socket = [[SRWebSocket alloc] initWithURL:url];
     _socket.delegate = self;
-    NSLog(@"Opening WebSocket.");
+    ARDLog(@"Opening WebSocket.");
     [_socket open];
   }
   return self;
@@ -104,12 +105,12 @@
     NSString *messageString =
         [[NSString alloc] initWithData:messageJSONObject
                               encoding:NSUTF8StringEncoding];
-    NSLog(@"C->WSS: %@", messageString);
+    ARDLog(@"C->WSS: %@", messageString);
     [_socket send:messageString];
   } else {
     NSString *dataString =
         [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
-    NSLog(@"C->WSS POST: %@", dataString);
+    ARDLog(@"C->WSS POST: %@", dataString);
     NSString *urlString =
         [NSString stringWithFormat:@"%@/%@/%@",
             [_restURL absoluteString], _roomId, _clientId];
@@ -126,7 +127,7 @@
     return;
   }
   [_socket close];
-  NSLog(@"C->WSS DELETE rid:%@ cid:%@", _roomId, _clientId);
+  ARDLog(@"C->WSS DELETE rid:%@ cid:%@", _roomId, _clientId);
   NSString *urlString =
       [NSString stringWithFormat:@"%@/%@/%@",
           [_restURL absoluteString], _roomId, _clientId];
@@ -140,7 +141,7 @@
 #pragma mark - SRWebSocketDelegate
 
 - (void)webSocketDidOpen:(SRWebSocket *)webSocket {
-  NSLog(@"WebSocket connection opened.");
+  ARDLog(@"WebSocket connection opened.");
   self.state = kARDSignalingChannelStateOpen;
   if (_roomId.length && _clientId.length) {
     [self registerWithCollider];
@@ -154,24 +155,24 @@
                                                   options:0
                                                     error:nil];
   if (![jsonObject isKindOfClass:[NSDictionary class]]) {
-    NSLog(@"Unexpected message: %@", jsonObject);
+    ARDLog(@"Unexpected message: %@", jsonObject);
     return;
   }
   NSDictionary *wssMessage = jsonObject;
   NSString *errorString = wssMessage[kARDWSSMessageErrorKey];
   if (errorString.length) {
-    NSLog(@"WSS error: %@", errorString);
+    ARDLog(@"WSS error: %@", errorString);
     return;
   }
   NSString *payload = wssMessage[kARDWSSMessagePayloadKey];
   ARDSignalingMessage *signalingMessage =
       [ARDSignalingMessage messageFromJSONString:payload];
-  NSLog(@"WSS->C: %@", payload);
+  ARDLog(@"WSS->C: %@", payload);
   [_delegate channel:self didReceiveMessage:signalingMessage];
 }
 
 - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
-  NSLog(@"WebSocket error: %@", error);
+  ARDLog(@"WebSocket error: %@", error);
   self.state = kARDSignalingChannelStateError;
 }
 
@@ -179,7 +180,7 @@
     didCloseWithCode:(NSInteger)code
               reason:(NSString *)reason
             wasClean:(BOOL)wasClean {
-  NSLog(@"WebSocket closed with code: %ld reason:%@ wasClean:%d",
+  ARDLog(@"WebSocket closed with code: %ld reason:%@ wasClean:%d",
       (long)code, reason, wasClean);
   NSParameterAssert(_state != kARDSignalingChannelStateError);
   self.state = kARDSignalingChannelStateClosed;
@@ -204,7 +205,7 @@
                                         error:nil];
   NSString *messageString =
       [[NSString alloc] initWithData:message encoding:NSUTF8StringEncoding];
-  NSLog(@"Registering on WSS for rid:%@ cid:%@", _roomId, _clientId);
+  ARDLog(@"Registering on WSS for rid:%@ cid:%@", _roomId, _clientId);
   // Registration can fail if server rejects it. For example, if the room is
   // full.
   [_socket send:messageString];
diff --git a/talk/examples/objc/AppRTCDemo/RTCICECandidate+JSON.m b/talk/examples/objc/AppRTCDemo/RTCICECandidate+JSON.m
index c15d633..01d666d 100644
--- a/talk/examples/objc/AppRTCDemo/RTCICECandidate+JSON.m
+++ b/talk/examples/objc/AppRTCDemo/RTCICECandidate+JSON.m
@@ -27,6 +27,8 @@
 
 #import "RTCICECandidate+JSON.h"
 
+#import "ARDLogging.h"
+
 static NSString const *kRTCICECandidateTypeKey = @"type";
 static NSString const *kRTCICECandidateTypeValue = @"candidate";
 static NSString const *kRTCICECandidateMidKey = @"id";
@@ -56,7 +58,7 @@
                                       options:NSJSONWritingPrettyPrinted
                                         error:&error];
   if (error) {
-    NSLog(@"Error serializing JSON: %@", error);
+    ARDLog(@"Error serializing JSON: %@", error);
     return nil;
   }
   return data;
diff --git a/talk/examples/objc/AppRTCDemo/common/ARDLogging.h b/talk/examples/objc/AppRTCDemo/common/ARDLogging.h
new file mode 100644
index 0000000..dfb31d8
--- /dev/null
+++ b/talk/examples/objc/AppRTCDemo/common/ARDLogging.h
@@ -0,0 +1,88 @@
+/*
+ * libjingle
+ * Copyright 2015 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+// We route all logging through the WebRTC logger. By doing this we will get
+// both app and WebRTC logs in the same place, which we can then route to a
+// file if we need to. A side effect of this is that we get severity for free.
+typedef NS_ENUM(NSInteger, ARDLogSeverity) {
+  kARDLogSeverityVerbose,
+  kARDLogSeverityInfo,
+  kARDLogSeverityWarning,
+  kARDLogSeverityError,
+};
+
+#if defined(__cplusplus)
+extern "C" void ARDLogToWebRTCLogger(ARDLogSeverity severity,
+                                     NSString *logString);
+extern "C" NSString *ARDFileName(const char *filePath);
+extern "C" void ARDLogInit();
+#else
+// Logs |logString| to the WebRTC logger at the given severity.
+extern void ARDLogToWebRTCLogger(ARDLogSeverity severity, NSString *logString);
+// Returns the filename with the path prefix removed.
+extern NSString *ARDFileName(const char *filePath);
+// Initializes the correct logging levels. This should be called once on app
+// startup.
+extern void ARDLogInit();
+#endif
+
+#define ARDLogString(format, ...)                    \
+  [NSString stringWithFormat:@"(%@:%d %s): " format, \
+      ARDFileName(__FILE__),                         \
+      __LINE__,                                      \
+      __FUNCTION__,                                  \
+      ##__VA_ARGS__]
+
+#define ARDLogEx(severity, format, ...)                        \
+  do {                                                         \
+    NSString *logString = ARDLogString(format, ##__VA_ARGS__); \
+    ARDLogToWebRTCLogger(severity, logString);                 \
+  } while (false)
+
+#define ARDLogVerbose(format, ...)                        \
+  ARDLogEx(kARDLogSeverityVerbose, format, ##__VA_ARGS__) \
+
+#define ARDLogInfo(format, ...)                           \
+  ARDLogEx(kARDLogSeverityInfo, format, ##__VA_ARGS__)    \
+
+#define ARDLogWarning(format, ...)                        \
+  ARDLogEx(kARDLogSeverityWarning, format, ##__VA_ARGS__) \
+
+#define ARDLogError(format, ...)                          \
+  ARDLogEx(kARDLogSeverityError, format, ##__VA_ARGS__)   \
+
+#ifdef _DEBUG
+#define ARDLogDebug(format, ...) ARDLogInfo(format, ##__VA_ARGS__)
+#else
+#define ARDLogDebug(format, ...) \
+  do {                           \
+  } while (false)
+#endif
+
+#define ARDLog(format, ...) ARDLogInfo(format, ##__VA_ARGS__)
diff --git a/talk/examples/objc/AppRTCDemo/common/ARDLogging.mm b/talk/examples/objc/AppRTCDemo/common/ARDLogging.mm
new file mode 100644
index 0000000..7bd773d
--- /dev/null
+++ b/talk/examples/objc/AppRTCDemo/common/ARDLogging.mm
@@ -0,0 +1,68 @@
+/*
+ * libjingle
+ * Copyright 2015 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+#import "ARDLogging.h"
+
+#include "webrtc/base/logging.h"
+
+void ARDLogInit() {
+#ifndef _DEBUG
+  // In debug builds the default level is LS_INFO and in non-debug builds it is
+  // disabled. Continue to log to console in non-debug builds, but only
+  // warnings and errors.
+  rtc::LogMessage::LogToDebug(rtc::LS_WARNING);
+#endif
+}
+
+void ARDLogToWebRTCLogger(ARDLogSeverity severity, NSString *logString) {
+  if (logString.length) {
+    const char* utf8String = logString.UTF8String;
+    switch (severity) {
+      case kARDLogSeverityVerbose:
+        LOG(LS_VERBOSE) << utf8String;
+        break;
+      case kARDLogSeverityInfo:
+        LOG(LS_INFO) << utf8String;
+        break;
+      case kARDLogSeverityWarning:
+        LOG(LS_WARNING) << utf8String;
+        break;
+      case kARDLogSeverityError:
+        LOG(LS_ERROR) << utf8String;
+        break;
+    }
+  }
+}
+
+NSString *ARDFileName(const char *filePath) {
+  NSString *nsFilePath =
+      [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(filePath)
+                                     length:strlen(filePath)
+                                   encoding:NSUTF8StringEncoding
+                               freeWhenDone:NO];
+  return nsFilePath.lastPathComponent;
+}
diff --git a/talk/examples/objc/AppRTCDemo/ARDUtilities.h b/talk/examples/objc/AppRTCDemo/common/ARDUtilities.h
similarity index 100%
rename from talk/examples/objc/AppRTCDemo/ARDUtilities.h
rename to talk/examples/objc/AppRTCDemo/common/ARDUtilities.h
diff --git a/talk/examples/objc/AppRTCDemo/ARDUtilities.m b/talk/examples/objc/AppRTCDemo/common/ARDUtilities.m
similarity index 93%
rename from talk/examples/objc/AppRTCDemo/ARDUtilities.m
rename to talk/examples/objc/AppRTCDemo/common/ARDUtilities.m
index 781e786..066ee02 100644
--- a/talk/examples/objc/AppRTCDemo/ARDUtilities.m
+++ b/talk/examples/objc/AppRTCDemo/common/ARDUtilities.m
@@ -25,6 +25,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#import "ARDLogging.h"
 #import "ARDUtilities.h"
 
 @implementation NSDictionary (ARDUtilites)
@@ -36,7 +37,7 @@
   NSDictionary *dict =
       [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
   if (error) {
-    NSLog(@"Error parsing JSON: %@", error.localizedDescription);
+    ARDLog(@"Error parsing JSON: %@", error.localizedDescription);
   }
   return dict;
 }
@@ -46,7 +47,7 @@
   NSDictionary *dict =
       [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
   if (error) {
-    NSLog(@"Error parsing JSON: %@", error.localizedDescription);
+    ARDLog(@"Error parsing JSON: %@", error.localizedDescription);
   }
   return dict;
 }
@@ -84,7 +85,7 @@
                                     NSData *data,
                                     NSError *error) {
     if (error) {
-      NSLog(@"Error posting data: %@", error.localizedDescription);
+      ARDLog(@"Error posting data: %@", error.localizedDescription);
       if (completionHandler) {
         completionHandler(NO, data);
       }
@@ -95,7 +96,7 @@
       NSString *serverResponse = data.length > 0 ?
           [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] :
           nil;
-      NSLog(@"Received bad response: %@", serverResponse);
+      ARDLog(@"Received bad response: %@", serverResponse);
       if (completionHandler) {
         completionHandler(NO, data);
       }
diff --git a/talk/examples/objc/AppRTCDemo/ios/ARDAppDelegate.m b/talk/examples/objc/AppRTCDemo/ios/ARDAppDelegate.m
index 2a7a155..352ade6 100644
--- a/talk/examples/objc/AppRTCDemo/ios/ARDAppDelegate.m
+++ b/talk/examples/objc/AppRTCDemo/ios/ARDAppDelegate.m
@@ -27,6 +27,7 @@
 
 #import "ARDAppDelegate.h"
 
+#import "ARDLogging.h"
 #import "ARDMainViewController.h"
 #import "RTCPeerConnectionFactory.h"
 
@@ -43,6 +44,7 @@
   [_window makeKeyAndVisible];
   ARDMainViewController *viewController = [[ARDMainViewController alloc] init];
   _window.rootViewController = viewController;
+  ARDLogInit();
   return YES;
 }
 
diff --git a/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallViewController.m b/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallViewController.m
index 2f07c7a..149beef 100644
--- a/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallViewController.m
+++ b/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallViewController.m
@@ -30,6 +30,7 @@
 #import "RTCAVFoundationVideoSource.h"
 
 #import "ARDAppClient.h"
+#import "ARDLogging.h"
 #import "ARDVideoCallView.h"
 
 @interface ARDVideoCallViewController () <ARDAppClientDelegate,
@@ -69,13 +70,13 @@
     didChangeState:(ARDAppClientState)state {
   switch (state) {
     case kARDAppClientStateConnected:
-      NSLog(@"Client connected.");
+      ARDLog(@"Client connected.");
       break;
     case kARDAppClientStateConnecting:
-      NSLog(@"Client connecting.");
+      ARDLog(@"Client connecting.");
       break;
     case kARDAppClientStateDisconnected:
-      NSLog(@"Client disconnected.");
+      ARDLog(@"Client disconnected.");
       [self hangup];
       break;
   }
@@ -83,7 +84,7 @@
 
 - (void)appClient:(ARDAppClient *)client
     didChangeConnectionState:(RTCICEConnectionState)state {
-  NSLog(@"ICE state changed: %d", state);
+  ARDLog(@"ICE state changed: %d", state);
   __weak ARDVideoCallViewController *weakSelf = self;
   dispatch_async(dispatch_get_main_queue(), ^{
     ARDVideoCallViewController *strongSelf = weakSelf;
diff --git a/talk/libjingle.gyp b/talk/libjingle.gyp
index 9de1c9b..3c0f26e 100755
--- a/talk/libjingle.gyp
+++ b/talk/libjingle.gyp
@@ -240,6 +240,7 @@
             'app/webrtc/objc/RTCDataChannel.mm',
             'app/webrtc/objc/RTCEnumConverter.h',
             'app/webrtc/objc/RTCEnumConverter.mm',
+            'app/webrtc/objc/RTCFileLogger.mm',
             'app/webrtc/objc/RTCI420Frame+Internal.h',
             'app/webrtc/objc/RTCI420Frame.mm',
             'app/webrtc/objc/RTCICECandidate+Internal.h',
@@ -280,6 +281,7 @@
             'app/webrtc/objc/public/RTCAudioSource.h',
             'app/webrtc/objc/public/RTCAudioTrack.h',
             'app/webrtc/objc/public/RTCDataChannel.h',
+            'app/webrtc/objc/public/RTCFileLogger.h',
             'app/webrtc/objc/public/RTCI420Frame.h',
             'app/webrtc/objc/public/RTCICECandidate.h',
             'app/webrtc/objc/public/RTCICEServer.h',
diff --git a/talk/libjingle_examples.gyp b/talk/libjingle_examples.gyp
index 5cd31b6..3401411 100755
--- a/talk/libjingle_examples.gyp
+++ b/talk/libjingle_examples.gyp
@@ -152,9 +152,36 @@
 
     ['OS=="ios" or (OS=="mac" and target_arch!="ia32" and mac_sdk>="10.8")', {
       'targets': [
-        { 'target_name': 'apprtc_signaling',
+        {
+          'target_name': 'apprtc_common',
+          'type': 'static_library',
+          'sources': [
+            'examples/objc/AppRTCDemo/common/ARDLogging.h',
+            'examples/objc/AppRTCDemo/common/ARDLogging.mm',
+            'examples/objc/AppRTCDemo/common/ARDUtilities.h',
+            'examples/objc/AppRTCDemo/common/ARDUtilities.m',
+          ],
+          'include_dirs': [
+            'examples/objc/AppRTCDemo/common',
+          ],
+          'direct_dependent_settings': {
+            'include_dirs': [
+              'examples/objc/AppRTCDemo/common',
+            ],
+          },
+          'conditions': [
+            ['OS=="mac"', {
+              'xcode_settings': {
+                'MACOSX_DEPLOYMENT_TARGET' : '10.8',
+              },
+            }],
+          ],
+        },
+        {
+          'target_name': 'apprtc_signaling',
           'type': 'static_library',
           'dependencies': [
+            'apprtc_common',
             'libjingle.gyp:libjingle_peerconnection_objc',
             'socketrocket',
           ],
@@ -179,8 +206,6 @@
             'examples/objc/AppRTCDemo/ARDSignalingMessage.h',
             'examples/objc/AppRTCDemo/ARDSignalingMessage.m',
             'examples/objc/AppRTCDemo/ARDTURNClient.h',
-            'examples/objc/AppRTCDemo/ARDUtilities.h',
-            'examples/objc/AppRTCDemo/ARDUtilities.m',
             'examples/objc/AppRTCDemo/ARDWebSocketChannel.h',
             'examples/objc/AppRTCDemo/ARDWebSocketChannel.m',
             'examples/objc/AppRTCDemo/RTCICECandidate+JSON.h',
@@ -217,6 +242,7 @@
           'product_name': 'AppRTCDemo',
           'mac_bundle': 1,
           'dependencies': [
+            'apprtc_common',
             'apprtc_signaling',
           ],
           'conditions': [
diff --git a/webrtc/base/BUILD.gn b/webrtc/base/BUILD.gn
index 60bed6e..2535a64 100644
--- a/webrtc/base/BUILD.gn
+++ b/webrtc/base/BUILD.gn
@@ -49,6 +49,8 @@
 
 config("ios_config") {
   libs = [
+    "CFNetwork.framework",
+
     #"Foundation.framework",  # Already included in //build/config:default_libs.
     "Security.framework",
     "SystemConfiguration.framework",
diff --git a/webrtc/base/base.gyp b/webrtc/base/base.gyp
index 7bfa3e3..b11f4de 100644
--- a/webrtc/base/base.gyp
+++ b/webrtc/base/base.gyp
@@ -588,6 +588,7 @@
           'all_dependent_settings': {
             'xcode_settings': {
               'OTHER_LDFLAGS': [
+                '-framework CFNetwork',
                 '-framework Foundation',
                 '-framework Security',
                 '-framework SystemConfiguration',
diff --git a/webrtc/base/bitbuffer_unittest.cc b/webrtc/base/bitbuffer_unittest.cc
index 89d3524..c47f92c 100644
--- a/webrtc/base/bitbuffer_unittest.cc
+++ b/webrtc/base/bitbuffer_unittest.cc
@@ -166,10 +166,12 @@
 
   // Disable death test on Android because it relies on fork() and doesn't play
   // nicely.
+#if defined(GTEST_HAS_DEATH_TEST)
 #if !defined(WEBRTC_ANDROID)
   // Passing a NULL out parameter is death.
   EXPECT_DEATH(buffer.GetCurrentOffset(&byte_offset, NULL), "");
 #endif
+#endif
 }
 
 uint64 GolombEncoded(uint32 val) {
diff --git a/webrtc/base/stream.cc b/webrtc/base/stream.cc
index e22c3d8..3e3afa0 100644
--- a/webrtc/base/stream.cc
+++ b/webrtc/base/stream.cc
@@ -517,6 +517,113 @@
   fclose(file_);
 }
 
+CircularFileStream::CircularFileStream(size_t max_size)
+    : max_write_size_(max_size),
+      position_(0),
+      marked_position_(max_size / 2),
+      last_write_position_(0),
+      read_segment_(READ_LATEST),
+      read_segment_available_(0) {
+}
+
+bool CircularFileStream::Open(const std::string& filename,
+                              const char* mode,
+                              int* error) {
+  if (!FileStream::Open(filename.c_str(), mode, error))
+    return false;
+
+  if (strchr(mode, "r") != NULL) {  // Opened in read mode.
+    // Check if the buffer has been overwritten and determine how to read the
+    // log in time sequence.
+    size_t file_size;
+    GetSize(&file_size);
+    if (file_size == position_) {
+      // The buffer has not been overwritten yet. Read 0 .. file_size
+      read_segment_ = READ_LATEST;
+      read_segment_available_ = file_size;
+    } else {
+      // The buffer has been over written. There are three segments: The first
+      // one is 0 .. marked_position_, which is the marked earliest log. The
+      // second one is position_ .. file_size, which is the middle log. The
+      // last one is marked_position_ .. position_, which is the latest log.
+      read_segment_ = READ_MARKED;
+      read_segment_available_ = marked_position_;
+      last_write_position_ = position_;
+    }
+
+    // Read from the beginning.
+    position_ = 0;
+    SetPosition(position_);
+  }
+
+  return true;
+}
+
+StreamResult CircularFileStream::Read(void* buffer,
+                                      size_t buffer_len,
+                                      size_t* read,
+                                      int* error) {
+  if (read_segment_available_ == 0) {
+    size_t file_size;
+    switch (read_segment_) {
+      case READ_MARKED:  // Finished READ_MARKED and start READ_MIDDLE.
+        read_segment_ = READ_MIDDLE;
+        position_ = last_write_position_;
+        SetPosition(position_);
+        GetSize(&file_size);
+        read_segment_available_ = file_size - position_;
+        break;
+
+      case READ_MIDDLE:  // Finished READ_MIDDLE and start READ_LATEST.
+        read_segment_ = READ_LATEST;
+        position_ = marked_position_;
+        SetPosition(position_);
+        read_segment_available_ = last_write_position_ - position_;
+        break;
+
+      default:  // Finished READ_LATEST and return EOS.
+        return rtc::SR_EOS;
+    }
+  }
+
+  size_t local_read;
+  if (!read)
+    read = &local_read;
+
+  size_t to_read = std::min(buffer_len, read_segment_available_);
+  rtc::StreamResult result =
+      rtc::FileStream::Read(buffer, to_read, read, error);
+  if (result == rtc::SR_SUCCESS) {
+    read_segment_available_ -= *read;
+    position_ += *read;
+  }
+  return result;
+}
+
+StreamResult CircularFileStream::Write(const void* data,
+                                       size_t data_len,
+                                       size_t* written,
+                                       int* error) {
+  if (position_ >= max_write_size_) {
+    ASSERT(position_ == max_write_size_);
+    position_ = marked_position_;
+    SetPosition(position_);
+  }
+
+  size_t local_written;
+  if (!written)
+    written = &local_written;
+
+  size_t to_eof = max_write_size_ - position_;
+  size_t to_write = std::min(data_len, to_eof);
+  rtc::StreamResult result =
+      rtc::FileStream::Write(data, to_write, written, error);
+  if (result == rtc::SR_SUCCESS) {
+    position_ += *written;
+  }
+  return result;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // MemoryStream
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/webrtc/base/stream.h b/webrtc/base/stream.h
index b331766..f67c704 100644
--- a/webrtc/base/stream.h
+++ b/webrtc/base/stream.h
@@ -418,6 +418,39 @@
   DISALLOW_COPY_AND_ASSIGN(FileStream);
 };
 
+// A stream that caps the output at a certain size, dropping content from the
+// middle of the logical stream and maintaining equal parts of the start/end of
+// the logical stream.
+class CircularFileStream : public FileStream {
+ public:
+  explicit CircularFileStream(size_t max_size);
+
+  bool Open(const std::string& filename, const char* mode, int* error) override;
+  StreamResult Read(void* buffer,
+                    size_t buffer_len,
+                    size_t* read,
+                    int* error) override;
+  StreamResult Write(const void* data,
+                     size_t data_len,
+                     size_t* written,
+                     int* error) override;
+
+ private:
+  enum ReadSegment {
+    READ_MARKED,  // Read 0 .. marked_position_
+    READ_MIDDLE,  // Read position_ .. file_size
+    READ_LATEST,  // Read marked_position_ .. position_ if the buffer was
+                  // overwritten or 0 .. position_ otherwise.
+  };
+
+  size_t max_write_size_;
+  size_t position_;
+  size_t marked_position_;
+  size_t last_write_position_;
+  ReadSegment read_segment_;
+  size_t read_segment_available_;
+};
+
 ///////////////////////////////////////////////////////////////////////////////
 // MemoryStream is a simple implementation of a StreamInterface over in-memory
 // data.  Data is read and written at the current seek position.  Reads return
diff --git a/webrtc/base/stream_unittest.cc b/webrtc/base/stream_unittest.cc
index 86eb722..4d5066a 100644
--- a/webrtc/base/stream_unittest.cc
+++ b/webrtc/base/stream_unittest.cc
@@ -8,7 +8,9 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
+#include "webrtc/base/fileutils.h"
 #include "webrtc/base/gunit.h"
+#include "webrtc/base/pathutils.h"
 #include "webrtc/base/stream.h"
 #include "webrtc/test/testsupport/gtest_disable.h"
 
@@ -370,4 +372,162 @@
   EXPECT_EQ(SR_BLOCK, buf.ReadOffset(out, 10, 16, NULL));
 }
 
+class CircularFileStreamTest : public ::testing::Test {
+ protected:
+  static size_t const kMaxSize = 12;
+
+  CircularFileStreamTest() : is_open_(false), stream_(kMaxSize) {
+    Pathname temp_dir;
+    if (Filesystem::GetAppTempFolder(&temp_dir)) {
+      logfile_name_ =
+          Filesystem::TempFilename(temp_dir, "CircularFileStreamTest");
+    }
+  }
+
+  virtual void SetUp() {
+    int error = -1;
+    is_open_ = stream_.Open(logfile_name_, "wb", &error);
+  }
+
+  virtual void TearDown() {
+    if (!Filesystem::IsAbsent(logfile_name_)) {
+      Filesystem::DeleteFile(logfile_name_);
+    }
+  }
+
+  bool is_open_;
+  CircularFileStream stream_;
+  std::string logfile_name_;
+};
+
+TEST_F(CircularFileStreamTest, ReadWriteWithinCapacity) {
+  EXPECT_TRUE(is_open_);
+  // Write contents.
+  const uint8_t bytes[] = {1, 2, 3, 4, 5, 6};
+  size_t written = 0;
+  int error = 0;
+  EXPECT_EQ(SR_SUCCESS, stream_.Write(bytes, sizeof(bytes), &written, &error));
+  EXPECT_EQ(0, error);
+  EXPECT_EQ(written, sizeof(bytes));
+  stream_.Close();
+
+  // Check file contents.
+  uint8_t content_bytes[sizeof(bytes)] = {};
+  scoped_ptr<FileStream> content_stream(
+      Filesystem::OpenFile(logfile_name_, "r"));
+  size_t num_content_bytes_read = 0;
+  EXPECT_TRUE(content_stream);
+  error = 0;
+  EXPECT_EQ(SR_SUCCESS,
+            content_stream->Read(content_bytes, sizeof(content_bytes),
+                                 &num_content_bytes_read, &error));
+  EXPECT_EQ(sizeof(content_bytes), num_content_bytes_read);
+  ASSERT_EQ(sizeof(content_bytes), sizeof(bytes));
+  EXPECT_EQ(0, memcmp(content_bytes, bytes, sizeof(content_bytes)));
+
+  // Check read result.
+  error = 0;
+  size_t file_size = 0;
+  EXPECT_TRUE(stream_.Open(logfile_name_, "r", &error));
+  EXPECT_TRUE(stream_.GetSize(&file_size));
+  EXPECT_EQ(0, error);
+  EXPECT_EQ(sizeof(bytes), file_size);
+  scoped_ptr<uint8_t[]> read_bytes(new uint8_t[file_size]);
+  size_t num_read_bytes = 0;
+  error = 0;
+  EXPECT_EQ(SR_SUCCESS, stream_.ReadAll(read_bytes.get(), file_size,
+                                        &num_read_bytes, &error));
+  EXPECT_EQ(sizeof(bytes), num_read_bytes);
+  EXPECT_EQ(0, memcmp(bytes, read_bytes.get(), file_size));
+}
+
+TEST_F(CircularFileStreamTest, ReadWriteAtCapacity) {
+  EXPECT_TRUE(is_open_);
+  // Write contents.
+  const uint8_t bytes[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+  size_t written = 0;
+  int error = 0;
+  EXPECT_EQ(SR_SUCCESS, stream_.Write(bytes, sizeof(bytes), &written, &error));
+  EXPECT_EQ(0, error);
+  EXPECT_EQ(written, sizeof(bytes));
+  stream_.Close();
+
+  // Check file contents.
+  uint8_t content_bytes[sizeof(bytes)] = {};
+  scoped_ptr<FileStream> content_stream(
+      Filesystem::OpenFile(logfile_name_, "r"));
+  size_t num_content_bytes_read = 0;
+  EXPECT_TRUE(content_stream);
+  error = 0;
+  EXPECT_EQ(SR_SUCCESS,
+            content_stream->Read(content_bytes, sizeof(content_bytes),
+                                 &num_content_bytes_read, &error));
+  EXPECT_EQ(sizeof(content_bytes), num_content_bytes_read);
+  ASSERT_EQ(sizeof(content_bytes), sizeof(bytes));
+  EXPECT_EQ(0, memcmp(content_bytes, bytes, sizeof(content_bytes)));
+
+  // Check read result.
+  error = 0;
+  size_t file_size = 0;
+  EXPECT_TRUE(stream_.Open(logfile_name_, "r", &error));
+  EXPECT_TRUE(stream_.GetSize(&file_size));
+  EXPECT_EQ(0, error);
+  EXPECT_EQ(sizeof(bytes), file_size);
+  scoped_ptr<uint8_t[]> read_bytes(new uint8_t[file_size]);
+  size_t num_read_bytes = 0;
+  error = 0;
+  EXPECT_EQ(SR_SUCCESS, stream_.ReadAll(read_bytes.get(), file_size,
+                                        &num_read_bytes, &error));
+  EXPECT_EQ(sizeof(bytes), num_read_bytes);
+  EXPECT_EQ(0, memcmp(bytes, read_bytes.get(), file_size));
+}
+
+TEST_F(CircularFileStreamTest, ReadWriteOverCapacity) {
+  EXPECT_TRUE(is_open_);
+  // Write contents.
+  const uint8_t bytes[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+  size_t written = 0;
+  int error = 0;
+  EXPECT_EQ(SR_SUCCESS,
+            stream_.WriteAll(bytes, sizeof(bytes), &written, &error));
+  EXPECT_EQ(0, error);
+  EXPECT_EQ(written, sizeof(bytes));
+  stream_.Close();
+
+  // Check file contents.
+  uint8_t content_bytes[kMaxSize] = {};
+  scoped_ptr<FileStream> content_stream(
+      Filesystem::OpenFile(logfile_name_, "r"));
+  size_t num_content_bytes_read = 0;
+  EXPECT_TRUE(content_stream);
+  error = 0;
+  EXPECT_EQ(SR_SUCCESS,
+            content_stream->Read(content_bytes, sizeof(content_bytes),
+                                 &num_content_bytes_read, &error));
+  EXPECT_EQ(sizeof(content_bytes), num_content_bytes_read);
+  const uint8_t expected_content_bytes[] = {
+      1, 2, 3, 4, 5, 6, 13, 14, 15, 10, 11, 12};
+  ASSERT_EQ(sizeof(content_bytes), sizeof(expected_content_bytes));
+  EXPECT_EQ(
+      0, memcmp(expected_content_bytes, content_bytes, sizeof(content_bytes)));
+
+  // Check read result.
+  error = 0;
+  size_t file_size = 0;
+  EXPECT_TRUE(stream_.Open(logfile_name_, "r", &error));
+  EXPECT_TRUE(stream_.GetSize(&file_size));
+  EXPECT_EQ(0, error);
+  EXPECT_EQ(sizeof(content_bytes), file_size);
+  scoped_ptr<uint8_t[]> read_bytes(new uint8_t[file_size]);
+  size_t num_read_bytes = 0;
+  error = 0;
+  EXPECT_EQ(SR_SUCCESS, stream_.ReadAll(read_bytes.get(), file_size,
+                                        &num_read_bytes, &error));
+
+  const uint8_t expected_read_bytes[] = {
+      1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 15};
+  EXPECT_EQ(sizeof(expected_read_bytes), num_read_bytes);
+  EXPECT_EQ(0, memcmp(expected_read_bytes, read_bytes.get(), file_size));
+}
+
 }  // namespace rtc