如何在运行时更改日志级别而不重新启动 Spring Boot 应用程序
Posted
技术标签:
【中文标题】如何在运行时更改日志级别而不重新启动 Spring Boot 应用程序【英文标题】:how do I change log level in runtime without restarting spring boot application 【发布时间】:2016-02-23 23:56:06 【问题描述】:我已经在 PCF 中部署了 springboot 应用程序。我想根据环境变量记录消息。我应该怎么做才能在不重新启动应用程序的情况下运行运行时日志级别更改?
【问题讨论】:
【参考方案1】:在 Spring Boot 1.5+ 中更改日志级别可以通过 http-endpoint 完成
添加
<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 中运行它会更好:他们的后端直接支持这一点。
【讨论】:
值得强调的是,Actuator loggers 端点是在 1.5.1 中添加的,如果您有一个较旧的 Spring Boot 应用程序使用 对于 Spring Boot 2.1,您必须通过将 loggers 值添加到 management.endpoints.web.exposure.include 设置来确保 loggers 端点向 Web 公开。如果您的application.properties
文件中没有这样的行,则添加:management.endpoints.web.exposure.include=health,info,loggers
,因为它的默认值为 health,info
本文中描述的过程:blog.codeleak.pl/2017/03/…。不要忘记在 logback.xml 中添加属性 scan=true,如下所示:***.com/a/42575844/379173【参考方案2】:
对于 Spring Boot 2.1.5+:
首先,你需要执行器插件:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
其次,你需要像 Dennis 在他的评论中所说的那样暴露端点(loggers
默认是禁用的):
management.endpoints.web.exposure.include=health,info,loggers
最后,您可以使用 Rest Endpoints 获取有关记录器的信息并设置记录级别。
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" '
【讨论】:
谢谢!management.endpoints.web.exposure.include=...,loggers
属性是缺失的部分!【参考方案3】:
这是@Michael Simons 答案的扩展。使用这种方法,您将拥有一个执行此操作的 UI:
这个方法有点长,但它解决了更多。我们将使用一个名为 Spring Boot Admin Server 的工具。
首先你需要包含一些依赖项
<!--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>
使用注解 @EnableAdminServer
使您的应用成为 Spring Boot Admin Server。
@SpringBootApplication
@EnableAdminServer
public class Application
public static void main(String[] args)
// ... your code as before ...
在您的application.properties
中添加以下内容:
将您的应用注册到仍然是您的应用的 Spring Boot 管理服务器
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
在您的logback.xml
中添加以下行<jmxConfigurator/>
。这允许通过 JMX 配置 logback。更多信息here
... 和 瞧 你完成了。现在您可以在运行时更改任何记录器的调试级别。
我。只需访问您的 Spring Boot Admin Server 的 url - 在我们的例子中是 (http:/localhost:8031
)。
二。注册的应用程序(客户端)列表将显示在主页上。
三。针对已注册的客户点击Details
,这将带您到另一个页面。
四。单击Logging
选项卡,该选项卡将列出在您的应用程序中注册的所有记录器。
v.您可以更改日志级别,它将在运行时更改您的日志级别。这是您所期望的sn-p
【讨论】:
【参考方案4】:如果您在项目中使用logback
api 配置登录,那么您可以使用logback
api 的AutoScan 功能。
根据文档
logback-classic 将扫描其配置文件中的更改并 当配置文件改变时自动重新配置。 为了指示 logback-classic 扫描它的变化 配置文件并自动重新配置自身设置 扫描元素的属性为true。
<configuration scan="true">
...
</configuration>
扫描频率:“By default, the configuration file will be scanned for changes once every minute
”。有关详细信息,请参阅logback
API documentation。
【讨论】:
【参考方案5】:从 Spring Boot 1.5.x 开始,you can use logger endpoint to POST desired logging level.
【讨论】:
【参考方案6】:默认的日志记录提供程序是 logback。要设置系统以便在运行时更改日志记录级别,您需要执行以下步骤:
首先在 src/main/resources
创建一个名为 logback-spring.xml
的自定义 logback 配置,其中包括 spring 的默认配置器,然后添加通过 JMX 公开 logback 配置的指令:
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<jmxConfigurator />
</configuration>
现在添加对 Jolokia JMX-over-HTTP 桥的依赖:org.jolokia:jolokia-core
。
您现在应该能够在您的 Spring Boot 应用程序上点击 /jolokia
端点。协议是documented here。这不漂亮。为了帮助您入门,这里有几个 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,那么它将需要身份验证。
【讨论】:
【参考方案7】:您可以在控制器中使用以下代码并调用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 等
【讨论】:
java.lang.ClassCastException:类 org.apache.logging.slf4j.Log4jLoggerFactory 不能转换为类 org.apache.logging.log4j.core.LoggerContext【参考方案8】:您可以创建一个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:
<input name="logNameFilter" type="text" size="50" value="<%=(logNameFilter == null ? "":logNameFilter)%>"
class="filterText"/>
<input name="logNameFilterType" type="submit" value="<%=beginsWithFilter%>" class="filterButton"/>
<input name="logNameFilterType" type="submit" value="<%=containsFilter%>" class="filterButton"/>
<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 >Logger</th>
<th >Parent Logger</th>
<th >Effective Level</th>
<th >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>
<%
%>
</td>
</tr>
<%
%>
</table>
<h2>
Revision: 1.0<br/>
Page Load Time (Millis): <%=(System.currentTimeMillis() - beginPageLoadTime)%>
</h2>
</div>
</body>
</html>
【讨论】:
【参考方案9】:有 3 种方法可以做到这一点。
-
Spring Actuator - 利用 /loggers 端点。
Spring Boot 管理员。
LogBack 自动扫描。
请查看 Amy DeGregorio 的 this 博客了解更多详情。
【讨论】:
【参考方案10】:对于 IntelliJ 用户:我遇到了类似的情况,最终编写了自己的 Intellij 插件,这对我来说是迄今为止最简单的解决方案。它本质上是执行器日志端点的包装器
-
如上述注释中所述,在 Spring Boot Actuator 中启用日志端点
安装 IntellIj 插件 LogBoot
连接到您的 Spring Boot 应用程序即可开始使用
在这里查看:https://plugins.jetbrains.com/plugin/17101-logboot LogBoot plugin
【讨论】:
【参考方案11】:您还可以在 Web 服务中添加设置页面来更新日志级别。这可以使用 ajax 来完成。以下示例包括登录和 csrf 令牌:
首先,添加一些表单来指定新的日志级别。例如可以通过使用select
元素来改进。
<form>
<input type="text" id="logClassName" name="logClassName"/>
<input type="text" id="logLevel" name="logLevel" />
<button onclick="submitLogLevelChange(); return false;">Submit</button>
</form>
然后,请求被发送:
function submitLogLevelChange()
var className = document.getElementById('logClassName').value;
var logLevel = document.getElementById("logLevel").value;
$.ajax(
// Set up security, see below.
beforeSend: setHeader,
type: 'POST',
// specify the logger to be modified
url: "/loggers/" + className,
// specify the new log level
data: '"configuredLevel":"' + logLevel + '"',
contentType: 'application/json',
processData: false,
).done(function(data, textStatus, jqXHR)
if (jqXHR.status === 200)
// Happy
else if (jqXHR.status === 401)
// Logged out or not enough user rights
else
//Some other problem
)
.fail(function(jqXHR, textStatus )
if (jqXHR.status === 200)
// Actually was successful, FireFox has some issues...
else
// Failure
);
以下函数将 csrf 令牌注入 POST 请求:
function setHeader(xhr)
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
xhr.setRequestHeader(header, token);
【讨论】:
【参考方案12】:如果您使用 Log4j 2 进行日志记录,您可以轻松配置它以根据环境变量或系统属性设置要使用的日志级别。如果你这样做,你就不需要因为环境改变而修改文件。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="ERROR" monitorInterval="300">
<properties>
<property name="LOG_DIR">$sys:user.dir/logs/</property>
<property name="log_env">$sys:env:-lab</property>
<property name="flow_lab">$sys:flow_match:-ACCEPT</property>
<property name="flow_prod">NEUTRAL</property>
<property name="level_lab">DEBUG</property>
<property name="level_prod">INFO</property>
</properties>
<MarkerFilter marker="FLOW" onMatch="$flow_$log_env" onMismatch="NEUTRAL"/>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%dABSOLUTE %-5level # %class.%method %m%n" />
</Console>
<RollingFile name="log4j" fileName="$LOG_DIR/log4j.txt" filePattern="$LOG_DIR/archive/log4j.txt.%dyyyyMMdd_HHmmss-%i">
<PatternLayout>
<MarkerPatternSelector defaultPattern="%d [%t] %-5p %XrequestId, sessionId, loginId, userId, ipAddress, corpAcctNumber %C1..%M:%L - %m%n">
<PatternMatch key="FLOW" pattern="%d [%t] %-5p %XrequestId, sessionId, loginId, userId, ipAddress, corpAcctNumber -------- %C1..%M:%L %msg --------%n"/>
</MarkerPatternSelector>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="30 MB"/>
</Policies>
<!-- A max of 20 will allow 20 files per second with the date pattern specified on the RollingFile declaration.
Hopefully that is a ridiculous value -->
<DefaultRolloverStrategy min="1" max="20">
<Delete basePath="$LOG_DIR/archive">
<!-- Nested conditions: the inner condition is only evaluated on files for which the outer conditions are true. -->
<IfFileName glob="log4j.txt.*">
<!-- Only allow 1 GB of files to accumulate -->
<IfAccumulatedFileSize exceeds="1 GB"/>
</IfFileName>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="com.mycorp.package1" level="$level_$log_env" additivity="false">
<AppenderRef ref="log4j"/>
</Logger>
<Logger name="com.mycorp.package2" level="info" additivity="false">
<AppenderRef ref="log4j"/>
</Logger>
<Root level="$level_$log_env">
<AppenderRef ref="log4j" />
</Root>
</Loggers>
【讨论】:
更改系统属性需要重启。以上是关于如何在运行时更改日志级别而不重新启动 Spring Boot 应用程序的主要内容,如果未能解决你的问题,请参考以下文章
在标准输出上显示 INFO 级别的 python 日志而不更改源代码
如何在运行时更新 Spring Boot 应用程序的配置而不重新加载整个 ApplicationContext
Undertow java.util.logging:如何使用 java util logging 在运行时更改 undertow 服务器的日志记录级别