Pull to refresh

[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); //директория+имя файла, желательное полное имя

Ссылка на код

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.