Skip to content

Commit e89226d

Browse files
authored
log (feature): Scala Native support (#3499)
- **log (feature): Scala Native support** - **format**
1 parent 578220f commit e89226d

File tree

11 files changed

+206
-84
lines changed

11 files changed

+206
-84
lines changed

.github/workflows/release-js.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@ jobs:
3232
env:
3333
SONATYPE_USERNAME: '${{ secrets.SONATYPE_USER }}'
3434
SONATYPE_PASSWORD: '${{ secrets.SONATYPE_PASS }}'
35-
run: SCALAJS=true ./sbt sonatypeBundleRelease
35+
run: SCALA_JS=true ./sbt sonatypeBundleRelease

.github/workflows/release-native.yml

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Release Scala Native
2+
3+
on:
4+
push:
5+
tags:
6+
- v*
7+
workflow_dispatch:
8+
9+
jobs:
10+
publish_js:
11+
name: Publish Scala Native
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
with:
16+
# Fetch all tags so that sbt-dynver can find the previous release version
17+
fetch-depth: 0
18+
- run: git fetch --tags -f
19+
- uses: actions/setup-java@v4
20+
with:
21+
distribution: 'zulu'
22+
java-version: '17'
23+
- name: Setup GPG
24+
env:
25+
PGP_SECRET: ${{ secrets.PGP_SECRET }}
26+
run: echo $PGP_SECRET | base64 --decode | gpg --import --batch --yes
27+
- name: Build for Scala Native
28+
env:
29+
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
30+
run: ./sbt publishNativeSigned
31+
- name: Release to Sonatype
32+
env:
33+
SONATYPE_USERNAME: '${{ secrets.SONATYPE_USER }}'
34+
SONATYPE_PASSWORD: '${{ secrets.SONATYPE_PASS }}'
35+
run: SCALA_NATIVE=true ./sbt sonatypeBundleRelease

.github/workflows/test.yml

+19
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,25 @@ jobs:
212212
check_name: Test Report Scala.js / Scala 3
213213
annotate_only: true
214214
detailed_summary: true
215+
test_native_3:
216+
name: Scala Native / Scala 3
217+
runs-on: ubuntu-latest
218+
steps:
219+
- uses: actions/checkout@v4
220+
- uses: actions/setup-java@v4
221+
with:
222+
distribution: 'zulu'
223+
java-version: '21'
224+
- name: Scala Native test
225+
run: JVM_OPTS=-Xmx4g ./sbt "++ 3; projectNative/test"
226+
- name: Publish Test Report
227+
uses: mikepenz/action-junit-report@v4
228+
if: always() # always run even if the previous step fails
229+
with:
230+
report_paths: '**/target/test-reports/TEST-*.xml'
231+
check_name: Test Report Scala Native / Scala 3
232+
annotate_only: true
233+
detailed_summary: true
215234
test_airspec:
216235
name: AirSpec
217236
runs-on: ubuntu-latest

airframe-log/.native/src/main/scala-3/java/util/logging/Level.scala

-20
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package java.util.logging
15+
16+
case class Level(name: String, value: Int) extends Ordered[Level] {
17+
override def compare(other: Level): Int = value.compare(other.value)
18+
def intValue(): Int = value
19+
override def toString: String = name
20+
}
21+
22+
object Level {
23+
val OFF = Level("OFF", 0)
24+
val SEVERE = Level("SEVERE", 1000)
25+
val WARNING = Level("WARNING", 900)
26+
val INFO = Level("INFO", 800)
27+
val CONFIG = Level("CONFIG", 700)
28+
val FINE = Level("FINE", 500)
29+
val FINER = Level("FINER", 400)
30+
val FINEST = Level("FINEST", 300)
31+
val ALL = Level("ALL", Integer.MIN_VALUE)
32+
}
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,84 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
114
package java.util.logging
215

3-
4-
abstract class Handler extends AutoCloseable:
16+
abstract class Handler extends AutoCloseable {
517
def publish(record: LogRecord): Unit
18+
619
def flush(): Unit
20+
}
721

822
/**
9-
* Implements java.util.logging.Logger interface, which is not avaialble
10-
* in Scala Native
11-
* @param name
12-
*/
23+
* Implements java.util.logging.Logger interface, which is not avaialble in Scala Native
24+
* @param name
25+
*/
1326
class Logger(parent: Option[Logger], name: String) {
14-
private var handlers = List.empty[Handler]
15-
private var useParentHandlers = true
27+
private var handlers = List.empty[Handler]
28+
private var useParentHandlers = true
1629
private var level: Option[Level] = None
1730

1831
def getName(): String = name
1932

20-
def log(level: Level, msg: String): Unit = {
33+
def log(level: Level, msg: String): Unit =
2134
log(LogRecord(level, msg))
22-
}
2335

2436
def log(record: LogRecord): Unit = {
25-
if(isLoggable(record.getLevel())) {
26-
if(record.getLoggerName() == null) {
27-
record.setLoggerName(name)
28-
}
29-
if(parent.nonEmpty && useParentHandlers) then
37+
if (isLoggable(record.getLevel())) {
38+
if (record.getLoggerName() == null) record.setLoggerName(name)
39+
if (parent.nonEmpty && useParentHandlers) {
3040
getParent().log(record)
31-
else
41+
} else {
3242
handlers.foreach { h => h.publish(record) }
43+
}
3344
}
3445
}
3546

3647
def isLoggable(level: Level): Boolean = {
3748
val l = getLevel()
38-
if(level.intValue() < l.intValue()) then false else true
49+
level.intValue() >= l.intValue()
3950
}
4051

41-
def getParent(): Logger = {
52+
def getParent(): Logger =
4253
parent.getOrElse(null)
43-
}
4454

45-
def getLevel(): Level = {
55+
def getLevel(): Level =
4656
level.orElse(parent.map(_.getLevel())).getOrElse(Level.INFO)
47-
}
4857

49-
def setLevel(newLevel: Level): Unit = {
50-
level = Some(newLevel)
51-
}
58+
def setLevel(newLevel: Level): Unit =
59+
level = Option(newLevel)
5260

53-
def resetLogLevel(): Unit = {
61+
def resetLogLevel(): Unit =
5462
level = None
55-
}
5663

57-
def setUseParentHandlers(useParentHandlers: Boolean): Unit = {
64+
def setUseParentHandlers(useParentHandlers: Boolean): Unit =
5865
this.useParentHandlers = useParentHandlers
59-
}
6066

61-
def addHandler(h: Handler): Unit = {
67+
def addHandler(h: Handler): Unit =
6268
handlers = h :: handlers
63-
}
6469

65-
def removeHandler(h: Handler): Unit = {
70+
def removeHandler(h: Handler): Unit =
6671
handlers = handlers.filter(_ != h)
67-
}
6872

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

72-
object Logger:
76+
object Logger {
77+
7378
import scala.jdk.CollectionConverters.*
79+
7480
private val loggerTable = new java.util.concurrent.ConcurrentHashMap[String, Logger]().asScala
75-
private val rootLogger = Logger(None, "")
81+
private val rootLogger = Logger(None, "")
7682

7783
def getLogger(name: String): Logger = {
7884
loggerTable.get(name) match {
@@ -90,31 +96,35 @@ object Logger:
9096
name match {
9197
case null | "" => rootLogger
9298
case other =>
93-
val parentName = name.substring(0, name.lastIndexOf('.').max(0))
99+
val parentName = name.substring(0, name.lastIndexOf('.').max(0))
94100
val parentLogger = getLogger(parentName)
95101
Logger(Some(parentLogger), name)
96102
}
97103
}
104+
}
98105

99-
100-
abstract class Formatter:
106+
abstract class Formatter {
101107
def format(record: LogRecord): String
108+
}
102109

103-
104-
class LogRecord(_level: Level, msg: String) extends Serializable:
105-
private val millis = System.currentTimeMillis()
106-
private var loggerName = ""
110+
class LogRecord(_level: Level, msg: String) extends Serializable {
111+
private val millis = System.currentTimeMillis()
112+
private var loggerName = ""
107113
private var thrown: Throwable = null
108114

109115
def getMessage(): String = msg
116+
110117
def getMillis(): Long = millis
118+
111119
def getLoggerName(): String = loggerName
120+
112121
def getLevel(): Level = _level
122+
113123
def getThrown(): Throwable = thrown
114124

115-
def setLoggerName(name: String): Unit = {
125+
def setLoggerName(name: String): Unit =
116126
this.loggerName = name
117-
}
118-
def setThrown(e: Throwable): Unit = {
127+
128+
def setThrown(e: Throwable): Unit =
119129
thrown = e
120-
}
130+
}

airframe-log/.native/src/main/scala-3/wvlet/log/LogEnv.scala renamed to airframe-log/.native/src/main/scala/wvlet/log/LogEnv.scala

+9-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,16 @@ private[log] object LogEnv extends LogEnvBase {
3131
* @param cl
3232
* @return
3333
*/
34-
override def getLoggerName(cl: Class[_]): String = cl.getName
34+
override def getLoggerName(cl: Class[?]): String = {
35+
var name = cl.getName
3536

37+
val pos = name.indexOf("$")
38+
if (pos > 0) {
39+
// Remove trailing $xxx
40+
name = name.substring(0, pos)
41+
}
42+
name
43+
}
3644
override def scheduleLogLevelScan: Unit = {}
3745
override def stopScheduledLogLevelScan: Unit = {}
3846

@@ -53,5 +61,4 @@ private[log] object LogEnv extends LogEnvBase {
5361
/**
5462
*/
5563
override def unregisterJMX: Unit = {}
56-
5764
}

airframe-log/.native/src/main/scala-3/wvlet/log/LogTimestampFormatter.scala renamed to airframe-log/.native/src/main/scala/wvlet/log/LogTimestampFormatter.scala

+16-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
114
package wvlet.log
215

316
import scalanative.posix.time.*
@@ -17,7 +30,7 @@ object LogTimestampFormatter {
1730
!ttPtr = (timeMillis / 1000).toSize
1831
val tmPtr = alloc[tm]()
1932
localtime_r(ttPtr, tmPtr)
20-
val bufSize = 26.toUSize
33+
val bufSize = 26.toUSize
2134
val buf: Ptr[Byte] = alloc[Byte](bufSize)
2235
strftime(buf, bufSize, pattern, tmPtr)
2336
val ms = timeMillis % 1000
@@ -33,11 +46,9 @@ object LogTimestampFormatter {
3346
}
3447
}
3548

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

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

airframe-log/src/test/scala/wvlet/log/LoggerTest.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -186,19 +186,19 @@ class LoggerTest extends Spec {
186186
}
187187

188188
test("use succinct name when used with anonymous trait") {
189-
if (LogEnv.isScalaJS) {
190-
pending("Scala.js cannot get a logger name")
189+
if (isScalaJS || isScalaNative) {
190+
pending("Scala.js/Native cannot get a logger name from anonymous trait")
191191
} else {
192192
val l = new Sample with LogSupport {
193193
self =>
194-
assert(self.logger.getName == ("wvlet.log.Sample"))
194+
self.logger.getName shouldBe "wvlet.log.Sample"
195195
}
196196
}
197197
}
198198

199199
test("Remove $ from object name") {
200200
val o = Sample
201-
assert(o.loggerName == "wvlet.log.Sample")
201+
o.loggerName shouldBe "wvlet.log.Sample"
202202
}
203203

204204
test("clear parent handlers") {

0 commit comments

Comments
 (0)