Как стать автором
Обновить

[Java] Logger с настройками через config.conf

Немного теории

Думаю, все те кто программирует обязательно да что-нибудь логирует. Почти любое логирование полезно как для самого разработчика, так и для конечных пользователей приложения. В частности программирования на Java используется такая библиотека как log4j. Однако в плане настроек логирования с этой библиотекой не все так гладко. Не, ну конечно кто любит форматы конфигурационных файлов отличающихся от стандартных, то ладно. По-умолчанию, библиотека использует настройки из файла log4j.properties

Вид настроек примерно может выглядеть так:

log4j.rootLogger=DEBUG, consoleAppender, fileAppender
log4j.appender.consoleAppender=org.apache.log4j.ConsoleAppender
log4j.appender.consoleAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.consoleAppender.layout.ConversionPattern=[%t] %-5p %c %x - %m%n
log4j.appender.fileAppender=org.apache.log4j.RollingFileAppender
log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.fileAppender.layout.ConversionPattern=[%t] %-5p %c %x - %m%n
log4j.appender.fileAppender.File=demoApplication.log

А может выглядеть как в xml-формате:

    <?xml version="1.0" encoding="UTF-8"?>;
    <Configuration>
      <Properties>
        <Property name="name1">value</property>
        <Property name="name2" value="value2"/>
      </Properties>
      <Filter type="type" ... />
      <Appenders>
        <Appender type="type" name="name">
          <Filter type="type" ... />
        </Appender>
        ...
      </Appenders>
      <Loggers>
        <Logger name="name1">
          <Filter type="type" ... />
        </Logger>
        ...
        <Root level="level">
          <AppenderRef ref="name"/>
        </Root>
      </Loggers>
    </Configuration>

А также в JSON-формате:

    { "configuration": { "status": "error", "name": "RoutingTest",
                         "packages": "org.apache.logging.log4j.test",
          "properties": {
            "property": { "name": "filename",
                          "value" : "target/rolling1/rollingtest-$${sd:type}.log" }
          },
        "ThresholdFilter": { "level": "debug" },
        "appenders": {
          "Console": { "name": "STDOUT",
            "PatternLayout": { "pattern": "%m%n" },
            "ThresholdFilter": { "level": "debug" }
          },
          "Routing": { "name": "Routing",
            "Routes": { "pattern": "$${sd:type}",
              "Route": [
                {
                  "RollingFile": {
                    "name": "Rolling-${sd:type}", "fileName": "${filename}",
                    "filePattern": "target/rolling1/test1-${sd:type}.%i.log.gz",
                    "PatternLayout": {"pattern": "%d %p %c{1.} [%t] %m%n"},
                    "SizeBasedTriggeringPolicy": { "size": "500" }
                  }
                },
                { "AppenderRef": "STDOUT", "key": "Audit"}
              ]
            }
          }
        },
        "loggers": {
          "logger": { "name": "EventLogger", "level": "info", "additivity": "false",
                      "AppenderRef": { "ref": "Routing" }},
          "root": { "level": "error", "AppenderRef": { "ref": "STDOUT" }}
        }
      }
    }

Каждый такой формат представляет из себя:

  1. Громоздкость

  2. Увеличение вероятности появления ошибки при конфигурировании администратором (и разработчиками)

  3. Сложность чтения

Давно принятый формат для конфигураций все же лежит в Linux (и не только). Вид его выглядит примерно так:

[section]
key1=value1
key2=value2
#какой-то комментарий

[section2]
key1=value1
key2=value2

Преимущества такого вида:

  • Аккуратность

  • Легкочитаемость

Практика

Теперь перейдем к собственно к коду для тех кто все же предпочитает конфигурационные файлы для своих программ иметь в стиле nix

Маны на сам Logger - https://logging.apache.org/log4j/2.x/

Первым делом, в своем пакете объявляете все необходимое для работы с log4j:

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.lang.Integer;
import java.lang.Boolean;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder;
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder;
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;

Потому как у меня это является отдельным классом, в котором приложение при запуске подсасывает все конфигурационные параметры, то соответственно создаете отдельный класс в пакете:

public class customLogger {
	static public class lg{
		static public Logger log;
	}
    private String loggingLevel;
    private String loggingSize_log;
    private String loggingDirectory; 
    private String loggingPattern;
    private int loggingRotation;
    
    private String parseConfigFile(String section, String param) throws Exception{
        String value = ""; //Значение для вывода
        final String contentFile = new String(Files.readAllBytes(Paths.get("/<... путь до конфига>/config.conf")), StandardCharsets.UTF_8); //файл с конфигурацией
        
        String regex = "\\["+section+"\\].*?[^#]"+param+"\\=(.*?)(\\n|$)"; //формируем правило для регулярки
        
        final Pattern patte = Pattern.compile(regex, Pattern.DOTALL | Pattern.MULTILINE);
        final Matcher match = patte.matcher(contentFile);
        
        match.find(); //инициализируем поиск
        value = match.group(1);
        
        return value;
    }
    
    public void init() throws Exception{
        
        loggingLevel = this.parseConfigFile("logging", "level");
        loggingDirectory = this.parseConfigFile("logging", "directory");
        loggingSize_log = this.parseConfigFile("logging", "size_log");
        loggingPattern = this.parseConfigFile("logging", "pattern");
        loggingRotation = Integer.parseInt(this.parseConfigFile("logging", "rotation"));
        
        Level lvl = Level.INFO; //задаем уровень логирования, подтягиваем из файла конфигурации
        switch(loggingLevel) {
        	case "info":
        		lvl = Level.INFO;
        		break;
        	case "standard":
        		lvl = Level.INFO;
        		break;
        	case "trace":
        		lvl = Level.TRACE;
        		break;
        	case "debug":
        		lvl = Level.DEBUG;
        		break;
        	case "all":
        		lvl = Level.ALL;
        		break;
        	case "none":
        		lvl = Level.OFF;
        		break;
        	default:
        		lvl = Level.ALL;
        		break;
        }
        
        ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
        builder.setStatusLevel(Level.ERROR); //уровень логирования самого Logger при инициализации
        builder.setConfigurationName("RollingBuilder"); //название конфигуратора логирования
        builder.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL).addAttribute("level", lvl)); //Пороговые фильтры
        
        AppenderComponentBuilder file = builder.newAppender("Stdout", "CONSOLE").addAttribute("target",
        	    ConsoleAppender.Target.SYSTEM_OUT);
        file.add(builder.newLayout("PatternLayout").addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable"));
        builder.add(file);
        
        LayoutComponentBuilder standard = builder.newLayout("PatternLayout").addAttribute("pattern", "%d %-5level: %msg%n%throwable");
        file.add(standard); //формат логирования (Дата, Уровень логирования, Передаваемое сообщение в логер)
        
        ComponentBuilder triggeringPolicy = builder.newComponent("Policies");
        triggeringPolicy.addComponent(builder.newComponent("CronTriggeringPolicy").addAttribute("schedule", "0 0 0 * * ?"));//периодичность создания нового лога
        triggeringPolicy.addComponent(builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", loggingSize_log));//размер лога при котором будет создан новый лог
        
        file = builder.newAppender("rolling", "RollingFile"); //инициализация блока для настройки записи в файл
        file.addAttribute("fileName", loggingDirectory); //директория+имя файла, желательное полное имя
        file.addAttribute("filePattern", loggingRotation); //директория+имя файла при ротации
        file.add(standard);
        file.addComponent(triggeringPolicy);
        builder.add(file);
        
        //Добавление логгеров, по которым будет происходить логирование.
        builder.add(builder.newLogger("PFSLogger", lvl).add(builder.newAppenderRef("rolling")).addAttribute("additivity", false));
        //Настройка рут логера, в случае если сообщения не будут определены через newLogger
        builder.add(builder.newRootLogger(lvl).add(builder.newAppenderRef("rolling")));      
        
        Configurator.initialize(builder.build());//Инициализируем весь соборанный конфиг
        
        //Получаем имя логгер, позже созданного (имя getLogger должно быть таким же как в newLogger, иначе будет происходить по newRootLogger, со своим уровнем логирования)
        lg.log = LogManager.getLogger("PFSLogger");
        
        lg.log.info("Запускается Logger...");  
               
    }
    
}

Далее, чтобы инициализировать логгер, нужно в главном пакете вызвать метод

/////Инициализируем логгер
customLogger log = new customLogger();
log.init();

В итоге, лог будет создан по директории, указанный в строке

file.addAttribute("fileName", loggingDirectory); //директория+имя файла, желательное полное имя

Ссылка на код

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.