Удаление доступа к System.out в Java

Я поддерживаю приложение, которое действует как контейнер для нескольких отдельных программ. Эти программы имеют свои собственные средства регистрации, то есть все, что они регистрируют, делает со специальным файлом журнала.

Тем не менее, разработчики приложений, кажется, любят бросать System.out.printlnи e.printStackTraceзвонить по всему, что делает невозможным поддерживать чистую консоль при запуске контейнера.

Как я могу предотвратить эти приложения от загрязняющих System.outи System.err?


Замечания по реализации:

  • приложения используют Log4j для регистрации;
  • контейнер также использует консоль для ведения журнала, но он строго зарезервирован для событий и проблем жизненного цикла, поэтому мне все еще нужна консоль;
  • приложения загружаются с использованием пользовательского загрузчика классов, но проверки безопасности не применяются.

Обновление :

Простое перенаправление System.outне будет работать, так как оно перенаправляет весь вывод, поэтому что-то вроде этого не получается:

    System.setOut(new PrintStream(new OutputStream() {

        @Override
        public void write(int b) {

            throw new Error("Not on my watch you don't");

        }
    }));

    Logger logger = Logger.getLogger(Runner.class);
    logger.info("My log message");

Это должно быть успешным.

Обновление 2:

Приложения загружаются и настраиваются с использованием кода, аналогичного

App app = new UrlClassLoader(...).loadClass(className)).newInstance();
app.setLogger(loggerForClass(app));

Log4j загружается из системного загрузчика классов.

10.11.2009 20:20:31
Это не совсем ответ, но: как разработчик, я склонен делать это также, потому что проще проверять консоль, чем копаться в файлах журналов. Поэтому, когда я регистрируюсь, я сначала делаю поиск / замену: поиск System.out(замена на log.debug()), поиск System.err(замена на log.info()), поиск printStackTrace(удаление). Я даже думаю, что это может быть автоматизировано для SVN, например (не знаю, как, но я думаю, что это может быть сделано)
laura 11.11.2009 09:51:06
Хм, на системном языке, идиома была бы в том fork(), чтобы перенаправить stdout и stderr дочернего процесса, а затем exec(), но я не уверен, что эквивалентен загрузчикам классов Java. Не могли бы вы подробнее рассказать о том, как вы загружаете приложения?
thirtyseven 11.11.2009 19:40:19
Контейнер использует log4j или печатает прямо на консоль?
Fedearne 11.11.2009 20:54:36
Контейнер используетlog4j
Robert Munteanu 13.11.2009 10:50:19
@thirtyseven: см. обновление 2 в описании вопроса.
Robert Munteanu 13.11.2009 10:54:10
12 ОТВЕТОВ

Используйте терапию отвращения. Посещение «Инспекторов» назначается всякий раз, когда проверяется любой код, содержащий неприятные конструкции.

Nice cubicle you got ere, be shame if anyfing appened to it.
5
10.11.2009 20:27:10
Вау, это предложение решило и мою совершенно не связанную проблему ... спасибо! ;-)
Andrzej Doyle 11.11.2009 10:57:27
У меня также есть еще одна полезная модель: «Награда», она включает в себя красное вино, праздничные шляпы и посещение «Хора». "Halleluia!"
djna 11.11.2009 13:37:10

Хотя Java определяет стандартные System.out и System.err, они могут быть перезаписаны вашими собственными потоками. См. Http://www.devx.com/tips/Tip/5616

По сути, вы можете настроить новые потоки, которые либо направляют в журналы, либо просто позволяют данным переходить в ничто. Я предпочел бы последнее, так как это мгновенно избавило бы разработчиков от необходимости полагаться на System.out и ошибаться, поскольку все, что они там пишут, просто исчезало.

** Обновление: я просто перечитал ваши условия в вопросе и вижу, что вам все еще нужна консоль для контейнерного приложения. Это может все еще работать, если вы напишите обертку вокруг стандартного потока, чтобы вы могли проверить каждый вызов и посмотреть, поступает ли он из родительского приложения (и передать его) или дочернего приложения (и заблокировать его)

6
21.04.2010 13:56:15

Вы можете использовать System.setOut()и System.setErr()для перенаправления stdoutи stderrдля экземпляров PrintStream.

10
10.11.2009 20:27:25

Предполагая, что вы можете контролировать вывод своих контейнеров, вы можете сделать следующее:

import java.io.*;
public class SysOut {
    public static void main(String[] args) throws Exception {
            PrintStream pw = new PrintStream(new FileOutputStream("a.txt"));
            PrintStream realout = System.out;
            System.setOut(pw);
            System.out.println("junk");
            realout.print("useful");
}
}

$ java SysOut 
useful
$ cat a.txt 
junk
12
10.11.2009 20:34:44

Преобразуйте потоки System.out и System.err в специальные реализации, которые генерируют исключение RuntimeException («Использовать ведение журнала вместо System.out») каждый раз, когда пишется символ.

Если ваш контейнер важен, они быстро поймут идею :)

(Для дополнительного бонуса бросьте OutOfMemoryException вместо ;-))

1
10.11.2009 20:41:12
В этом случае приведите пример того, как отличить вывод, который вам нравится, и вывод, который вам не нравится. Разрешено ли некоторым классам делать это, а другим нет?
Thorbjørn Ravn Andersen 11.11.2009 10:57:41
Если у вас есть источник для контейнера, то почему бы не реорганизовать его, чтобы сохранить и использовать исходные потоки, ПОТОМ вы можете заменить System.out и System.err?
Thorbjørn Ravn Andersen 11.11.2009 11:02:38

То, что я сделал, это перенаправил PrintStream for System.out и System.err для регистрации общего доступа в виде регистрации уровня INFO и ERROR соответственно.

Это становится сложнее, если вы хотите, чтобы некоторые потоки могли писать на консоль, или если вы хотите, чтобы логи тоже шли на консоль, но это можно сделать.

1
10.11.2009 20:58:07
Вы можете сделать код повторно входящим. т.е. установить флаг, когда вызывается OutputStream.write. если флаг не установлен, распечатайте в регистраторах. Если он установлен, распечатайте как обычно, как это должно быть из регистратора.
Peter Lawrey 14.11.2009 16:51:40
Примечание: вы должны сбросить флаг в блоке finally. ;)
Peter Lawrey 14.11.2009 16:52:12

Мы используем трюк log4j, но ведем журнал для разделения файлов (stdout.log, stderr.log). Бесполезно смешивать их вывод с частями, которые на самом деле понимают ведение журнала ...

0
10.11.2009 21:03:51

Вы можете получить и сохранить System.out / err перед их заменой.

OutputStream out=System.getOut();  // I think the names are right
System.setOut(some predefined output stream, null won't work);
out.println("Hey, this still goes to the output");
System.out.println("Oh noes, this does not");

Я использовал это для перехвата всех System.out.println в кодовой базе и префикса каждой строки вывода с именем метода / номером строки, из которой он получен.

1
10.11.2009 21:04:01

Закройте потоки System.out и System.err.

1
10.11.2009 21:05:52

Если у вас есть безголовый механизм сборки, ant или что-то подобное, вы можете добавить CheckStyle к сборке и настроить checkstyle на сбой сборки, если он обнаружит в коде любой System.out.println или e.printStackTrace.

Если у вас нет безголовой сборки, я бы порекомендовал вам ее построить, поскольку это означает, что у вас есть повторяемые, предсказуемые сборки.

3
11.11.2009 08:55:12

System.setOut будет перенаправлять весь вывод - но PrintStream вы поставляете можете решить , как обрабатывается выход. Таким образом, я уверен, что вы могли бы предоставить такой поток, который фактически печатал бы заявления из вашего приложения.

Единственная сложность на самом деле заключается в возможности определить, что является допустимым вызовом, а что нет. Работающим, но, вероятно, очень медленным способом сделать это, было бы позвонить Thread.currentThread().getStackTrace()и посмотреть, какой код (или пакет, по крайней мере) вызывает вас (просто возвращая, если он не верен). Я бы не рекомендовал это, так как падение производительности было бы ошеломляющим, особенно при каждом чтении байта.

Лучшей идеей может быть установка флага ThreadLocal во всех ваших действительных контейнерах. Затем вы можете реализовать PrintStream примерно так:

public class ThreadValidity extends ThreadLocal<Boolean>
{
    private static final INSTANCE = new ThreadValidity();

    @Override Boolean initialValue() { return false; }
    public static ThreadValidity getInstance() { return INSTANCE; }
}

class VerifyingPrintStream extends PrintStream
{
    private boolean isValidThread()
    {
        return ThreadValidity.instance().get();
    }

    public void println(String s)
    {
        if (!isValidThread()) return;
        super.println(s);
    }

    public void println(Object o)
    {
        if (!isValidThread()) return;
        super.println(o);
    }

    // etc
}

Кроме того, если вы можете изменить printlns в коде контейнера, все становится проще. Вы можете передать все записи консоли конкретному работнику; и попросите этого работника «украсть» System.out (сохраните его в своем собственном поле и используйте его непосредственно для записи выходных данных) при установке фактического System.out для неоперативного записывающего.

3
11.11.2009 10:08:24
РЕШЕНИЕ

Ключевым моментом здесь является настройка log4j перед перенаправлением выходных потоков, например

BasicConfigurator.configure();
System.setOut(...);
System.setErr(...);

System.out.println("I fail");
Logger.getLogger(...).info("I work");
2
18.11.2009 07:58:10