Skip to content

Commit

Permalink
Redesign the Logger API
Browse files Browse the repository at this point in the history
  • Loading branch information
tgoyne committed Jul 27, 2024
1 parent 0936815 commit 139cda0
Show file tree
Hide file tree
Showing 8 changed files with 334 additions and 349 deletions.
1 change: 0 additions & 1 deletion Realm/ObjectServerTests/RLMSyncTestCase.mm
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,6 @@ - (RLMApp *)appWithId:(NSString *)appId {
RLMApp *app = [RLMApp appWithConfiguration:config];
RLMSyncManager *syncManager = app.syncManager;
syncManager.userAgent = self.name;
[RLMLogger setLevel:RLMLogLevelWarn forCategory:RLMLogCategorySync];
return app;
}

Expand Down
65 changes: 36 additions & 29 deletions Realm/RLMLogger.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

#import <Realm/RLMConstants.h>

@class RLMLoggerToken;

RLM_HEADER_AUDIT_BEGIN(nullability)

/// An enum representing different levels of sync-related logging that can be configured.
Expand Down Expand Up @@ -115,7 +117,8 @@ 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, RLMLogCategory category, NSString *message) NS_REFINED_FOR_SWIFT;
typedef void (^RLMLogCategoryFunction)(RLMLogLevel level, RLMLogCategory category, NSString *message);

/**
Global logger class used by all Realm components.
Expand All @@ -132,57 +135,61 @@ typedef void (^RLMLogCategoryFunction)(RLMLogLevel level, RLMLogCategory categor
*/
@interface RLMLogger : NSObject

/**
Gets the logging threshold level used by the logger.
*/
@property (nonatomic) RLMLogLevel level
__attribute__((deprecated("Use `setLevel(level:category)` or `setLevel:category` instead.")));

/// :nodoc:
- (instancetype)init NS_UNAVAILABLE;

#pragma mark Category-based API

+ (RLMLoggerToken *)addLogFunction:(RLMLogCategoryFunction)function NS_REFINED_FOR_SWIFT;
+ (void)removeAll;
+ (void)resetToDefault;

/**
Creates a logger with the associated log level and the logic function to define your own logging logic.
Sets the gobal log level for a given category.
@param level The log level to be set for the logger.
@param logFunction The log function which will be invoked whenever there is a log message.
@note This will set the log level for the log category `RLMLogCategoryRealm`.
@param category The log function which will be invoked whenever there is a log message.
*/
- (instancetype)initWithLevel:(RLMLogLevel)level logFunction:(RLMLogFunction)logFunction
__attribute__((deprecated("Use `initWithLogFunction:` instead.")));
+ (void)setLevel:(RLMLogLevel)level forCategory:(RLMLogCategory)category NS_REFINED_FOR_SWIFT;

/**
Creates a logger with a callback, which will be invoked whenever there is a log message.
Gets the global log level for the specified category.
@param logFunction The log function which will be invoked whenever there is a log message.
@param category The log category which we need the level.
@returns The log level for the specified category
*/
- (instancetype)initWithLogFunction:(RLMLogCategoryFunction)logFunction;
+ (RLMLogLevel)levelForCategory:(RLMLogCategory)category NS_REFINED_FOR_SWIFT;


#pragma mark RLMLogger Default Logger API
#pragma mark Deprecated API

/**
The current default logger. When setting a logger as default, this logger will replace the current default logger and will
be used whenever information must be logged.
Gets the logging threshold level used by the logger.
*/
@property (class) RLMLogger *defaultLogger NS_SWIFT_NAME(shared);
@property (nonatomic) RLMLogLevel level
__attribute__((deprecated("Use `setLevel(level:category)` or `setLevel:category` instead.")));

/**
Sets the gobal log level for a given category.
Creates a logger with the associated log level and the logic function to define your own logging logic.
@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.
@param logFunction The log function which will be invoked whenever there is a log message.
@note This will set the log level for the log category `RLMLogCategoryRealm`.
*/
+ (void)setLevel:(RLMLogLevel)level forCategory:(RLMLogCategory)category NS_REFINED_FOR_SWIFT;
- (instancetype)initWithLevel:(RLMLogLevel)level logFunction:(RLMLogFunction)logFunction
__attribute__((deprecated("Use `+[Logger addLogFunction:]` instead.")));

/**
Gets the global log level for the specified category.
@param category The log category which we need the level.
@returns The log level for the specified category
*/
+ (RLMLogLevel)levelForCategory:(RLMLogCategory)category NS_REFINED_FOR_SWIFT;
The current default logger. When setting a logger as default, this logger will replace the current default logger and will
be used whenever information must be logged.
*/
@property (class) RLMLogger *defaultLogger NS_SWIFT_NAME(shared)
__attribute__((deprecated("Use `+[Logger addLogFunction:]` instead.")));
@end

@interface RLMLoggerToken : NSObject
- (void)invalidate;
@end

RLM_HEADER_AUDIT_END(nullability)
149 changes: 115 additions & 34 deletions Realm/RLMLogger.mm
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,21 @@ static RLMLogLevel logLevelForLevel(Level logLevel) {
REALM_UNREACHABLE(); // Unrecognized log level.
}

static NSString* levelPrefix(RLMLogLevel logLevel) {
switch (levelForLogLevel(logLevel)) {
case Level::off: return @"";
case Level::all: return @"";
case Level::trace: return @"Trace";
case Level::debug: return @"Debug";
case Level::detail: return @"Detail";
case Level::info: return @"Info";
case Level::error: return @"Error";
case Level::warn: return @"Warning";
case Level::fatal: return @"Fatal";
}
REALM_UNREACHABLE(); // Unrecognized log level.
}

static LogCategory& categoryForLogCategory(RLMLogCategory logCategory) {
switch (logCategory) {
case RLMLogCategoryRealm: return LogCategory::realm;
Expand Down Expand Up @@ -136,11 +151,30 @@ static RLMLogCategory logCategoryForCategory(const LogCategory& category) {
return find(category);
}

struct CocoaLogger : public Logger {
struct DynamicLogger : Logger {
RLMUnfairMutex _mutex;
NSArray<RLMLogCategoryFunction> *_logFunctions;

void do_log(const LogCategory& category, Level level, const std::string& message) override {
NSLog(@"%@:%s %@", levelPrefix(level), category.get_name().c_str(), RLMStringDataToNSString(message));
NSArray *loggers;
{
std::lock_guard lock(_mutex);
loggers = _logFunctions;
}
if (loggers.count == 0) {
return;
}

@autoreleasepool {
NSString *nsMessage = RLMStringDataToNSString(message);
RLMLogCategory rlmCategory = logCategoryForCategory(category);
RLMLogLevel rlmLevel = logLevelForLevel(level);
for (RLMLogCategoryFunction fn : loggers) {
fn(rlmLevel, rlmCategory, nsMessage);
}
}
}
};
} s_dynamic_logger;

class CustomLogger : public Logger {
public:
Expand All @@ -153,11 +187,82 @@ void do_log(const LogCategory& category, Level level, const std::string& message
};
} // anonymous namespace

@implementation RLMLoggerToken {
RLMLogCategoryFunction _function;
}

- (instancetype)initWithFunction:(RLMLogCategoryFunction)function {
if (self = [super init]) {
_function = function;
}
return self;
}

- (void)invalidate {
std::lock_guard lock(s_dynamic_logger._mutex);
if (!_function) {
return;
}
auto& functions = s_dynamic_logger._logFunctions;
functions = [functions filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF != %@", _function]];
_function = nil;
}

@end

@implementation RLMLogger {
std::shared_ptr<Logger> _logger;
}

typedef void(^LoggerBlock)(RLMLogLevel level, NSString *message);
#pragma mark - Dynamic category-based API

+ (void)initialize {
[self resetToDefault];
}

+ (void)resetToDefault {
{
std::lock_guard lock(s_dynamic_logger._mutex);
static RLMLogCategoryFunction defaultLogger = ^(RLMLogLevel level, RLMLogCategory category, NSString *message) {
NSLog(@"%@:%s %@", levelPrefix(level), categoryForLogCategory(category).get_name().c_str(), message);
};
s_dynamic_logger._logFunctions = @[defaultLogger];
s_dynamic_logger.set_level_threshold(LogCategory::realm, Level::info);
}
Logger::set_default_logger(std::shared_ptr<Logger>(&s_dynamic_logger, [](Logger *) {}));
}

+ (RLMLoggerToken *)addLogFunction:(RLMLogCategoryFunction)function {
{
std::lock_guard lock(s_dynamic_logger._mutex);
// We construct a new array each time rather than using a mutable array
// so that do_log() can just acquire the pointer under lock without
// having to worry about the array being mutated on another thread
auto& functions = s_dynamic_logger._logFunctions;
if (functions.count) {
functions = [functions arrayByAddingObject:function];
}
else {
functions = @[function];
}
}
return [[RLMLoggerToken alloc] initWithFunction:function];
}

+ (void)removeAll {
std::lock_guard lock(s_dynamic_logger._mutex);
s_dynamic_logger._logFunctions = nil;
}

+ (void)setLevel:(RLMLogLevel)level forCategory:(RLMLogCategory)category {
s_dynamic_logger.set_level_threshold(categoryForLogCategory(category), levelForLogLevel(level));
}

+ (RLMLogLevel)levelForCategory:(RLMLogCategory)category {
return logLevelForLevel(s_dynamic_logger.get_level_threshold(categoryForLogCategory(category)));
}

#pragma mark - Deprecated API

- (RLMLogLevel)level {
return logLevelForLevel(_logger->get_level_threshold());
Expand All @@ -167,12 +272,6 @@ - (void)setLevel:(RLMLogLevel)level {
_logger->set_level_threshold(levelForLogLevel(level));
}

+ (void)initialize {
auto defaultLogger = std::make_shared<CocoaLogger>();
defaultLogger->set_level_threshold(LogCategory::realm, Level::info);
Logger::set_default_logger(defaultLogger);
}

- (instancetype)initWithLogger:(std::shared_ptr<Logger>)logger {
if (self = [self init]) {
self->_logger = logger;
Expand All @@ -193,24 +292,14 @@ - (instancetype)initWithLevel:(RLMLogLevel)level
return self;
}

- (instancetype)initWithLogFunction:(RLMLogCategoryFunction)logFunction {
if (self = [super init]) {
auto logger = std::make_shared<CustomLogger>();
logger->function = logFunction;
self->_logger = logger;
}
return self;
+ (instancetype)defaultLogger {
return [[RLMLogger alloc] initWithLogger:Logger::get_default_logger()];
}

+ (void)setLevel:(RLMLogLevel)level forCategory:(RLMLogCategory)category {
auto defaultLogger = Logger::get_default_logger();
defaultLogger->set_level_threshold(categoryForLogCategory(category).get_name(), levelForLogLevel(level));
+ (void)setDefaultLogger:(RLMLogger *)logger {
Logger::set_default_logger(logger->_logger);
}

+ (RLMLogLevel)levelForCategory:(RLMLogCategory)category {
auto defaultLogger = Logger::get_default_logger();
return logLevelForLevel(defaultLogger->get_level_threshold(categoryForLogCategory(category).get_name()));
}

#pragma mark Testing

Expand All @@ -229,18 +318,10 @@ void RLMTestLog(RLMLogCategory category, RLMLogLevel level, const char *message)
levelForLogLevel(level),
"%1", message);
}

#pragma mark Global Logger Setter

+ (instancetype)defaultLogger {
return [[RLMLogger alloc] initWithLogger:Logger::get_default_logger()];
}

+ (void)setDefaultLogger:(RLMLogger *)logger {
Logger::set_default_logger(logger->_logger);
}
@end

#pragma mark - Internal logging functions

void RLMLog(RLMLogLevel logLevel, NSString *format, ...) {
auto level = levelForLogLevel(logLevel);
auto logger = Logger::get_default_logger();
Expand Down
8 changes: 4 additions & 4 deletions Realm/RLMSyncManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,25 +105,25 @@ __attribute__((deprecated("This property is not used for anything")));
The logging threshold which newly opened synced Realms will use. Defaults to
`RLMSyncLogLevelInfo`.
By default logging strings are output to Apple System Logger. Set `logger` to
By default logging strings are output to NSLog. Set `logger` to
perform custom logging logic instead.
@warning This property must be set before any synced Realms are opened. Setting it after
opening any synced Realm will do nothing.
*/
@property (atomic) RLMSyncLogLevel logLevel
__attribute__((deprecated("Use `RLMLogger.default.level`/`Logger.shared.level` to set/get the default logger threshold level.")));
__attribute__((deprecated("Use `Logger.set(level: level, category: Category.Sync.all)` to set the log level for sync operations.")));

/**
The function which will be invoked whenever the sync client has a log message.
If nil, log strings are output to Apple System Logger instead.
If nil, log strings are output to RLMLogger instead.
@warning This property must be set before any synced Realms are opened. Setting
it after opening any synced Realm will do nothing.
*/
@property (atomic, nullable) RLMSyncLogFunction logger
__attribute__((deprecated("Use `RLMLogger.default`/`Logger.shared` to set/get the default logger.")));
__attribute__((deprecated("Use `Logger.add(function:)` and filter messages by category.")));

/**
The name of the HTTP header to send authorization data in when making requests to Atlas App Services which has
Expand Down
8 changes: 8 additions & 0 deletions Realm/TestUtils/RLMTestCase.m
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ + (void)setUp {
// resetting the simulator
[NSFileManager.defaultManager createDirectoryAtURL:RLMDefaultRealmURL().URLByDeletingLastPathComponent
withIntermediateDirectories:YES attributes:nil error:nil];

[RLMLogger resetToDefault];
[RLMLogger setLevel:RLMLogLevelWarn forCategory:RLMLogCategorySync];
}

// This ensures the shared schema is initialized outside of of a test case,
Expand All @@ -116,6 +119,11 @@ @implementation RLMTestCase {
dispatch_queue_t _bgQueue;
}

- (void)tearDown {
[RLMLogger resetToDefault];
[RLMLogger setLevel:RLMLogLevelWarn forCategory:RLMLogCategorySync];
}

- (void)deleteFiles {
// Clear cache
[self resetRealmState];
Expand Down
Loading

0 comments on commit 139cda0

Please sign in to comment.