Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Java_Промышленное программирование1.doc
Скачиваний:
173
Добавлен:
13.04.2015
Размер:
5.58 Mб
Скачать

П риложение7 журнал сообщений (logger)

В процессе функционирования сложных приложений необходимо вести журнал сообщений и ошибок, чтобы была возможность отследить время входа и выхода пользователя из системы, возникновение исключительных ситуаций и т.д. Существуют различные API регистрации сообщений и ошибок. В практическом программировании для этих целей применяется API Log4j, разработанный в рамках проекта Jakarta Apache.

API Log4j – это инструмент для формирования журнала сообщений (отладочных, информационных, сообщений об ошибках). API Log4j можно загрузить по адресу: http://logging.apache.org/log4j/. Перед использованием необходимо зарегистрировать загруженную библиотеку log4j-1.2.13.jar в приложении.

Log4j состоит из трех элементов:

  • регистрирующего (logger);

  • направляющего вывод (appender);

  • форматирующего (layout).

Таким образом logger регистрирует и направляет вывод события в пункт назначения, определяемый элементом appender, в формате, заданном элементом layout.

В стандартной библиотеке java.util.logging также существует возмож­ность журналирования событий. Однако функциональность классов этого пакета несколько уже, чем у классов проекта Log4j, поэтому профессиональные программисты предпочитают использовать последний.

Logger

Основным элементом API регистрации событий и ошибок является регистра­тор Logger, который управляет регистрацией сообщений. Вывод регистратора может быть направлен на консоль, в файл, базу данных, GUI-компонент или сокет. Это компонент приложения, принимающий и выполняющий запросы на запись в регистрационный журнал.

Каждый класс приложения может иметь свой собственный logger или быть прикреплен к общему для всего приложения. Регистраторы образуют иерархию, как и пакеты Java. Регистратор может быть создан или получен с помощью ста­тического метода getLogger(String name), где name – имя пакета. В вершине иерархии находится корневой регистратор. Он всегда существует и у него нет имени. Он может быть получен статическим методом getRootLogger().

У каждого регистратора есть уровень сообщения по возрастанию (TRACE, DEBUG, INFO, WARN, ERROR, FATAL), который управляет выводом сообщений. Для вывода сообщений конкретного уровня используются методы debug(), info(), warn(), error(), fatal(). Чтобы вывести информацию о возник­шем исключении в качестве второго параметра, в вышеперечисленные методы нужно передать объект класса, производного от Throwable. Для вывода сообщения необходимо, чтобы уровень выводимого сообщения был не ниже, чем уровень регистратора (TRACE < DEBUG < INFO < WARN < ERROR < FATAL), т. е. если уровень регистратора INFO, то вызов logger.debug(“message”) не даст никакого эффекта, т. к. DEBUG < INFO. Уровень регистратора можно указать с помощью метода setLevel(Level level), который принимает объект класса Level, содержащий одноименные константы для каждого уровня. Если уровень регистратора не указывается, то наследуется уровень от его родителя. Уровень корневого регистратора DEBUG.

Существуют следующие методы для вывода сообщений:

log(Priority priority, Object message, Throwable t) –выводит сообщения указанного уровня с информацией об исключительной ситуации t.

log(Priority priority, Object message) выводит сообщения указанного уровня.

Appender и Layout

Вывод регистратора может быть направлен в различные места назначения. Каждому из них соответствует класс, реализующий интерфейс Appender. С помощью метода addAppender(Appender newAppender) класса Logger можно добавить Apeender к регистратору. Один регистратор может иметь несколько элементов Appender. Вывод на консоль осуществляется с помощью класса ConsoleAppender. Класс FileAppender используется для вывода сообщений в файл. Для установки файла, в который будет выполняться вывод, нужно передать имя файла в конструктор FileAppender(Layout layout, String filename) или метод setFile(String file). По умолчанию любые сообщения, записанные в файл, будут добавляться к уже имеющимся. Изменить это можно с помощью конструктора FileAppender(Layout layout, String filename, boolean append) сбросив флаг append или с помощью метода setAppend(boolean append).

Кроме того, вывод в базу данных можно произвести с помощью класса JDBCAppender, в журнал событий ОС – NTEventLogAppender, на SMTP-сервер – SMTPAppender, на удаленный сервер – SocketAppender.

Любой вывод, сделанный в регистраторе, будет направлен всем его предкам. Чтобы этого избежать, в регистраторе следует установить флаг аддитивности с помощью метода setAdditivity(boolean additive). В этом случае вывод будет направлен всем его предкам вплоть до регистратора с установленным флагом аддитивности.

Вывод регистратора может иметь различный формат. Каждый формат представлен классом, производным от Layout. Все методы класса Layout пред­наз­начены только для создания подклассов. В библиотеке определены следующие:

HTMLLayout – вывод в HTML-формате;

XMLLayout – вывод в XML-формате;

SimpleLayout – вывод в простом текстовом формате.

Более информативен вывод в XML-формате.

Установить Layout для FileAppender или ConsoleAppender можно с помощью метода setLayout(Layout layout) или передать его в вышеперечисленные конструкторы этих классов.

В приведенном ниже примере производятся регистрация и вывод как обычных информационных сообщений о выполненных действиях, так и сообщений о возникающих ошибках (попытке вычисления факториала отрицательного числа).

/* пример # 1: регистратор ошибок : Demo Log.java */

package app6;

import org.apache.log4j.Logger;

import org.apache.log4j.FileAppender;

import org.apache.log4j.SimpleLayout;

import org.apache.log4j.Level;

import java.io.IOException;

public class DemoLog {

static Logger logger = Logger.getLogger(DemoLog.class);

public static void main(String[] args) {

try {

//возможна и программная настройка

factorial(9);

factorial(-3);

} catch (IllegalArgumentException e) {

//вывод сообщения уровня ERROR

logger.error("negative argument", e);

}

}

public static int factorial(int n) {

if (n < 0)

throw new IllegalArgumentException(

"argument " + n +" less then zero");

//вывод сообщения уровня DEBUG

logger.debug("Argument n is " + n);

int result = 1;

for (int i = n; i >= 1; i--)

result *= i;

//вывод сообщения уровня INFO

logger.info("Result is " + result);

return result;

}

}

При этом в корне проекта должен находиться конфигурационный файл "log4j.xml" со следующим содержимым:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>

<appender name="TxtAppender" class="org.apache.log4j.FileAppender">

<param name="File" value="log.txt" />

<layout class="org.apache.log4j.SimpleLayout"/>

</appender>

<logger name="app6">

<level value="debug" />

</logger>

<root>

<appender-ref ref="TxtAppender" />

</root>

</log4j:configuration>

Вывод регистратора "app6.DemoLog", в файл log.txt будет следующим:

DEBUG - Argument n is 9

INFO - Result is 362880

ERROR - negative argument java.lang.IllegalArgumentException: argument -3 less then zero

at app6.DemoLog.factorial(DemoLog.java:35)

at app6.DemoLog.main(DemoLog.java:27)

Возможна также и программная настройка логгирования. Тогда в код программы необходимо добавить следующее:

FileAppender appender =

new FileAppender(

new SimpleLayout(), "log.txt");

logger.addAppender(appender);

logger.setLevel(Level.DEBUG);

Для вывода на консоль и в XML необходимо добавить следующее в конфигурационный файл:

<appender name="ConsAppender" class="org.apache.log4j.ConsoleAppender">

<layout class="org.apache.log4j.SimpleLayout"/>

</appender>

<appender name="XMLAppender" class="org.apache.log4j.FileAppender">

<param name="File" value="log.xml" />

<layout class="org.apache.log4j.xml.XMLLayout"/>

</appender>

<root>

<appender-ref ref="ConsAppender" />

<appender-ref ref="XMLAppender" />

</root>

Или программно:

ConsoleAppender appender2 =

new ConsoleAppender(new SimpleLayout());

FileAppender appender3 =

new FileAppender(new XMLLayout(), "log.xml");

logger.addAppender(appender2);

logger.addAppender(appender3);

В классе Logger объявлены методы, реагирующие на соответствующие события, а именно: fine(), info(), warning(), log(), throwing() и др.

В приведенном примере запись в файл log.xml производится в зависимости от значения остатка после деления.

/* пример # 2: стандартный регистратор ошибок : StandartDemoLog.java */

package app6;

import java.io.IOException;

import java.util.logging.FileHandler;

import java.util.logging.Level

import java.util.logging.Logger;

public class StandartDemoLog {

static Logger log = Logger.getLogger("app6");

public static void main(String[] args)

throws SecurityException, IOException {

/*инициализация и назначение для вывода логов простого файла, который по умолчанию использует XMLFormatter, то есть в файле информация будет сохраняться в виде XML */

FileHandler fh = new FileHandler("log.xml");

log.addHandler(fh);

log.setLevel(Level.WARNING);//установка уровня сообщений

int arr[] = { 5, 6, 1, -4 };

for (int i = 0; i < arr.length; i++) {

int j = arr[i] % 3;

switch (j) {

case 0:

log.fine(arr[i] + "%3 = 0");

break;

case 1:

log.info(arr[i] + "%3 = 1");

break;

case 2:

log.warning(arr[i] + "%3 = 2");

break;

default:

log.severe(arr[i] + "%3 < 0");

}

}

}

}

В результате на консоль будет выведено:

17.03.2006 15:39:03 app6.DemoLog main

WARNING: 5%3 = 2

17.03.2006 15:39:03 app6.DemoLog main

INFO: 1%3 = 1

17.03.2006 15:39:03 app6.DemoLog main

SEVERE: -4%3 < 0

В файле log.xml та же информация будет сохранена в виде

<?xml version="1.0" encoding="windows-1251"

standalone="no"?>

<!DOCTYPE log SYSTEM "logger.dtd">

<log>

<record>

<date>2006-03-17T15:39:03</date>

<millis>1142602743635</millis>

<sequence>0</sequence>

<logger>app6</logger>

<level>WARNING</level>

<class>app6.DemoLog</class>

<method>main</method>

<thread>10</thread>

<message>5%3 = 2</message>

</record>

<record>

<date>2006-03-17T15:39:03</date>

<millis>1142602743736</millis>

<sequence>1</sequence>

<logger>app6</logger>

<level>INFO</level>

<class>app6.DemoLog</class>

<method>main</method>

<thread>10</thread>

<message>1%3 = 1</message>

</record>

<record>

<date>2006-03-17T15:39:03</date>

<millis>1142602743736</millis>

<sequence>2</sequence>

<logger>app6</logger>

<level>SEVERE</level>

<class>app6.DemoLog</class>

<method>main</method>

<thread>10</thread>

<message>-4%3 < 0</message>

</record>

</log>

В несложных учебных проектах проще использовать стандартную библиотеку.

JUnit

Технология JUnit предлагает сделать более тесной связь между разработкой кода и его тестированием. В Java существует возможность проверить корректность работы класса, не прибегая к пробному выводу при отладке программы.

Технология JUnit позволяет вне класса создавать тесты, при выполнении которых произойдет корректное завершение программы в результате неправильной ее работы. Кроме того, будет создано сообщение о возникшей ошибке. Если же результат работы теста не выявит ошибок, то программа продолжит её выполнение.

Тесты создаются в процессе разработки в случае, когда требуется расширить функциональность системы. Разработка завершается, когда тест пройдет успешно.

В процессе отладки тесты создаются, если в коде обнаруживается ошибка. Затем отладку следует продолжить до момента корректной отработки теста.

Компиляция и запуск

  1. Загрузить JUnit с сервера www.junit.org

  2. Распаковать загруженный архив

  3. Добавить в переменную окружения CLASSPATH CLASSPATH=%CLASSPATH%;%JUNIT_HOME%\junit.jar

  4. Для консольного режима запуск выполнить: java junit.textui.TestRunner junit.samples.AllTests

Пусть класс ChangedName манипулирует именами файлов и генерирует имя файла с определенным расширением на основании заданного имени. Тестируемый класс будет содержать метод String rename(String ext), где параметр ext – новое расширение файла. Также класс будет иметь конструктор, принимающий имя изменяемого файла.

/*пример # 3: генерация имени файла с заданным расширением:

ChangedName.java */

package app6;

public class ChangedName {

private String name;

// реализация

public ChangedName(String name){

this.name = name;

}

public String rename(String ext) {

String old = name;

int dot_pos = old.indexOf('.');

if (dot_pos > 0)

old = old.substring(0, dot_pos);

return old + "." + ext;

}

}

Далее создан тест для метода rename() с учетом того, что он должен задавать имени файла новое расширение.

/*пример # 4: тестирование метода rename() класса ChangedName:

ChangedNameTest.java */

package app6;

public class ChangedNameTest extends TestCase {

// метод-тестировщик должен называться testИмя, где Имя – это имя тестируемого метода

public void testRename(){

ChangedName changed = new ChangedName("report");

/*метод проверяет, равны ли ожидаемая и полученная строки, и если результатом будет false, то тест завершит работу приложения*/

assertEquals("report.txt", changed.rename("txt"));

}

}

/* пример # 5: класс, который проверяет, является ли одно число делителем другого: Devisor.java*/

package app6;

public class Devisor {

public boolean isDevisor(int num1,int num2){

if((num2!=0)&&(num1%num2==0))return true; //1

// if(num1%num2==0) return true; // 2

else return false;

}

}

Ниже реализован тест, который проверят корректность работы метода isDevisor().

/* пример # 6: тестирование метода isDevisor() класса Devisor:

DevisorTest.java*/

package app6;

import junit.framework.TestCase;

public class DevisorTest extends TestCase {

public void testIsDevisor() {

Devisor obj = new Devisor();

boolean result1 = obj.IsDevisor(2,1);

boolean result2 = obj.IsDevisor(1,0);

assertEquals(true, result1); // test1

/*test1 проверяет, действительно ли результат работы метода для чисел 2 и 1 равен true. Если это верно, то выполнится следующий тест, если нет, то приложение завершит работу, а тестировщик сообщит об ошибке.*/

assertEquals(false, result2);// test2

}

}

В данном случае программа верна. Если же закомментировать строку 1 и убрать комментарий со строки 2 в классе Devisor, то test1 выполнится корректно, а test2 завершит работу приложения и выдаст сообщение об оши- бке из-за генерации при работе метода необработанного исключения ArithmeticException.

/*пример # 7: класс позволяет считывать информацию из заданного файла и преобразовывать её в строку: ReadFile.java*/

package app6;

import java.io.FileReader;

import java.io.IOException;

public class ReadFile {

public String fileIntoString(String st) {

String str = "";

try {

FileReader stream = new FileReader(st);

int s;

while ((s = stream.read()) != -1) {

str = str + s;

}

System.out.println(str);

stream.close();

} catch (IOException e) {

System.err.println(e);

}

return str;

}

}

Тест, проверяющий корректную работу этого класса:

/* пример # 8: тестирование класса ReadFile: ReadFileTest.java*/

package app609;

import junit.framework.TestCase;

public class ReadFileTest extends TestCase {

public void testFileIntoString() {

ReadFile obj = new ReadFile();

String st =

obj.fileIntoString("D:\\temp\\test.txt");

assertFalse("".equals(st));

/*если файл не существует или не пуст, то программа завершит работу с сообщением об ошибке*/

}

}

В простом пользовательском классе StringConCat реализован метод concat(String s1, String s2), который объединяет две строки.

/*пример # 9: тестируемый класс: StringConCat.java*/

package app609;

public class StringConCat {

// реализация

public String concat(String st1, String st2) {

String str = st1 + st2;

return str;

}

}

Тест, проверяющий корректность работы этого метода:

/* пример # 10: тестирование метода concat() класса StringConCat:

StringConCatTest.java*/

package app6;

import junit.framework.TestCase;

public class StringConCatTest extends TestCase {

public void testConcat(){

StringConCat obj = new StringConCat();

String st = obj.concat("Java", "2");

assertEquals("Java2", st);

}

}

Последняя версия JUnit полностью основана на аннотациях и в явном виде не использует тип TestCase.