diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogEvent.m b/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogEvent.m index ae7ca6eb586..36c3cc78497 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogEvent.m +++ b/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogEvent.m @@ -14,7 +14,9 @@ * limitations under the License. */ -#import "GDLLogEvent.h" +#import + +#import "GDLLogEvent_Private.h" @implementation GDLLogEvent @@ -29,4 +31,56 @@ - (instancetype)initWithLogMapID:(NSString *)logMapID logTarget:(NSInteger)logTa return self; } +#pragma mark - NSSecureCoding and NSCoding Protocols + +/** NSCoding key for logMapID property. */ +static NSString *logMapIDKey = @"_logMapID"; + +/** NSCoding key for logTarget property. */ +static NSString *logTargetKey = @"_logTarget"; + +/** NSCoding key for extensionBytes property. */ +static NSString *extensionBytesKey = @"_extensionBytes"; + +/** NSCoding key for qosTier property. */ +static NSString *qosTierKey = @"_qosTier"; + +/** NSCoding key for clockSnapshot.timeMillis property. */ +static NSString *clockSnapshotTimeMillisKey = @"_clockSnapshotTimeMillis"; + +/** NSCoding key for clockSnapshot.uptimeMillis property. */ +static NSString *clockSnapshotUpTimeMillis = @"_clockSnapshotUpTimeMillis"; + +/** NSCoding key for clockSnapshot.timezoneOffsetMillis property. */ +static NSString *clockSnapshotTimezoneOffsetMillis = @"_clockSnapshotTimezoneOffsetMillis"; + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (id)initWithCoder:(NSCoder *)aDecoder { + NSString *logMapID = [aDecoder decodeObjectOfClass:[NSObject class] forKey:logMapIDKey]; + NSInteger logTarget = [aDecoder decodeIntegerForKey:logTargetKey]; + self = [self initWithLogMapID:logMapID logTarget:logTarget]; + if (self) { + _extensionBytes = [aDecoder decodeObjectOfClass:[NSData class] forKey:extensionBytesKey]; + _qosTier = [aDecoder decodeIntegerForKey:qosTierKey]; + _clockSnapshot.timeMillis = [aDecoder decodeInt64ForKey:clockSnapshotTimeMillisKey]; + _clockSnapshot.uptimeMillis = [aDecoder decodeInt64ForKey:clockSnapshotUpTimeMillis]; + _clockSnapshot.timezoneOffsetMillis = + [aDecoder decodeInt64ForKey:clockSnapshotTimezoneOffsetMillis]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_logMapID forKey:logMapIDKey]; + [aCoder encodeInteger:_logTarget forKey:logTargetKey]; + [aCoder encodeObject:_extensionBytes forKey:extensionBytesKey]; + [aCoder encodeInteger:_qosTier forKey:qosTierKey]; + [aCoder encodeInt64:_clockSnapshot.timeMillis forKey:clockSnapshotTimeMillisKey]; + [aCoder encodeInt64:_clockSnapshot.uptimeMillis forKey:clockSnapshotUpTimeMillis]; + [aCoder encodeInt64:_clockSnapshot.timezoneOffsetMillis forKey:clockSnapshotTimezoneOffsetMillis]; +} + @end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogWriter.m b/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogWriter.m index 71590b480e9..7a9d2cb2fcb 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogWriter.m +++ b/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogWriter.m @@ -20,6 +20,7 @@ #import #import "GDLConsoleLogger.h" +#import "GDLLogEvent_Private.h" #import "GDLLogStorage.h" @implementation GDLLogWriter @@ -60,6 +61,17 @@ - (void)writeLog:(GDLLogEvent *)log return; } } + + // Convert the extension to bytes after transforming. + NSAssert([log.extension respondsToSelector:@selector(protoBytes)], + @"The log event's extension must respond to -protoBytes."); + if ([log.extension respondsToSelector:@selector(protoBytes)]) { + log.extensionBytes = [log.extension protoBytes]; + log.extension = nil; + } else { + GDLLogWarning(GDLMCWExtensionMissingBytesImpl, @"%@", + @"The log event's extension must respond to -protoBytes."); + } // TODO(mikehaney24): [[GDLLogStorage sharedInstance] storeLog:transformedLog]; }); } diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLLogEvent_Private.h b/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLLogEvent_Private.h new file mode 100644 index 00000000000..1df364bcb58 --- /dev/null +++ b/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLLogEvent_Private.h @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDLLogEvent.h" + +#import "GDLLogClock.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface GDLLogEvent () + +/** The serialized bytes of the log object. */ +@property(nonatomic) NSData *extensionBytes; + +/** The quality of service tier this log belongs to. */ +@property(nonatomic) GDLLogQoS qosTier; + +/** The clock snapshot at the time of logging. */ +@property(nonatomic) GDLLogClockSnapshot clockSnapshot; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogEvent.h b/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogEvent.h similarity index 87% rename from GoogleDataLogger/GoogleDataLogger/Classes/GDLLogEvent.h rename to GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogEvent.h index 8f5d0e81e2d..f3931b2e076 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogEvent.h +++ b/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogEvent.h @@ -16,7 +16,6 @@ #import -#import "GDLLogClock.h" #import "GDLLogProto.h" NS_ASSUME_NONNULL_BEGIN @@ -39,7 +38,7 @@ typedef NS_ENUM(NSInteger, GDLLogQoS) { GDLLogQoSFast = 4 }; -@interface GDLLogEvent : NSObject +@interface GDLLogEvent : NSObject /** The log map identifier, to allow backends to map the extension property to a proto. */ @property(readonly, nonatomic) NSString *logMapID; @@ -49,13 +48,7 @@ typedef NS_ENUM(NSInteger, GDLLogQoS) { /** The log object itself, encapsulated in the transport of your choice, as long as it implements * the GDLLogProto protocol. */ -@property(nonatomic) id extension; - -/** The quality of service tier this log belongs to. */ -@property(nonatomic) GDLLogQoS qosTier; - -/** The clock snapshot at the time of logging. */ -@property(nonatomic) GDLLogClockSnapshot clockSnapshot; +@property(nullable, nonatomic) id extension; // Please use the designated initializer. - (instancetype)init NS_UNAVAILABLE; diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogProto.h b/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogProto.h similarity index 100% rename from GoogleDataLogger/GoogleDataLogger/Classes/GDLLogProto.h rename to GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogProto.h diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogger.h b/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogger.h index 1938c36f16d..ea52804dc38 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogger.h +++ b/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogger.h @@ -38,15 +38,19 @@ NS_ASSUME_NONNULL_BEGIN logTransformers:(nullable NSArray> *)logTransformers logTarget:(NSInteger)logTarget NS_DESIGNATED_INITIALIZER; -/** Logs an internal telemetry event. Logs sent using this API are lower in priority, and sometimes - * won't be sent on their own. +/** Copies and logs an internal telemetry event. Logs sent using this API are lower in priority, and + * sometimes won't be sent on their own. + * + * @note This will convert the log event's extension proto to data and release the original log. * * @param logEvent The log event to log. */ - (void)logTelemetryEvent:(GDLLogEvent *)logEvent; -/** Logs an SDK service data event. Logs send using this API are higher in priority, and will cause - * a network request at some point in the relative near future. +/** Copies and logs an SDK service data event. Logs send using this API are higher in priority, and + * will cause a network request at some point in the relative near future. + * + * @note This will convert the log event's extension proto to data and release the original log. * * @param logEvent The log event to log. */ diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GoogleDataLogger.h b/GoogleDataLogger/GoogleDataLogger/Classes/Public/GoogleDataLogger.h index 6ba563c5ea5..091625895eb 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GoogleDataLogger.h +++ b/GoogleDataLogger/GoogleDataLogger/Classes/Public/GoogleDataLogger.h @@ -14,4 +14,6 @@ * limitations under the License. */ +#import "GDLLogEvent.h" +#import "GDLLogTransformer.h" #import "GDLLogger.h" diff --git a/GoogleDataLogger/GoogleDataLogger/DependencyWrappers/GDLConsoleLogger.h b/GoogleDataLogger/GoogleDataLogger/DependencyWrappers/GDLConsoleLogger.h index aaaa23c6edd..b1ce4fbe3a1 100644 --- a/GoogleDataLogger/GoogleDataLogger/DependencyWrappers/GDLConsoleLogger.h +++ b/GoogleDataLogger/GoogleDataLogger/DependencyWrappers/GDLConsoleLogger.h @@ -28,7 +28,10 @@ static GULLoggerService kGDLConsoleLogger = @"[GoogleDataLogger]"; typedef NS_ENUM(NSInteger, GDLMessageCode) { /** For warning messages concerning transform: not being implemented by a log transformer. */ - GDLMCWTransformerDoesntImplementTransform = 1 + GDLMCWTransformerDoesntImplementTransform = 1, + + /** For warning messages concerning protoBytes: not being implemented by a log extension. */ + GDLMCWExtensionMissingBytesImpl = 2 }; /** */ diff --git a/GoogleDataLogger/Tests/GDLLogEventTest.m b/GoogleDataLogger/Tests/GDLLogEventTest.m index c96c00907ee..d12468cc593 100644 --- a/GoogleDataLogger/Tests/GDLLogEventTest.m +++ b/GoogleDataLogger/Tests/GDLLogEventTest.m @@ -16,7 +16,9 @@ #import -#import "GDLLogEvent.h" +#import + +#import "GDLLogEvent_Private.h" @interface GDLLogEventTest : XCTestCase @@ -24,9 +26,35 @@ @interface GDLLogEventTest : XCTestCase @implementation GDLLogEventTest +/** Tests the designated initializer. */ - (void)testInit { XCTAssertNotNil([[GDLLogEvent alloc] initWithLogMapID:@"1" logTarget:1]); XCTAssertThrows([[GDLLogEvent alloc] initWithLogMapID:@"" logTarget:1]); } +/** Tests NSKeyedArchiver encoding and decoding. */ +- (void)testArchiving { + XCTAssertTrue([GDLLogEvent supportsSecureCoding]); + GDLLogClockSnapshot clockSnapshot = {10, 100, 1000}; + GDLLogEvent *logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"testID" logTarget:42]; + logEvent.extensionBytes = [@"someData" dataUsingEncoding:NSUTF8StringEncoding]; + logEvent.qosTier = GDLLogQoSTelemetry; + logEvent.clockSnapshot = clockSnapshot; + + NSData *archiveData = [NSKeyedArchiver archivedDataWithRootObject:logEvent]; + + // To ensure that all the objects being retained by the original logEvent are dealloc'd. + logEvent = nil; + + GDLLogEvent *decodedLogEvent = [NSKeyedUnarchiver unarchiveObjectWithData:archiveData]; + XCTAssertEqualObjects(decodedLogEvent.logMapID, @"testID"); + XCTAssertEqual(decodedLogEvent.logTarget, 42); + XCTAssertEqualObjects(decodedLogEvent.extensionBytes, + [@"someData" dataUsingEncoding:NSUTF8StringEncoding]); + XCTAssertEqual(decodedLogEvent.qosTier, GDLLogQoSTelemetry); + XCTAssertEqual(decodedLogEvent.clockSnapshot.timeMillis, 10); + XCTAssertEqual(decodedLogEvent.clockSnapshot.uptimeMillis, 100); + XCTAssertEqual(decodedLogEvent.clockSnapshot.timezoneOffsetMillis, 1000); +} + @end diff --git a/GoogleDataLogger/Tests/GDLLogWriterTest.m b/GoogleDataLogger/Tests/GDLLogWriterTest.m index d7e88416915..5a9f80e9302 100644 --- a/GoogleDataLogger/Tests/GDLLogWriterTest.m +++ b/GoogleDataLogger/Tests/GDLLogWriterTest.m @@ -19,6 +19,7 @@ #import #import "GDLLogEvent.h" +#import "GDLLogExtensionTesterClasses.h" #import "GDLLogWriter.h" #import "GDLLogWriter_Private.h" @@ -66,6 +67,7 @@ - (void)testSharedInstance { - (void)testWriteLogWithoutTransformers { GDLLogWriter *writer = [GDLLogWriter sharedInstance]; GDLLogEvent *log = [[GDLLogEvent alloc] initWithLogMapID:@"1" logTarget:1]; + log.extension = [[GDLLogExtensionTesterSimple alloc] init]; XCTAssertNoThrow([writer writeLog:log afterApplyingTransformers:nil]); dispatch_sync(writer.logWritingQueue, ^{ // TODO(mikehaney24): Assert that storage contains the log. @@ -76,6 +78,7 @@ - (void)testWriteLogWithoutTransformers { - (void)testWriteLogWithTransformersThatNilTheLog { GDLLogWriter *writer = [GDLLogWriter sharedInstance]; GDLLogEvent *log = [[GDLLogEvent alloc] initWithLogMapID:@"2" logTarget:1]; + log.extension = [[GDLLogExtensionTesterSimple alloc] init]; NSArray> *transformers = @[ [[GDLLogWriterTestNilingTransformer alloc] init] ]; XCTAssertNoThrow([writer writeLog:log afterApplyingTransformers:transformers]); @@ -88,6 +91,7 @@ - (void)testWriteLogWithTransformersThatNilTheLog { - (void)testWriteLogWithTransformersThatCreateANewLog { GDLLogWriter *writer = [GDLLogWriter sharedInstance]; GDLLogEvent *log = [[GDLLogEvent alloc] initWithLogMapID:@"2" logTarget:1]; + log.extension = [[GDLLogExtensionTesterSimple alloc] init]; NSArray> *transformers = @[ [[GDLLogWriterTestNewLogTransformer alloc] init] ]; XCTAssertNoThrow([writer writeLog:log afterApplyingTransformers:transformers]); @@ -100,6 +104,7 @@ - (void)testWriteLogWithTransformersThatCreateANewLog { - (void)testWriteLogWithBadTransformer { GDLLogWriter *writer = [GDLLogWriter sharedInstance]; GDLLogEvent *log = [[GDLLogEvent alloc] initWithLogMapID:@"2" logTarget:1]; + log.extension = [[GDLLogExtensionTesterSimple alloc] init]; NSArray *transformers = @[ [[NSObject alloc] init] ]; @try { dispatch_sync(writer.logWritingQueue, ^{ diff --git a/GoogleDataLogger/Tests/GDLLoggerTest.m b/GoogleDataLogger/Tests/GDLLoggerTest.m index 2c7f5d4cebb..b205400e076 100644 --- a/GoogleDataLogger/Tests/GDLLoggerTest.m +++ b/GoogleDataLogger/Tests/GDLLoggerTest.m @@ -16,8 +16,11 @@ #import +#import #import +#import "GDLLogExtensionTesterClasses.h" + @interface GDLLoggerTest : XCTestCase @end @@ -34,6 +37,7 @@ - (void)testInit { - (void)testLogTelemetryEvent { GDLLogger *logger = [[GDLLogger alloc] initWithLogMapID:@"1" logTransformers:nil logTarget:1]; GDLLogEvent *event = [logger newEvent]; + event.extension = [[GDLLogExtensionTesterSimple alloc] init]; XCTAssertNoThrow([logger logTelemetryEvent:event]); } @@ -41,6 +45,7 @@ - (void)testLogTelemetryEvent { - (void)testLogDataEvent { GDLLogger *logger = [[GDLLogger alloc] initWithLogMapID:@"1" logTransformers:nil logTarget:1]; GDLLogEvent *event = [logger newEvent]; + event.extension = [[GDLLogExtensionTesterSimple alloc] init]; XCTAssertNoThrow([logger logDataEvent:event]); } diff --git a/GoogleDataLogger/Tests/Helpers/GDLLogExtensionTesterClasses.h b/GoogleDataLogger/Tests/Helpers/GDLLogExtensionTesterClasses.h new file mode 100644 index 00000000000..9c476446107 --- /dev/null +++ b/GoogleDataLogger/Tests/Helpers/GDLLogExtensionTesterClasses.h @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** A class to represent a simple log proto. */ +@interface GDLLogExtensionTesterSimple : NSObject + +/** A string that will be turned into bytes. */ +@property(nonatomic) NSString *aString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataLogger/Tests/Helpers/GDLLogExtensionTesterClasses.m b/GoogleDataLogger/Tests/Helpers/GDLLogExtensionTesterClasses.m new file mode 100644 index 00000000000..e071e98658f --- /dev/null +++ b/GoogleDataLogger/Tests/Helpers/GDLLogExtensionTesterClasses.m @@ -0,0 +1,33 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GDLLogExtensionTesterClasses.h" + +@implementation GDLLogExtensionTesterSimple + +- (instancetype)init { + self = [super init]; + if (self) { + _aString = @"test"; + } + return self; +} + +- (NSData *)protoBytes { + return [_aString dataUsingEncoding:NSUTF8StringEncoding]; +} + +@end