Skip to content

Commit

Permalink
airspec (feature): Scala Native 0.5 support (Scala 3 only) (#3494)
Browse files Browse the repository at this point in the history
  • Loading branch information
xerial authored Apr 21, 2024
1 parent a0d2503 commit 95cc7ab
Show file tree
Hide file tree
Showing 21 changed files with 255 additions and 62 deletions.
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

0 comments on commit 95cc7ab

Please sign in to comment.