Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redesign the Logger API #8645

Closed
wants to merge 4 commits into from
Closed

Redesign the Logger API #8645

wants to merge 4 commits into from

Conversation

tgoyne
Copy link
Member

@tgoyne tgoyne commented Jul 5, 2024

Rather than creating Logger instances but then setting the log level via static methods, skip creating Logger instances entirely and do something similar to Kotlin's API with Logger.add(logFn) which registers a new callback to be called. More than one callback can be registered at once, and callbacks can be dynamically registered and unregistered rather than having to set up the logger before opening the Realm. This differs from Kotlin's design in that we use a token to unregister rather than Logger.remove() as the latter has some potentially surprising quirks.

Logging messages from the SDK is now decoupled from RLMLogger and uses a separate RLMLog() function which logs directly to the registered callbacks.

logCategoryForCategoryName() was unnecessarily slow as it converted the category's name to a NSString, constructed a large NSDictionary, performed a single lookup in that dictionary, and then discarded it for every message logged. Although obj-c does now support creating literal NSDictionaries as compile-time constants that would make this reasonable, that requires a deployment target of iOS 14 and we currently target iOS 12. This change cuts the time spent in do_log() (which includes the actual logging of the strings) when running the tests with loglevel=all by about 20%.

The dictionary lookup also turns out to just not be faster than a linear scan on a const array. This may change if we add sufficiently more categories in the future.

The deprecated old version of the logger function still needs to be tested until it's removed entirely.

@tgoyne tgoyne added the no-jira-ticket Skip checking the PR title for Jira reference label Jul 5, 2024
@tgoyne tgoyne self-assigned this Jul 5, 2024
@cla-bot cla-bot bot added the cla: yes label Jul 5, 2024
@tgoyne tgoyne force-pushed the tg/logging-fixes branch 2 times, most recently from 4f3ac24 to 6ee666b Compare July 6, 2024 04:09
@dianaafanador3
Copy link
Contributor

dianaafanador3 commented Jul 6, 2024

The change to set/get a level for a specific category was changed to a static function in all SDKs to avoid the user of the API setting different levels for categories in different loggers and expect the log messages to be filtered by the level for a category set specifically for this logger.
https://github.com/realm/realm-kotlin/pull/1692/files#diff-834583a499b615bec40974670a893a7d34c2972fbf6d42c705cb7a1df7963f86
You can also check the design document

@dianaafanador3
Copy link
Contributor

Even though we don't want to expose logging to the end user (even though there is a category for App logs intended for this), my personal opinion is that we may want to log some things sdk side, which is done by others SDKs as well. That's why there are specifically some categories for SDK in the log categories.
So, I don't see any harm exposing this internally.

@tgoyne
Copy link
Member Author

tgoyne commented Jul 8, 2024

The change to set/get a level for a specific category was changed to a static function in all SDKs to avoid the user of the API setting different levels for categories in different loggers and expect the log messages to be filtered by the level for a category set specifically for this logger.

Why would anyone expect the log level set on a logger other than the one in use to have any effect? This concern makes zero sense to me. Using static functions is just setting ourselves up for another API break when we reintroduce the ability to actually use multiple loggers.

my personal opinion is that we may want to log some things sdk side, which is done by others SDKs as well.

We should not do this via RLMLogger. Despite the name, RLMLogger is a log sink, and producing logs is a different operation which should be done via a different type.

@tgoyne
Copy link
Member Author

tgoyne commented Jul 8, 2024

I do not see a design doc which mentions anything other than the switch to a nested enum.

@nirinchev
Copy link
Member

Both https://docs.google.com/document/d/1Bna-4oBtcj9Ha3hAGmrKsQz3Jb2cGSG4zbFxauZUkcY/edit and https://docs.google.com/document/d/1Okr1jTBunco0S524GBc_RU9PKZPWLXIsdPJd4Q1U-Qw/edit talk about the logger being singleton and having a single log level. Similarly, all the other SDKs have exposed an API where the log level is a static property that affects all the configured loggers. I don't think we should be deviating from the other SDKs here or from how Core is using it.

@tgoyne
Copy link
Member Author

tgoyne commented Jul 8, 2024

Core specifically does not use static functions and puts quite a bit of effort into passing around loggers. The only place where the global logger is read is at the entry points which take a config.

A static API and the logger being a singleton are two contradictory ideas. The entire idea of a singleton is that you aren't using static/global functions and there just happens to only be one instance of the object. "A static function that affects all the configured loggers" is also pretty weird because there's only one configured logger. The static function is just fetching Logger.shared and modifying that, and that's weirdly roundabout compared to having the user just write that.

@tgoyne
Copy link
Member Author

tgoyne commented Jul 8, 2024

For broader context, the main reason I don't like the static function approach is that as soon as I tried to actually use the new logger functionality I found it both confusing and obnoxious. I first couldn't figure out how to set the log level at all because the idea that you construct a logger, set it as a shared logger, and then set the log levels via a different thing did not even occur to me as an option. After that I tried to switch loggers around to only get trace logging on the specific thing I was debugging, and the API for that turned out to be incredibly weird. Even though the log level is set via a static function, assigning a new logger to Logger.shared resets all of the log levels to the default because despite what the API implies, the log levels are actually set on specific logger and aren't a global setting.

If the API was instead that you don't create Logger objects at all and there's just global log levels and a global callback then the API would at least be consistent, but I think that's a worse API than having a singleton object.

@tgoyne tgoyne marked this pull request as draft July 27, 2024 01:16
@tgoyne tgoyne changed the title Do not publicly expose -[RLMLogger logWithLevel:category:message:] Redesign the Logger API Jul 27, 2024
@tgoyne tgoyne force-pushed the tg/logging-fixes branch 4 times, most recently from 8abdb10 to cee9c86 Compare July 27, 2024 16:22
@tgoyne tgoyne marked this pull request as ready for review July 27, 2024 18:15
@tgoyne
Copy link
Member Author

tgoyne commented Jul 27, 2024

Updated this to an API based on the Kotlin one but tweaked a bit for language differences. You now do Logger.add { level, category, message in ... } and dynamically adding/removing logger callbacks is supported.

Copy link
Contributor

@dianaafanador3 dianaafanador3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!! Just some minor comments and questions

Realm/RLMLogger.mm Show resolved Hide resolved
Realm/RLMLogger.mm Show resolved Hide resolved
Realm/RLMLogger.mm Show resolved Hide resolved
Comment on lines +133 to +137
To remove the default NSLog-logger, call `[RLMLogger removeAll];` first.

To change the log level, call `[RLMLogger setLevel:forCategory:]`. The
`RLMLogCategoryRealm` will update all log categories at once. All log
callbacks share the same log levels and are called for every category.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To remove the default NSLog-logger, call [RLMLogger removeAll]; first.
To change the log level, call [RLMLogger setLevel:forCategory:].
The RLMLogCategoryRealm will update all log categories at once. All log
callbacks share the same log levels and are called for every category.

*/
@property (nonatomic) RLMLogLevel level
__attribute__((deprecated("Use `setLevel(level:category)` or `setLevel:category` instead.")));
+ (RLMLoggerToken *)addLogFunction:(RLMLogCategoryFunction)function NS_REFINED_FOR_SWIFT;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like docstrings for the new APIs are missing parameters and returns details

@tgoyne tgoyne closed this Aug 16, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 15, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
cla: yes no-jira-ticket Skip checking the PR title for Jira reference
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants