项目中日志的管理是基础功能之一,不同的用户和场景下对日志都有特定的需求,从而需要用不同的策略进行日志采集和管理,如果是在分布式的项目中,日志的体系设计更加复杂。

用户与需求
不同的场景中,需要选用不同的技术手段去实现日志采集管理,例如日志打印、操作记录、ELK体系等,注意要避免日志管理导致程序异常中断的情况。
越是复杂的系统设计和业务场景,就越依赖日志的输出信息,在大规模的架构中,通常还会搭建独立的日志平台,提供日志数据的采集、存储、分析等整套解决方案。
日志的组件遵守外观设计模式,Slf4j作为日志体系的外观对象,定义规范日志的标准,日志能力的具体实现交由各个子模块去实现;Slf4j明确日志对象的加载方法和功能接口,与客户端交互提供日志管理功能;

private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Impl.class) ;
通常禁止直接使用Logback、Log4j等具体实现组件的API,避免组件替换带来不必要的麻烦,可以做到日志的统一维护。
从Slf4j和Logback组件交互来看,在日志的使用过程中,基本的切入点即使用Slf4j的接口,识别并加载Logback中的具体实现;SPI定义的接口规范,通常作为第三方(外部)组件的实现。

上述SPI作为两套组件的连接点,通过源码大致看下加载过程,追溯LoggerFactory的源码即可:
public final class org.slf4j.LoggerFactory {
private final static void performInitialization() {
bind();
}
private final static void bind() {
try {
StaticLoggerBinder.getSingleton();
} catch (NoClassDefFoundError ncde) {
String msg = ncde.getMessage();
if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
}
}
}
}
此处只贴出了几行示意性质的源码,在LoggerFactory中执行初始化绑定关联的时候,如果没有找到具体的日志实现组件,是会报告出相应的异常信息,并且采用的是System.err输出错误提示。
对于日志(或其他)常用功能,通常会在代码工程中封装独立的代码包,作为公共依赖,统一管理和维护,对于日志的自定义封装可以参考之前的文档,这里通常涉及几个核心点:
至于如何组装日志内容,适配业务语义,以及后续的管理流程,则根据具体场景设计相应的策略即可,比如日志怎么存储、是否实时分析、是否异步执行等。
在自定义注解中,会涉及到对象解析的问题,即在注解中放入要从对象中解析的属性,并且把值拼接到日志内容中,可以增强业务日志的语义可读性。
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class Test {
public static void main(String[] args) {
// Map集合
HashMap<String,Object> infoMap = new HashMap<>() ;
infoMap.put("info","Map的描述") ;
// List集合
ArrayList<Object> arrayList = new ArrayList<>() ;
arrayList.add("List-00");
arrayList.add("List-01");
// User对象
People oldUser = new People("Wang",infoMap,arrayList) ;
People newUser = new People("LiSi",infoMap,arrayList) ;
// 包装对象
WrapObj wrapObj = new WrapObj("WrapObject",oldUser,newUser) ;
// 对象属性解析
SpelExpressionParser parser = new SpelExpressionParser();
// objName
Expression objNameExp = parser.parseExpression("#root.objName");
System.out.println(objNameExp.getValue(wrapObj));
// oldUser
Expression oldUserExp = parser.parseExpression("#root.oldUser");
System.out.println(oldUserExp.getValue(wrapObj));
// newUser.userName
Expression userNameExp = parser.parseExpression("#root.newUser.userName");
System.out.println(userNameExp.getValue(wrapObj));
// newUser.hashMap[info]
Expression ageMapExp = parser.parseExpression("#root.newUser.hashMap[info]");
System.out.println(ageMapExp.getValue(wrapObj));
// oldUser.arrayList[1]
Expression arr02Exp = parser.parseExpression("#root.oldUser.arrayList[1]");
System.out.println(arr02Exp.getValue(wrapObj));
}
}
@Data
@AllArgsConstructor
class WrapObj {
private String objName ;
private People oldUser ;
private People newUser ;
}
@Data
@AllArgsConstructor
class People {
private String userName ;
private HashMap<String,Object> hashMap ;
private ArrayList<Object> arrayList ;
}
注意上面使用的SpelExpressionParser解析器,即Spring框架的原生API;业务中遇到的很多问题,建议都优先从核心依赖(Spring+JDK)中寻找解决方式,多花时间熟悉系统中核心组件的全貌,对开发视野和思路会有极大的帮助。
这里看一个比较复杂的自定义日志解决思路,通过AOP模式识别日志注解,并解析注解中要记录的对象属性,构建相应的日志主体,最后根据注解标记的场景去适配不同的业务策略:

对于功能的通用性要求越高,在封装时内置的适配策略就要越抽象,在处理复杂的逻辑流程时,要善于将不同的组件搭配使用,可以分担业务支撑的压力,形成稳定可靠的解决方案。
基于微服务实现的分布式系统,处理一个请求会经过多个子服务,如果过程中某个服务发生异常,需要定位这个异常归属的请求动作,从而更好的去判断异常原因并复现解决。
定位的动作则依赖一个核心的标识:TraceId-轨迹ID,即请求在各个服务流转时,会携带该请求绑定的TraceId,这样可以识别不同服务的哪些动作为同一个请求产生的。

通过TraceId和SpanId即可还原出请求的链路视图,再结合相关日志打印记录等动作,则可以快速解决异常问题。在微服务体系中Sleuth组件提供了该能力的支撑。
链路视图的核心参数可以集成Slf4j组件中,这里可以参考org.slf4j.MDC语法,MDC提供日志前后的参数传递映射能力,内部包装Map容器管理参数;在Logback组件中,StaticMDCBinder提供该能力的绑定,这样日志打印也可以携带链路视图的标识,做到该能力的完整集成。
链路视图产生的日志是非常庞大的,那这些文档类的日志如何管理和快速查询使用同样是个关键问题,很常见的一个解决方案即ELK体系,现在已更新换代为ElasticStack产品。

Logstash提供日志采集和传输能力,Elasticsearch存储大量JSON格式的日志记录,Kibana则可以视图化展现数据。
配置依赖:需要在服务中配置Logstash地址和端口,即日志传输地址,以及服务名称;
spring:
application:
name: app_serve
logstash:
destination:
uri: Logstash-地址
port: Logstash-端口
配置读取:Logback组件配置中加载上述核心参数,这样在配置上下文中可以通过name的值使用该参数;
<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="butte_app" />
<springProperty scope="context" name="DES_URI" source="logstash.destination.uri" />
<springProperty scope="context" name="DES_PORT" source="logstash.destination.port" />
日志传输:对传输内容做相应的配置,指定LogStash服务配置,编码,核心参数等;
<appender name="LogStash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<!-- 日志传输地址 -->
<destination>${DES_URI:- }:${DES_PORT:- }</destination>
<!-- 日志传输编码 -->
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>UTC</timeZone>
</timestamp>
<!-- 日志传输参数 -->
<pattern>
<pattern>
{
"severity": "%level",
"service": "${APP_NAME:-}",
"trace": "%X{X-B3-TraceId:-}",
"span": "%X{X-B3-SpanId:-}",
"exportable": "%X{X-Span-Export:-}",
"pid": "${PID:-}",
"thread": "%thread",
"class": "%logger{40}",
"rest": "%message"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
输出格式:还可以通过日志的格式设定,管理日志文件或者控制台的输出内容;
<pattern>%d{yyyy-MM-dd HH:mm:ss} %contextName [%thread] %-5level %logger{100} - %msg %n</pattern>
关于Logback组件日志的其他配置,例如输出位置,级别,数据传输方式等,可以多参考官方文档,不断优化。
再看看数据传输到Logstash服务后,如何再传输到ES的,这里也需要相应的传输配置,注意logstash和ES推荐使用相同的版本,本案例中是6.8.6版本。

配置文件:logstash-butte.conf
input {
tcp {
host => "192.168.37.139"
port => "5044"
codec => "json"
}
}
output {
elasticsearch {
hosts => ["http://localhost:9200"]
index => "log-%{+YYYY.MM.dd}"
}
}
启动logstash服务
/opt/logstash-6.8.6/bin/logstash -f /opt/logstash-6.8.6/config/logstash-butte.conf
这样完整的ELK日志管理链路就实现了,通过使用Kibana工具就可以查看日志记录,根据TraceId就可以找到视图链路。
应用仓库:
https://gitee.com/cicadasmile/butte-flyer-parent
组件封装:
https://gitee.com/cicadasmile/butte-frame-parent
我正在使用i18n从头开始构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在rubyonrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi
我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
question的一些答案关于redirect_to让我想到了其他一些问题。基本上,我正在使用Rails2.1编写博客应用程序。我一直在尝试自己完成大部分工作(因为我对Rails有所了解),但在需要时会引用Internet上的教程和引用资料。我设法让一个简单的博客正常运行,然后我尝试添加评论。靠我自己,我设法让它进入了可以从script/console添加评论的阶段,但我无法让表单正常工作。我遵循的其中一个教程建议在帖子Controller中创建一个“评论”操作,以添加评论。我的问题是:这是“标准”方式吗?我的另一个问题的答案之一似乎暗示应该有一个CommentsController参
我安装了ruby版本管理器,并将RVM安装的ruby实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby。有没有办法让emacs像shell一样尊重ruby的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el
是否可以为特定(或所有)项目使用多个布局?例如,我有几个项目,我想对其应用两种不同的布局。一个是绿色的,一个是蓝色的(但是)。我想将它们编译到我的输出目录中的两个不同文件夹中(例如v1和v2)。我一直在玩弄规则和编译block,但我不知道这是怎么回事。因为,每个项目在编译过程中只编译一次,我不能告诉nanoc第一次用layout1编译,第二次用layout2编译。我试过这样的东西,但它导致输出文件损坏。compile'*'doifitem.binary?#don’tfilterbinaryitemselsefilter:erblayout'layout1'layout'layout2'
是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s
电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。 准备工作: 1、U盘一个(尽量使用8G以上的U盘)。 2、一台正常联网可使用的电脑。 3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。 4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。 U盘启动盘制作步骤: 注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手