日志选择
skywalking(以下简称sw)支持两种日志格式,pattern和json。
在启动流程中
public class SkyWalkingAgent {
private static ILog LOGGER = LogManager.getLogger(SkyWalkingAgent.class);
首先默认会getLogger
然后在初始化配置时
try {
SnifferConfigInitializer.initializeCoreConfig(agentArgs);
} catch (Exception e) {
// try to resolve a new logger, and use the new logger to write the error log here
LogManager.getLogger(SkyWalkingAgent.class)
.error(e, "SkyWalking agent initialized failure. Shutting down.");
return;
} finally {
// refresh logger again after initialization finishes
LOGGER = LogManager.getLogger(SkyWalkingAgent.class);
}
因为会读取用户自定义的配置,所以在catch和finally里重新getLogger,默认的是pattern模式,如果用户配置是json,则重新get了logger,这样在后面的流程里会输出用户希望的日志格式,但是在读取用户配置之前如果抛出了异常,则还是默认的pattern模式
initializeConfig(Config.class);
// reconfigure logger after config initialization
configureLogger();
LOGGER = LogManager.getLogger(SnifferConfigInitializer.class);
if (StringUtil.isEmpty(Config.Agent.SERVICE_NAME)) {
throw new ExceptionInInitializerError("`agent.service_name` is missing.");
}
if (StringUtil.isEmpty(Config.Collector.BACKEND_SERVICE)) {
throw new ExceptionInInitializerError("`collector.backend_service` is missing.");
}
if (Config.Plugin.PEER_MAX_LENGTH <= 3) {
LOGGER.warn(
"PEER_MAX_LENGTH configuration:{} error, the default value of 200 will be used.",
Config.Plugin.PEER_MAX_LENGTH
);
Config.Plugin.PEER_MAX_LENGTH = 200;
}
configureLogger()根据配置决定日志格式,Config.Logging.RESOLVER是用户可定义的配置项,sw将配置映射到config类中
static void configureLogger() {
switch (Config.Logging.RESOLVER) {
case JSON:
LogManager.setLogResolver(new JsonLogResolver());
break;
case PATTERN:
default:
LogManager.setLogResolver(new PatternLogResolver());
}
}
日志级别
首先是skywalking定义的日志级别
public enum LogLevel {
TRACE, DEBUG, INFO, WARN, ERROR, OFF
}
利用枚举的compareTo()方法
public final int compareTo(E o) {
Enum> other = (Enum>)o;
Enum
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
org.apache.skywalking.apm.agent.core.logging.core.AbstractLogger
@Override
public boolean isWarnEnable() {
return LogLevel.WARN.compareTo(Config.Logging.LEVEL) >= 0;
}
只要配置日志级别大于当前使用的日志级别输出就可以打印
@Override
public void warn(String message, Object... objects) {
if (this.isWarnEnable()) {
this.logger(LogLevel.WARN, replaceParam(message, objects), null);
}
}
replaceParam()方法匹配{}占位符
protected String replaceParam(String message, Object... parameters) {
if (message == null) {
return message;
}
int startSize = 0;
int parametersIndex = 0;
int index;
String tmpMessage = message;
while ((index = message.indexOf("{}", startSize)) != -1) {
if (parametersIndex >= parameters.length) {
break;
}
/**
* @Fix the Illegal group reference issue
*/
tmpMessage = tmpMessage.replaceFirst("\\{\\}", Matcher.quoteReplacement(String.valueOf(parameters[parametersIndex++])));
startSize = index + 2;
}
return tmpMessage;
}
设计
logManger
获取日志静态对象使用manager
public class LogManager {
private static LogResolver RESOLVER = new PatternLogResolver();
public static void setLogResolver(LogResolver resolver) {
LogManager.RESOLVER = resolver;
}
public static ILog getLogger(Class> clazz) {
if (RESOLVER == null) {
return NoopLogger.INSTANCE;
}
return LogManager.RESOLVER.getLogger(clazz);
}
public static ILog getLogger(String clazz) {
if (RESOLVER == null) {
return NoopLogger.INSTANCE;
}
return LogManager.RESOLVER.getLogger(clazz);
}
}
resolver里来获取logger对象
下面以parttern为例,json的
PatternLogger
public class PatternLogResolver implements LogResolver {
@Override
public ILog getLogger(Class> clazz) {
return new PatternLogger(clazz, Config.Logging.PATTERN);
}
@Override
public ILog getLogger(String clazz) {
return new PatternLogger(clazz, Config.Logging.PATTERN);
}
}
默认的pattern格式
public static String PATTERN = "%level %timestamp %thread %class : %msg %throwable";
PatternLogger继承AbstractLogger,其中AbstractLogger利用策略模式创建一个存放各类日志关键字替换内容的converter map
Converter
static {
DEFAULT_CONVERTER_MAP.put("thread", ThreadConverter.class);
DEFAULT_CONVERTER_MAP.put("level", LevelConverter.class);
DEFAULT_CONVERTER_MAP.put("agent_name", AgentNameConverter.class);
DEFAULT_CONVERTER_MAP.put("timestamp", DateConverter.class);
DEFAULT_CONVERTER_MAP.put("msg", MessageConverter.class);
DEFAULT_CONVERTER_MAP.put("throwable", ThrowableConverter.class);
DEFAULT_CONVERTER_MAP.put("class", ClassConverter.class);
}
以threadConverter为例
public class ThreadConverter implements Converter {
@Override
public String convert(LogEvent logEvent) {
return Thread.currentThread().getName();
}
@Override
public String getKey() {
return "thread";
}
}
实现converter接口,实现了转换后的内容和获取key值
值得一提的是LiteralConverter没有装在DEFAULT_CONVERTER_MAP里,作用大概是装载匹配表达式不需要转换的文本
public class LiteralConverter implements Converter {
private final String literal;
public LiteralConverter(String literal) {
this.literal = literal;
}
@Override
public String convert(LogEvent logEvent) {
return literal;
}
@Override
public String getKey() {
return "";
}
}
parser
在构建patternLogger时,会通过构造parser类传入匹配模式和converter map来决定需要用到哪些转换器
public void setPattern(String pattern) {
if (StringUtil.isEmpty(pattern)) {
pattern = DEFAULT_PATTERN;
}
this.pattern = pattern;
this.converters = new Parser(pattern, DEFAULT_CONVERTER_MAP).parse();
}
public List
List
StringBuilder buf = new StringBuilder();
while (pointer < patternLength) {
char c = pattern.charAt(pointer);
pointer++;
switch (state) {
case LITERAL_STATE:
handleLiteralState(c, buf, patternConverters);
break;
case KEYWORD_STATE:
handleKeywordState(c, buf, patternConverters);
break;
default:
}
}
state是个枚举,分为LITERAL_STATE, KEYWORD_STATE
LITERAL_STATE我理解为是初始化的状态,KEYWORD_STATE是需要转换器替换掉值的文本,例如info,thread
解析过程类似log4j
首先默认的是LITERAL_STATE,进入处理方法
private void handleLiteralState(char c, StringBuilder buf, List
switch (c) {
case ESCAPE_CHAR:
escape("%", buf);
break;
case PERCENT_CHAR:
addConverter(buf, patternConverters, LiteralConverter.class);
state = State.KEYWORD_STATE;
break;
default:
buf.append(c);
}
}
因为最开始的首字符是%s,则进入PERCENT_CHAR,添加LiteralConverter转换器,同时修改state为KEYWORD_STATE
在下一次指针右移进入handleKeywordState()方法
private void handleKeywordState(char c, StringBuilder buf, List
if (Character.isJavaIdentifierPart(c)) {
buf.append(c);
} else if (c == PERCENT_CHAR) {
addConverterWithKeyword(buf, patternConverters);
} else {
addConverterWithKeyword(buf, patternConverters);
if (c == ESCAPE_CHAR) {
escape("%", buf);
} else {
buf.append(c);
}
state = State.LITERAL_STATE;
}
}
Character.isJavaIdentifierPart(c)判断这个字符是否能作为标识符,可以则添加到buffer里,否则进行判断,如果是%s,则认为是前面遍历的需要对应转换的字符结束了,addConverterWithKeyword()添加到转换器集合中
private void addConverterWithKeyword(StringBuilder buf, List
String keyword = buf.toString();
if (convertMaps.containsKey(keyword)) {
addConverter(buf, patternConverters, convertMaps.get(keyword));
} else {
buf.insert(0, "%");
addConverter(buf, patternConverters, LiteralConverter.class);
}
}
如果在map里取不到则视为用户自定义的文本,注意会在buf首位添加%s,因为这时会认为这一段是需要输出的文本,所以之前的%s也是用户设置需要输出的
c == ESCAPE_CHAR判断后面的字符是否需要转义
判断逻辑如下:
private void escape(String escapeChars, StringBuilder buf, char next) {
if (escapeChars.indexOf(next) >= 0) {
buf.append(next);
} else {
switch (next) {
case '_':
// the \_ sequence is swallowed
break;
case '\\':
buf.append(next);
break;
case 't':
buf.append('\t');
break;
case 'r':
buf.append('\r');
break;
case 'n':
buf.append('\n');
break;
default:
throw new IllegalArgumentException("Illegal char " + next + ". It not allowed as escape characters.");
}
}
}
输出
输出方式
sw根据配置项配置输出到控制台还是文件,默认是文件输出
public static LogOutput OUTPUT = LogOutput.FILE
如果是文件输出则会读取配置读取地址
public static String DIR = "";
如果是空则输出到agent所在路径下/logs
打印顺序
文件输出使用ArrayBlockingQueue来保证日志输出的顺序
控制台输出本身就是加了synchronized的锁
异常打印
在异常的转换器中
public class ThrowableConverter implements Converter {
@Override
public String convert(LogEvent logEvent) {
Throwable t = logEvent.getThrowable();
return t == null ? "" : format(t);
}
public static String format(Throwable t) {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
t.printStackTrace(new java.io.PrintWriter(buf, true));
String expMessage = buf.toString();
try {
buf.close();
} catch (IOException e) {
e.printStackTrace();
}
return Constants.LINE_SEPARATOR + expMessage;
}
@Override
public String getKey() {
return "throwable";
}
}
t.printStackTrace(new java.io.PrintWriter(buf, true));
这行代码的作用是将异常堆栈信息转存到buf里
文章来源
发表评论