-
Notifications
You must be signed in to change notification settings - Fork 80
SAM Type Interface #920
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
base: main
Are you sure you want to change the base?
SAM Type Interface #920
Conversation
core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala
Outdated
Show resolved
Hide resolved
final case class KernelLogLevel(name: String, value: Double) { | ||
def namePadded: String = KernelLogLevel.padded(this) | ||
|
||
KernelLogLevel.add(this) | ||
} |
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.
issue (structure): I'm sick right now, but we should get on a call towards the end of next week and go over bincompat.
Case classes are wonderful for applications, but we're going to tend to avoid them because they don't play nicely with bincompat.
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.
agreed 💯
core/shared/src/main/scala/org/typelevel/log4cats/KernelLogLevel.scala
Outdated
Show resolved
Hide resolved
def add(level: KernelLogLevel): Unit = synchronized { | ||
val length = level.name.length | ||
map += level.name.toLowerCase -> level | ||
if (length > maxLength) { | ||
maxLength = length | ||
padded = map.map { case (_, level) => | ||
level -> level.name.padTo(maxLength, ' ').mkString | ||
} | ||
} else { | ||
padded += level -> level.name.padTo(maxLength, ' ').mkString | ||
} | ||
} |
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.
issue: I'm not convinced we need to support dynamically adding log levels, or looking them up by name.
core/shared/src/main/scala/org/typelevel/log4cats/KernelLogLevel.scala
Outdated
Show resolved
Hide resolved
final def logTrace(record: Log.Builder => Log.Builder): F[Unit] = | ||
log(KernelLogLevel.Trace, record) | ||
final def logDebug(record: Log.Builder => Log.Builder): F[Unit] = | ||
log(KernelLogLevel.Debug, record) | ||
final def logInfo(record: Log.Builder => Log.Builder): F[Unit] = | ||
log(KernelLogLevel.Info, record) | ||
final def logWarn(record: Log.Builder => Log.Builder): F[Unit] = | ||
log(KernelLogLevel.Warn, record) | ||
final def logError(record: Log.Builder => Log.Builder): F[Unit] = | ||
log(KernelLogLevel.Error, record) |
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.
suggestion (naming): If we do end up needing these, we should simplify down the names
final def logTrace(record: Log.Builder => Log.Builder): F[Unit] = | |
log(KernelLogLevel.Trace, record) | |
final def logDebug(record: Log.Builder => Log.Builder): F[Unit] = | |
log(KernelLogLevel.Debug, record) | |
final def logInfo(record: Log.Builder => Log.Builder): F[Unit] = | |
log(KernelLogLevel.Info, record) | |
final def logWarn(record: Log.Builder => Log.Builder): F[Unit] = | |
log(KernelLogLevel.Warn, record) | |
final def logError(record: Log.Builder => Log.Builder): F[Unit] = | |
log(KernelLogLevel.Error, record) | |
final def trace(record: Log.Builder => Log.Builder): F[Unit] = | |
log(KernelLogLevel.Trace, record) | |
final def debug(record: Log.Builder => Log.Builder): F[Unit] = | |
log(KernelLogLevel.Debug, record) | |
final def info(record: Log.Builder => Log.Builder): F[Unit] = | |
log(KernelLogLevel.Info, record) | |
final def warn(record: Log.Builder => Log.Builder): F[Unit] = | |
log(KernelLogLevel.Warn, record) | |
final def error(record: Log.Builder => Log.Builder): F[Unit] = | |
log(KernelLogLevel.Error, record) |
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.
lets keep it until final change, I tried to change as suggested but its giving error due to duplicates
js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala
Outdated
Show resolved
Hide resolved
Thank you for all the suggestions, I will rework on it |
// For Java/legacy interop, if needed (not implicit) | ||
val LevelOrdering: Ordering[KernelLogLevel] = | ||
Ordering.by[KernelLogLevel, Int](_.value).reverse |
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.
Java interop is a non-issue, since attempting to use cats-effect from Java would be painful (at best) and the idioms are all sorts of incompatible.
There's an implicit which provides an Ordering[A]
if there is an Order[A]
in scope, so this can be removed.
private var _timestamp: Option[FiniteDuration] = None | ||
private var _level: Option[KernelLogLevel] = None | ||
private var _levelValue: Option[Int] = None | ||
private var _message: Option[String] = None | ||
private var _message: () => String = noopMessage |
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.
Question: is the use case of a log without a message something we want to support?
): SamLogger[F, Ctx] = | ||
new SamLogger[F, Ctx] { | ||
def log(level: KernelLogLevel, record: Builder => Builder): F[Unit] = { | ||
val modifiedRecord = (builder: Builder) => { |
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.
Needing to materialize the log and manually copy over the full message hints to me that we may need to refine Builder
.
Pretty sure this would materialize message
as well, so that's something to check.
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.
With the added adaptMessage
method, this becomes:
def log(level: KernelLogLevel, record: Builder => Builder): F[Unit] = {
l.log(level, record(_).adaptMessage(f))
}
trait Log[Ctx] { | ||
def timestamp: Option[FiniteDuration] | ||
def level: KernelLogLevel | ||
def message: String |
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 probably be lazy all the way to the edge.
def message: String | |
def message: () => String |
def unsafeThrowable: Throwable | ||
def unsafeContext: Map[String, Ctx] |
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 didn't see these used anywhere, I think we can probably remove them.
contextMap: Map[String, A] | ||
)(implicit E: Context.Encoder[A, Ctx]): Builder[Ctx] = | ||
contextMap.foldLeft(this) { case (builder, (k, v)) => builder.withContext(k)(v) } | ||
|
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.
Implementing withModifiedString
and addContext
will be easier if we add some helpers here.
def adaptTimestamp(f: FiniteDuration => FiniteDuration): Builder[Ctx] | |
def adaptLevel(f: KernelLogLevel => KernelLogLevel): Builder[Ctx] | |
def adaptMessage(f: String => String): Builder[Ctx] | |
def adaptThrowable(f: Throwable => Throwable): Builder[Ctx] | |
def adaptContext(f: Map[String, Ctx] => Map[String, Ctx]): Builder[Ctx] | |
def adaptFileName(f: String => String): Builder[Ctx] | |
def adaptClassName(f: String => String): Builder[Ctx] | |
def adaptMethodName(f: String => String): Builder[Ctx] | |
def adaptLine(f: Int => Int): Builder[Ctx] |
private class MutableBuilder[Ctx] extends Builder[Ctx] { | ||
private var _timestamp: Option[FiniteDuration] = None | ||
private var _level: Option[KernelLogLevel] = None | ||
private var _message: Option[String] = None |
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.
To keep this lazy, we'd need to update here as well
private var _message: Option[String] = None | |
private var _message: () => String = () => "" |
private var _level: Option[KernelLogLevel] = None | ||
private var _message: Option[String] = None | ||
private var _throwable: Option[Throwable] = None | ||
private val _context: mutable.Map[String, Ctx] = mutable.Map.empty[String, Ctx] |
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.
Using a builder here will make things a bit easier when writing adaptContext
private val _context: mutable.Map[String, Ctx] = mutable.Map.empty[String, Ctx] | |
private var _context: mutable.Builder[(String, Ctx), Map[String, Ctx]] = Map.newBuilder[String, Ctx] |
|
||
private class MutableBuilder[Ctx] extends Builder[Ctx] { | ||
private var _timestamp: Option[FiniteDuration] = None | ||
private var _level: Option[KernelLogLevel] = None |
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 can be simplified, since we have a default value we're going to use anyway.
private var _level: Option[KernelLogLevel] = None | |
private var _level: KernelLogLevel = KernelLogLevel.Info |
_line = if (line > 0) Some(line) else None | ||
this | ||
} | ||
} |
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.
Here's the adapt*
methods
} | |
override def adaptTimestamp(f: FiniteDuration => FiniteDuration): Builder[Ctx] = { | |
_timestamp = _timestamp.map(f) | |
this | |
} | |
override def adaptLevel(f: KernelLogLevel => KernelLogLevel): Builder[Ctx] = { | |
_level = f(_level) | |
this | |
} | |
override def adaptMessage(f: String => String): Builder[Ctx] = { | |
_message = () => f(_message()) | |
this | |
} | |
override def adaptThrowable(f: Throwable => Throwable): Builder[Ctx] = { | |
_throwable = _throwable.map(f) | |
this | |
} | |
override def adaptContext(f: Map[String, Ctx] => Map[String, Ctx]): Builder[Ctx] = { | |
_context = _context.mapResult[Map[String, Ctx]](f) | |
this | |
} | |
override def adaptFileName(f: String => String): Builder[Ctx] = { | |
_fileName = _fileName.map(f) | |
this | |
} | |
override def adaptClassName(f: String => String): Builder[Ctx] = { | |
_className = _className.map(f) | |
this | |
} | |
override def adaptMethodName(f: String => String): Builder[Ctx] = { | |
_methodName = _methodName.map(f) | |
this | |
} | |
override def adaptLine(f: Int => Int): Builder[Ctx] = { | |
_line = _line.map(f) | |
this | |
} | |
} |
val builder = (b: Log.Builder[String]) => { | ||
var current = b.withMessage(msg) | ||
ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } | ||
current | ||
} | ||
kernel.logTrace(builder) |
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.
Might be worth exercising the methods on Log.Builder
to see if they pull their weight.
For example, this can be simplified to:
val builder = (b: Log.Builder[String]) => { | |
var current = b.withMessage(msg) | |
ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } | |
current | |
} | |
kernel.logTrace(builder) | |
kernel.logTrace(_.withMessage(msg).withContextMap(ctx)) |
This PR's objective is to create a SAM type interface in log4cats