jstachio / rainbowgum

Fast, Small, JDK 21+, GraalVM native friendly SLF4J logging framework
BSD 3-Clause "New" or "Revised" License
66 stars 3 forks source link

How to configure ansi color output #55

Closed cmdjulian closed 6 months ago

cmdjulian commented 6 months ago

I have the following logger:

@AutoService(RainbowGumProvider::class)
class RainbowGumProviderImpl : RainbowGumProvider {
    override fun provide(config: LogConfig): Optional<RainbowGum> = RainbowGum.builder(config)
        .route { builder ->
            builder.level(Level.INFO)
            builder.appender("console") { appender ->
                val encoder = PatternEncoderBuilder("console")
                    .pattern("%cyan(%d{YYYY-MM-dd HH:mm:ss.SSS}) [%yellow(%thread)] %highlight(%-5level) %logger{36}: %msg%n")
                    .fromProperties(config.properties())
                    .build()

                appender.encoder(encoder)
                appender.output(LogOutput.ofStandardOut())
            }
        }
        .optional()
}

This is my gradle config:

dependencies {
    ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
    implementation("com.google.auto.service:auto-service-annotations:1.1.1")

    // logging
    implementation("io.jstach.rainbowgum:rainbowgum:0.2.0")
    implementation("io.jstach.rainbowgum:rainbowgum-core:0.2.0")
    implementation("io.jstach.rainbowgum:rainbowgum-pattern:0.2.0")
    implementation("io.jstach.rainbowgum:rainbowgum-jansi:0.2.0")
}

As you can see in my pattern I have configured colored output. Regardless of that the output is just white. Is there something I do wrong?

cmdjulian commented 6 months ago

Okay it seems like when compiling and using the jar as is, it works. Just in the IntelliJ terminal no colored output is provided, any idea here?

agentgt commented 6 months ago

JAnsi detects that it is not compatible. In the long run because of the draft security JEP we may drop JAnsi.

An easy fix that I can put in is to detect intellij via system properties or similar and turn of JAnsi.

agentgt commented 6 months ago

Anyway I'm rolling out a new release very soon of 0.3.0. Hopefully the builder API didn't change too much that it breaks for you.

Once I hit 0.5.0 sometime this month the API will be more stable and by July hopefully 1.0.0

agentgt commented 6 months ago

Also see this bug: https://github.com/fusesource/jansi/issues/31

The easiest solution for now is to detect a system property that intellij hopefully sets.

We already do this for surefire because it has issues as well:

    private boolean installJansi(LogConfig config) {
        if (!System.getProperty("surefire.real.class.path", "").isEmpty()) {
            return false;
        }
        var disableProperty = Boolean.parseBoolean(config.properties().valueOrNull(JANSI_DISABLE));
        if (disableProperty) {
            return true;
        }
        return true;
    }

However it may not set any properties when it forks the processes and or they are not official so it would be kind of a hack.

agentgt commented 6 months ago

@cmdjulian I can't load up intellij at the moment (yes I'm using Eclipse right now for null analysis but I do use Intellij from time to time).

In your application at boot up can you dump System.getProperties() to stdout. Don't paste it here but instead look for properties that look intellij specific like jetbrains.loader or something. Maybe System.getenv as well (absolutely do not paste the results of that here other than the jetbrains/intellij looking vars).

It may not set any but I'm hoping it might.

cmdjulian commented 6 months ago

Also see this bug: fusesource/jansi#31

Oh, that's unfortunate 😞

Anyway I'm rolling out a new release very soon of 0.3.0. Hopefully the builder API didn't change too much that it breaks for you.

Once I hit 0.5.0 sometime this month the API will be more stable and by July hopefully 1.0.0

Cool, thanks for proving the lib! πŸ˜„ I integrated it into one of my OSS projects whats-the-los which is using GraalVM with native-image. Not a real benchmark, but I noticed using you lib in favor of using logback shaves of roughly 1ms from staring time in native image (considering a start time of 32ms its somewhat significant) and shrinks the resulting binary from roughly 90MB to 80MB (no stdlib xml stuff included anymore).

In your application at boot up can you dump System.getProperties() to stdout. Don't paste it here but instead look for properties that look intellij specific like jetbrains.loader or something. Maybe System.getenv as well (absolutely do not paste the results of that here other than the jetbrains/intellij looking vars).

I checked but unfortunately there is no special property nor a certain env variable differentiating my default gnome terminal and the intellij one 😞

PS: one small suggestion though, you may have noticed that I did include not just core, but also pattern explicitly, as having it pulled transitively by gradle does not make it accessible as implementation even if io.jstach.rainbowgum:rainbowgum is included as such. I think to have a better developer ergonomic, when including io.jstach.rainbowgum:rainbowgum, the transitive dependencies should be pulled in with the same scope so that having implementation("io.jstach.rainbowgum:rainbowgum:0.2.0") is enough. When just wanting to use the defaults, having runtimeOnly("io.jstach.rainbowgum:rainbowgum:0.2.0") should be sufficient.

agentgt commented 6 months ago

PS: one small suggestion though, you may have noticed that I did include not just core, but also pattern explicitly, as having it pulled transitively by gradle does not make it accessible as implementation even if io.jstach.rainbowgum:rainbowgum is included as such. I think to have a better developer ergonomic, when including io.jstach.rainbowgum:rainbowgum, the transitive dependencies should be pulled in with the same scope so that having implementation("io.jstach.rainbowgum:rainbowgum:0.2.0") is enough. When just wanting to use the defaults, having runtimeOnly("io.jstach.rainbowgum:rainbowgum:0.2.0") should be sufficient.

I think unless I misunderstood that it is by design.

io.jstach.rainbowgum:rainbowgum is not a real jar but just a fake bundle to include transitive runtime dependencies. That is even core should be pulled in transitive as runtime and apparently it is not ... so that is a bug.

This is in the doc: https://jstach.io/rainbowgum/#installation

See the text:

If you plan on configuring Rainbow Gum programmatically you will need to make a module and create a service loader registration. In that case you will want the dependency like:

The idea is basically if you are using builders to create a custom rainbow gum you are a developer that knows how to manage deps but I agree the doc and or ergonomics could be better.

Because what you really want to do is (and I'm going to use mavens of scopes as gradle can do all of it and more):

mygroupId:my-rainbowgum-config.jar:

<dependencies>
    <dependency>
        <groupId>io.jstach.rainbowgum</groupId>
        <artifactId>rainbowgum-core</artifactId>
        <version>${rainbowgum.version}</version>
        <scope>compile</scope>
        <optional>true</optional> <!-- this is optional pun intended -->
    </dependency>
</dependencies>

Then in your application you do:

<dependencies>
    <dependency>
        <groupId>io.jstach.rainbowgum</groupId>
        <artifactId>rainbowgum</artifactId>
        <version>${rainbowgum.version}</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>mygroupId</groupId>
        <artifactId>my-rainbowgum-config</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

The above is the modular way to do it but some folks probably would prefer everything in one application and thus why I wrote the doc as <scope>compile</scope> instead of explaining the complicated scenario of managing runtime and compile.

EDIT I see what your saying. You are saying if you do:

    <dependency>
        <groupId>io.jstach.rainbowgum</groupId>
        <artifactId>rainbowgum</artifactId>
        <version>${rainbowgum.version}</version>
        <scope>compile</scope>
    </dependency>

All the others should be compile aka implementation. That is a good suggestion and I don't know why I did the others as runtime.