簡體   English   中英

如何在不重新啟動 Spring Boot 應用程序的情況下在運行時更改日志級別

[英]how do I change log level in runtime without restarting spring boot application

我已經在 PCF 中部署了 springboot 應用程序。 我想根據環境變量記錄消息。我應該怎么做才能在不重新啟動應用程序的情況下更改運行時日志級別?

可以使用 http 端點更改 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的工具。

  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 管理服務器。

     @SpringBootApplication @EnableAdminServer public class Application { public static void main(String[] args) { // ... your code as before ... } }
  3. 在您的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
  4. 在您的logback.xml只需添加以下行<jmxConfigurator/> 這允許通過 JMX 配置 logback。 更多信息在這里

......你做。 現在您可以在運行時更改任何記錄器的調試級別。

一世。 只需訪問您的 Spring Boot 管理服務器的 url——在我們的例子中 ( http:/localhost:8031 )。

ii. 注冊的應用程序(客戶端)列表將顯示在主頁上。

三、 單擊注冊客戶的Details ,這將帶您到另一個頁面。

四、 單擊Logging選項卡,該選項卡將列出在您的應用程序中注冊的所有記錄器。

v. 您可以更改日志級別,它會在運行時更改您的日志記錄級別。 這是您期望的片段

在運行時更改日志記錄級別

如果你在項目中使用logback api 配置日志,那么你可以使用logback api 的AutoScan功能。 根據文檔

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 文檔

默認日志記錄提供程序是 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端點。 該協議記錄在此處 它不漂亮。 為了讓您開始,這里有一些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 等

您可以創建一個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>

有3種方法可以做到。

  1. Spring Actuator - 利用/loggers端點。
  2. Spring Boot 管理員。
  3. LogBack 自動掃描。

有關更多詳細信息,請查看Amy DeGregorio 的這篇博客。

對於 IntelliJ 用戶:我有一個類似的場景,最終編寫了自己的 Intellij 插件,這對我來說是迄今為止最簡單的解決方案。 它本質上是執行器日志端點的包裝器

  1. 如上述注釋中所述,在 Spring Boot Actuator 中啟用日志端點
  2. 安裝 IntellIj 插件LogBoot
  3. 連接到您的 Spring Boot 應用程序,您就可以開始使用了

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

您還可以在 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);
}

如果您使用 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="%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>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM