当前位置: 首页 > news >正文

logback日志自定义占位符

前言

在大型系统运维中,很大程度上是需要依赖日志的。在java大型web工程中,一般都会使用slf4j+logback这一个组合来实现日志的管理。

logback中很多现成的占位符可以可以直接使用,比如线程号【%t】、时间【%d】、日志等级【%p】,更多详细的占位符可以参考后文的占位符总结。

实际应用中,仅仅使用这些已经存在的占位符可能不够,比如我想给我的日志加一个traceId,通过这个traceId我可以快速的定位到我这个请求的所有日志,方便日志追踪。

针对日志中自定义占位符,logback提供了两种解决方案。下面分别来介绍一下

自定义traceId占位符

使用MDC

mdc的全称是Mapped Diagnostic Context,是Logback日志框架中的一个功能,它允许你在同一个线程的执行路径上设置一系列的上下文信息(即键值对),这些上下文信息可以自动地添加到日志输出中

logback.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false"><contextName>${APP_NAME}</contextName><property name="APP_NAME" value="MouseDemo"/><property name="LOG_PATH" value="./system_log/MouseDemo"/><property name="CONSOLE_LOG_PATTERN"value="%d | %X{traceId}| %highlight(%-5level) | %boldYellow(%thread) | %boldGreen(%logger) | %msg%n"/><!--输出到控制台--><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>${CONSOLE_LOG_PATTERN}</pattern><charset>UTF-8</charset></encoder></appender><root level="info"><appender-ref ref="console"/></root></configuration>

在logback中,自定义的占位符是通过%X{配置的键}来获取的

设置MDC

在web工程中,设置traceId的时机很重要,一般是放在拦截器中。

自定义一个拦截器

package com.tml.mouseDemo.config;import cn.hutool.core.lang.UUID;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import static com.tml.mouseDemo.constants.CommonConstants.TRACE_ID;@Slf4j
public class TraceInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String traceId = UUID.fastUUID().toString(true);log.info("TraceInterceptor preHandle,generate traceId is:{}", traceId);MDC.put(TRACE_ID, traceId);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("TraceInterceptor afterCompletion,ready to clean traceId:{}", MDC.get(TRACE_ID));MDC.remove(TRACE_ID);}
}

注册拦截器

package com.tml.mouseDemo.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.nio.charset.Charset;
import java.util.List;@Configuration
public class WebAppConfig implements WebMvcConfigurer {@Beanpublic HttpMessageConverter<String> responseBodyConverter() {StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(Charset.forName("UTF-8"));return stringHttpMessageConverter;}@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(responseBodyConverter());}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new TraceInterceptor()).addPathPatterns("/**");registry.addInterceptor(new RequestInterceptor()).addPathPatterns("/**");}
}

 

演示案例

经过上面两步的配置,日志上就会额外多一个traceId了

先定义一个service类

package com.tml.mouseDemo.service;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;@Service
@Slf4j
public class TraceService {public void trace(String type) throws InterruptedException {log.info("trace start,type:{}", type);Thread.sleep(1000L);log.info("trace end");}
}

 

定义一个restful接口

package com.tml.mouseDemo.controller;import com.tml.mouseDemo.constants.CommonResponse;
import com.tml.mouseDemo.service.TraceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;@RestController
@Slf4j
public class TraceController {@Autowiredprivate TraceService traceService;@PostMapping("/trace")public CommonResponse<String> trace(String type) throws InterruptedException {log.info("trace type:{}", type);traceService.trace(type);return CommonResponse.success("success");}}

这里的案例是单线程,看下运行结果

 日志中,确实是多了一个traceId,符合预期

不过MDC有其局限性,仅支持单线程的数据传递。因为其底层是基于ThreadLocal来实现的,如下图

 

 局限性

下面来看一下再多线程环境下,使用MDC会有什么样的效果

package com.tml.mouseDemo.controller;import com.tml.mouseDemo.constants.CommonResponse;
import com.tml.mouseDemo.service.TraceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;@RestController
@Slf4j
public class TraceController {@Autowiredprivate ThreadPoolExecutor executor;@Autowiredprivate Executor ttlExecutor;@Autowiredprivate TraceService traceService;@PostMapping("/traceWithThread")public CommonResponse<String> traceWithThread(String type) throws InterruptedException {log.info("traceWithThread type:{}", type);traceService.trace(type);//模拟异步发短信new Thread(() -> {try {Thread.sleep(2000);String sendMsg = "hello world";log.info("sendMsg:{}", sendMsg);} catch (Exception e) {log.error("traceWithThread occur error", e);}}, "trace--001").start();return CommonResponse.success("success");}}

这里通过在主线程中new一个子线程来模拟多线程,运行一下看下结果

从图中可以看出,子线程的日志中是没有输出traceId的,这个就是MDC的局限性。看过前文阿里巴巴TransmittableThreadLocal使用指南的朋友们应该知道,这种new出来的线程不支持,那么使用线程池也肯定是不支持数据传递。

 

使用自定义Converter

相比MDC,使用自定义的Converter就会显得更加的灵活了,看下使用自定义的Converter的使用流程

定义traceId的存取

package com.tml.mouseDemo.config;import com.alibaba.ttl.TransmittableThreadLocal;public class TraceContext {private static final ThreadLocal<String> TRACE_CONTEXT = new TransmittableThreadLocal<>();public static String get() {return TRACE_CONTEXT.get();}public static void set(String age) {TRACE_CONTEXT.set(age);}public static void clean() {if (TRACE_CONTEXT.get() != null) {TRACE_CONTEXT.remove();}}
}

MDC使用的是ThreadLocalL来存储,我们这里自定义采用阿里巴巴的TransmittableThreadLocal,为什么要使用这个,详细的可以参考这篇博文阿里巴巴TransmittableThreadLocal使用指南 

定义Converter

package com.tml.mouseDemo.core.log;import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import com.tml.mouseDemo.config.TraceContext;import java.util.Optional;public class TraceIdConverter extends ClassicConverter {@Overridepublic String convert(ILoggingEvent iLoggingEvent) {return Optional.ofNullable(TraceContext.get()).orElse("");}
}

拦截器调整

package com.tml.mouseDemo.config;import cn.hutool.core.lang.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Slf4j
public class TraceInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String traceId = UUID.fastUUID().toString(true);log.info("TraceInterceptor preHandle,generate traceId is:{}", traceId);TraceContext.set(traceId);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("TraceInterceptor afterCompletion,ready to clean traceId:{}", TraceContext.get());TraceContext.clean();}
}

改成从TraceContext中获取traceId,拦截器的注册和上文的保持一致。 

 

注册Converter

 <conversionRule conversionWord="traceId" converterClass="com.tml.mouseDemo.core.log.TraceIdConverter"/>

logback.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false"><conversionRule conversionWord="traceId" converterClass="com.tml.mouseDemo.core.log.TraceIdConverter"/><contextName>${APP_NAME}</contextName><property name="APP_NAME" value="MouseDemo"/><property name="LOG_PATH" value="./system_log/MouseDemo"/><property name="CONSOLE_LOG_PATTERN"value="%d | %traceId| %highlight(%-5level) | %boldYellow(%thread) | %boldGreen(%logger) | %msg%n"/><!--输出到控制台--><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>${CONSOLE_LOG_PATTERN}</pattern><charset>UTF-8</charset></encoder></appender><root level="info"><appender-ref ref="console"/></root></configuration>

这里直接使用注册的Converter的traceId来作为占位符,而不是使用%X{键}来获取traceId

演示案例

直接new线程

代码案例和MDC中使用new创建线程一样,直接看下运行结果

子线程的日志中有traceId,并且是和父线程的traceId保持一致,达到预期

使用线程池

为了达到演示效果,我这里定义了两个线程池

package com.tml.mouseDemo.config;import com.alibaba.ttl.threadpool.TtlExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.concurrent.*;@Slf4j
@Configuration
public class CommonConfig {Thread.UncaughtExceptionHandler exceptionHandler = (Thread t, Throwable e) -> {log.info("current thread occurs error!", e);};@Beanpublic ThreadPoolExecutor executor() {ThreadFactory threadFactory = new ThreadFactoryBuilder().setUncaughtExceptionHandler(exceptionHandler).setNameFormat("mouse-worker-%d").build();int processors = Runtime.getRuntime().availableProcessors();log.info("processors:{}", processors);ThreadPoolExecutor executor = new ThreadPoolExecutor(1,processors * 2,0L,TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(1000),threadFactory,new ThreadPoolExecutor.AbortPolicy());return executor;}/*** 使用阿里的 TransmittableThreadLocal 装饰线程池* @return*/@Beanpublic Executor ttlExecutor() {ThreadFactory threadFactory = new ThreadFactoryBuilder().setUncaughtExceptionHandler(exceptionHandler).setNameFormat("mouse-worker-ttl-%d").build();int processors = Runtime.getRuntime().availableProcessors();log.info("processors:{}", processors);ThreadPoolExecutor executor = new ThreadPoolExecutor(1,processors * 2,0L,TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(1000),threadFactory,new ThreadPoolExecutor.AbortPolicy());Executor ttlExecutor = TtlExecutors.getTtlExecutor(executor);return ttlExecutor;}}

这里线程池的核心线程数设置为1,是为了方便测试,和这篇文章的思路一致web项目国际化指南

 

测试代码

package com.tml.mouseDemo.controller;import com.tml.mouseDemo.constants.CommonResponse;
import com.tml.mouseDemo.service.TraceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;@RestController
@Slf4j
public class TraceController {@Autowiredprivate ThreadPoolExecutor executor;@Autowiredprivate Executor ttlExecutor;@Autowiredprivate TraceService traceService;@PostMapping("/traceWithThreadPool")public CommonResponse<String> traceWithThreadPool(String type) throws InterruptedException {log.info("traceWithThreadPool type:{}", type);traceService.trace(type);//模拟异步发短信executor.execute(() -> {try {Thread.sleep(2000);String sendMsg = "hello world";log.info("sendMsg:{}", sendMsg);} catch (Exception e) {log.error("traceWithThreadPool occur error", e);}});//模拟异步发短信ttlExecutor.execute(() -> {try {Thread.sleep(2000);String sendMsg = "hello world";log.info("sendMsg:{}", sendMsg);} catch (Exception e) {log.error("traceWithThreadPool occur error", e);}});return CommonResponse.success("success");}
}

运行两次,看下效果

 

 可以看到,使用ttl装饰的线程池的日志是正常的,使用普通的线程池的日志的traceId错乱了,这个也是尤其需要注意的点。

总结

在logback中,可以使用多种方式来自定义占位符,通常一般的做法主要是上面两种。使用MDC的方式就是简单,不过他的缺陷也是显而易见的,就是他不支持多线程的数据传递。

所以,如果你的项目中想要自定义占位符,强烈建议使用自定义Convertor的方式。详细的代码已上传至github,欢迎前来围观我的github

常见的日志占位符总结

这里总结下logback中常见的占位符,欢迎补充

占位符名称占位符含义备注
%d日期和时间

%d{HH:mm:ss.SSS}

%d{yyyy-MM-dd HH:mm:ss}

可以自定义时间的格式

%t线程名称
%p日志的优先级
%c日志记录器名称

一般表示类名,可以%c{20},可以通过

这样来设置类名的最大长度

%m日志消息
%n换行符
%L日志行号
%X{key}代表 MDC中存储的 key 对应的信息不建议使用,不支持多线程

相关文章:

logback日志自定义占位符

前言 在大型系统运维中&#xff0c;很大程度上是需要依赖日志的。在java大型web工程中&#xff0c;一般都会使用slf4jlogback这一个组合来实现日志的管理。 logback中很多现成的占位符可以可以直接使用&#xff0c;比如线程号【%t】、时间【%d】、日志等级【%p】&#xff0c;…...

Vue平台开发三——项目管理页面

前言 对于多个项目的使用&#xff0c;可能需要进行项目切换管理&#xff0c;所以这里创建一个项目管理页面&#xff0c;登录成功后跳转这个页面&#xff0c;进行选择项目&#xff0c;再进入Home页面展示对应项目的内容。 一、实现效果图预览 二、页面内容 功能1、项目列表展…...

用于牙科的多任务视频增强

Multi-task Video Enhancement for Dental Interventions 2022 miccai Abstract 微型照相机牢牢地固定在牙科手机上&#xff0c;这样牙医就可以持续地监测保守牙科手术的进展情况。但视频辅助牙科干预中的视频增强减轻了低光、噪音、模糊和相机握手等降低视觉舒适度的问题。…...

【Node.js]

一、概述 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境 &#xff0c;使用了一个事件驱动、非阻塞式I/O模型&#xff0c; 让JavaScript 运行在服务端的开发平台&#xff0c;它让JavaScript成为与PHP、Python、Perl、Ruby等服务端语言平起平坐的脚本语言。 官网地…...

【Elasticsearch】腾讯云安装Elasticsearch

Elasticsearch 认识Elasticsearch安装Elasticsearch安装Kibana安装IK分词器分词器的作用是什么&#xff1f;IK分词器有几种模式&#xff1f;IK分词器如何拓展词条&#xff1f;如何停用词条&#xff1f; 认识Elasticsearch Elasticsearch的官方网站如下 Elasticsearch官网 Ela…...

【网络协议】ACL(访问控制列表)第一部分

概述 网络安全在网络中的重要性不言而喻。本文&#xff08;即第一部分&#xff09;将介绍ACL的基本概念以及标准ACL的配置。第二部分将重点讨论扩展ACL、其他相关概念以及ACL的故障排除。 文章目录 概述ACL定义数据包过滤ACLACL配置指导原则配置ACL的三条规则ACL功能ACL工作原…...

2025.1.20——一、[RCTF2015]EasySQL1 二次注入|报错注入|代码审计

题目来源&#xff1a;buuctf [RCTF2015]EasySQL1 目录 一、打开靶机&#xff0c;整理信息 二、解题思路 step 1&#xff1a;初步思路为二次注入&#xff0c;在页面进行操作 step 2&#xff1a;尝试二次注入 step 3&#xff1a;已知双引号类型的字符型注入&#xff0c;构造…...

Spring Boot 整合 Knife4j:打造更优雅的 API 文档

在现代 Web 应用开发中&#xff0c;API 文档的重要性不言而喻。清晰、准确、易用的 API 文档不仅可以方便开发者理解和使用 API&#xff0c;还能提高团队协作效率。Knife4j 是一个基于 Swagger 的增强型 API 文档工具&#xff0c;它可以为 Spring Boot 项目生成美观、易于交互的…...

Kafka 源码分析(一) 日志段

首先我们的 kafka 的消息本身是存储在日志段中的, 对应的源码是下面这段代码: class LogSegment private[log] (val log: FileRecords,val lazyOffsetIndex: LazyIndex[OffsetIndex],val lazyTimeIndex: LazyIndex[TimeIndex],val txnIndex: TransactionIndex,val baseOffset:…...

javaEE初阶————多线程初阶(2)

今天给大家带来第二期啦&#xff0c;保证给大家讲懂嗷&#xff1b; 1&#xff0c;线程状态 NEW安排了工作还未开始行动RUNNABLE可工作的&#xff0c;或者即将工作&#xff0c;正在工作BLOCKED排队等待WAITING排队等待其他事TIMED_WAITING排队等待其他事TERMINATED工作完成了 …...

Redis学习笔记1【数据类型和常用命令】

Redis学习笔记 基础语法 1.数据类型 String: 最基本的类型&#xff0c;可以存储任何数据&#xff0c;例如文本或数字。示例值为 hello world。Hash: 用于存储键值对&#xff0c;适合存储对象或结构体。示例值为 {"name": "Jack", "age": 21}。…...

JavaWeb项目——查询角色列表到页面中——转发模式

一、知识点 1、req.getRequestDispatch与resp.sendRedirect跳转方式的比较 一、实现原理 1、req.getRequestDispatcher&#xff1a; 属于服务器端跳转&#xff0c;在服务器内部将请求转发给另一个资源&#xff08;如另一个 Servlet 或 JSP 页面&#xff09;。调用 getReques…...

feign调用跳过HTTPS的SSL证书校验配置详解

一、问题抛出 如果不配置跳过SSL证书校验&#xff0c;当Feign客户端尝试连接到一个使用自签名证书的服务器时&#xff0c;可能会抛出类似以下的异常&#xff1a; javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building faile…...

今天也是记录小程序进展的一天(破晓时8)

嗨嗨嗨朋友们&#xff0c;今天又来记录一下小程序的进展啦&#xff01;真是太激动了&#xff0c;项目又迈出了重要的一步&#xff0c;231啦&#xff01;感觉每一步的努力都在积累&#xff0c;功能逐渐完善&#xff0c;离最终上线的目标越来越近了。大家一直支持着这个项目&…...

SQL-leetcode—1084. 销售分析 III

1084. 销售分析 III 表&#xff1a; Product --------------------- | Column Name | Type | --------------------- | product_id | int | | product_name | varchar | | unit_price | int | --------------------- product_id 是该表的主键&#xff08;具有唯一值的列&…...

Linux C\C++编程-文件位置指针与读写文件数据块

【图书推荐】《Linux C与C一线开发实践&#xff08;第2版&#xff09;》_linux c与c一线开发实践pdf-CSDN博客 《Linux C与C一线开发实践&#xff08;第2版&#xff09;&#xff08;Linux技术丛书&#xff09;》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 Linu…...

Flask简介与安装以及实现一个糕点店的简单流程

目录 1. Flask简介 1.1 Flask的核心特点 1.2 Flask的基本结构 1.3 Flask的常见用法 1.3.1 创建Flask应用 1.3.2 路由和视图函数 1.3.3 动态URL参数 1.3.4 使用模板 1.4 Flask的优点 1.5 总结 2. Flask 环境创建 2.1 创建虚拟环境 2.2 激活虚拟环境 1.3 安装Flask…...

【自动化测试】—— Appium使用保姆教程

目录 一. 连接手机 1. 授权 2. 调试 3. 获取参数 二. 启动APP 1. 启动Appium服务 2. 启动Appium Inspector 3. 配置Appium Inspector 三. 功能说明 1. 主菜单功能 2. 快照视图菜单 3. 元素视图菜单 四. 常见问题 1. appPackage有多个设备时 一. 连接手机 1. 授权 首先将手机的开…...

西门子【Library of General Functions (LGF) for SIMATIC S7-1200 / S7-1500】

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 通用函数库 (LGF) 扩展了 TIA Portal 中用于 PLC 编程的 STEP 7 指令&#xff08;数学函数、时间、计数器 等&#xff09;。该库可以不受限制地使用&#xff0c;并包含 FIFO 、搜索功能、矩阵计算、 astro 计…...

IntelliJ IDEA 2023.3 中配置 Spring Boot 项目的热加载

IntelliJ IDEA 2023.3 中配置 Spring Boot 项目的热加载 在 IntelliJ IDEA 2023.3 中配置 Spring Boot 项目的热加载&#xff0c;可以让你在不重启应用的情况下看到代码修改的效果。以下是详细的配置步骤&#xff1a; 添加 spring-boot-devtools 依赖 在 pom.xml 文件中添加 …...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

微服务商城-商品微服务

数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

如何在最短时间内提升打ctf(web)的水平?

刚刚刷完2遍 bugku 的 web 题&#xff0c;前来答题。 每个人对刷题理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟着writeup做了一遍就等于刷了&#xff0c;还有的人是独立思考做了一遍就等于刷了。…...

服务器--宝塔命令

一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行&#xff01; sudo su - 1. CentOS 系统&#xff1a; yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式&#xff08;本地调用&#xff09; SSE模式&#xff08;远程调用&#xff09; 4. 注册工具提…...

SQL慢可能是触发了ring buffer

简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

Linux 中如何提取压缩文件 ?

Linux 是一种流行的开源操作系统&#xff0c;它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间&#xff0c;使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的&#xff0c;要在 …...