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
Add Logger ability to contacts API #145
Conversation
@alorma, as I mentioned in #144, I'm super excited about this idea. I'm hoping you can own this part of the library! It might take a while because I have work and we live in different parts of the world (I live in Hawaii), so please be patient with my responses. I'll look at this tomorrow. We'll have to come to an agreement about the internal architecture, consumer usage, and what to log and when. Then it would be AWESOME if you can own this part of the library and implement all part of it 😁 ❤️ |
For sure! I live on Barcelona, no problem on async communication |
Sorry I clicked the close button by accident! |
I like how it is currently looking! There are a several things we need to discuss. This is a long comment but it should contain most of the stuff we need to discuss and agree on. After this, writing the code should be a 🍰
Please note that code samples I provide in this comment are just suggestions to help you because you are a new contributor to this lib and I kinda want to keep the design the same for all APIs, including the logger. 1. Logging functions in the
|
Point number 1, totally agree, I've thought about |
Point number 2 Not sure about that part, I was not thinking about logging results of queries, mostly I wanted to be available to log queries themselves, like operations, are done on the database, etc... Of course, those operations must be GDPR compliant if logged to some analytics server, as it can contain sensitive information... but as point 1 mentions, logs must not go to production, and that is the responsibility of the app. |
Point number 3: What I just told on the previous comment, I would not log the query response, but that's up to you, and it can be discussed on following PRs, to keep the changes to minimum. |
@alorma, I'm thinking that it would be very useful to log the consumer input and API output for all CRUD APIs. The log output would look something like, Insert {
allowBlanks: true
include: data1, data2, ...
account: Account(...)
rawContacts: (NewRawContact(...), NewRawContact(...), ...)
}
InsertResult {
isSuccessful: true
rawContactIds: ...
} Those logs can either be plain (not safe for production) or redacted (safe for production).
I will be implementing the commit {
contacts.loggerRegistry.log(this)
...
return InsertResult(results).also(contacts.loggerRegistry::log)
} But first, let's take a step back and ask why we want to add logging. We should always have a problem that we are trying to solve. Once we have a potential solution, we should see if it solves the problem. We should do this before writing any code. Otherwise, we just added code without adding any value for library maintainers and consumers. Problem(s)We need to...
If there is anything else I missed, let me know. SolutionProvide a logger that can log the input and output of all APIs. To be GDPR-compliant, both input and output must be redacted in production. However, redaction does not need to occur during development. This can be accomplished with the val contacts = Contacts(
context = ...,
logger = LoggerRegistry(AndroidLogger(), redactMessages = isProductionBuild)
)
class LoggerRegistry @JvmOverloads constructor(
val logger: Logger = EmptyLogger()
val redactMessages: Boolean = true
) {
internal fun log(redactable: Redactable) {
logger.log(
if (redactMessages) {
redactable.redactedCopy().toString()
} else {
redactable.toString()
}
)
}
} This is just the initial implementation. We can always iterate and add more things to it if we want to in the future. Like you said, we should keep it as simple as possible 😁 |
@alorma, I merged the changes that you need to complete this PR 😁 Of course, I can merge this PR as it is right now if you don't have time or don't want to do the grunt work. That way, you will still appear as a contributor (which I would very much like). My plan is to include this in the next release (v0.1.10), which I'm aiming to complete by January 14. So you have about 2 weeks to work on this if you want to 😄 Let me know what you want to do 🔥 If you decide to work on this, here is a recap of what I think this logging API should look like for the initial implementation. Using the code you already have in this PR and the code that I've merged in #147, #152, #153, and #154, class LoggerRegistry @JvmOverloads constructor(
private val logger: Logger = EmptyLogger(),
private val redactMessages: Boolean = true
) {
internal val apiListener: CrudApi.Listener = Listener()
// Prevent consumers from invoking the listener functions by not having the registry implement
// it directly.
private inner class Listener : CrudApi.Listener {
override fun onPreExecute(api: CrudApi) {
logRedactable(api)
}
override fun onPostExecute(result: CrudApi.Result) {
logRedactable(result)
}
private fun logRedactable(redactable: Redactable) {
logger.log(redactable.redactedCopyOrThis(redactMessages).toString())
}
}
} This registry would be the one that we would keep a reference to in the Contacts interface, interface Contacts {
...
val loggerRegistry: LoggerRegistry
val apiListenerRegistry: CrudApiListenerRegistry
} Hooking up the logger registry to receive CRUD API events to enable logging will be as simple as, fun Contacts(
...
loggerRegistry: LoggerRegistry = LoggerRegistry(),
apiListenerRegistry: CrudApiListenerRegistry = CrudApiListenerRegistry()
): Contacts = ContactsImpl(
...
loggerRegistry,
apiListenerRegistry.register(loggerRegistry.apiListener)
) Consumer initialization (e.g. in the val contacts = Contacts(
...
loggerRegistry = LoggerRegistry(AndroidLogger(), redactMessages = !BuildConfig.DEBUG),
) Let me know if you have any other questions. I think this is a good initial implementation 😁 Regardless of whether you choose to work on this or not, I will merge this PR! Thanks in advance! 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥 |
Thanks @vestrel00 ! Here on Spain I'm on holiday season until 6th of January 😅 Btw, I planned to open back my laptop here on 2022, so next week I will work on that! Thanks for your work! Happy new year's eve! |
Thanks! Take your time. No rush 😁 Happy new year! |
No redacted content:
Redacted content:
Great! |
What annoys me on this is... should the lib pass the whole string to log? or maybe we must add here an option to print less verbose logs? Like Retrofit, or Koin, that allow different levels of logging:
Of course, that is matter for another PR, this one can be merged as it's |
I've updated a litte bit your idea... I've moved the This way we win some things: API consumers won't need to know about the Also, we simplify the creation of Maybe we must found a way to create a "Constructor" with |
@alorma, thank you so much for your contribution! It looks great!!! I'll leave some comments but there is really nothing that needs to be changed here. I can tell that you've put a lot of thought on this and I like it 👍 I will handle adding documentation and the howto page for it. Great work! Thanks again ❤️
This is really good because then individual loggers can be configured differently if we ever decide to support multiple loggers. Genius!
I agree! It is still unfortunately exposed through
This is also a great idea! I will do the same for the
I don't see any issues with the way you've written this PR 😁 |
/** | ||
* Registry for [Logger] | ||
*/ | ||
var loggerRegistry: LoggerRegistry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a val
. I'll make the change after merging this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made the update here e321874
apiListenerRegistry: CrudApiListenerRegistry = CrudApiListenerRegistry(), | ||
logger: Logger = EmptyLogger(), | ||
): Contacts { | ||
val loggerRegistry = LoggerRegistry(logger) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea hiding the creation of the LoggerRegistry
. It does not need to be exposed to consumers at this point. We might change this in the future if we decide to support multiple loggers and configurations that apply to all loggers. But this is perfect for its current state!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will actually also do the same for the apiListenerRegistry: CrudApiListenerRegistry = CrudApiListenerRegistry()
. It does not need to be a parameter. It can be hidden.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made the update here e321874
class AndroidLogger( | ||
private val tag: String = TAG, | ||
override val redactMessages: Boolean = true, | ||
) : Logger { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice making the every logger instance configurable!
import contacts.core.Redactable | ||
import contacts.core.redactedCopyOrThis | ||
|
||
class LoggerRegistry @JvmOverloads constructor( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
GenderRegistration(), | ||
HandleNameRegistration() | ||
), | ||
logger = AndroidLogger(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome!
import contacts.core.redactedCopyOrThis | ||
|
||
class LoggerRegistry @JvmOverloads constructor( | ||
private val logger: Logger = EmptyLogger(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't really need to have a default value for this as we construct this elsewhere anyways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made the update here e321874
// Prevent consumers from invoking the listener functions by not having the registry implement | ||
// it directly. | ||
private inner class Listener( | ||
private val logger: Logger, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logger
actually overshadows the outer logger
. I think I know what you are trying to do here. This whole thing can be simplified to,
class LoggerRegistry(logger: Logger = EmptyLogger()) {
internal val apiListener: CrudApi.Listener = Listener(logger)
// Prevent consumers from invoking the listener functions by not having the registry implement
// it directly.
private class Listener(private val logger: Logger) : CrudApi.Listener {
override fun onPreExecute(api: CrudApi) {
logRedactable(api)
}
override fun onPostExecute(result: CrudApi.Result) {
logRedactable(result)
}
private fun logRedactable(redactable: Redactable) {
logger.log(redactable.redactedCopyOrThis(logger.redactMessages).toString())
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made the update here e321874
This PR adds
Logger
toContacts
API, so it can be used to track features like query, insert, ...I not plan to add on this PR the logs to every feature, but my idea is to add it later following up a discussion with you about what and how should be tracked.
Code example with android logger:
Code example with custom logger:
This PR closes #144