1/*
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/filerotatingstream.h"
32#include "webrtc/base/logging.h"
33#include "webrtc/base/logsinks.h"
34#include "webrtc/base/scoped_ptr.h"
35
36NSString *const kDefaultLogDirName = @"webrtc_logs";
37NSUInteger const kDefaultMaxFileSize = 10 * 1024 * 1024; // 10MB.
38const char *kRTCFileLoggerRotatingLogPrefix = "rotating_log";
39
40@implementation RTCFileLogger {
41  BOOL _hasStarted;
42  NSString *_dirPath;
43  NSUInteger _maxFileSize;
44  rtc::scoped_ptr<rtc::FileRotatingLogSink> _logSink;
45}
46
47@synthesize severity = _severity;
48@synthesize rotationType = _rotationType;
49
50- (instancetype)init {
51  NSArray *paths = NSSearchPathForDirectoriesInDomains(
52      NSDocumentDirectory, NSUserDomainMask, YES);
53  NSString *documentsDirPath = [paths firstObject];
54  NSString *defaultDirPath =
55      [documentsDirPath stringByAppendingPathComponent:kDefaultLogDirName];
56  return [self initWithDirPath:defaultDirPath
57                   maxFileSize:kDefaultMaxFileSize];
58}
59
60- (instancetype)initWithDirPath:(NSString *)dirPath
61                    maxFileSize:(NSUInteger)maxFileSize {
62  return [self initWithDirPath:dirPath
63                   maxFileSize:maxFileSize
64                  rotationType:kRTCFileLoggerTypeCall];
65}
66
67- (instancetype)initWithDirPath:(NSString *)dirPath
68                    maxFileSize:(NSUInteger)maxFileSize
69                   rotationType:(RTCFileLoggerRotationType)rotationType {
70  NSParameterAssert(dirPath.length);
71  NSParameterAssert(maxFileSize);
72  if (self = [super init]) {
73    BOOL isDir = NO;
74    NSFileManager *fileManager = [NSFileManager defaultManager];
75    if ([fileManager fileExistsAtPath:dirPath isDirectory:&isDir]) {
76      if (!isDir) {
77        // Bail if something already exists there.
78        return nil;
79      }
80    } else {
81      if (![fileManager createDirectoryAtPath:dirPath
82                  withIntermediateDirectories:NO
83                                   attributes:nil
84                                        error:nil]) {
85        // Bail if we failed to create a directory.
86        return nil;
87      }
88    }
89    _dirPath = dirPath;
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  switch (_rotationType) {
105    case kRTCFileLoggerTypeApp:
106      _logSink.reset(
107          new rtc::FileRotatingLogSink(_dirPath.UTF8String,
108                                       kRTCFileLoggerRotatingLogPrefix,
109                                       _maxFileSize,
110                                       _maxFileSize / 10));
111      break;
112    case kRTCFileLoggerTypeCall:
113      _logSink.reset(
114          new rtc::CallSessionFileRotatingLogSink(_dirPath.UTF8String,
115                                                  _maxFileSize));
116      break;
117  }
118  if (!_logSink->Init()) {
119    LOG(LS_ERROR) << "Failed to open log files at path: "
120                  << _dirPath.UTF8String;
121    _logSink.reset();
122    return;
123  }
124  rtc::LogMessage::LogThreads(true);
125  rtc::LogMessage::LogTimestamps(true);
126  rtc::LogMessage::AddLogToStream(_logSink.get(), [self rtcSeverity]);
127  _hasStarted = YES;
128}
129
130- (void)stop {
131  if (!_hasStarted) {
132    return;
133  }
134  RTC_DCHECK(_logSink);
135  rtc::LogMessage::RemoveLogToStream(_logSink.get());
136  _hasStarted = NO;
137  _logSink.reset();
138}
139
140- (NSData *)logData {
141  if (_hasStarted) {
142    return nil;
143  }
144  NSMutableData* logData = [NSMutableData data];
145  rtc::scoped_ptr<rtc::FileRotatingStream> stream;
146  switch(_rotationType) {
147    case kRTCFileLoggerTypeApp:
148      stream.reset(
149          new rtc::FileRotatingStream(_dirPath.UTF8String,
150                                      kRTCFileLoggerRotatingLogPrefix));
151      break;
152    case kRTCFileLoggerTypeCall:
153      stream.reset(new rtc::CallSessionFileRotatingStream(_dirPath.UTF8String));
154      break;
155  }
156  if (!stream->Open()) {
157    return logData;
158  }
159  size_t bufferSize = 0;
160  if (!stream->GetSize(&bufferSize) || bufferSize == 0) {
161    return logData;
162  }
163  size_t read = 0;
164  // Allocate memory using malloc so we can pass it direcly to NSData without
165  // copying.
166  rtc::scoped_ptr<uint8_t[]> buffer(static_cast<uint8_t*>(malloc(bufferSize)));
167  stream->ReadAll(buffer.get(), bufferSize, &read, nullptr);
168  logData = [[NSMutableData alloc] initWithBytesNoCopy:buffer.release()
169                                                length:read];
170  return logData;
171}
172
173#pragma mark - Private
174
175- (rtc::LoggingSeverity)rtcSeverity {
176  switch (_severity) {
177    case kRTCFileLoggerSeverityVerbose:
178      return rtc::LS_VERBOSE;
179    case kRTCFileLoggerSeverityInfo:
180      return rtc::LS_INFO;
181    case kRTCFileLoggerSeverityWarning:
182      return rtc::LS_WARNING;
183    case kRTCFileLoggerSeverityError:
184      return rtc::LS_ERROR;
185  }
186}
187
188@end
189