Skip to content

Commit

Permalink
Create MosaicLogger for output logs to another terminal or file
Browse files Browse the repository at this point in the history
Solution for #494
  • Loading branch information
EpicDima committed Jan 3, 2025
1 parent 321290f commit 3c4af6f
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ New:
- Create `mosaic-animation` library, that provides various possibilities for animating Mosaic. An analog of [androidx.compose.animation-core](https://developer.android.com/reference/kotlin/androidx/compose/animation/core/package-summary).
- Add `IntrinsicSize` and related `Modifier.width/height/requiredWidth/requiredHeight`.
- New `mosaic-testing` artifact for testing Mosaic.
- Create [MosaicLogger] that can be used for output logs to another terminal or file.

Changed:
- Rendering now occurs as fast as possible, although still only when necessary. Previously the maximum FPS was capped to 20 which could cause minor visual delays when processing events.
Expand Down
23 changes: 23 additions & 0 deletions mosaic-runtime/api/mosaic-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,29 @@ public final class com/jakewharton/mosaic/MosaicKt {
public static final fun runMosaicBlocking (Lkotlin/jvm/functions/Function2;)V
}

public final class com/jakewharton/mosaic/MosaicLogLevel : java/lang/Enum {
public static final field DEBUG Lcom/jakewharton/mosaic/MosaicLogLevel;
public static final field VERBOSE Lcom/jakewharton/mosaic/MosaicLogLevel;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lcom/jakewharton/mosaic/MosaicLogLevel;
public static fun values ()[Lcom/jakewharton/mosaic/MosaicLogLevel;
}

public abstract interface class com/jakewharton/mosaic/MosaicLogWriter {
public abstract fun close ()V
public abstract fun writeLine (Ljava/lang/String;)V
}

public final class com/jakewharton/mosaic/MosaicLogger {
public static final field $stable I
public static final field INSTANCE Lcom/jakewharton/mosaic/MosaicLogger;
public static field logLevel Lcom/jakewharton/mosaic/MosaicLogLevel;
public static field logWriter Lcom/jakewharton/mosaic/MosaicLogWriter;
public final fun d (Lkotlin/jvm/functions/Function0;)V
public final fun log (Lcom/jakewharton/mosaic/MosaicLogLevel;Lkotlin/jvm/functions/Function0;)V
public final fun v (Lkotlin/jvm/functions/Function0;)V
}

public final class com/jakewharton/mosaic/Terminal {
public static final field $stable I
public synthetic fun <init> (JLkotlin/jvm/internal/DefaultConstructorMarker;)V
Expand Down
31 changes: 31 additions & 0 deletions mosaic-runtime/api/mosaic-runtime.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ final enum class com.jakewharton.mosaic.ui/AnsiLevel : kotlin/Enum<com.jakewhart
final fun values(): kotlin/Array<com.jakewharton.mosaic.ui/AnsiLevel> // com.jakewharton.mosaic.ui/AnsiLevel.values|values#static(){}[0]
}

final enum class com.jakewharton.mosaic/MosaicLogLevel : kotlin/Enum<com.jakewharton.mosaic/MosaicLogLevel> { // com.jakewharton.mosaic/MosaicLogLevel|null[0]
enum entry DEBUG // com.jakewharton.mosaic/MosaicLogLevel.DEBUG|null[0]
enum entry VERBOSE // com.jakewharton.mosaic/MosaicLogLevel.VERBOSE|null[0]

final val entries // com.jakewharton.mosaic/MosaicLogLevel.entries|#static{}entries[0]
final fun <get-entries>(): kotlin.enums/EnumEntries<com.jakewharton.mosaic/MosaicLogLevel> // com.jakewharton.mosaic/MosaicLogLevel.entries.<get-entries>|<get-entries>#static(){}[0]

final fun valueOf(kotlin/String): com.jakewharton.mosaic/MosaicLogLevel // com.jakewharton.mosaic/MosaicLogLevel.valueOf|valueOf#static(kotlin.String){}[0]
final fun values(): kotlin/Array<com.jakewharton.mosaic/MosaicLogLevel> // com.jakewharton.mosaic/MosaicLogLevel.values|values#static(){}[0]
}

abstract fun interface com.jakewharton.mosaic.layout/MeasurePolicy { // com.jakewharton.mosaic.layout/MeasurePolicy|null[0]
abstract fun (com.jakewharton.mosaic.layout/MeasureScope).measure(kotlin.collections/List<com.jakewharton.mosaic.layout/Measurable>, com.jakewharton.mosaic.ui.unit/Constraints): com.jakewharton.mosaic.layout/MeasureResult // com.jakewharton.mosaic.layout/MeasurePolicy.measure|[email protected](kotlin.collections.List<com.jakewharton.mosaic.layout.Measurable>;com.jakewharton.mosaic.ui.unit.Constraints){}[0]
open fun maxIntrinsicHeight(kotlin.collections/List<com.jakewharton.mosaic.layout/IntrinsicMeasurable>, kotlin/Int): kotlin/Int // com.jakewharton.mosaic.layout/MeasurePolicy.maxIntrinsicHeight|maxIntrinsicHeight(kotlin.collections.List<com.jakewharton.mosaic.layout.IntrinsicMeasurable>;kotlin.Int){}[0]
Expand Down Expand Up @@ -209,6 +220,11 @@ abstract interface com.jakewharton.mosaic/Mosaic { // com.jakewharton.mosaic/Mos
abstract suspend fun awaitComplete() // com.jakewharton.mosaic/Mosaic.awaitComplete|awaitComplete(){}[0]
}

abstract interface com.jakewharton.mosaic/MosaicLogWriter { // com.jakewharton.mosaic/MosaicLogWriter|null[0]
abstract fun close() // com.jakewharton.mosaic/MosaicLogWriter.close|close(){}[0]
abstract fun writeLine(kotlin/String) // com.jakewharton.mosaic/MosaicLogWriter.writeLine|writeLine(kotlin.String){}[0]
}

abstract interface com.jakewharton.mosaic/TextCanvas { // com.jakewharton.mosaic/TextCanvas|null[0]
abstract val height // com.jakewharton.mosaic/TextCanvas.height|{}height[0]
abstract fun <get-height>(): kotlin/Int // com.jakewharton.mosaic/TextCanvas.height.<get-height>|<get-height>(){}[0]
Expand Down Expand Up @@ -625,6 +641,19 @@ final object com.jakewharton.mosaic.ui/Arrangement { // com.jakewharton.mosaic.u
}
}

final object com.jakewharton.mosaic/MosaicLogger { // com.jakewharton.mosaic/MosaicLogger|null[0]
final var logLevel // com.jakewharton.mosaic/MosaicLogger.logLevel|{}logLevel[0]
final fun <get-logLevel>(): com.jakewharton.mosaic/MosaicLogLevel // com.jakewharton.mosaic/MosaicLogger.logLevel.<get-logLevel>|<get-logLevel>(){}[0]
final fun <set-logLevel>(com.jakewharton.mosaic/MosaicLogLevel) // com.jakewharton.mosaic/MosaicLogger.logLevel.<set-logLevel>|<set-logLevel>(com.jakewharton.mosaic.MosaicLogLevel){}[0]
final var logWriter // com.jakewharton.mosaic/MosaicLogger.logWriter|{}logWriter[0]
final fun <get-logWriter>(): com.jakewharton.mosaic/MosaicLogWriter? // com.jakewharton.mosaic/MosaicLogger.logWriter.<get-logWriter>|<get-logWriter>(){}[0]
final fun <set-logWriter>(com.jakewharton.mosaic/MosaicLogWriter?) // com.jakewharton.mosaic/MosaicLogger.logWriter.<set-logWriter>|<set-logWriter>(com.jakewharton.mosaic.MosaicLogWriter?){}[0]

final inline fun d(kotlin/Function0<kotlin/String>) // com.jakewharton.mosaic/MosaicLogger.d|d(kotlin.Function0<kotlin.String>){}[0]
final inline fun log(com.jakewharton.mosaic/MosaicLogLevel, kotlin/Function0<kotlin/String>) // com.jakewharton.mosaic/MosaicLogger.log|log(com.jakewharton.mosaic.MosaicLogLevel;kotlin.Function0<kotlin.String>){}[0]
final inline fun v(kotlin/Function0<kotlin/String>) // com.jakewharton.mosaic/MosaicLogger.v|v(kotlin.Function0<kotlin.String>){}[0]
}

final const val com.jakewharton.mosaic.ui/EmptyTextStyle // com.jakewharton.mosaic.ui/EmptyTextStyle|{}EmptyTextStyle[0]
final fun <get-EmptyTextStyle>(): kotlin/Int // com.jakewharton.mosaic.ui/EmptyTextStyle.<get-EmptyTextStyle>|<get-EmptyTextStyle>(){}[0]
final const val com.jakewharton.mosaic.ui/UnspecifiedColor // com.jakewharton.mosaic.ui/UnspecifiedColor|{}UnspecifiedColor[0]
Expand Down Expand Up @@ -686,6 +715,7 @@ final val com.jakewharton.mosaic/com_jakewharton_mosaic_AnsiRendering$stableprop
final val com.jakewharton.mosaic/com_jakewharton_mosaic_DebugRendering$stableprop // com.jakewharton.mosaic/com_jakewharton_mosaic_DebugRendering$stableprop|#static{}com_jakewharton_mosaic_DebugRendering$stableprop[0]
final val com.jakewharton.mosaic/com_jakewharton_mosaic_GlobalSnapshotManager$stableprop // com.jakewharton.mosaic/com_jakewharton_mosaic_GlobalSnapshotManager$stableprop|#static{}com_jakewharton_mosaic_GlobalSnapshotManager$stableprop[0]
final val com.jakewharton.mosaic/com_jakewharton_mosaic_MosaicComposition$stableprop // com.jakewharton.mosaic/com_jakewharton_mosaic_MosaicComposition$stableprop|#static{}com_jakewharton_mosaic_MosaicComposition$stableprop[0]
final val com.jakewharton.mosaic/com_jakewharton_mosaic_MosaicLogger$stableprop // com.jakewharton.mosaic/com_jakewharton_mosaic_MosaicLogger$stableprop|#static{}com_jakewharton_mosaic_MosaicLogger$stableprop[0]
final val com.jakewharton.mosaic/com_jakewharton_mosaic_MosaicNodeApplier$stableprop // com.jakewharton.mosaic/com_jakewharton_mosaic_MosaicNodeApplier$stableprop|#static{}com_jakewharton_mosaic_MosaicNodeApplier$stableprop[0]
final val com.jakewharton.mosaic/com_jakewharton_mosaic_Terminal$stableprop // com.jakewharton.mosaic/com_jakewharton_mosaic_Terminal$stableprop|#static{}com_jakewharton_mosaic_Terminal$stableprop[0]
final val com.jakewharton.mosaic/com_jakewharton_mosaic_TextPixel$stableprop // com.jakewharton.mosaic/com_jakewharton_mosaic_TextPixel$stableprop|#static{}com_jakewharton_mosaic_TextPixel$stableprop[0]
Expand Down Expand Up @@ -793,6 +823,7 @@ final fun com.jakewharton.mosaic/com_jakewharton_mosaic_AnsiRendering$stableprop
final fun com.jakewharton.mosaic/com_jakewharton_mosaic_DebugRendering$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic/com_jakewharton_mosaic_DebugRendering$stableprop_getter|com_jakewharton_mosaic_DebugRendering$stableprop_getter(){}[0]
final fun com.jakewharton.mosaic/com_jakewharton_mosaic_GlobalSnapshotManager$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic/com_jakewharton_mosaic_GlobalSnapshotManager$stableprop_getter|com_jakewharton_mosaic_GlobalSnapshotManager$stableprop_getter(){}[0]
final fun com.jakewharton.mosaic/com_jakewharton_mosaic_MosaicComposition$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic/com_jakewharton_mosaic_MosaicComposition$stableprop_getter|com_jakewharton_mosaic_MosaicComposition$stableprop_getter(){}[0]
final fun com.jakewharton.mosaic/com_jakewharton_mosaic_MosaicLogger$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic/com_jakewharton_mosaic_MosaicLogger$stableprop_getter|com_jakewharton_mosaic_MosaicLogger$stableprop_getter(){}[0]
final fun com.jakewharton.mosaic/com_jakewharton_mosaic_MosaicNodeApplier$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic/com_jakewharton_mosaic_MosaicNodeApplier$stableprop_getter|com_jakewharton_mosaic_MosaicNodeApplier$stableprop_getter(){}[0]
final fun com.jakewharton.mosaic/com_jakewharton_mosaic_Terminal$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic/com_jakewharton_mosaic_Terminal$stableprop_getter|com_jakewharton_mosaic_Terminal$stableprop_getter(){}[0]
final fun com.jakewharton.mosaic/com_jakewharton_mosaic_TextPixel$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic/com_jakewharton_mosaic_TextPixel$stableprop_getter|com_jakewharton_mosaic_TextPixel$stableprop_getter(){}[0]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.jakewharton.mosaic

import com.github.ajalt.mordant.platform.MultiplatformSystem
import kotlin.jvm.JvmField

private const val MOSAIC_LOG_PATH = "MOSAIC_LOG_PATH"
private const val MOSAIC_LOG_LEVEL = "MOSAIC_LOG_LEVEL"

public enum class MosaicLogLevel(internal val envVarValue: String) {
VERBOSE(envVarValue = "verbose"),
DEBUG(envVarValue = "debug"),
}

public object MosaicLogger {

@JvmField
@PublishedApi
internal var logWriter: MosaicLogWriter? = null

@JvmField
@PublishedApi
internal var logLevel: MosaicLogLevel = MosaicLogLevel.DEBUG

internal fun init() {
logWriter = getDefaultLogWriter()
logLevel = getDefaultLogLevel()
}

private fun getDefaultLogWriter(): MosaicLogWriter? {
val logPath = MultiplatformSystem.readEnvironmentVariable(MOSAIC_LOG_PATH)
if (logPath.isNullOrEmpty()) return null
return createDefaultLogWriter(logPath)
}

private fun getDefaultLogLevel(): MosaicLogLevel {
val logLevelValue = MultiplatformSystem.readEnvironmentVariable(MOSAIC_LOG_LEVEL)
return MosaicLogLevel.entries.find { it.envVarValue == logLevelValue }
?: MosaicLogLevel.DEBUG
}

internal fun finalize() {
logWriter?.let {
logWriter = null
it.close()
}
}

public inline fun log(level: MosaicLogLevel, message: () -> String) {
val logWriter = logWriter
if (logWriter != null && level >= logLevel) {
logWriter.writeLine(message())
}
}

public inline fun v(message: () -> String): Unit = log(MosaicLogLevel.VERBOSE, message)

public inline fun d(message: () -> String): Unit = log(MosaicLogLevel.DEBUG, message)
}

@PublishedApi
internal interface MosaicLogWriter {
fun writeLine(message: String)
fun close()
}

internal expect fun createDefaultLogWriter(logPath: String): MosaicLogWriter?
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public suspend fun runMosaic(content: @Composable () -> Unit) {
}

internal suspend fun runMosaic(enterRawMode: Boolean, content: @Composable () -> Unit) {
MosaicLogger.init()
val mordantTerminal = MordantTerminal()
val rendering = createRendering(mordantTerminal.terminalInfo.ansiLevel.toMosaicAnsiLevel())

Expand All @@ -83,6 +84,7 @@ internal suspend fun runMosaic(enterRawMode: Boolean, content: @Composable () ->
hook = {
platformDisplay(cursorShow)
rawMode?.close()
MosaicLogger.finalize()
},
block = {
val clock = BroadcastFrameClock()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.jakewharton.mosaic

import java.io.Writer
import java.nio.file.StandardOpenOption
import kotlin.io.path.Path
import kotlin.io.path.writer

internal actual fun createDefaultLogWriter(logPath: String): MosaicLogWriter? {
return object : MosaicLogWriter {

private val writer: Writer = Path(logPath).writer(
options = arrayOf(StandardOpenOption.WRITE, StandardOpenOption.APPEND),
)

override fun writeLine(message: String) {
writer.write(message)
writer.write("\n")
writer.flush()
}

override fun close() {
writer.close()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.jakewharton.mosaic

import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.cstr
import platform.posix.write

@OptIn(ExperimentalForeignApi::class)
internal actual inline fun write(fileDescriptor: Int, str: String) {
write(fileDescriptor, str.cstr, str.length.toULong())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.jakewharton.mosaic

import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.cstr
import platform.posix.write

@OptIn(ExperimentalForeignApi::class)
internal actual inline fun write(fileDescriptor: Int, str: String) {
write(fileDescriptor, str.cstr, str.length.toULong())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.jakewharton.mosaic

import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.cstr
import platform.posix.write

@OptIn(ExperimentalForeignApi::class)
internal actual inline fun write(fileDescriptor: Int, str: String) {
write(fileDescriptor, str.cstr, str.length.toUInt())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.jakewharton.mosaic

import platform.posix.O_APPEND
import platform.posix.O_WRONLY
import platform.posix.close
import platform.posix.open

internal actual fun createDefaultLogWriter(logPath: String): MosaicLogWriter? {
val fileDescriptor = open(logPath, O_WRONLY or O_APPEND)
if (fileDescriptor < 0) {
return null
}
return object : MosaicLogWriter {

override fun writeLine(message: String) {
write(fileDescriptor, message)
write(fileDescriptor, "\n")
}

override fun close() {
close(fileDescriptor)
}
}
}

internal expect inline fun write(fileDescriptor: Int, str: String)

0 comments on commit 3c4af6f

Please sign in to comment.