blob: b474d7a5c65dec7f9b0673300998980009e93b4c [file] [log] [blame]
Zeke Chin2d3b7e22015-07-14 12:55:44 -07001/*
2 * libjingle
3 * Copyright 2015 Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#import "RTCFileLogger.h"
29
30#include "webrtc/base/checks.h"
31#include "webrtc/base/logging.h"
32#include "webrtc/base/scoped_ptr.h"
33#include "webrtc/base/stream.h"
34
35NSString *const kDefaultLogFileName = @"webrtc.log";
36NSUInteger const kDefaultMaxFileSize = 10 * 1024 * 1024; // 10MB.
37
38namespace rtc {
39
40class CircularFileStreamLogSink : public LogSink {
41 public:
42 // Creates a log sink that writes to the given stream. This log sink takes
43 // ownership of |stream|.
44 CircularFileStreamLogSink(CircularFileStream *stream) {
45 DCHECK(stream);
46 _stream.reset(stream);
47 }
48
49 ~CircularFileStreamLogSink() override {}
50
51 void OnLogMessage(const std::string &message) override {
52 if (_stream) {
53 _stream->WriteAll(message.data(), message.size(), nullptr, nullptr);
54 }
55 }
56
57 CircularFileStream *GetStream() { return _stream.get(); }
58
59 private:
60 scoped_ptr<CircularFileStream> _stream;
61};
62
63} // namespace rtc
64
65@implementation RTCFileLogger {
66 BOOL _hasStarted;
67 NSString *_filePath;
68 NSUInteger _maxFileSize;
69 rtc::scoped_ptr<rtc::CircularFileStreamLogSink> _logSink;
70}
71
72@synthesize severity = _severity;
73
74- (instancetype)init {
75 NSArray *paths = NSSearchPathForDirectoriesInDomains(
76 NSDocumentDirectory, NSUserDomainMask, YES);
77 NSString *documentsDirPath = [paths firstObject];
78 NSString *defaultFilePath =
79 [documentsDirPath stringByAppendingPathComponent:kDefaultLogFileName];
80 return [self initWithFilePath:defaultFilePath
81 maxFileSize:kDefaultMaxFileSize];
82}
83
84- (instancetype)initWithFilePath:(NSString *)filePath
85 maxFileSize:(NSUInteger)maxFileSize {
86 NSParameterAssert(filePath.length);
87 NSParameterAssert(maxFileSize);
88 if (self = [super init]) {
89 _filePath = filePath;
90 _maxFileSize = maxFileSize;
91 _severity = kRTCFileLoggerSeverityInfo;
92 }
93 return self;
94}
95
96- (void)dealloc {
97 [self stop];
98}
99
100- (void)start {
101 if (_hasStarted) {
102 return;
103 }
104 rtc::scoped_ptr<rtc::CircularFileStream> stream;
105 stream.reset(new rtc::CircularFileStream(_maxFileSize));
106 _logSink.reset(new rtc::CircularFileStreamLogSink(stream.release()));
107 int error = 0;
108 if (!_logSink->GetStream()->Open(_filePath.UTF8String, "wb", &error)) {
109 LOG(LS_ERROR) << "Failed to open log file at path: "
110 << _filePath.UTF8String
111 << " Error: "
112 << error;
113 _logSink.reset();
114 return;
115 }
116 // TODO(tkchin): Log thead info on iOS, currently this doesn't do anything.
117 rtc::LogMessage::LogThreads(true);
118 rtc::LogMessage::LogTimestamps(true);
119 rtc::LogMessage::AddLogToStream(_logSink.get(), [self rtcSeverity]);
120 _hasStarted = YES;
121}
122
123- (void)stop {
124 if (!_hasStarted) {
125 return;
126 }
127 DCHECK(_logSink);
128 rtc::LogMessage::RemoveLogToStream(_logSink.get());
129 _hasStarted = NO;
130
131 // Read the ordered version of the log.
132 NSData *logData = [self reorderedLogData];
133 NSError *error = nil;
134 // Write the ordered version back to disk.
135 if (![logData writeToFile:_filePath
136 options:NSDataWritingAtomic
137 error:&error]) {
138 LOG(LS_ERROR) << "Failed to rewrite log to disk at path: "
139 << _filePath.UTF8String;
140 if (error) {
141 LOG(LS_ERROR) << "Error: " << error.localizedDescription.UTF8String;
142 }
143 } else {
144 // If we succeeded in writing to disk we don't need to hold on to the
145 // stream anymore.
146 _logSink.reset();
147 }
148}
149
150- (NSData *)logData {
151 if (_hasStarted) {
152 return nil;
153 }
154 if (!_logSink.get()) {
155 // If there isn't a previously used stream just return contents of file.
156 return [[self class] contentsOfFileAtPath:_filePath];
157 }
158 return [self reorderedLogData];
159}
160
161#pragma mark - Private
162
163+ (NSData *)contentsOfFileAtPath:(NSString *)path {
164 NSError *error = nil;
165 NSData *contents = [NSData dataWithContentsOfFile:path
166 options:0
167 error:&error];
168 if (error) {
169 LOG(LS_ERROR) << "Failed to read contents of file at path: "
170 << path.UTF8String
171 << " Error: "
172 << error.localizedDescription.UTF8String;
173 return nil;
174 }
175 return contents;
176}
177
178- (NSData *)reorderedLogData {
179 if (_hasStarted || !_logSink.get()) {
180 return nil;
181 }
182 // We have a stream we used for writing in memory and we're not writing. The
183 // stream has a pointer to where the log boundary is so it can reorder the
184 // log correctly. We just need to reopen the file in read mode.
185 int error = 0;
186 rtc::CircularFileStream *stream = _logSink->GetStream();
187 if (!stream->Open(_filePath.UTF8String, "r", &error)) {
188 LOG(LS_ERROR) << "Failed to open log file at path: "
189 << _filePath.UTF8String
190 << " Error: "
191 << error;
192 return nil;
193 }
194 size_t logSize = 0;
195 size_t bytesRead = 0;
196 error = 0;
197 if (!stream->GetSize(&logSize)) {
198 LOG(LS_ERROR) << "Failed to get log file size.";
199 return nil;
200 }
201 // Allocate memory using malloc so we can pass it direcly to NSData without
202 // copying.
203 rtc::scoped_ptr<uint8_t[]> buffer(static_cast<uint8_t*>(malloc(logSize)));
204 if (stream->ReadAll(buffer.get(), logSize, &bytesRead, &error)
205 != rtc::SR_SUCCESS) {
206 LOG(LS_ERROR) << "Failed to read log file at path: "
207 << _filePath.UTF8String
208 << " Error: "
209 << error;
210 }
211 DCHECK_LE(bytesRead, logSize);
212 // NSData takes ownership of the bytes and frees it on dealloc.
213 return [NSData dataWithBytesNoCopy:buffer.release()
214 length:bytesRead];
215}
216
217- (rtc::LoggingSeverity)rtcSeverity {
218 switch (_severity) {
219 case kRTCFileLoggerSeverityVerbose:
220 return rtc::LS_VERBOSE;
221 case kRTCFileLoggerSeverityInfo:
222 return rtc::LS_INFO;
223 case kRTCFileLoggerSeverityWarning:
224 return rtc::LS_WARNING;
225 case kRTCFileLoggerSeverityError:
226 return rtc::LS_ERROR;
227 }
228}
229
230@end