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

Best practices for dealing with interfering log statements #214

Open
sschuberth opened this issue Aug 21, 2024 · 5 comments
Open

Best practices for dealing with interfering log statements #214

sschuberth opened this issue Aug 21, 2024 · 5 comments

Comments

@sschuberth
Copy link
Contributor

I'm wondering whether there are and hints / best practices on how to deal with log statements from third-party libraries while displaying a MultiProgressBarAnimation on the console. Log output from third-part libraries causes lines being printed to the console that mess up Mordant's rendering of progress bars etc.

As the log out is useful and should not be suppressed, would it maybe even make sense to have a Mordant-based log "appender" that can be used with common logging frameworks like Log4j or SLF4J to direct log output to a console are that Mordant "knows of" and thus does not mess with the progress bars?

@sschuberth sschuberth changed the title Best practices for dealing wioth interfering log statements Best practices for dealing with interfering log statements Aug 21, 2024
@sschuberth
Copy link
Contributor Author

I realize #99 might be a bit related, though I do not want to write to a file.

@ajalt
Copy link
Owner

ajalt commented Aug 21, 2024

I can see something like that being useful. On JVM at least, you can also redirect all of stdout through mordant: #171 (comment)

@sschuberth
Copy link
Contributor Author

Yeah, though a Mordant log appender which would allow you to redirect all log output e.g. to a Mordant table cell / panel would really be nice, I guess.

@dimabran
Copy link

dimabran commented Sep 8, 2024

A Mordant appender would be great

@mikehearn
Copy link
Contributor

I have an internal app framework that wraps Mordant and it does this. At some point I hope to be able to contribute some of this stuff upstream, but unfortunately I don't have a whole lot of time to work on it. The gist is:

class RedirectingTerminalInterface(
        private val stdOut: PrintStream,
        private val stdErr: PrintStream,
        private val delegate: TerminalInterface
    ) : TerminalInterface by delegate {
        override fun completePrintRequest(request: PrintRequest) {
            val target = if (request.stderr) stdErr else stdOut
            if (request.trailingLinebreak) {
                if (request.text.isEmpty()) {
                    target.println()
                } else {
                    target.println(request.text)
                }
            } else {
                target.print(request.text)
            }
        }

        override fun readLineOrNull(hideInput: Boolean): String? {
            if (hideInput) {
                val console = System.console()
                if (console != null) {
                    // Workaround to a bug in macOS Terminal: if we don't send anything in the prompt to readPassword, the little "key" glyph
                    // that indicates the input isn't going to be echoed doesn't display consistently. So we send the ANSI "reset" escape, which
                    // doesn't really do anything.
                    //
                    // TODO(low): Is this still required after the upgrade to Mordant 2.6?
                    return console.readPassword("\u001B[m")?.concatToString()
                }
            }
            return readlnOrNull()
        }
    }

    private class PrintStreamWrapper(private val terminal: Terminal, target: PrintStream) : PrintStream(target) {
        override fun print(obj: Any?) {
            // TODO: Accept progress report objects.
            print(obj?.toString())
        }

        override fun print(s: String?) {
            // Work around a bug in Mordant.
            if (s != null && s.endsWith('\n'))
                terminal.println(s)
            else
                terminal.print(s)
        }

        override fun print(b: Boolean) {
            terminal.print(b)
        }

        override fun print(c: Char) {
            terminal.print(c)
        }

        override fun print(i: Int) {
            terminal.print(i)
        }

        override fun print(l: Long) {
            terminal.print(l)
        }

        override fun print(f: Float) {
            terminal.print(f)
        }

        override fun print(d: Double) {
            terminal.print(d)
        }

        override fun print(s: CharArray) {
            terminal.print(s)
        }

        override fun println() {
            terminal.println()
        }

        override fun println(x: Boolean) {
            terminal.println(x)
        }

        override fun println(x: Char) {
            terminal.println(x)
        }

        override fun println(x: Int) {
            terminal.println(x)
        }

        override fun println(x: Long) {
            terminal.println(x)
        }

        override fun println(x: Float) {
            terminal.println(x)
        }

        override fun println(x: Double) {
            terminal.println(x)
        }

        override fun println(x: CharArray) {
            terminal.println(x)
        }

        override fun println(x: String?) {
            terminal.println(x)
        }
    }


            val terminal = Terminal(terminalInterface = RedirectingTerminalInterface(System.out, System.err, Terminal().terminalInterface))

            // Ensure printing to stdout still works and pushes the messages _above_ the animation.
            System.setOut(PrintStreamWrapper(terminal, System.out))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants