diff --git a/Realm/ObjectServerTests/RLMSyncTestCase.mm b/Realm/ObjectServerTests/RLMSyncTestCase.mm index 2776bad936..40e97ec060 100644 --- a/Realm/ObjectServerTests/RLMSyncTestCase.mm +++ b/Realm/ObjectServerTests/RLMSyncTestCase.mm @@ -613,7 +613,7 @@ - (RLMApp *)appWithId:(NSString *)appId { RLMApp *app = [RLMApp appWithConfiguration:config]; RLMSyncManager *syncManager = app.syncManager; syncManager.userAgent = self.name; - RLMLogger.defaultLogger.level = RLMLogLevelWarn; + [RLMLogger.defaultLogger setLevel:RLMLogLevelWarn category:RLMLogCategoryRealmSync]; return app; } diff --git a/Realm/RLMLogger.h b/Realm/RLMLogger.h index 7d71458d4e..0aea5f3114 100644 --- a/Realm/RLMLogger.h +++ b/Realm/RLMLogger.h @@ -48,6 +48,40 @@ typedef RLM_CLOSED_ENUM(NSUInteger, RLMLogLevel) { RLMLogLevelAll } NS_SWIFT_NAME(LogLevel); +/// An enum representing different levels of sync-related logging that can be configured. +typedef NS_ENUM(NSUInteger, RLMLogCategory) { + /// Top level log category for Realm, updating this category level would set all other subcategories too. + RLMLogCategoryRealm, + /// Log category for all sdk related logs. + RLMLogCategoryRealmSDK, + /// Log category for all app related logs. + RLMLogCategoryRealmApp, + /// Log category for all database related logs. + RLMLogCategoryRealmStorage, + /// Log category for all database transaction related logs. + RLMLogCategoryRealmStorageTransaction, + /// Log category for all database queries related logs. + RLMLogCategoryRealmStorageQuery, + /// Log category for all database object related logs. + RLMLogCategoryRealmStorageObject, + /// Log category for all database notification related logs. + RLMLogCategoryRealmStorageNotification, + /// Log category for all sync related logs. + RLMLogCategoryRealmSync, + /// Log category for all sync client related logs. + RLMLogCategoryRealmSyncClient, + /// Log category for all sync client session related logs. + RLMLogCategoryRealmSyncClientSession, + /// Log category for all sync client changeset related logs. + RLMLogCategoryRealmSyncClientChangeset, + /// Log category for all sync client network related logs. + RLMLogCategoryRealmSyncClientNetwork, + /// Log category for all sync client reset related logs. + RLMLogCategoryRealmSyncClientReset, + /// Log category for all sync server related logs. + RLMLogCategoryRealmSyncServer +}; + /// A log callback function which can be set on RLMLogger. /// /// The log function may be called from multiple threads simultaneously, and is @@ -60,7 +94,7 @@ typedef void (^RLMLogFunction)(RLMLogLevel level, NSString *message); /// The log function may be called from multiple threads simultaneously, and is /// responsible for performing its own synchronization if any is required. RLM_SWIFT_SENDABLE // invoked on a background thread -typedef void (^RLMLogCategoryFunction)(RLMLogLevel level, NSString *category, NSString *message) NS_REFINED_FOR_SWIFT; +typedef void (^RLMLogCategoryFunction)(RLMLogLevel level, RLMLogCategory category, NSString *message) NS_REFINED_FOR_SWIFT; /** `RLMLogger` is used for creating your own custom logging logic. @@ -69,8 +103,8 @@ typedef void (^RLMLogCategoryFunction)(RLMLogLevel level, NSString *category, NS Set this custom logger as you default logger using `setDefaultLogger`. RLMLogger.defaultLogger = [[RLMLogger alloc] initWithLevel:RLMLogLevelDebug - category:RLMLogCategoryRealm - logFunction:^(RLMLogLevel level, NSString *category, NSString *message) { + category:RLMLogCategoryRealm + logFunction:^(RLMLogLevel level, NSString *category, NSString *message) { NSLog(@"Realm Log - %lu, %@, %@", (unsigned long)level, category, message); }]; @@ -104,7 +138,7 @@ __attribute__((deprecated("Use `initWithLevel:logFunction:` instead."))); @param logFunction The log function which will be invoked whenever there is a log message. */ - (instancetype)initWithLevel:(RLMLogLevel)level - category:(NSString *)category + category:(RLMLogCategory)category logFunction:(RLMLogCategoryFunction)logFunction; #pragma mark RLMLogger Default Logger API @@ -120,7 +154,7 @@ __attribute__((deprecated("Use `initWithLevel:logFunction:` instead."))); @param level The log level to be set for the logger. @param category The log function which will be invoked whenever there is a log message. */ -- (void)setLevel:(RLMLogLevel)level category:(NSString *)category NS_REFINED_FOR_SWIFT; +- (void)setLevel:(RLMLogLevel)level category:(RLMLogCategory)category NS_REFINED_FOR_SWIFT; /** Gets the logger's associated level for the specified category. @@ -128,7 +162,16 @@ __attribute__((deprecated("Use `initWithLevel:logFunction:` instead."))); @param category The log category which we need the level. @returns The log level for the specified category */ -- (RLMLogLevel)getLevelForCategory:(NSString *)category NS_REFINED_FOR_SWIFT; +- (RLMLogLevel)getLevelForCategory:(RLMLogCategory)category NS_REFINED_FOR_SWIFT; + +/** + Log a message to the supplied level. + + @param logLevel The log level for the message. + @param category The log category for the message. + @param message The message to log. + */ +- (void)logWithLevel:(RLMLogLevel)logLevel category:(RLMLogCategory)category message:(NSString *)message; @end diff --git a/Realm/RLMLogger.mm b/Realm/RLMLogger.mm index 5fbfaabbc9..b55cfbc9ef 100644 --- a/Realm/RLMLogger.mm +++ b/Realm/RLMLogger.mm @@ -22,7 +22,7 @@ #import -typedef void (^RLMLoggerFunction)(RLMLogLevel level, NSString *category, NSString *message); +typedef void (^RLMLoggerFunction)(RLMLogLevel level, RLMLogCategory category, NSString *message); using namespace realm; using Logger = realm::util::Logger; @@ -75,6 +75,57 @@ static RLMLogLevel logLevelForLevel(Level logLevel) { REALM_UNREACHABLE(); // Unrecognized log level. } +static NSArray *categories = [NSArray arrayWithObjects: + @"Realm", + @"Realm.SDK", + @"Realm.App", + @"Realm.Storage", + @"Realm.Storage.Transaction", + @"Realm.Storage.Query", + @"Realm.Storage.Object", + @"Realm.Storage.Notification", + @"Realm.Sync", + @"Realm.Sync.Client", + @"Realm.Sync.Client.Session", + @"Realm.Sync.Client.Changeset", + @"Realm.Sync.Client.Network", + @"Realm.Sync.Client.Reset", + @"Realm.Sync.Server", + nil]; + +static std::string categoryNameForLogCategory(RLMLogCategory logCategory) { + if (logCategory < [categories count]) { + if (auto categoryName = [categories objectAtIndex:logCategory]) { + return categoryName.UTF8String; + } + } + REALM_UNREACHABLE(); +} + +static RLMLogCategory logCategoryForCategoryName(std::string category) { + auto index = [categories indexOfObject:RLMStringDataToNSString(category)]; + if (index != NSNotFound) { + switch (index) { + case 0: return RLMLogCategoryRealm; + case 1: return RLMLogCategoryRealmSDK; + case 2: return RLMLogCategoryRealmApp; + case 3: return RLMLogCategoryRealmStorage; + case 4: return RLMLogCategoryRealmStorageTransaction; + case 5: return RLMLogCategoryRealmStorageQuery; + case 6: return RLMLogCategoryRealmStorageObject; + case 7: return RLMLogCategoryRealmStorageNotification; + case 8: return RLMLogCategoryRealmSync; + case 9: return RLMLogCategoryRealmSyncClient; + case 10: return RLMLogCategoryRealmSyncClientSession; + case 11: return RLMLogCategoryRealmSyncClientChangeset; + case 12: return RLMLogCategoryRealmSyncClientNetwork; + case 13: return RLMLogCategoryRealmSyncClientReset; + case 14: return RLMLogCategoryRealmSyncServer; + } + } + REALM_UNREACHABLE(); +} + struct CocoaLogger : public Logger { void do_log(const LogCategory& category, Level level, const std::string& message) override { NSLog(@"%@:%@ %@", levelPrefix(level), RLMStringDataToNSString(category.get_name()), RLMStringDataToNSString(message)); @@ -87,7 +138,7 @@ void do_log(const LogCategory& category, Level level, const std::string& message void do_log(const LogCategory& category, Level level, const std::string& message) override { @autoreleasepool { if (function) { - function(logLevelForLevel(level), RLMStringDataToNSString(category.get_name()), RLMStringDataToNSString(message)); + function(logLevelForLevel(level), logCategoryForCategoryName(category.get_name()), RLMStringDataToNSString(message)); } } } @@ -126,7 +177,7 @@ - (instancetype)initWithLevel:(RLMLogLevel)level if (self = [super init]) { auto logger = std::make_shared(); logger->set_level_threshold(levelForLogLevel(level)); - auto block = [logFunction](RLMLogLevel level, NSString *, NSString *message) { + auto block = [logFunction](RLMLogLevel level, RLMLogCategory, NSString *message) { logFunction(level, message); }; logger->function = block; @@ -136,11 +187,11 @@ - (instancetype)initWithLevel:(RLMLogLevel)level } - (instancetype)initWithLevel:(RLMLogLevel)level - category:(NSString *)category + category:(RLMLogCategory)category logFunction:(RLMLogCategoryFunction)logFunction { if (self = [super init]) { auto logger = std::make_shared(); - logger->set_level_threshold(LogCategory::get_category(category.UTF8String), levelForLogLevel(level)); + logger->set_level_threshold(categoryNameForLogCategory(category), levelForLogLevel(level)); logger->function = logFunction; self->_logger = logger; } @@ -157,22 +208,28 @@ - (void)logWithLevel:(RLMLogLevel)logLevel message:(NSString *)message, ... { } } -- (void)logWithLevel:(RLMLogLevel)logLevel category:(NSString *)category message:(NSString *)message { +- (void)logWithLevel:(RLMLogLevel)logLevel category:(RLMLogCategory)category message:(NSString *)message { + auto level = levelForLogLevel(logLevel); + if (_logger->would_log(level)) { + _logger->log(LogCategory::get_category(categoryNameForLogCategory(category)), levelForLogLevel(logLevel), message.UTF8String); + } +} + +- (void)logWithLevel:(RLMLogLevel)logLevel categoryName:(NSString *)categoryName message:(NSString *)message { auto level = levelForLogLevel(logLevel); if (_logger->would_log(level)) { - _logger->log(level, "%1", message.UTF8String); - _logger->log(LogCategory::get_category(category.UTF8String), levelForLogLevel(logLevel), message.UTF8String); + _logger->log(LogCategory::get_category(categoryName.UTF8String), levelForLogLevel(logLevel), message.UTF8String); } } -- (void)setLevel:(RLMLogLevel)level category:(NSString *)category { +- (void)setLevel:(RLMLogLevel)level category:(RLMLogCategory)category { RLMLogger *defaultLogger = [RLMLogger defaultLogger]; - defaultLogger->_logger->set_level_threshold(LogCategory::get_category(category.UTF8String), levelForLogLevel(level)); + defaultLogger->_logger->set_level_threshold(categoryNameForLogCategory(category), levelForLogLevel(level)); } -- (RLMLogLevel)getLevelForCategory:(NSString *)category { +- (RLMLogLevel)getLevelForCategory:(RLMLogCategory)category { RLMLogger *defaultLogger = [RLMLogger defaultLogger]; - return logLevelForLevel(defaultLogger->_logger->get_level_threshold(LogCategory::get_category(category.UTF8String))); + return logLevelForLevel(defaultLogger->_logger->get_level_threshold(categoryNameForLogCategory(category))); } #pragma mark Testing @@ -187,6 +244,10 @@ - (RLMLogLevel)getLevelForCategory:(NSString *)category { return a; } ++ (RLMLogCategory)categoryFromString:(NSString *)string { + return logCategoryForCategoryName(string.UTF8String); +} + #pragma mark Global Logger Setter + (instancetype)defaultLogger { diff --git a/Realm/RLMLogger_Private copy.h b/Realm/RLMLogger_Private copy.h new file mode 100644 index 0000000000..654493b77f --- /dev/null +++ b/Realm/RLMLogger_Private copy.h @@ -0,0 +1,51 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2023 Realm Inc. +// +// 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 + +RLM_HEADER_AUDIT_BEGIN(nullability) + +@interface RLMLogger() + +/** + Log a message to the supplied level. + + @param logLevel The log level for the message. + @param message The message to log. + */ +- (void)logWithLevel:(RLMLogLevel)logLevel message:(NSString *)message, ... NS_SWIFT_UNAVAILABLE(""); + +/** + Log a message to the supplied level. + + @param logLevel The log level for the message. + @param category The log category for the message. + @param message The message to log. + */ +- (void)logWithLevel:(RLMLogLevel)logLevel category:(NSString *)category message:(NSString *)message; + +/** +Gets all the categories from Core. This is to be used for testing purposes only. + */ ++ (NSArray *)getAllCategories; + ++ (RLMLogCategory)categoryFromString:(NSString *)string; +@end + +RLM_HEADER_AUDIT_END(nullability) diff --git a/Realm/RLMLogger_Private.h b/Realm/RLMLogger_Private.h index 2a2ba269dc..7e725b1cb7 100644 --- a/Realm/RLMLogger_Private.h +++ b/Realm/RLMLogger_Private.h @@ -35,15 +35,22 @@ RLM_HEADER_AUDIT_BEGIN(nullability) Log a message to the supplied level. @param logLevel The log level for the message. - @param category The log category for the message. + @param category The log category name for the message. @param message The message to log. */ -- (void)logWithLevel:(RLMLogLevel)logLevel category:(NSString *)category message:(NSString *)message; +- (void)logWithLevel:(RLMLogLevel)logLevel categoryName:(NSString *)categoryName message:(NSString *)message; + +#pragma mark Testing /** Gets all the categories from Core. This is to be used for testing purposes only. */ + (NSArray *)getAllCategories; + +/** +Returns a `RLMLogCategory` from a string. + */ ++ (RLMLogCategory)categoryFromString:(NSString *)string; @end RLM_HEADER_AUDIT_END(nullability) diff --git a/Realm/Tests/RealmTests.mm b/Realm/Tests/RealmTests.mm index 5fd970e92e..55a812c2df 100644 --- a/Realm/Tests/RealmTests.mm +++ b/Realm/Tests/RealmTests.mm @@ -2960,8 +2960,8 @@ - (void)tearDown { } - (void)testSetDefaultLogLevel { __block NSMutableString *logs = [[NSMutableString alloc] init]; - NSString *category = @"Realm"; - RLMLogger *logger = [[RLMLogger alloc] initWithLevel:RLMLogLevelAll category:category logFunction:^(RLMLogLevel level, NSString *category, NSString *message) { + RLMLogCategory category = RLMLogCategoryRealm; + RLMLogger *logger = [[RLMLogger alloc] initWithLevel:RLMLogLevelAll category:category logFunction:^(RLMLogLevel level, RLMLogCategory category, NSString *message) { [logs appendFormat:@" %@ %lu %@", [NSDate date], level, message]; }]; RLMLogger.defaultLogger = logger; @@ -2981,10 +2981,10 @@ - (void)testSetDefaultLogLevel { - (void)testDefaultLogger { __block NSMutableString *logs = [[NSMutableString alloc] init]; - NSString *category = @"Realm"; + RLMLogCategory category = RLMLogCategoryRealm; RLMLogger *logger = [[RLMLogger alloc] initWithLevel:RLMLogLevelOff category:category - logFunction:^(RLMLogLevel level, NSString *category, NSString *message) { + logFunction:^(RLMLogLevel level, RLMLogCategory category, NSString *message) { [logs appendFormat:@" %@ %lu %@", [NSDate date], level, message]; }]; RLMLogger.defaultLogger = logger; @@ -3011,7 +3011,7 @@ - (void)testDefaultLogger { // Init Custom Logger RLMLogger.defaultLogger = [[RLMLogger alloc] initWithLevel:RLMLogLevelDebug category:category - logFunction:^(RLMLogLevel level, NSString *category, NSString * message) { + logFunction:^(RLMLogLevel level, RLMLogCategory category, NSString * message) { [logs appendFormat:@" %@ %lu %@", [NSDate date], level, message]; }]; @@ -3023,10 +3023,10 @@ - (void)testDefaultLogger { - (void)testCustomLoggerLogMessage { __block NSMutableString *logs = [[NSMutableString alloc] init]; - NSString *category = @"Realm"; + RLMLogCategory category = RLMLogCategoryRealm; RLMLogger *logger = [[RLMLogger alloc] initWithLevel:RLMLogLevelInfo category:category - logFunction:^(RLMLogLevel level, NSString *category, NSString * message) { + logFunction:^(RLMLogLevel level, RLMLogCategory category, NSString * message) { [logs appendFormat:@" %@ %lu %@.", [NSDate date], level, message]; }]; RLMLogger.defaultLogger = logger; @@ -3036,6 +3036,14 @@ - (void)testCustomLoggerLogMessage { XCTAssertTrue([logs containsString:@"TEST: IMPORTANT INFO 0"]); // Detail XCTAssertFalse([logs containsString:@"IMPORTANT TRACE"]); // Trace } + +// Core defines the different categories in runtime, forcing the SDK to define the categories again. +// This test validates that we have added new defined categories to the RLMLogCategory enum. +- (void)testAllCategoriesWatchDog { + for (id category in [RLMLogger getAllCategories]) { + XCTAssertNoThrow([RLMLogger categoryFromString:category]); + } +} @end @interface RLMMetricsTests : RLMTestCase @@ -3052,10 +3060,10 @@ - (void)tearDown { - (void)testSyncConnectionMetrics { __block NSMutableString *logs = [[NSMutableString alloc] init]; - NSString *category = @"Realm"; + RLMLogCategory category = RLMLogCategoryRealm; RLMLogger *logger = [[RLMLogger alloc] initWithLevel:RLMLogLevelDebug category:category - logFunction:^(RLMLogLevel level, NSString *category, NSString * message) { + logFunction:^(RLMLogLevel level, RLMLogCategory category, NSString * message) { [logs appendFormat:@" %@ %lu %@.", [NSDate date], level, message]; }]; RLMLogger.defaultLogger = logger; diff --git a/RealmSwift/Logger.swift b/RealmSwift/Logger.swift index 0a0acfee54..b6c59632a2 100644 --- a/RealmSwift/Logger.swift +++ b/RealmSwift/Logger.swift @@ -77,8 +77,8 @@ extension Logger { - SeeAlso: `LogCategory` */ public convenience init(level: LogLevel, category: LogCategory = Category.realm, function: @escaping @Sendable (LogLevel, LogCategory, String) -> Void) { - self.init(level: level, category: category.toString()) { level, cat, message in - function(level, Category.fromString(cat)!, message) + self.init(level: level, category: ObjectiveCSupport.convert(value: category)) { level, cat, message in + function(level, ObjectiveCSupport.convert(value: cat), message) } } @@ -92,7 +92,7 @@ extension Logger { - SeeAlso: `LogCategory` */ public func setLogLevel(_ level: LogLevel, for category: LogCategory = Category.realm) { - Logger.shared.__setLevel(level, category: category.toString()) + Logger.shared.__setLevel(level, category: ObjectiveCSupport.convert(value: category)) } /** @@ -104,7 +104,7 @@ extension Logger { - SeeAlso: `LogCategory` */ public func getLogLevel(for category: LogCategory) -> LogLevel { - Logger.shared.__getLevelForCategory(category.toString()) + Logger.shared.__getLevelFor(ObjectiveCSupport.convert(value: category)) } } @@ -273,3 +273,86 @@ public enum Category: String, LogCategory { } } } + +private extension ObjectiveCSupport { + + /// Converts a Swift category `LogCategory` to an Objective-C `RLMLogCategory. + /// - Parameter value: The `LogCategory`. + /// - Returns: Conversion of `value` to its Objective-C representation. + static func convert(value: LogCategory) -> RLMLogCategory { + switch value { + case Category.realm: + return RLMLogCategory.realm + case Category.sdk: + return RLMLogCategory.realmSDK + case Category.app: + return RLMLogCategory.realmApp + case Category.Storage.all: + return RLMLogCategory.realmStorage + case Category.Storage.transaction: + return RLMLogCategory.realmStorageTransaction + case Category.Storage.query: + return RLMLogCategory.realmStorageQuery + case Category.Storage.object: + return RLMLogCategory.realmStorageObject + case Category.Storage.notification: + return RLMLogCategory.realmStorageNotification + case Category.Sync.all: + return RLMLogCategory.realmSync + case Category.Sync.Client.all: + return RLMLogCategory.realmSyncClient + case Category.Sync.Client.session: + return RLMLogCategory.realmSyncClientSession + case Category.Sync.Client.changeset: + return RLMLogCategory.realmSyncClientChangeset + case Category.Sync.Client.network: + return RLMLogCategory.realmSyncClientNetwork + case Category.Sync.Client.reset: + return RLMLogCategory.realmSyncClientReset + case Category.Sync.server: + return RLMLogCategory.realmSyncServer + default: + throwRealmException("") + } + } + + /// Converts an Objective-C category `RLMLogCategory` to a Swift `LogCategory. + /// - Parameter value: The `RLMLogCategory`. + /// - Returns: Conversion of `value` to its Swift representation. + static func convert(value: RLMLogCategory) -> LogCategory { + switch value { + case RLMLogCategory.realm: + return Category.realm + case RLMLogCategory.realmSDK: + return Category.sdk + case RLMLogCategory.realmApp: + return Category.app + case RLMLogCategory.realmStorage: + return Category.Storage.all + case RLMLogCategory.realmStorageTransaction: + return Category.Storage.transaction + case RLMLogCategory.realmStorageQuery: + return Category.Storage.query + case RLMLogCategory.realmStorageObject: + return Category.Storage.object + case RLMLogCategory.realmStorageNotification: + return Category.Storage.notification + case RLMLogCategory.realmSync: + return Category.Sync.all + case RLMLogCategory.realmSyncClient: + return Category.Sync.Client.all + case RLMLogCategory.realmSyncClientSession: + return Category.Sync.Client.session + case RLMLogCategory.realmSyncClientChangeset: + return Category.Sync.Client.changeset + case RLMLogCategory.realmSyncClientNetwork: + return Category.Sync.Client.network + case RLMLogCategory.realmSyncClientReset: + return Category.Sync.Client.reset + case RLMLogCategory.realmSyncServer: + return Category.Sync.server + default: + throwRealmException("") + } + } +}