K8S同一个deployment多个pod向pvc输出logback日志到不同文件

K8S同一个deployment多个pod向pvc输出logback日志到不同文件

1.使用内置变量HOSTNAME

    <property name="LOG_PATH" value="/data"/>


    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_PATH}/servicelog-rt-${HOSTNAME}.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/servicelog-%d{yyyyMMdd}-${HOSTNAME}.log.%i</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>500MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>100</maxHistory>
        </rollingPolicy>

        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n</Pattern>
        </layout>
    </appender>

2.使用自定义hostname变量

第一种方式k8s主机名过长,若想只截取后缀部分字符串可以参考以下代码

package com.example;

import ch.qos.logback.core.PropertyDefinerBase;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * <p> 功能描述:自定义主机名变量 </p>
 * 参考:https://logback.qos.ch/manual/configuration.html#definingPropsOnTheFly
 *
 * @author ouruyi
 * @version 1.0
 * @date Created in 2022/9/24 23:08
 */
public class CustomHostNamePropertyDefiner extends PropertyDefinerBase {

    /**
     * 最大长度
     */
    private Integer maxLength = 5;

    /**
     * 默认值
     */
    private String defaultValue = "localhost";

    public Integer getMaxLength() {
        return maxLength;
    }

    public void setMaxLength(Integer maxLength) {
        this.maxLength = maxLength;
    }

    public String getDefaultValue() {
        return defaultValue;
    }

    public void setDefaultValue(String defaultValue) {
        this.defaultValue = defaultValue;
    }

    @Override
    public String getPropertyValue() {
        InetAddress ia;
        try {
            ia = InetAddress.getLocalHost();
            String host = ia.getHostName();
            final int length = host.length();
            if (length > maxLength) {
                return host.substring(length - maxLength, length);
            } else {
                return host;
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return defaultValue;
    }
}

以下截取部分logback.xml配置文件,其中maxLength建议填写为5或15,其中5仅使用k8s pod name最后5位,15使用pod-template-hash + 最后5位

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <define name="custom.hostname" scope="system" class="com.example.CustomHostNamePropertyDefiner">
        <!-- k8s pod name 可写5或15 -->
        <maxLength>5</maxLength>
        <defaultValue>localhost</defaultValue>
    </define>


	<!-- 系统日志输出 -->
	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
	    <file>${log.path}/sys-info-${custom.hostname}.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
			<fileNamePattern>${log.path}/sys-info-%d{yyyy-MM-dd}-${custom.hostname}.log</fileNamePattern>
			<!-- 日志最大的历史 60天 -->
			<maxHistory>60</maxHistory>
		</rollingPolicy>
		<encoder>
			<pattern>${log.pattern}</pattern>
		</encoder>
		<filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 -->
            <level>INFO</level>
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
	</appender>
</configuration> 	

3.自动清理日志

  • META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.CustomLogbackConfiguration
  • CustomLogbackConfiguration.java
package com.example;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author ryou
 * @since 2022/10/14 13:58
 */
@Configuration
public class CustomLogbackConfiguration {

    @Bean
    public CustomHistoryLogCleaner customHistoryLogCleaner() {
        return new CustomHistoryLogCleaner();
    }

}

  • CustomHistoryLogCleaner.java
package com.example;

import ch.qos.logback.classic.AsyncAppender;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.RollingPolicy;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
import ch.qos.logback.core.util.OptionHelper;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.StopWatch;

import java.io.File;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Iterator;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author ryou
 * @since 2022/10/13 18:54
 */
public class CustomHistoryLogCleaner implements InitializingBean {

    private static final String CUSTOM_HOSTNAME = "custom.hostname";
    public static final Pattern PATTERN = Pattern.compile("%d\\{.+}");
    public static final Logger log = LoggerFactory.getLogger(CustomHistoryLogCleaner.class);

    @Override
    public void afterPropertiesSet() {
        final ExecutorService executorService = Executors.newFixedThreadPool(1);
        executorService.execute(this::handle);
        executorService.shutdown();
    }

    /**
     * 遍历ROOT关联的所有RollingFileAppender
     */
    public void handle() {
        final StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        final ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
        if (loggerFactory instanceof LoggerContext) {
            final Logger root = loggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
            if (root instanceof ch.qos.logback.classic.Logger) {
                final ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) root;
                final Iterator<Appender<ILoggingEvent>> it = logger.iteratorForAppenders();
                while (it.hasNext()) {
                    final Appender<ILoggingEvent> appender = it.next();
                    if (appender instanceof RollingFileAppender) {
                        final RollingFileAppender<?> fileAppender = (RollingFileAppender<?>) appender;
                        handleFileAppender(fileAppender);
                    } else if (appender instanceof AsyncAppender) {
                        final AsyncAppender asyncAppender = (AsyncAppender) appender;
                        final Iterator<Appender<ILoggingEvent>> attachIt = asyncAppender.iteratorForAppenders();
                        while (attachIt.hasNext()) {
                            final Appender<ILoggingEvent> attachAppender = attachIt.next();
                            if (attachAppender instanceof RollingFileAppender) {
                                final RollingFileAppender<?> attachFileAppender = (RollingFileAppender<?>) attachAppender;
                                handleFileAppender(attachFileAppender);
                            }
                        }
                    }
                }
            }
        }
        stopWatch.stop();
        log.info("历史日志清理耗时{}毫秒", stopWatch.getTotalTimeMillis());
    }

    /**
     * 获取RollingFileAppender滚动策略
     */
    public void handleFileAppender(RollingFileAppender<?> appender) {
        int orphanLogSavePeriod = -1;
        // 清理历史日志
        final RollingPolicy rollingPolicy = appender.getRollingPolicy();
        if (rollingPolicy instanceof TimeBasedRollingPolicy) {
            final TimeBasedRollingPolicy<?> timeBasedRollingPolicy = (TimeBasedRollingPolicy<?>) rollingPolicy;
            final String fileNamePattern = timeBasedRollingPolicy.getFileNamePattern();
            final int maxHistory = timeBasedRollingPolicy.getMaxHistory();
            orphanLogSavePeriod = maxHistory;
            // 启动时清理日志
            if (timeBasedRollingPolicy.isCleanHistoryOnStart()) {
                cleanHistoryLogExpired(fileNamePattern, maxHistory);
            }
        }

        // 清理孤儿日志
        if (orphanLogSavePeriod > 0) {
            final String fileNameInUse = appender.getFile();
            cleanOrphanLogExpired(fileNameInUse, orphanLogSavePeriod);
        }
    }

    /**
     * 清理过期日志
     *
     * @param fileNamePattern 文件匹配模式 /data/interview-realtime/application-%d{yyyyMMdd}-86kx2.log.%i
     * @param maxHistory      最大保存历史天数 200
     */
    private void cleanHistoryLogExpired(String fileNamePattern, int maxHistory) {
        log.info("logback日志自动清理:fileNamePattern=>[{}], maxHistory=>[{}]", fileNamePattern, maxHistory);
        final int index = fileNamePattern.lastIndexOf("/");
        String dir = fileNamePattern.substring(0, index);
        String fileName = fileNamePattern.substring(index + 1);
        Matcher matcher = PATTERN.matcher(fileName);
        if (matcher.find()) {
            String pattern = matcher.group(0);
            int start = matcher.start();
            String fileNamePrefix = fileName.substring(0, start);
            String datePattern = pattern.substring(3, pattern.length() - 1);
            final LocalDateTime now = LocalDateTime.now();
            final LocalDateTime expiredDay = now.minusDays(maxHistory);
            final LocalDateTime min = LocalDateTime.of(1970, 1, 1, 12, 0);
            final String maxDay = expiredDay.format(DateTimeFormatter.ofPattern(datePattern));
            final String minDay = min.format(DateTimeFormatter.ofPattern(datePattern));
            final String maxFileName = fileNamePrefix + maxDay;
            final String minFileName = fileNamePrefix + minDay;
            log.info("logback日志自动清理:minFileName=>[{}], maxFileName=>[{}]", minFileName, maxFileName);
            final File file = new File(dir);
            final File[] files = file.listFiles(e -> e.isFile() && e.getName().startsWith(fileNamePrefix));
            for (File f : files) {
                final String name = f.getName();
                log.info("logback日志自动清理:历史日志文件name=>{}", name);
                if (name.compareTo(minFileName) > 0 && name.compareTo(maxFileName) < 0) {
                    log.info("logback日志自动清理:正在删除历史日志文件{}...", name);
                    f.delete();
                }
            }
        }
    }

    /**
     * 清理孤儿日志
     * 注意自定义变量作用域为system
     *
     * @param fileNameInUse 正在写入的日志文件 /data/interview-realtime/interview-realtime-rt-86kx2.log
     * @param maxHistory    最大保存历史天数 200
     * @see ch.qos.logback.core.joran.action.ActionUtil#setProperty
     */
    private void cleanOrphanLogExpired(String fileNameInUse, int maxHistory) {
        log.info("logback日志自动清理:正在写入的日志文件=>[{}], 最大保存周期=>[{}]", fileNameInUse, maxHistory);
        final LocalDateTime now = LocalDateTime.now();
        final LocalDateTime endDateTime = now.minusDays(maxHistory);
        final long maxExpired = endDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
        final String customHostName = OptionHelper.getSystemProperty(CUSTOM_HOSTNAME);
        if (Objects.isNull(customHostName)) {
            log.info("logback日志自动清理:找不到自定义变量[{}], 已跳过!", CUSTOM_HOSTNAME);
            return;
        }
        final int index = fileNameInUse.lastIndexOf("/");
        String dir = fileNameInUse.substring(0, index);
        String fileName = fileNameInUse.substring(index + 1);
        final int end = fileName.lastIndexOf(customHostName);
        String fileNamePrefix = fileName.substring(0, end);
        final File file = new File(dir);
        final File[] files = file.listFiles(e -> e.isFile() && e.getName().startsWith(fileNamePrefix));
        for (File f : files) {
            final String name = f.getName();
            if (fileName.equals(name)) {
                continue;
            }
            log.info("logback日志自动清理:孤儿日志文件name=>{}", name);
            final long lastModified = f.lastModified();
            if (lastModified < maxExpired) {
                log.info("logback日志自动清理:正在删除孤儿日志文件{}...", name);
                f.delete();
            }
        }

    }

}

参考:
GlusterFS集群文件系统研究
多个spring boot实例输出logback日志到一个文件导致日志混乱问题
多项目写入同一Logback日志文件导致的滚动混乱问题(修改Logback源码)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值