如何在运行时更改日志级别而无需重新启动Spring Boot应用程序

62
我在PCF上部署了一个Spring Boot应用程序。我想根据环境变量记录消息。为了使运行时日志级别的更改生效,我应该怎么做,而不需要重新启动应用程序?
12个回答

61

在Spring Boot 1.5+中,可以通过http端点来更改日志级别。

添加

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

然后你可以使用

curl -X "POST" "http://localhost:8080/loggers/de.springbootbuch" \
     -H "Content-Type: application/json; charset=utf-8" \
     -d $'{
  "configuredLevel": "WARN"
}'  

除了 /loggers/ 以外的所有内容都是记录器的名称。

如果您在 PCF 中运行此操作,它会变得更好:这直接受到其后端支持。


6
值得强调的是,Actuator loggers 端点是在 1.5.1 中添加的。如果你使用的是旧版本的 Spring Boot 应用程序(< 1.5.1),尽管提供了其他 Actuator 端点,但你找不到用于 loggers 的端点。我知道这一点,因为我刚花费很长时间才搞清楚为什么它无法启动 :-) - Kevin Hooke
8
对于Spring Boot 2.1,你需要确保将日志记录器端点暴露给Web。方法是在application.properties文件中添加management.endpoints.web.exposure.include设置,并将其值设置为loggers。如果你的application.properties文件中没有这行设置,那么就加上:management.endpoints.web.exposure.include=health,info,loggers。因为它的默认值只包括health和info。 - Dennis
本文描述了该过程:https://blog.codeleak.pl/2017/03/spring-boot-configure-log-level-in.html。不要忘记在logback.xml中添加属性scan=true,如此处所述:https://dev59.com/VlsX5IYBdhLWcg3wau4r#42575844。 - Enrico Giurin
唯一的问题是当您有很多实例正在运行时。您将调用API,它将转到1个实例并更改日志级别。但是现在,如果您必须恢复它,则负载均衡器发送该请求到完全相同的实例的机会非常小。当实例数量更多时,这变得更加困难。 - yashjain12yj

35
对于Spring Boot 2.1.5+版本:
首先,您需要使用actuator插件:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

其次,您需要按照Dennis在评论中所说的方式公开端点(loggers默认情况下被禁用):

management.endpoints.web.exposure.include=health,info,loggers

最后,您可以使用Rest端点获取有关日志记录器的信息并设置日志记录级别。
curl -X "GET" "http://localhost:8080/actuator/loggers"

要设置Root日志级别,您可以使用

curl -X "POST" "http://localhost:8080/actuator/loggers/ROOT" -H "Content-Type: application/json; charset=utf-8"   -d $'{ "configuredLevel": "INFO" }'

2
谢谢!management.endpoints.web.exposure.include=...,loggers 属性是缺失的一部分! - t0r0X

13

这是 @Michael Simons 答案的扩展。使用此方法,您将拥有用于执行此操作的用户界面:

这种方法可能会多一些步骤,但可以解决更多问题。我们将使用一个名为Spring Boot Admin Server的工具。

  1. 首先,您需要包含一些依赖项

    <!--Dependency for registering your app as a Spring Boot Admin Server-->
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-server</artifactId>
        <version>1.3.3</version>
    </dependency>
    
    <!--Provide a nice looking ui-->
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-server-ui</artifactId>
        <version>1.3.3</version>
    </dependency>
    
    <!--Dependency for registering your app as a Spring Boot Admin Client-->
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-client</artifactId>
        <version>1.3.0</version>
    </dependency>
    <dependency>
            <groupId>org.jolokia</groupId>
            <artifactId>jolokia-core</artifactId>
    </dependency>
    
  2. 使用注解@EnableAdminServer,使您的应用成为Spring Boot Admin Server。

  3. @SpringBootApplication
    @EnableAdminServer
    public class Application {
       public static void main(String[] args) {
          // ... your code as before ...
       }
    }
    
  4. 在你的application.properties文件中添加以下内容:

    将你的应用程序注册到Spring Boot Admin Server中,这仍然是你的应用程序

  5. spring.boot.admin.url=http://localhost:8031
    

    指导Spring Boot Admin Server在哪里找到客户端

    // For versions 2.*.*
    spring.boot.admin.client.url=http://localhost:8031
    // For versions 1.*.*
    spring.boot.admin.client.service-url=http://localhost:8031
    spring.boot.admin.client.management-url=http://localhost:8031
    spring.boot.admin.client.health-url=http://localhost:8031/health
    
  6. 在你的logback.xml中只需添加以下行:<jmxConfigurator/>。这将通过JMX配置logback。更多信息在此处

  7. ... 然后voila,完成了。现在您可以在运行时更改任何记录器的调试级别。

    i. 只需访问Spring Boot Admin服务器的网址-在我们这里(http:/localhost:8031)。

    ii. 在主页上将显示已注册的应用程序(客户端)列表。

    iii. 单击“详细信息”以针对已注册的客户端进入另一个页面。

    iv. 单击“日志记录”选项卡,其中将列出应用程序中注册的所有记录器。

    v. 您可以更改日志级别,它将在运行时更改您的日志记录级别。这是您期望的一部分代码片段

    Change logging levels at runtime


如果我有多个实例运行,这个方法适用吗?比如在一个给定的命名空间中的K8s集群中,如果我有多个Pod(实例)为特定服务运行... 这个更改会影响所有实例吗? - marley89
@marley89 是的。刚刚测试过了,它会对所有实例进行更改。 - Kihats

10

如果您在项目中使用logback api配置日志记录,则可以使用logback api的自动扫描功能。 根据文档:

logback-classic会扫描其配置文件中的更改,并在配置文件更改时自动重新配置自身。 为了指示logback-classic扫描其配置文件中的更改并自动重新配置自身,请将<configuration>元素的scan属性设置为true。

<configuration scan="true"> 
  ... 
</configuration> 

扫描频率: "默认情况下,配置文件将每分钟扫描一次以检测更改"。有关更多详细信息,请参阅logbackAPI文档



8
您可以在控制器中使用以下代码,并调用API更改日志级别。
@PostMapping("/changeloglevel")
public void changeloglevel(@RequestParam String loglevel)
{
    LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory();
    loggerContext.getLogger("package where springboot main class resides").setLevel(Level.toLevel(loglevel));   
}

日志级别可以是ERROR(错误)、INFO(信息)、WARN(警告)等。


1
java.lang.ClassCastException: 类 org.apache.logging.slf4j.Log4jLoggerFactory 无法转换为类 org.apache.logging.log4j.core.LoggerContext - Peterson V
@PetersonV,请检查您的导入并确保使用正确的logback类,而不是log4j类或其他类。 - Ahmed Nabil
1
如果您需要为所有类[根]全局设置日志级别,可以使用loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.valueOf(loglevel)); - Ahmed Nabil

3

默认的日志记录提供程序是logback。要设置系统,以便在运行时可以更改日志记录级别,您需要执行以下步骤:

首先,在src/main/resources中创建一个自定义的logback配置,命名为logback-spring.xml,其中包括spring的默认配置器,然后添加指令,通过JMX公开logback配置:

<configuration>
  <include resource="org/springframework/boot/logging/logback/base.xml"/>
  <jmxConfigurator />    
</configuration>

现在添加一个依赖项到 Jolokia JMX-over-HTTP bridge:org.jolokia:jolokia-core
现在,您应该能够访问 Spring Boot 应用程序上的 /jolokia 端点。该协议在此处有文档说明,但并不易读。以下是一些您可以直接从浏览器访问的GET示例:
显示 ROOT 日志记录器级别:
/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/getLoggerLevel/ROOT

将ROOT记录器级别更改为调试:

/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/setLoggerLevel/ROOT/debug

spring-boot-actuator意识到/jolokia端点,并将其标记为sensitive=true,因此如果在类路径上有spring-security,则需要进行身份验证。


0

有三种方法可以做到这一点。

  1. Spring Actuator - 利用 /loggers 端点。
  2. Spring Boot Admin。
  3. LogBack 自动扫描。

请查看 Amy DeGregoriothis 博客以获取更多详细信息。


0

对于IntelliJ用户:我有一个类似的情况,最终编写了自己的Intellij插件,这对我来说是最简单的解决方案。它本质上是actuator日志端点的包装器。

  1. 按照上面的评论中所述,在Spring Boot Actuator中启用日志端点
  2. 安装IntellIj插件LogBoot
  3. 连接到您的Spring Boot应用程序,然后您就可以开始了

在此处查看:https://plugins.jetbrains.com/plugin/17101-logboot LogBoot插件


-1
您可以创建一个 JSP 并直接使用它,例如: https://gist.github.com/iamkristian/943918/043ac51bd80321a0873d93277979c8a9a20a9a48#file-log4jadmin-jsp
<%@ page language="java" contentType="text/html;charset=UTF-8" %>
<%@ page import="org.apache.log4j.Level" %>
<%@ page import="org.apache.log4j.LogManager" %>
<%@ page import="org.apache.log4j.Logger" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Enumeration" %>
<%@ page import="java.util.Set" %>
<%@ page import="java.util.Arrays" %>
<% long beginPageLoadTime = System.currentTimeMillis();%>

<html>
<head>
    <title>Log4J Administration</title>
    <style type="text/css">
        <!--
        #content {
            margin: 0px;
            padding: 0px;
            text-align: center;
            background-color: #ccc;
            border: 1px solid #000;
            width: 100%;
        }
        body {
            position: relative;
            margin: 10px;
            padding: 0px;
            color: #333;
        }
        h1 {
            margin-top: 20px;
            font: 1.5em Verdana, Arial, Helvetica sans-serif;
        }
        h2 {
            margin-top: 10px;
            font: 0.75em Verdana, Arial, Helvetica sans-serif;
            text-align: left;
        }
        a, a:link, a:visited, a:active {
            color: red;
            text-decoration: none;
            text-transform: uppercase;
        }
        table {
            width: 100%;
            background-color: #000;
            padding: 3px;
            border: 0px;
        }
        th {
            font-size: 0.75em;
            background-color: #ccc;
            color: #000;
            padding-left: 5px;
            text-align: center;
            border: 1px solid #ccc;
            white-space: nowrap;
        }
        td {
            font-size: 0.75em;
            background-color: #fff;
            white-space: nowrap;
        }
        td.center {
            font-size: 0.75em;
            background-color: #fff;
            text-align: center;
            white-space: nowrap;
        }
        .filterForm {
            font-size: 0.9em;
            background-color: #000;
            color: #fff;
            padding-left: 5px;
            text-align: left;
            border: 1px solid #000;
            white-space: nowrap;
        }
        .filterText {
            font-size: 0.75em;
            background-color: #fff;
            color: #000;
            text-align: left;
            border: 1px solid #ccc;
            white-space: nowrap;
        }
        .filterButton {
            font-size: 0.75em;
            background-color: #000;
            color: #fff;
            padding-left: 5px;
            padding-right: 5px;
            text-align: center;
            border: 1px solid #ccc;
            width: 100px;
            white-space: nowrap;
        }
        -->
    </style>
</head>
<body onLoad="javascript:document.logFilterForm.logNameFilter.focus();">
<%
    String containsFilter = "Contains";
    String beginsWithFilter = "Begins With";
    String[] logLevels = {"debug", "info", "warn", "error", "fatal", "off"};
    String targetOperation = (String) request.getParameter("operation");
    String targetLogger = (String) request.getParameter("logger");
    String targetLogLevel = (String) request.getParameter("newLogLevel");
    String logNameFilter = (String) request.getParameter("logNameFilter");
    String logNameFilterType = (String) request.getParameter("logNameFilterType");
%>
<div id="content">
<h1>Log4J Administration</h1>
<div class="filterForm">
    <form action="log4jAdmin.jsp" name="logFilterForm">Filter Loggers:&nbsp;&nbsp;
        <input name="logNameFilter" type="text" size="50" value="<%=(logNameFilter == null ? "":logNameFilter)%>"
               class="filterText"/>
        <input name="logNameFilterType" type="submit" value="<%=beginsWithFilter%>" class="filterButton"/>&nbsp;
        <input name="logNameFilterType" type="submit" value="<%=containsFilter%>" class="filterButton"/>&nbsp;
        <input name="logNameClear" type="button" value="Clear" class="filterButton"
               onmousedown='javascript:document.logFilterForm.logNameFilter.value="";'/>
        <input name="logNameReset" type="reset" value="Reset" class="filterButton"/>
        <param name="operation" value="changeLogLevel"/>
    </form>
</div>
<table cellspacing="1">
    <tr>
        <th width="25%">Logger</th>
        <th width="25%">Parent Logger</th>
        <th width="15%">Effective Level</th>
        <th width="35%">Change Log Level To</th>
    </tr>
    <%
        Enumeration loggers = LogManager.getCurrentLoggers();
        HashMap loggersMap = new HashMap(128);
        Logger rootLogger = LogManager.getRootLogger();
        if (!loggersMap.containsKey(rootLogger.getName())) {
            loggersMap.put(rootLogger.getName(), rootLogger);
        }
        while (loggers.hasMoreElements()) {
            Logger logger = (Logger) loggers.nextElement();
            if (logNameFilter == null || logNameFilter.trim().length() == 0) {
                loggersMap.put(logger.getName(), logger);
            } else if (containsFilter.equals(logNameFilterType)) {
                if (logger.getName().toUpperCase().indexOf(logNameFilter.toUpperCase()) >= 0) {
                    loggersMap.put(logger.getName(), logger);
                }
            } else {
// Either was no filter in IF, contains filter in ELSE IF, or begins with in ELSE
                if (logger.getName().startsWith(logNameFilter)) {
                    loggersMap.put(logger.getName(), logger);
                }
            }
        }
        Set loggerKeys = loggersMap.keySet();
        String[] keys = new String[loggerKeys.size()];
        keys = (String[]) loggerKeys.toArray(keys);
        Arrays.sort(keys, String.CASE_INSENSITIVE_ORDER);
        for (int i = 0; i < keys.length; i++) {
            Logger logger = (Logger) loggersMap.get(keys[i]);
// MUST CHANGE THE LOG LEVEL ON LOGGER BEFORE GENERATING THE LINKS AND THE
// CURRENT LOG LEVEL OR DISABLED LINK WON'T MATCH THE NEWLY CHANGED VALUES
            if ("changeLogLevel".equals(targetOperation) && targetLogger.equals(logger.getName())) {
                Logger selectedLogger = (Logger) loggersMap.get(targetLogger);
                selectedLogger.setLevel(Level.toLevel(targetLogLevel));
            }
            String loggerName = null;
            String loggerEffectiveLevel = null;
            String loggerParent = null;
            if (logger != null) {
                loggerName = logger.getName();
                loggerEffectiveLevel = String.valueOf(logger.getEffectiveLevel());
                loggerParent = (logger.getParent() == null ? null : logger.getParent().getName());
            }
    %>
    <tr>
        <td><%=loggerName%>
        </td>
        <td><%=loggerParent%>
        </td>
        <td><%=loggerEffectiveLevel%>
        </td>
        <td class="center">
            <%
                for (int cnt = 0; cnt < logLevels.length; cnt++) {
                    String url = "log4jAdmin.jsp?operation=changeLogLevel&logger=" + loggerName + "&newLogLevel=" + logLevels[cnt] + "&logNameFilter=" + (logNameFilter != null ? logNameFilter : "") + "&logNameFilterType=" + (logNameFilterType != null ? logNameFilterType : "");
                    if (logger.getLevel() == Level.toLevel(logLevels[cnt]) || logger.getEffectiveLevel() == Level.toLevel(logLevels[cnt])) {
            %>
            [<%=logLevels[cnt].toUpperCase()%>]
            <%
            } else {
            %>
            <a href='<%=url%>'>[<%=logLevels[cnt]%>]</a>&nbsp;
            <%
                    }
                }
            %>
        </td>
    </tr>
    <%
        }
    %>
</table>
<h2>
    Revision: 1.0<br/>
    Page Load Time (Millis): <%=(System.currentTimeMillis() - beginPageLoadTime)%>
</h2>
</div>
</body>
</html>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接