diff --git a/NetworkTablesDesktopClient/gradle/wrapper/gradle-wrapper.jar b/NetworkTablesDesktopClient/gradle/wrapper/gradle-wrapper.jar index 41d9927..7454180 100644 Binary files a/NetworkTablesDesktopClient/gradle/wrapper/gradle-wrapper.jar and b/NetworkTablesDesktopClient/gradle/wrapper/gradle-wrapper.jar differ diff --git a/simgui-ds.json b/simgui-ds.json deleted file mode 100644 index b16ea5c..0000000 --- a/simgui-ds.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "keyboardJoysticks": [ - { - "axisConfig": [ - { - "decKey": 65, - "incKey": 68 - }, - { - "decKey": 87, - "incKey": 83 - }, - { - "decKey": 69, - "decayRate": 0.0, - "incKey": 82, - "keyRate": 0.009999999776482582 - } - ], - "axisCount": 3, - "buttonCount": 4, - "buttonKeys": [ - 90, - 88, - 67, - 86 - ], - "povConfig": [ - { - "key0": 328, - "key135": 323, - "key180": 322, - "key225": 321, - "key270": 324, - "key315": 327, - "key45": 329, - "key90": 326 - } - ], - "povCount": 1 - }, - { - "axisConfig": [ - { - "decKey": 74, - "incKey": 76 - }, - { - "decKey": 73, - "incKey": 75 - } - ], - "axisCount": 2, - "buttonCount": 4, - "buttonKeys": [ - 77, - 44, - 46, - 47 - ], - "povCount": 0 - }, - { - "axisConfig": [ - { - "decKey": 263, - "incKey": 262 - }, - { - "decKey": 265, - "incKey": 264 - } - ], - "axisCount": 2, - "buttonCount": 6, - "buttonKeys": [ - 260, - 268, - 266, - 261, - 269, - 267 - ], - "povCount": 0 - }, - { - "axisCount": 0, - "buttonCount": 0, - "povCount": 0 - } - ], - "robotJoysticks": [ - { - "useGamepad": true - }, - { - "guid": "78696e70757401000000000000000000", - "useGamepad": true - } - ] -} diff --git a/simgui.json b/simgui.json deleted file mode 100644 index ddf1e31..0000000 --- a/simgui.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "HALProvider": { - "Other Devices": { - "SPARK MAX [5]": { - "header": { - "open": true - } - }, - "SPARK MAX [6]": { - "header": { - "open": true - } - }, - "Talon FX[9]/Integrated Sensor": { - "header": { - "open": true - } - } - } - }, - "NTProvider": { - "types": { - "/FMSInfo": "FMSInfo", - "/LiveWindow/BoomBoom": "Subsystem", - "/LiveWindow/Hood": "Subsystem", - "/LiveWindow/Intake": "Subsystem", - "/LiveWindow/LED": "Subsystem", - "/LiveWindow/Serializer": "Subsystem", - "/LiveWindow/Storage": "Subsystem", - "/LiveWindow/SwerveDrive": "Subsystem", - "/LiveWindow/SwerveModule": "Subsystem", - "/LiveWindow/Turret": "Subsystem", - "/LiveWindow/Ungrouped/DigitalInput[27]": "Digital Input", - "/LiveWindow/Ungrouped/DigitalInput[28]": "Digital Input", - "/LiveWindow/Ungrouped/DigitalInput[29]": "Digital Input", - "/LiveWindow/Ungrouped/DigitalInput[3]": "Digital Input", - "/LiveWindow/Ungrouped/PIDController[1]": "PIDController", - "/LiveWindow/Ungrouped/PIDController[2]": "PIDController", - "/LiveWindow/Ungrouped/PIDController[3]": "PIDController", - "/LiveWindow/Ungrouped/Scheduler": "Scheduler", - "/LiveWindow/Ungrouped/Spark[0]": "Motor Controller", - "/LiveWindow/Ungrouped/Talon FX [14]": "Motor Controller", - "/LiveWindow/Ungrouped/Talon FX [15]": "Motor Controller", - "/LiveWindow/Ungrouped/Talon FX [21]": "Motor Controller", - "/LiveWindow/Ungrouped/Talon FX [22]": "Motor Controller", - "/LiveWindow/Ungrouped/Talon FX [23]": "Motor Controller", - "/LiveWindow/Ungrouped/Talon FX [24]": "Motor Controller", - "/LiveWindow/Ungrouped/Talon FX [2]": "Motor Controller", - "/LiveWindow/Ungrouped/Talon FX [3]": "Motor Controller", - "/LiveWindow/Ungrouped/Talon FX [4]": "Motor Controller", - "/LiveWindow/Ungrouped/Talon FX [5]": "Motor Controller", - "/LiveWindow/Ungrouped/Talon FX [6]": "Motor Controller", - "/LiveWindow/Ungrouped/Talon FX [7]": "Motor Controller", - "/LiveWindow/Ungrouped/Talon FX [8]": "Motor Controller", - "/LiveWindow/Ungrouped/Talon FX [9]": "Motor Controller", - "/LiveWindow/Vision": "Subsystem", - "/LiveWindow/VisionOdometry": "Subsystem", - "/SmartDashboard/Field": "Field2d", - "/SmartDashboard/JVM Memory": "Command", - "/SmartDashboard/Odometry Chooser": "String Chooser", - "/SmartDashboard/Scheduler": "Scheduler", - "/SmartDashboard/SendableChooser[0]": "String Chooser", - "/SmartDashboard/Usable Deploy Space": "Command" - }, - "windows": { - "/LiveWindow/SwerveDrive": { - "window": { - "visible": true - } - }, - "/SmartDashboard/Scheduler": { - "window": { - "visible": true - } - } - } - }, - "NetworkTables": { - "SmartDashboard": { - "Odometry Chooser": { - "open": true - }, - "SendableChooser[0]": { - "open": true - }, - "open": true - } - } -} diff --git a/src/main/java/frc4388/robot/RobotContainer.java b/src/main/java/frc4388/robot/RobotContainer.java index e55e50c..3ce7eca 100644 --- a/src/main/java/frc4388/robot/RobotContainer.java +++ b/src/main/java/frc4388/robot/RobotContainer.java @@ -18,6 +18,7 @@ import edu.wpi.first.wpilibj.XboxController; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; import edu.wpi.first.wpilibj2.command.Command; import edu.wpi.first.wpilibj2.command.InstantCommand; +import edu.wpi.first.wpilibj2.command.PrintCommand; import edu.wpi.first.wpilibj2.command.RunCommand; import edu.wpi.first.wpilibj2.command.SequentialCommandGroup; import edu.wpi.first.wpilibj2.command.button.JoystickButton; @@ -71,42 +72,33 @@ public class RobotContainer { configureButtonBindings(); /* Default Commands */ - // Swerve Drive with Input + // Swerve Drive with Input m_robotSwerveDrive.setDefaultCommand( new RunCommand(() -> m_robotSwerveDrive.driveWithInput( getDriverController().getLeftX(), getDriverController().getLeftY(), - //getDriverController().getRightX(), getDriverController().getRightX(), - getDriverController().getRightY(), + getDriverController().getRightY(), true), - m_robotSwerveDrive).withName("Swerve driveWithInput defaultCommand")); - // Intake with Triggers + m_robotSwerveDrive)); + // Intake with Triggers m_robotIntake.setDefaultCommand( new RunCommand(() -> m_robotIntake.runWithTriggers( getOperatorController().getLeftTriggerAxis(), getOperatorController().getRightTriggerAxis()), - m_robotIntake).withName("Intake runWithTriggers defaultCommand")); - // Storage Management - /*m_robotStorage.setDefaultCommand( - new RunCommand(() -> m_robotStorage.manageStorage(), - m_robotStorage).withName("Storage manageStorage defaultCommand"));*/ - // Serializer Management - // m_robotSerializer.setDefaultCommand( - // new RunCommand(() -> m_robotSerializer.setSerializer(0.8),//m_robotSerializer.setSerializerStateWithBeam(), - // m_robotSerializer).withName("Serializer setSerializerStateWithBeam defaultCommand")); - // Turret Manual - m_robotTurret.setDefaultCommand( - new RunCommand(() -> m_robotTurret.runTurretWithInput(getOperatorController().getLeftX()), - m_robotTurret).withName("Turret runTurretWithInput defaultCommand")); - m_robotHood.setDefaultCommand( - new RunCommand(() -> m_robotHood.runHood(getOperatorController().getRightY() * 0.1), m_robotHood)); - // m_robotTurret.setDefaultCommand( - // new AimToCenter(m_robotTurret, m_robotSwerveDrive, m_robotVisionOdometry)); + m_robotIntake)); + // Storage Management + // m_robotStorage.setDefaultCommand(new RunCommand(() -> m_robotStorage.manageStorage(), m_robotStorage).withName("Storage manageStorage defaultCommand")); + // Serializer Management + // m_robotSerializer.setDefaultCommand(new RunCommand(() -> m_robotSerializer.setSerializer(0.8),//m_robotSerializer.setSerializerStateWithBeam(), m_robotSerializer).withName("Serializer setSerializerStateWithBeam defaultCommand")); + // Turret Manual + m_robotTurret.setDefaultCommand(new RunCommand(() -> m_robotTurret.runTurretWithInput(getOperatorController().getLeftX()), m_robotTurret)); + m_robotHood.setDefaultCommand(new RunCommand(() -> m_robotHood.runHood(getOperatorController().getRightY() * 0.1), m_robotHood)); + // m_robotTurret.setDefaultCommand(new AimToCenter(m_robotTurret, m_robotSwerveDrive, m_robotVisionOdometry)); // continually sends updates to the Blinkin LED controller to keep the lights on - // m_robotLED.setDefaultCommand(new RunCommand(m_robotLED::updateLED, m_robotLED).withName("LED update defaultCommand")); + // Creates a button on the SmartDashboard that will record the path of the robot. SmartDashboard.putData("Path Recording", m_pathChooser); } @@ -118,6 +110,7 @@ public class RobotContainer { * passing it to a {@link edu.wpi.first.wpilibj2.command.button.JoystickButton}. */ private void configureButtonBindings() { + // Iterate over all Xbox controller buttons. for (XboxController.Button binding : XboxController.Button.values()) { /* ------------------------------------ Driver ------------------------------------ */ JoystickButton button = new JoystickButton(getDriverController(), binding.value); @@ -126,13 +119,13 @@ public class RobotContainer { else if (binding == XboxController.Button.kRightBumper) /* Right Bumper > Shift Up */ button.whenPressed(() -> m_robotSwerveDrive.highSpeed(true)); else if (binding == XboxController.Button.kLeftStick) - /* Left Stick > Unbound */ {/* No Commands */} + /* Left Stick > Unbound */ button.whenPressed(new PrintCommand("Unbound")); else if (binding == XboxController.Button.kRightStick) - /* Right Stick > Unbound */ {/* No Commands */} + /* Right Stick > Unbound */ button.whenPressed(new PrintCommand("Unbound")); else if (binding == XboxController.Button.kA) - /* A > Unbound */ {/* No Commands */} + /* A > Unbound */ button.whenPressed(new PrintCommand("Unbound")); else if (binding == XboxController.Button.kB) - /* B > Unbound */ {/* No Commands */} + /* B > Unbound */ button.whenPressed(new PrintCommand("Unbound")); else if (binding == XboxController.Button.kX) /* X > TEMP */ button.whenPressed(() -> { m_robotMap.leftFront.reset(); @@ -141,45 +134,37 @@ public class RobotContainer { m_robotMap.rightBack.reset(); }); else if (binding == XboxController.Button.kY) - /* Y > Unbound */ {/* No Commands */} + /* Y > Unbound */ button.whenPressed(new PrintCommand("Unbound")); else if (binding == XboxController.Button.kBack) - /* Start > Reset Odometry */ button.whenPressed(() -> resetOdometry(new Pose2d(0, 0, new Rotation2d(0)))); + /* Back > Reset Odometry */ button.whenPressed(() -> resetOdometry(new Pose2d(0, 0, new Rotation2d(0)))); else if (binding == XboxController.Button.kStart) /* Start > Reset Gyro */ button.whenPressed(m_robotSwerveDrive::resetGyro); /* ------------------------------------ Operator ------------------------------------ */ button = new JoystickButton(getDriverController(), binding.value); if (binding == XboxController.Button.kLeftBumper) - /* Left Bumper > Storage Out */ button - .whileHeld(() -> m_robotStorage.runStorage(-StorageConstants.STORAGE_SPEED), m_robotStorage) - .whenReleased(() -> m_robotStorage.runStorage(0.0), m_robotStorage); + /* Left Bumper > Storage Out */ button.whileHeld(() -> m_robotStorage.runStorage(-StorageConstants.STORAGE_SPEED), m_robotStorage) + .whenReleased(() -> m_robotStorage.runStorage(0.0), m_robotStorage); else if (binding == XboxController.Button.kRightBumper) - /* Right Bumper > Storage In */ button - .whileHeld(() -> m_robotStorage.runStorage(StorageConstants.STORAGE_SPEED), m_robotStorage) - .whenReleased(() -> m_robotStorage.runStorage(0.0), m_robotStorage); + /* Right Bumper > Storage In */ button.whileHeld(() -> m_robotStorage.runStorage(StorageConstants.STORAGE_SPEED), m_robotStorage) + .whenReleased(() -> m_robotStorage.runStorage(0.0), m_robotStorage); else if (binding == XboxController.Button.kLeftStick) - /* Left Stick > Unbound */ {/* No Commands */} + /* Left Stick > Unbound */ button.whenPressed(new PrintCommand("Unbound")); else if (binding == XboxController.Button.kRightStick) - /* Right Stick > Unbound */ {/* No Commands */} + /* Right Stick > Unbound */ button.whenPressed(new PrintCommand("Unbound")); else if (binding == XboxController.Button.kA) - // /* A > Shoot with Odo */ // button.whenPressed(new Shoot(m_robotSwerveDrive, m_robotBoomBoom, m_robotTurret, m_robotHood)) - /* A > Unbound */ {/* No Commands */} + /* A > Unbound */ button.whenPressed(new PrintCommand("Unbound")); else if (binding == XboxController.Button.kB) - // /* B > Shoot with Lime */ // button.whenPressed(new TrackTarget(m_robotTurret, m_robotBoomBoom, m_robotHood, m_robotSwerveDrive, m_robotVisionOdometry)) - /* B > Reset Hood*/ - button.whenPressed(new InstantCommand(() -> m_robotHood.m_angleEncoder.setPosition(0))); + /* B > Reset Hood*/ button.whenPressed(new InstantCommand(() -> m_robotHood.m_angleEncoder.setPosition(0))); else if (binding == XboxController.Button.kX) - // /* X > Extend Intake */ // button.whenPressed(() -> m_robotIntake.runExtender(true)) - /* X > Run Shooter */ button - .whileHeld(() -> m_robotBoomBoom.runDrumShooter(0.3)) - .whenReleased(() -> m_robotBoomBoom.runDrumShooter(0.0)); + /* X > Run Shooter */ button.whileHeld(() -> m_robotBoomBoom.runDrumShooter(0.3)) + .whenReleased(() -> m_robotBoomBoom.runDrumShooter(0.0)); else if (binding == XboxController.Button.kY) - // /* Y > Retract Intake */ // operatorButton.whenPressed(() -> m_robotIntake.runExtender(false)) - /* Y > Unbound */ {/* No Commands */} + /* Y > Unbound */ button.whenPressed(new PrintCommand("Unbound")); else if (binding == XboxController.Button.kBack) - /* Back > Unbound */ {/* No Commands */} + /* Back > Unbound */ button.whenPressed(new PrintCommand("Unbound")); else if (binding == XboxController.Button.kStart) - /* Start > Unbound */ {/* No Commands */} + /* Start > Unbound */ button.whenPressed(new PrintCommand("Unbound")); } } @@ -215,6 +200,10 @@ public class RobotContainer { return m_driverXbox; } + public XboxController getOperatorController() { + return m_operatorXbox; + } + /** * Get odometry. * @@ -232,8 +221,4 @@ public class RobotContainer { public void resetOdometry(Pose2d pose) { m_robotSwerveDrive.resetOdometry(pose); } - - public XboxController getOperatorController() { - return m_operatorXbox; - } } diff --git a/src/main/java/frc4388/utility/AnsiLogging.java b/src/main/java/frc4388/utility/AnsiLogging.java index 57a1d90..ce8df45 100644 --- a/src/main/java/frc4388/utility/AnsiLogging.java +++ b/src/main/java/frc4388/utility/AnsiLogging.java @@ -2,8 +2,8 @@ package frc4388.utility; import static org.fusesource.jansi.Ansi.ansi; +import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.OutputStream; import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringWriter; @@ -11,78 +11,64 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Map; import java.util.Optional; -import java.util.logging.ConsoleHandler; import java.util.logging.Formatter; +import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.LogRecord; import java.util.logging.Logger; +import java.util.logging.StreamHandler; -import org.fusesource.jansi.Ansi; import org.fusesource.jansi.Ansi.Attribute; import org.fusesource.jansi.Ansi.Color; import org.fusesource.jansi.AnsiConsole; +import org.fusesource.jansi.AnsiPrintStream; + +public class AnsiLogging { + private static final AnsiPrintStream ANSI_CONSOLE_STREAM = AnsiConsole.out(); + private static final Level LEVEL = Level.ALL; -public class AnsiLogging extends ConsoleHandler { public static void systemInstall() { try { // Configure java.util.logging.Logger to output additional colored information. LogManager.getLogManager().updateConfiguration(key -> (o, n) -> { switch (key) { case ".level": - return Level.ALL.getName(); + return LEVEL.getName(); case "handlers": - return AnsiColorConsoleHandler.class.getName(); + return LoggingAnsiConsoleHandler.class.getName(); default: return n; } }); - // Replace standard output streams with org.fusesource.jansi.AnsiPrintStreams. - AnsiConsole.systemInstall(); - // Replace standard output stream with java.util.logging.Logger. - System.setOut(printStreamLogger(Logger.getGlobal(), Level.INFO)); - // Replace standard error output stream with java.util.logging.Logger. - System.setErr(printStreamLogger(Logger.getGlobal(), Level.SEVERE)); + // Set the console to process ANSI escape codes. + ANSI_CONSOLE_STREAM.install(); + // Sends standard output stream messages through a logger. + System.setOut(printStreamLogger(Logger.getGlobal(), "out", Level.INFO)); + // Sends standard error output stream messages through a logger. + System.setErr(printStreamLogger(Logger.getGlobal(), "err", Level.SEVERE)); } catch (IOException exception) { exception.printStackTrace(AnsiConsole.sysErr()); } } /** - * This class is a ConsoleHandler that uses ANSI escape codes to colorize the output + * This class is a StreamHandler that uses ANSI escape codes to colorize the log messages */ - public static class AnsiColorConsoleHandler extends ConsoleHandler { - @Override - public void publish(LogRecord logRecord) { - AnsiConsole.err().print(getFormatter().format(logRecord)); - AnsiConsole.err().flush(); + public static class LoggingAnsiConsoleHandler extends StreamHandler { + public LoggingAnsiConsoleHandler() { + super(ANSI_CONSOLE_STREAM, new LoggingAnsiFormatter()); + setLevel(LEVEL); } - @Override - public Formatter getFormatter() { - return formatter; - } + private static class LoggingAnsiFormatter extends Formatter { + private static final ZoneId ZONE_ID = ZoneId.systemDefault(); + // Specify colors for the different message levels. + private static final Map LEVEL_COLORS = Map.of(Level.OFF.intValue(), "", Level.SEVERE.intValue(), ansi().fgBright(Color.RED).toString(), Level.WARNING.intValue(), ansi().fgBright(Color.YELLOW).toString(), Level.INFO.intValue(), ansi().fg(Color.GREEN).toString(), Level.CONFIG.intValue(), ansi().fgBright(Color.BLUE).toString(), Level.FINE.intValue(), ansi().fg(Color.CYAN).toString(), Level.FINER.intValue(), ansi().fg(Color.MAGENTA).toString(), Level.FINEST.intValue(), ansi().fgBright(Color.BLACK).toString(), Level.ALL.intValue(), ansi().fg(Color.DEFAULT).toString()); + private static final String FORMAT = ansi().a("%s").bold().a(Attribute.UNDERLINE).a("[%tb % levelColors = Map.of( - Level.OFF.intValue(), "", - Level.SEVERE.intValue(), makeMessageFormatString(ansi().fgBright(Color.RED)), - Level.WARNING.intValue(), makeMessageFormatString(ansi().fgBright(Color.YELLOW)), - Level.INFO.intValue(), makeMessageFormatString(ansi().fg(Color.GREEN)), - Level.CONFIG.intValue(), makeMessageFormatString(ansi().fgBright(Color.BLUE)), - Level.FINE.intValue(), makeMessageFormatString(ansi().fg(Color.CYAN)), - Level.FINER.intValue(), makeMessageFormatString(ansi().fg(Color.MAGENTA)), - Level.FINEST.intValue(), makeMessageFormatString(ansi().fgBright(Color.BLACK)), - Level.ALL.intValue(), makeMessageFormatString(ansi().fg(Color.DEFAULT)) - ); - - private String makeMessageFormatString(Ansi base) { - return base.bold().a(Attribute.UNDERLINE).a("[%1$tb %1$td %1$tk:%1$tM:%1$tS.%1$tL] %2$s %3$s:").boldOff().a(Attribute.UNDERLINE_OFF).a("%4$s%5$s").a(Attribute.INTENSITY_FAINT).a("%6$s").reset().a("%n").toString(); - } - - private String makeStackTraceString(Throwable throwable) { + private static String makeStackTraceString(Throwable throwable) { StringWriter stringWriter = new StringWriter(); try (PrintWriter printWriter = new PrintWriter(stringWriter)) { printWriter.println(); @@ -93,43 +79,44 @@ public class AnsiLogging extends ConsoleHandler { @Override public String format(LogRecord logRecord) { - ZonedDateTime time = ZonedDateTime.ofInstant(logRecord.getInstant(), zoneId); + ZonedDateTime time = ZonedDateTime.ofInstant(logRecord.getInstant(), ZONE_ID); // Get the logger name, source class name, and/or source method name. String source = Optional.ofNullable(logRecord.getLoggerName()).or(() -> Optional.ofNullable(logRecord.getSourceClassName())).map(s -> s + " ").orElse("") + Optional.ofNullable(logRecord.getSourceMethodName()).orElse(""); String message = formatMessage(logRecord); // Get the stack trace of the exception if it was thrown. - String throwable = Optional.ofNullable(logRecord.getThrown()).map(this::makeStackTraceString).orElse(""); + String throwable = Optional.ofNullable(logRecord.getThrown()).map(LoggingAnsiFormatter::makeStackTraceString).orElse(""); // Select the appropriate format string for the log level. - String format = levelColors.getOrDefault(logRecord.getLevel().intValue(), levelColors.get(Level.ALL.intValue())); + String color = LEVEL_COLORS.getOrDefault(logRecord.getLevel().intValue(), LEVEL_COLORS.get(Level.ALL.intValue())); + + boolean multiline = message.lines().skip(1).findAny().isPresent(); + boolean ansi = message.contains("\033"); + String prefix = (ansi ? RESET : "") + (multiline ? System.lineSeparator() : " "); + // Format the log message. - return String.format(format, time, source, logRecord.getLevel().getLocalizedName(), message.lines().count() > 1 ? System.lineSeparator() : " ", message.contains("\033") ? "\033[0m" + message : message, throwable); + return String.format(FORMAT, color, time, source, logRecord.getLevel().getLocalizedName(), prefix, message, throwable); } - }; + + @Override + public String getHead(Handler h) { + return String.format("%s%s level set to %s%s%n", LEVEL_COLORS.get(h.getLevel().intValue()), h.getClass().getSimpleName(), h.getLevel().getName(), RESET); + } + } + + @Override + public synchronized void publish(LogRecord logRecord) { + super.publish(logRecord); + flush(); + } } - /** - * Create a PrintStream that writes to the given logger at the given level - * - * @param logger The logger to use. - * @param level The level of the log message. - * @return A new PrintStream object. - */ - private static PrintStream printStreamLogger(Logger logger, Level level) { - return new PrintStream(new OutputStream() { - // This is a buffer that is used to store the characters that are written to the PrintStream. - private final StringBuilder stringBuilder = new StringBuilder(); - - /** - * If the character is a newline, flush the buffer to the logger, otherwise add the character to the - * buffer. - */ + private static PrintStream printStreamLogger(Logger logger, String source, Level level) { + return new PrintStream(new ByteArrayOutputStream() { @Override - public void write(int i) throws IOException { - if (i == '\n') { - logger.log(level, stringBuilder::toString); - stringBuilder.setLength(0); - } else stringBuilder.appendCodePoint(i); + public void flush() throws IOException { + String s = toString(); + if (!s.isBlank()) logger.logp(level, null, source, toString()); + reset(); } - }); + }, true); } } \ No newline at end of file