日志选择

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 self = this;

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 parse() {

List patternConverters = new ArrayList();

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 patternConverters) {

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 patternConverters) {

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 patternConverters) {

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里

文章来源

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: