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

log (feature): Scala Native support #3499

Merged
merged 7 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/release-js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ jobs:
env:
SONATYPE_USERNAME: '${{ secrets.SONATYPE_USER }}'
SONATYPE_PASSWORD: '${{ secrets.SONATYPE_PASS }}'
run: SCALAJS=true ./sbt sonatypeBundleRelease
run: SCALA_JS=true ./sbt sonatypeBundleRelease
35 changes: 35 additions & 0 deletions .github/workflows/release-native.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Release Scala Native

on:
push:
tags:
- v*
workflow_dispatch:

jobs:
publish_js:
name: Publish Scala Native
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Fetch all tags so that sbt-dynver can find the previous release version
fetch-depth: 0
- run: git fetch --tags -f
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- name: Setup GPG
env:
PGP_SECRET: ${{ secrets.PGP_SECRET }}
run: echo $PGP_SECRET | base64 --decode | gpg --import --batch --yes
- name: Build for Scala Native
env:
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
run: ./sbt publishNativeSigned
- name: Release to Sonatype
env:
SONATYPE_USERNAME: '${{ secrets.SONATYPE_USER }}'
SONATYPE_PASSWORD: '${{ secrets.SONATYPE_PASS }}'
run: SCALA_NATIVE=true ./sbt sonatypeBundleRelease
19 changes: 19 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,25 @@ jobs:
check_name: Test Report Scala.js / Scala 3
annotate_only: true
detailed_summary: true
test_native_3:
name: Scala Native / Scala 3
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '21'
- name: Scala Native test
run: JVM_OPTS=-Xmx4g ./sbt "++ 3; projectNative/test"
- name: Publish Test Report
uses: mikepenz/action-junit-report@v4
if: always() # always run even if the previous step fails
with:
report_paths: '**/target/test-reports/TEST-*.xml'
check_name: Test Report Scala Native / Scala 3
annotate_only: true
detailed_summary: true
test_airspec:
name: AirSpec
runs-on: ubuntu-latest
Expand Down

This file was deleted.

32 changes: 32 additions & 0 deletions airframe-log/.native/src/main/scala/java/util/logging/Level.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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 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)
}
Original file line number Diff line number Diff line change
@@ -1,78 +1,84 @@
/*
* 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 java.util.logging


abstract class Handler extends AutoCloseable:
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
*/
* 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 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 = {
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
if (isLoggable(record.getLevel())) {
if (record.getLoggerName() == null) record.setLoggerName(name)
if (parent.nonEmpty && useParentHandlers) {
getParent().log(record)
else
} else {
handlers.foreach { h => h.publish(record) }
}
}
}

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

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

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

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

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

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

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

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

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

object Logger:
object Logger {

import scala.jdk.CollectionConverters.*

private val loggerTable = new java.util.concurrent.ConcurrentHashMap[String, Logger]().asScala
private val rootLogger = Logger(None, "")
private val rootLogger = Logger(None, "")

def getLogger(name: String): Logger = {
loggerTable.get(name) match {
Expand All @@ -90,31 +96,35 @@ object Logger:
name match {
case null | "" => rootLogger
case other =>
val parentName = name.substring(0, name.lastIndexOf('.').max(0))
val parentName = name.substring(0, name.lastIndexOf('.').max(0))
val parentLogger = getLogger(parentName)
Logger(Some(parentLogger), name)
}
}
}


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


class LogRecord(_level: Level, msg: String) extends Serializable:
private val millis = System.currentTimeMillis()
private var loggerName = ""
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 = {
def setLoggerName(name: String): Unit =
this.loggerName = name
}
def setThrown(e: Throwable): Unit = {

def setThrown(e: Throwable): Unit =
thrown = e
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,16 @@ private[log] object LogEnv extends LogEnvBase {
* @param cl
* @return
*/
override def getLoggerName(cl: Class[_]): String = cl.getName
override def getLoggerName(cl: Class[?]): String = {
var name = cl.getName

val pos = name.indexOf("$")
if (pos > 0) {
// Remove trailing $xxx
name = name.substring(0, pos)
}
name
}
override def scheduleLogLevelScan: Unit = {}
override def stopScheduledLogLevelScan: Unit = {}

Expand All @@ -53,5 +61,4 @@ private[log] object LogEnv extends LogEnvBase {
/**
*/
override def unregisterJMX: Unit = {}

}
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
/*
* 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 wvlet.log

import scalanative.posix.time.*
Expand All @@ -17,7 +30,7 @@ object LogTimestampFormatter {
!ttPtr = (timeMillis / 1000).toSize
val tmPtr = alloc[tm]()
localtime_r(ttPtr, tmPtr)
val bufSize = 26.toUSize
val bufSize = 26.toUSize
val buf: Ptr[Byte] = alloc[Byte](bufSize)
strftime(buf, bufSize, pattern, tmPtr)
val ms = timeMillis % 1000
Expand All @@ -33,11 +46,9 @@ object LogTimestampFormatter {
}
}

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

def formatTimestampWithNoSpaace(timeMillis: Long): String = {
def formatTimestampWithNoSpaace(timeMillis: Long): String =
format(c"%Y-%m-%dT%H:%M:%S.", timeMillis)
}
}
8 changes: 4 additions & 4 deletions airframe-log/src/test/scala/wvlet/log/LoggerTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -186,19 +186,19 @@ class LoggerTest extends Spec {
}

test("use succinct name when used with anonymous trait") {
if (LogEnv.isScalaJS) {
pending("Scala.js cannot get a logger name")
if (isScalaJS || isScalaNative) {
pending("Scala.js/Native cannot get a logger name from anonymous trait")
} else {
val l = new Sample with LogSupport {
self =>
assert(self.logger.getName == ("wvlet.log.Sample"))
self.logger.getName shouldBe "wvlet.log.Sample"
}
}
}

test("Remove $ from object name") {
val o = Sample
assert(o.loggerName == "wvlet.log.Sample")
o.loggerName shouldBe "wvlet.log.Sample"
}

test("clear parent handlers") {
Expand Down
Loading
Loading