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

airspec (feature): Scala Native 0.5 support (Scala 3 only) #3494

Merged
merged 16 commits into from
Apr 21, 2024
2 changes: 1 addition & 1 deletion .github/workflows/release-airspec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
env:
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
run: |
../sbt "+airspecJVM/publishSigned; +airspecJS/publishSigned"
../sbt "+airspecJVM/publishSigned; +airspecJS/publishSigned; +airspecNative/publishSigned"
working-directory: ./airspec
- name: Release to Sonatype
env:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ jobs:
- name: Scala JVM and Scala.js Test
run: ../sbt "++airspecJVM/test; ++airspecJS/test"
working-directory: ./airspec
- name: Scala Native Test
run: ../sbt "++ 3; airspecNative/test"
working-directory: ./airspec
- name: Publish Test Report
uses: mikepenz/action-junit-report@v4
if: always() # always run even if the previous step fails
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package wvlet.log
import scala.scalajs.js

/**
* Use scalajs.js.Date to foramte timestamp
* Use scalajs.js.Date to foramt timestamps
*/
object LogTimestampFormatter {
def formatTimestamp(timeMillis: Long): String = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package java.util.logging


case class Level(name: String, value: Int) extends Ordered[Level] {
override def compare(other: Level): Int = value.compare(other.value)
def intValue(): Int = value
override def toString: String = name
}

object Level:
val OFF = Level("OFF", 0)
val SEVERE = Level("SEVERE", 1000)
val WARNING = Level("WARNING", 900)
val INFO = Level("INFO", 800)
val CONFIG = Level("CONFIG", 700)
val FINE = Level("FINE", 500)
val FINER = Level("FINER", 400)
val FINEST = Level("FINEST", 300)
val ALL = Level("ALL", Integer.MIN_VALUE)

120 changes: 120 additions & 0 deletions airframe-log/.native/src/main/scala-3/java/util/logging/Logger.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package java.util.logging


abstract class Handler extends AutoCloseable:
def publish(record: LogRecord): Unit
def flush(): Unit

/**
* Implements java.util.logging.Logger interface, which is not avaialble
* in Scala Native
* @param name
*/
class Logger(parent: Option[Logger], name: String) {
private var handlers = List.empty[Handler]
private var useParentHandlers = true
private var level: Option[Level] = None

def getName(): String = name

def log(level: Level, msg: String): Unit = {
log(LogRecord(level, msg))
}

def log(record: LogRecord): Unit = {
if(isLoggable(record.getLevel())) {
if(record.getLoggerName() == null) {
record.setLoggerName(name)
}
if(parent.nonEmpty && useParentHandlers) then
getParent().log(record)
else
handlers.foreach { h => h.publish(record) }
}
}

def isLoggable(level: Level): Boolean = {
val l = getLevel()
if(level.intValue() < l.intValue()) then false else true
}

def getParent(): Logger = {
parent.getOrElse(null)
}

def getLevel(): Level = {
level.orElse(parent.map(_.getLevel())).getOrElse(Level.INFO)
}

def setLevel(newLevel: Level): Unit = {
level = Some(newLevel)
}

def resetLogLevel(): Unit = {
level = None
}

def setUseParentHandlers(useParentHandlers: Boolean): Unit = {
this.useParentHandlers = useParentHandlers
}

def addHandler(h: Handler): Unit = {
handlers = h :: handlers
}

def removeHandler(h: Handler): Unit = {
handlers = handlers.filter(_ != h)
}

def getHandlers: Array[Handler] = handlers.toArray
}

object Logger:
import scala.jdk.CollectionConverters.*
private val loggerTable = new java.util.concurrent.ConcurrentHashMap[String, Logger]().asScala
private val rootLogger = Logger(None, "")

def getLogger(name: String): Logger = {
loggerTable.get(name) match {
case Some(logger) => logger
case None =>
val logger = newLogger(name)
synchronized {
loggerTable.put(name, logger)
}
logger
}
}

private def newLogger(name: String): Logger = {
name match {
case null | "" => rootLogger
case other =>
val parentName = name.substring(0, name.lastIndexOf('.').max(0))
val parentLogger = getLogger(parentName)
Logger(Some(parentLogger), name)
}
}


abstract class Formatter:
def format(record: LogRecord): String


class LogRecord(_level: Level, msg: String) extends Serializable:
private val millis = System.currentTimeMillis()
private var loggerName = ""
private var thrown: Throwable = null

def getMessage(): String = msg
def getMillis(): Long = millis
def getLoggerName(): String = loggerName
def getLevel(): Level = _level
def getThrown(): Throwable = thrown

def setLoggerName(name: String): Unit = {
this.loggerName = name
}
def setThrown(e: Throwable): Unit = {
thrown = e
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package wvlet.log

import scalanative.posix.time.*
import scalanative.unsafe.*
import scalanative.unsigned.*
import scalanative.libc.stdio.*
import scalanative.libc.string.*

/**
* Use strftime to format timestamps in Scala Native
*/
object LogTimestampFormatter {

private def format(pattern: CString, timeMillis: Long): String = {
Zone {
val ttPtr = alloc[time_t]()
!ttPtr = (timeMillis / 1000).toSize
val tmPtr = alloc[tm]()
localtime_r(ttPtr, tmPtr)
val bufSize = 26.toUSize
val buf: Ptr[Byte] = alloc[Byte](bufSize)
strftime(buf, bufSize, pattern, tmPtr)
val ms = timeMillis % 1000

val msBuf: Ptr[Byte] = alloc[Byte](3)
sprintf(msBuf, c"%03d", ms)
strcat(buf, msBuf)

val tzBuf: Ptr[Byte] = alloc[Byte](5)
strftime(tzBuf, 5.toUSize, c"%z", tmPtr)
strcat(buf, tzBuf)
fromCString(buf)
}
}

def formatTimestamp(timeMillis: Long): String = {
format(c"%Y-%m-%d %H:%M:%S.", timeMillis)
}

def formatTimestampWithNoSpaace(timeMillis: Long): String = {
format(c"%Y-%m-%dT%H:%M:%S.", timeMillis)
}
}

This file was deleted.

24 changes: 12 additions & 12 deletions airframe-log/src/main/scala/wvlet/log/LogFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,11 @@ object LogFormatter extends AnsiColorPalette {
object TSVLogFormatter extends LogFormatter {
override def formatLog(record: LogRecord): String = {
val s = Seq.newBuilder[String]
s += formatTimestampWithNoSpaace(record.getMillis)
s += formatTimestampWithNoSpaace(record.getMillis())
s += record.level.toString
s += currentThreadName
s += record.leafLoggerName
s += record.getMessage
s += record.getMessage()

val log = s.result().mkString("\t")
record.cause match {
Expand All @@ -128,7 +128,7 @@ object LogFormatter extends AnsiColorPalette {
object SimpleLogFormatter extends LogFormatter {
override def formatLog(r: LogRecord): String = {
val log =
s"[${highlightLog(r.level, r.leafLoggerName)}] ${highlightLog(r.level, r.getMessage)}"
s"[${highlightLog(r.level, r.leafLoggerName)}] ${highlightLog(r.level, r.getMessage())}"
appendStackTrace(log, r)
}
}
Expand All @@ -140,7 +140,7 @@ object LogFormatter extends AnsiColorPalette {
override def formatLog(r: LogRecord): String = {
val logTag = highlightLog(r.level, r.level.name)
val log =
f"${withColor(Console.BLUE, formatTimestamp(r.getMillis))} ${logTag}%14s [${withColor(Console.WHITE, r.leafLoggerName)}] ${highlightLog(r.level, r.getMessage)}"
f"${withColor(Console.BLUE, formatTimestamp(r.getMillis()))} ${logTag}%14s [${withColor(Console.WHITE, r.leafLoggerName)}] ${highlightLog(r.level, r.getMessage())}"
appendStackTrace(log, r)
}
}
Expand All @@ -157,10 +157,10 @@ object LogFormatter extends AnsiColorPalette {

val logTag = highlightLog(r.level, r.level.name)
val log =
f"${withColor(Console.BLUE, formatTimestamp(r.getMillis))} ${logTag}%14s [${withColor(
f"${withColor(Console.BLUE, formatTimestamp(r.getMillis()))} ${logTag}%14s [${withColor(
Console.WHITE,
r.leafLoggerName
)}] ${highlightLog(r.level, r.getMessage)} ${loc}"
)}] ${highlightLog(r.level, r.getMessage())} ${loc}"
appendStackTrace(log, r)
}
}
Expand All @@ -174,10 +174,10 @@ object LogFormatter extends AnsiColorPalette {

val logTag = highlightLog(r.level, r.level.name)
val log =
f"${withColor(Console.BLUE, formatTimestamp(r.getMillis))} [${withColor(BRIGHT_BLUE, currentThreadName)}] ${logTag}%14s [${withColor(
f"${withColor(Console.BLUE, formatTimestamp(r.getMillis()))} [${withColor(BRIGHT_BLUE, currentThreadName)}] ${logTag}%14s [${withColor(
Console.WHITE,
r.leafLoggerName
)}] ${highlightLog(r.level, r.getMessage)} ${loc}"
)}] ${highlightLog(r.level, r.getMessage())} ${loc}"
appendStackTrace(log, r)
}
}
Expand All @@ -193,7 +193,7 @@ object LogFormatter extends AnsiColorPalette {
.getOrElse("")

val log =
f"${formatTimestamp(r.getMillis)} ${r.level.name}%5s [${r.leafLoggerName}] ${r.getMessage} ${loc}"
f"${formatTimestamp(r.getMillis())} ${r.level.name}%5s [${r.leafLoggerName}] ${r.getMessage()} ${loc}"
appendStackTrace(log, r, coloring = false)
}
}
Expand All @@ -205,11 +205,11 @@ object LogFormatter extends AnsiColorPalette {
override def formatLog(r: LogRecord): String = {
val loc =
r.source
.map(source => s" ${withColor(Console.BLUE, s"- ${r.getLoggerName}(${source.fileLoc})")}")
.map(source => s" ${withColor(Console.BLUE, s"- ${r.getLoggerName()}(${source.fileLoc})")}")
.getOrElse("")

val log =
s"[${highlightLog(r.level, r.level.name)}] ${highlightLog(r.level, r.getMessage)}$loc"
s"[${highlightLog(r.level, r.level.name)}] ${highlightLog(r.level, r.getMessage())}$loc"
appendStackTrace(log, r)
}
}
Expand All @@ -219,7 +219,7 @@ object LogFormatter extends AnsiColorPalette {
*/
object BareFormatter extends LogFormatter {
override def formatLog(r: LogRecord): String = {
val m = r.getMessage
val m = r.getMessage()
r.cause match {
case Some(ex) =>
s"${m}\n${formatStacktrace(ex)}"
Expand Down
6 changes: 3 additions & 3 deletions airframe-log/src/main/scala/wvlet/log/LogRecord.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import java.util.{logging => jl}

object LogRecord {
def apply(record: jl.LogRecord): LogRecord = {
val l = LogRecord(LogLevel(record.getLevel), None, record.getMessage, Option(record.getThrown))
l.setLoggerName(record.getLoggerName)
val l = LogRecord(LogLevel(record.getLevel()), None, record.getMessage(), Option(record.getThrown()))
l.setLoggerName(record.getLoggerName())
l
}

Expand All @@ -40,7 +40,7 @@ case class LogRecord(level: LogLevel, source: Option[LogSource], message: String
cause.foreach(setThrown(_))

def leafLoggerName: String = {
val name = getLoggerName
val name = getLoggerName()
leafLoggerNameCache.getOrElseUpdate(
name, {
name match {
Expand Down
6 changes: 3 additions & 3 deletions airframe-log/src/main/scala/wvlet/log/Logger.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ class Logger(
if (l == null) {
LogLevel.INFO
} else {
val jlLevel = l.getLevel
val jlLevel = l.getLevel()
if (jlLevel != null) {
LogLevel(jlLevel)
} else {
getLogLevelOf(l.getParent)
getLogLevelOf(l.getParent())
}
}
}
Expand All @@ -77,7 +77,7 @@ class Logger(
}

def getParent: Option[Logger] = {
Option(wrapped.getParent).map(x => Logger(x.getName))
Option(wrapped.getParent()).map(x => Logger(x.getName()))
}

def addHandler(h: jl.Handler): Unit = {
Expand Down
4 changes: 3 additions & 1 deletion airspec/.js/src/main/scala/wvlet/airspec/Compat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import scala.util.Try
/**
*/
private[airspec] object Compat extends CompatApi with LogSupport {
override def isScalaJs = true
override def isScalaJVM = false
override def isScalaJs = true
override def isScalaNative = false

override private[airspec] val executionContext: ExecutionContext =
org.scalajs.macrotaskexecutor.MacrotaskExecutor.Implicits.global
Expand Down
4 changes: 3 additions & 1 deletion airspec/.jvm/src/main/scala/wvlet/airspec/Compat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ import scala.concurrent.ExecutionContext
/**
*/
private[airspec] object Compat extends CompatApi with LogSupport {
override def isScalaJs = false
override def isScalaJVM = true
override def isScalaJs = false
override def isScalaNative = false

override private[airspec] val executionContext: ExecutionContext =
ExecutionContext.fromExecutorService(Executors.newCachedThreadPool(newDaemonThreadFactory("airspec-executor")))
Expand Down
Loading
Loading