如何配置 Tomcat Catalina 使用自定义日志记录器?

问题描述 投票:0回答:1

我正在尝试了解有关 Spring 框架的更多信息(不使用 SpringBoot),目前我已经有了一个带有嵌入式 Tomcat 服务器的基本

RestController
设置(版本
10
)。

一切正常,但我想自定义 Catalina 的

Log
对象,以便我可以执行诸如控制台日志的 JSON 序列化之类的操作。

我有 Nest.JS 背景,这非常简单。只需向 Nest 注册一个新的 Logger 即可。我希望我能做类似的事情。

显然,Catalina 在

java.util.logging
周围使用了一种包装器,它是
LogFactory
。当为此类创建单例时,它会验证是否有任何服务实现并使用
Log
接口。如果没有,那么它使用默认实现(这是我试图覆盖的)

private LogFactory() {
        FileSystems.getDefault();

        // Look via a ServiceLoader for a Log implementation that has a
        // constructor taking the String name.
        ServiceLoader<Log> logLoader = ServiceLoader.load(Log.class);
        Constructor<? extends Log> m=null;
        for (Log log: logLoader) {
            Class<? extends Log> c=log.getClass();
            try {
                m=c.getConstructor(String.class);
                break;
            }
            catch (NoSuchMethodException | SecurityException e) {
                throw new Error(e);
            }
        }
        discoveredLogConstructor=m;
    }
public Log getInstance(String name) throws LogConfigurationException {
        if (discoveredLogConstructor == null) {
            return DirectJDKLog.getInstance(name);
        }

        try {
            return discoveredLogConstructor.newInstance(name);
        } catch (ReflectiveOperationException | IllegalArgumentException e) {
            throw new LogConfigurationException(e);
        }
    }

我已经实现了这个 Log 接口(它只是一个测试类),但我不知道如何让这个

ServiceLoader
使用它。

class CatalinaLogger implements Log {
  private Logger logger;
  private String name;

  public CatalinaLogger(String name) {
    this.name = name;

    this.logger = Logger.getLogger(name);
  }

  @Override
  public boolean isDebugEnabled() {
    return true;
  }

  @Override
  public boolean isErrorEnabled() {
    return true;
  }

  @Override
  public boolean isFatalEnabled() {
    return true;
  }

  @Override
  public boolean isInfoEnabled() {
    return true;
  }

  @Override
  public boolean isTraceEnabled() {
    return true;
  }

  @Override
  public boolean isWarnEnabled() {
    return true;
  }

  @Override
  public void trace(Object message) {
    logger.log(Level.FINER, String.valueOf(message));
  }

  @Override
  public void trace(Object message, Throwable t) {
    logger.log(Level.FINER, String.valueOf(message));
  }

  @Override
  public void debug(Object message) {
    logger.log(Level.FINE, String.valueOf(message));
  }

  @Override
  public void debug(Object message, Throwable t) {
    logger.log(Level.FINE, String.valueOf(message));
  }

  @Override
  public void info(Object message) {
    logger.log(Level.INFO, String.valueOf(message));
  }

  @Override
  public void info(Object message, Throwable t) {
    logger.log(Level.INFO, String.valueOf(message));
  }

  @Override
  public void warn(Object message) {
    logger.log(Level.WARNING, String.valueOf(message));
  }

  @Override
  public void warn(Object message, Throwable t) {
    logger.log(Level.WARNING, String.valueOf(message));
  }

  @Override
  public void error(Object message) {
    logger.log(Level.SEVERE, String.valueOf(message));
  }

  @Override
  public void error(Object message, Throwable t) {
    logger.log(Level.SEVERE, String.valueOf(message));
  }

  @Override
  public void fatal(Object message) {
    logger.log(Level.SEVERE, String.valueOf(message));
  }

  @Override
  public void fatal(Object message, Throwable t) {
    logger.log(Level.SEVERE, String.valueOf(message));
  }
  
}

我对这个问题的理解正确吗?不管怎样,我该如何解决这个问题?

tomcat logging catalina
1个回答
0
投票

我找到了一种方法来完成我正在尝试的事情。基本上,Catalina 的默认

Log
实现使用
java.util.logging
提供的相同记录器。这包括为此类所做的配置,例如格式化程序。

如果Catalina检测到该包有配置文件,它不会自动自定义该类,这让我们可以自己自定义它:

class DirectJDKLog implements Log {
    // no reason to hide this - but good reasons to not hide
    public final Logger logger;

    // Alternate config reader and console format
    private static final String SIMPLE_FMT="java.util.logging.SimpleFormatter";
    private static final String FORMATTER="org.apache.juli.formatter";

    static {
        if (System.getProperty("java.util.logging.config.class") == null  &&
                System.getProperty("java.util.logging.config.file") == null) {
    ...default configurations

我无法使用自己的

Log
实现,但我可以使用
logging.properties
目录中的
${CATALINA_HOME}
文件配置自定义格式化程序:

handlers=java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level=INFO
java.util.logging.ConsoleHandler.formatter=com.springtutorial.httpconfig.LoggerFormatter

然后,这是定制东西的问题:

package com.springtutorial.httpconfig;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class LoggerFormatter extends Formatter {
  private ObjectMapper JSON = new ObjectMapper();
  private static String kubernetesEnvironment = "false";

  public LoggerFormatter() {
    String kubeEnvEnabled = System.getProperty("com.springtutorial.httpconfig.kubernetesEnvironment");

    if (kubeEnvEnabled != null) {
      kubernetesEnvironment = kubeEnvEnabled;
    }
  }

  @Override
  public String format(LogRecord record) {
    if (LoggerFormatter.kubernetesEnvironment == "false") {
      return formatForDevelopment(record);
    }

    return formatForKubernetes(record);
  }
  
  public String formatForDevelopment(LogRecord record) {
    // [{Level} | {DateTime} | {Class} -> {Method}] - {Message}
    Date eventDate = new Date(record.getMillis());
    String[] fullClassName = record.getSourceClassName().split("\\.");
    String shortClassName = fullClassName[fullClassName.length - 1];

    return "[" + record.getLevel() + " | " +
      new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(eventDate) + " | " +
      shortClassName + " -> " +
      record.getSourceMethodName() + "] - " +
      record.getMessage() + '\n';
  }

  public String formatForKubernetes(LogRecord record) {
    // {level: $level$, date: $date$, sourceClass: $class$, sourceMethod: $method$, businessData: $businessDataObj$}
    Date eventDate = new Date(record.getMillis());
    String[] fullClassName = record.getSourceClassName().split("\\.");
    String shortClassName = fullClassName[fullClassName.length - 1];
    BusinessData<?> receivedBusinessData;

    if (fullClassName[1].equals("springframework") || fullClassName[1].equals("apache")) {
      // Server is logging so we must format the businessData object by hand
      receivedBusinessData = new BusinessData<Object>(new Object() {
        @JsonProperty
        String serverInfo = record.getMessage();
      });
    } else {
      // Message was logged by the application so i will assume it was made from the application i wrote
      receivedBusinessData = (BusinessData<?>) record.getParameters()[0];
    }

    var log = new Object() {
      @JsonProperty
      String level = record.getLevel().toString();

      @JsonProperty
      String date = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(eventDate);

      @JsonProperty
      String sourceClass = shortClassName;

      @JsonProperty
      String sourceMethod = record.getSourceMethodName();

      @JsonProperty
      BusinessData<?> businessData = receivedBusinessData;
    };

    try {
      return JSON.writeValueAsString(log) + '\n';
    } catch (JsonProcessingException exp) {
      return exp.getMessage();
    }
  }
}

仍有很大的改进空间(我很想听到一些诚实的反馈,因为这里的一些事情感觉像是在编写战争罪行),但按预期工作:

从服务器(Catalina、Tomcat、Coyote 等)进行的日志记录根据所选模式(开发或集群部署模式)进行格式化:

// Development
[INFO | 11/05/2024 21:11:12 | AbstractProtocol -> init] - Initializing ProtocolHandler ["http-nio-8080"]
[INFO | 11/05/2024 21:11:12 | StandardService -> startInternal] - Starting service [Tomcat]
[INFO | 11/05/2024 21:11:12 | StandardEngine -> startInternal] - Starting Servlet engine: [Apache Tomcat/10.1.23]

// Cluster deployed
{"level":"INFO","date":"11/05/2024 21:11:51","sourceClass":"AbstractProtocol","sourceMethod":"init","businessData":{"data":{"serverInfo":"Initializing ProtocolHandler [\"http-nio-8080\"]"}}}
{"level":"INFO","date":"11/05/2024 21:11:51","sourceClass":"StandardService","sourceMethod":"startInternal","businessData":{"data":{"serverInfo":"Starting service [Tomcat]"}}}

可以使用默认的

java.util.logging.Logger
实现从应用程序进行日志记录:

// This could be wrapped in the custom Log implementation maybe...?
Logger.getLogger("com.springtutorial").logp(Level.INFO, this.getClass().getPackageName(), "onStartup", "anymsg", new BusinessData<Object>(new Object(){
  @JsonProperty
  String myCustomObjectData = "this is some custom data";
}));

结果是一样的:

{"level":"INFO","date":"11/05/2024 21:15:29","sourceClass":"springtutorial","sourceMethod":"onStartup","businessData":{"data":{"myCustomObjectData":"this is some custom data"}}}
© www.soinside.com 2019 - 2024. All rights reserved.