我已经在 PCF 中部署了 springboot 应用程序。我想根据环境变量记录消息。我应该怎么做才能在不重新启动应用程序的情况下运行运行时日志级别更改?
12 回答
可以使用 http-endpoint 更改 Spring Boot 1.5+ 中的日志级别
添加
<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 中运行它会更好:这直接从他们的后端得到支持。
对于 Spring Boot 2.1.5+:
首先,您需要执行器插件:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
其次,您需要像丹尼斯在他的评论中所说的那样公开端点(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" }'
这是@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>
使用 annotation 使您的应用程序成为 Spring Boot Admin Server
@EnableAdminServer
。@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。更多信息在这里
......瞧,你完成了。现在您可以在运行时更改任何记录器的调试级别。
一世。只需访问您的 Spring Boot Admin Server 的 url - 在我们的例子中 ( http:/localhost:8031
)。
ii. 注册的应用程序(客户端)列表将显示在主页上。
iii. 单击Details
已注册的客户,这将带您到另一个页面。
iv. 单击Logging
将列出在您的应用程序中注册的所有记录器的选项卡。
v. 您可以更改日志级别,它将在运行时更改您的日志级别。这是您所期望的片段
如果您logback
在项目中使用 api 配置日志记录,则可以使用 api 的AutoScan功能logback
。根据文档
logback-classic 将扫描其配置文件中的更改,并在配置文件更改时自动重新配置自己。为了指示 logback-classic 扫描其配置文件中的更改并自动重新配置自身,请将元素的 scan 属性设置为 true。
<configuration scan="true">
...
</configuration>
扫描频率:“ By default, the configuration file will be scanned for changes once every minute
”。有关详细信息,请参阅logback
API 文档。
从 Spring Boot 1.5.x 开始,您可以使用 logger 端点来发布所需的日志记录级别。
默认的日志记录提供程序是 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 桥的依赖:org.jolokia:jolokia-core
.
您现在应该能够/jolokia
在您的 Spring Boot 应用程序上访问端点。该协议在此处记录。这不漂亮。为了帮助您入门,这里有一些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 那么它将需要身份验证。
您可以在控制器中使用以下代码并调用 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 等
对于 IntelliJ 用户:我遇到了类似的情况,最终编写了自己的 Intellij 插件,这对我来说是迄今为止最简单的解决方案。它本质上是执行器日志端点的包装器
- 如上述注释中所述,在 Spring Boot Actuator 中启用日志端点
- 安装 IntellIj 插件LogBoot
- 连接到您的 Spring Boot 应用程序,您就可以开始了
在这里查看:https: //plugins.jetbrains.com/plugin/17101-logboot LogBoot 插件
您可以创建一个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 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>
<%
}
}
%>
</td>
</tr>
<%
}
%>
</table>
<h2>
Revision: 1.0<br/>
Page Load Time (Millis): <%=(System.currentTimeMillis() - beginPageLoadTime)%>
</h2>
</div>
</body>
</html>
有 3 种方法可以做到这一点。
- Spring Actuator - 利用/loggers端点。
- Spring Boot 管理员。
- LogBack 自动扫描。
请查看Amy DeGregorio的此博客以了解更多详细信息。
If you use Log4j 2 for logging you can easily configuration it to set the log level to use based on an environment variable or system property. If you do it this way you won't need to modify the file just because the environment changed.
<?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="%d{ABSOLUTE} %-5level # %class.%method %m%n" />
</Console>
<RollingFile name="log4j" fileName="${LOG_DIR}/log4j.txt" filePattern="${LOG_DIR}/archive/log4j.txt.%d{yyyyMMdd_HHmmss}-%i">
<PatternLayout>
<MarkerPatternSelector defaultPattern="%d [%t] %-5p %X{requestId, sessionId, loginId, userId, ipAddress, corpAcctNumber} %C{1.}.%M:%L - %m%n">
<PatternMatch key="FLOW" pattern="%d [%t] %-5p %X{requestId, sessionId, loginId, userId, ipAddress, corpAcctNumber} -------- %C{1.}.%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>
您还可以在 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);
}