-
Notifications
You must be signed in to change notification settings - Fork 19
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 a way to create log without side effects #116
base: master
Are you sure you want to change the base?
Add a way to create log without side effects #116
Conversation
@@ -76,7 +78,92 @@ object Log { | |||
|
|||
def summon[F[_]](implicit F: Log[F]): Log[F] = F | |||
|
|||
def apply[F[_]: Sync](logger: Logger): Log[F] = new Log[F] { | |||
def cached[F[_] : Sync](source: String, factory: ILoggerFactory): Log[F] = new Log[F] { |
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 is still an effectful unpure code, hence - NO from my side. however you can already use logOf(…).toTry.get
in your code base, which will clearly express to readers unsafeness, and this is easy to use
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.
Why is that an effectual code?
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.
If you worry about getting the factory in safe manner then check out test file in this PR. It shows that we still should get ILoggerFactory by delaying slf4j stuff.
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.
because any manipulations with factory
are not safe, and in runtime it really references mutable shared state and changes it
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.
Well, I see what you mean. It easily can be solved by putting function here (it should be done anyway because of Logback) like so
def cached[F[_] : Sync](source: String, factory: String => F[Logger]): Log[F] = ???
or by putting factory inside of Ref
. I guess it would be pure code then, right?
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.
Also I see that every manipulation with factory
is delayed here, isn't it enough to be pure?
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.
right!
however such changes would enforce your Log
implementation to call factory.getLogger
as part of log.info/debug/etc
- adds some runtime overhead
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.
Such runtime overhead is so little we can ignore it. If you dive in (not so deep) to the implementation, you can see that:
private Map<String, Logger> loggerCache;
//...
public final Logger getLogger(final String name) {
if (name == null) {
throw new IllegalArgumentException("name argument cannot be null");
}
// if we are asking for the root logger, then let us return it without
// wasting time
if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
return root;
}
int i = 0;
Logger logger = root;
// check if the desired logger exists, if it does, return it
// without further ado.
Logger childLogger = (Logger) loggerCache.get(name);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this line
// if we have the child, then let us return it without wasting time
if (childLogger != null) {
return childLogger;
}
// if the desired logger does not exist, them create all the loggers
// in between as well (if they don't already exist)
//....
}
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.
You know when you create instances of IO and flatMap classes for every operation you make this overhead seems totally fine.
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.
sure it might be totally fine, it depends on actual implementation :)
def cached[F[_] : Sync](source: String, factory: String => F[Logger]): Log[F]
signaturewise & typewise - is ok
One important thing to discuss is how to integrate it in existing code. |
trait MyClass[A[_], B[_], C[_]] {
def a: A[…]
def b: B[…]
def c: C[…]
} gives the most flexibility, however usually you just do not need that, basically single hence I'd recommend to NOT overcomplicate, imho |
I do not want to make my code impure. |
Regarding multiple type parameters: I was talking about such approach: trait LogOf[I[_], F[_]] {
def apply(source: String): I[Log[F]]
} where |
Currently
LogOf
creates desiredLog
instances with a side-effect. But there is a simple way to avoid that and get plainLog[F]
, notF[Log[F]]
. This is possible because of the fact that both SLF4J and Logback cache their loggers (whichLogOf
relies on).This means that it is totally fine to call
loggerFactory.getLogger(source)
on each log message, because the overhead is simply a hash table call and a couple ofif
-s.The names and the details in this PR are subject to change, let's discuss it all.