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

Add PagingSelfAwareStructuredLogger #518

Merged
merged 40 commits into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
279bbbd
Add PagingSelfAwareStructuredLogger
dj707chen Nov 5, 2021
e4c1acb
Fix test failure for Scala 2.12; Adjust page header and footer
dj707chen Nov 5, 2021
c5448e5
Refactor to use PagingSelfAwareStructuredLogger object only
dj707chen Nov 5, 2021
9327993
Added comments to withPaging method
dj707chen Nov 6, 2021
9f38038
Fix format issue
dj707chen Nov 6, 2021
6e22112
Add /PagingSelfAwareStructuredLogger test
dj707chen Nov 7, 2021
be8e9b2
Add /PagingSelfAwareStructuredLogger test
dj707chen Nov 7, 2021
2502176
Cleaned up PagingSelfAwareStructuredLogger
dj707chen Nov 8, 2021
521b15e
Added Added maxPageNumNeeded
dj707chen Nov 8, 2021
004892d
Update core/shared/src/main/scala/org/typelevel/log4cats/PagingSelfAw…
dj707chen Nov 24, 2021
9ede048
Update core/shared/src/main/scala/org/typelevel/log4cats/PagingSelfAw…
dj707chen Nov 24, 2021
8570c76
Merge branch 'typelevel:main' into main
dj707chen Nov 25, 2021
24c2979
Refactor PagingSelfAwareStructuredLogger.scala
dj707chen Nov 25, 2021
2a9be0d
Add unit test case for PagingSelfAwareStructuredLogger.scala
dj707chen Nov 25, 2021
ef52925
Correct commnent in PagingSelfAwareStructuredLogger.scala
dj707chen Nov 25, 2021
fff15da
Make numOfPages no greater than maxPageNeeded
dj707chen Nov 25, 2021
ad97f8d
Add test cases for maxPageNeeded
dj707chen Nov 25, 2021
67cbf2d
Add assertions
dj707chen Nov 25, 2021
e4bb681
Add test cases for parameter assertions
dj707chen Nov 25, 2021
b42b74e
Update PagingSelfAwareStructuredLogger.scala
weipingc Nov 26, 2021
3e95c6d
Merge pull request #1 from weipingc/main
dj707chen Nov 26, 2021
a29a629
Enrich unit test cases to validate logged messages; refined code
dj707chen Nov 26, 2021
6a8dbfd
Update build.sbt
dj707chen Nov 30, 2021
d2e99a1
Try to fix the specs2 not found issue in cross build
dj707chen Nov 30, 2021
23e407a
add check in unit test
dj707chen Dec 5, 2021
13e4d95
Address Ross's comment
dj707chen Dec 9, 2021
e039ffc
Remove wartremover annotations
dj707chen Dec 9, 2021
49834b4
Restore pageIndices
dj707chen Dec 9, 2021
48d6665
Convert specs2 test suite to MUnit
dj707chen Dec 9, 2021
401c2d2
Clean up
dj707chen Dec 9, 2021
10ee5b4
Fix logger casting issue
dj707chen Jan 9, 2022
effa481
Fix issue: value unsafeRunSync is not a member of cats.effect.IO
dj707chen Jan 10, 2022
2cffe20
Fix issue: munit.internal.JSFs$ needs to be imported from module 'fs'…
dj707chen Jan 10, 2022
13209f5
Fix formatting issue found by scalafmtCheck
dj707chen Jan 10, 2022
7b8f414
Merge branch 'typelevel:main' into main
dj707chen Jan 10, 2022
fd4d6b8
Fix issue: org.scalajs.testing.adapter.JSEnvRPC
dj707chen Jan 10, 2022
959cd29
Merge branch 'typelevel:main' into main
dj707chen Jan 12, 2022
aa95160
Test runner returns IO[Unit] assertion
rossabaker Jan 12, 2022
4657323
Another scalafmt
rossabaker Jan 12, 2022
a33238d
Save copyright statement changes generated by sbt
dj707chen Jan 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ tags
project/metals.sbt
project/project
.bsp

# mac
.DS_Store
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ lazy val testing = crossProject(JSPlatform, JVMPlatform)
.settings(
name := "log4cats-testing",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect" % catsEffectV
"org.typelevel" %%% "cats-effect" % catsEffectV,
"ch.qos.logback" % "logback-classic" % logbackClassicV % Test
)
)
lazy val testingJVM = testing.jvm
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/*
* Copyright 2018 Typelevel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.typelevel.log4cats

import cats._
import cats.implicits._

import java.io.{PrintWriter, StringWriter}
import java.util.UUID

object PagingSelfAwareStructuredLogger {

/**
* Wrap a SelfAwareStructuredLogger adding pagination functionality to avoid the truncation (hard
* splitting) by the underline logging provider. For the functions with a Throwable parameter, the
* stack trace is appended to the message before pagination.
*
* The actual text to be logged has 3 parts:
*
* Part 1. The chunk of message in each page; Part 2. This function will add the page header and
* footer; Part 3. The base class SelfAwareStructuredLogger may add tracing data etc logging
* context.
*
* The total of the above 3 parts should be smaller than the log size limit of the underline
* logging provider to avoid hard splitting.
*
* Example: Assume the log size limit of the underline logging provider is 75 Kb, setting
* pageSizeK to 64 leaves 11 Kb for (part 2) and (part 3).
*
* @param pageSizeK
* The size (in unit kibibyte) of the chunk of message in each page.
* @param maxPageNeeded
* The maximum number of pages to be logged.
* @param logger
* The SelfAwareStructuredLogger to be used to do the actual logging.
* @tparam F
* Effect type class.
* @return
* SelfAwareStructuredLogger with pagination.
*/
def withPaging[F[_]: Monad](pageSizeK: Int = 64, maxPageNeeded: Int = 999)(
logger: SelfAwareStructuredLogger[F]
): SelfAwareStructuredLogger[F] =
new PagingSelfAwareStructuredLogger[F](pageSizeK, maxPageNeeded)(logger)

private class PagingSelfAwareStructuredLogger[F[_]: Monad](pageSizeK: Int, maxPageNeeded: Int)(
sl: SelfAwareStructuredLogger[F]
) extends SelfAwareStructuredLogger[F] {
if (pageSizeK <= 0 || maxPageNeeded <= 0)
throw new IllegalArgumentException(
s"pageSizeK(=$pageSizeK) and maxPageNeeded(=$maxPageNeeded) must be positive numbers."
)

private val pageIndices = (1 to maxPageNeeded).toList
dj707chen marked this conversation as resolved.
Show resolved Hide resolved
private val logSplitIdN = "log_split_id"
private val pageSize = pageSizeK * 1024

private def pagedLogging(
loggingOp: (=> String) => F[Unit],
logSplitId: String,
msg: => String
): F[Unit] = {
val numOfPagesRaw = (msg.length - 1) / pageSize + 1
val numOfPages = Math.min(numOfPagesRaw, maxPageNeeded)
if (numOfPages <= 1)
loggingOp(msg)
else {
val logSplitIdPart1 = logSplitId.split('-').head
dj707chen marked this conversation as resolved.
Show resolved Hide resolved
val pageHeaderTail = s"$numOfPages $logSplitIdPart1"
val pageFooterTail = s"$numOfPages $logSplitIdN=$logSplitId page_size=$pageSizeK Kib"
pageIndices
.take(numOfPages)
.traverse_ { pi =>
val beginIndex = (pi - 1) * pageSize
val pageContent = msg.slice(beginIndex, beginIndex + pageSize)

loggingOp(show"""Page $pi/$pageHeaderTail
|
|$pageContent
|
|Page $pi/$pageFooterTail""".stripMargin)
}
}
}

private def addCtx(
msg: => String,
ctx: Map[String, String]
): (String, Map[String, String]) = {
val logSplitId = UUID.randomUUID().show
(
logSplitId,
ctx
.updated(logSplitIdN, logSplitId)
.updated("page_size", s"${pageSizeK.show} Kib")
.updated("log_size", s"${msg.length.show} Byte")
)
}

private def doLogging(
loggingLevelChk: => F[Boolean],
logOpWithCtx: Map[String, String] => (=> String) => F[Unit],
msg: => String,
ctx: Map[String, String] = Map()
): F[Unit] = {
loggingLevelChk.ifM(
{
val (logSplitId, newCtx) = addCtx(msg, ctx)
pagedLogging(logOpWithCtx(newCtx), logSplitId, msg)
},
Applicative[F].unit
)
}

private def doLoggingThrowable(
loggingLevelChk: => F[Boolean],
logOpWithCtx: Map[String, String] => (=> String) => F[Unit],
t: Throwable,
msg: => String,
ctx: Map[String, String] = Map()
): F[Unit] = {
loggingLevelChk.ifM(
doLogging(loggingLevelChk, logOpWithCtx, s"$msg\n${getStackTrace(t)}", ctx),
Applicative[F].unit
)
}

def getStackTrace(t: Throwable): String = {
val sw = new StringWriter()
val pw = new PrintWriter(sw, true)
t.printStackTrace(pw)
sw.getBuffer.toString
}

override def isTraceEnabled: F[Boolean] = sl.isTraceEnabled

override def isDebugEnabled: F[Boolean] = sl.isDebugEnabled

override def isInfoEnabled: F[Boolean] = sl.isInfoEnabled

override def isWarnEnabled: F[Boolean] = sl.isWarnEnabled

override def isErrorEnabled: F[Boolean] = sl.isErrorEnabled

// Log message

override def trace(msg: => String): F[Unit] =
doLogging(isTraceEnabled, sl.trace, msg)

override def debug(msg: => String): F[Unit] =
doLogging(isDebugEnabled, sl.debug, msg)

override def info(msg: => String): F[Unit] =
doLogging(isInfoEnabled, sl.info, msg)

override def warn(msg: => String): F[Unit] =
doLogging(isWarnEnabled, sl.warn, msg)

override def error(msg: => String): F[Unit] =
doLogging(isErrorEnabled, sl.error, msg)

// Log message and throwable

override def trace(t: Throwable)(msg: => String): F[Unit] =
doLoggingThrowable(isTraceEnabled, sl.trace, t, msg)

override def debug(t: Throwable)(msg: => String): F[Unit] =
doLoggingThrowable(isDebugEnabled, sl.debug, t, msg)

override def info(t: Throwable)(msg: => String): F[Unit] =
doLoggingThrowable(isInfoEnabled, sl.info, t, msg)

override def warn(t: Throwable)(msg: => String): F[Unit] =
doLoggingThrowable(isWarnEnabled, sl.warn, t, msg)

override def error(t: Throwable)(msg: => String): F[Unit] =
doLoggingThrowable(isErrorEnabled, sl.error, t, msg)

// Log message, passing context

override def trace(ctx: Map[String, String])(msg: => String): F[Unit] =
doLogging(isTraceEnabled, sl.trace, msg, ctx)

override def debug(ctx: Map[String, String])(msg: => String): F[Unit] =
doLogging(isDebugEnabled, sl.debug, msg, ctx)

override def info(ctx: Map[String, String])(msg: => String): F[Unit] =
doLogging(isInfoEnabled, sl.info, msg, ctx)

override def warn(ctx: Map[String, String])(msg: => String): F[Unit] =
doLogging(isWarnEnabled, sl.warn, msg, ctx)

override def error(ctx: Map[String, String])(msg: => String): F[Unit] =
doLogging(isErrorEnabled, sl.error, msg, ctx)

// Log message and throwable, passing context

override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] =
doLoggingThrowable(isTraceEnabled, sl.trace, t, msg, ctx)

override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] =
doLoggingThrowable(isDebugEnabled, sl.debug, t, msg, ctx)

override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] =
doLoggingThrowable(isInfoEnabled, sl.info, t, msg, ctx)

override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] =
doLoggingThrowable(isWarnEnabled, sl.warn, t, msg, ctx)

override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] =
doLoggingThrowable(isErrorEnabled, sl.error, t, msg, ctx)
}
}
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.1")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.8.0")
addSbtPlugin("com.47deg" % "sbt-microsites" % "1.3.4")
addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3")
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3")
Expand Down
Loading