Based on SkyWalking Java Agent version 8.8.0

SkyWalkingAgent class is the entry class of SkyWalking Java Agent and the premain method is located in the class. Today we are not going to analyze the Premain method, but the logging framework that any application needs. Instead of relying on existing logging frameworks such as Log4j, SkyWalking Java Agent implements one of its own.

/** * The main entrance of sky-walking agent, based on javaagent mechanism. */
public class SkyWalkingAgent {
    private static ILog LOGGER = LogManager.getLogger(SkyWalkingAgent.class);

    /** * Main entrance. Use byte-buddy transform to enhance all classes, which define in plugins. */
    public static void premain(String agentArgs, Instrumentation instrumentation) throws PluginException {
        // omit some code....}}Copy the code

The first line of the SkyWalkingAgent class is its own logging component, which looks like any other common logging component.

private static ILog LOGGER = LogManager.getLogger(SkyWalkingAgent.class);
Copy the code

The first step is to get a concrete implementation of the ILog interface through the LogManager LogManager, which is typical Java polymorphism – references to the parent class (interface) point to a subclass implementation.

ILog interface

The ILog interface provides a common way to print logs and defines a set of log usage specifications.

/** * The Log interface. It's very easy to understand, like any other log-component. Do just like log4j or log4j2 does. * 

*/

public interface ILog { void info(String format); void info(String format, Object... arguments); void info(Throwable t, String format, Object... arguments); void debug(String format); void debug(String format, Object... arguments); // omit some code.... } Copy the code

LogManager

Let’s look at the implementation of the LogManager class

/**
 * LogManager is the {@link LogResolver} implementation manager. By using {@link LogResolver}, {@link
 * LogManager#getLogger(Class)} returns a {@link ILog} implementation. This module use this class as the main entrance,
 * and block the implementation detail about log-component. In different modules, like server or sniffer, it will use
 * different implementations.
 *
 * <p> If no {@link LogResolver} is registered, return {@link NoopLogger#INSTANCE} to avoid
 * {@link NullPointerException}. If {@link LogManager#setLogResolver(LogResolver)} is called twice, the second will
 * override the first without any warning or exception.
 *
 * <p> Created by xin on 2016/11/10.
 */
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;
        }
        returnLogManager.RESOLVER.getLogger(clazz); }}Copy the code

The LogManager Class is the manager of the LogResolver implementation Class. Using LogResolver, logManager.getLogger (Class) returns an implementation of the ILog interface. The LogManager Class is the main entry to the logging component. Internally encapsulates the implementation details of the logging component.

LogManager provides the setLogResolver method to register the specified LogResolver. If LogResolver is set to NULL, the NoopLogger instance is returned.

LogResolver simply returns an implementation of the ILog interface

/ * * * {@link LogResolver} just do only one thing: return the {@link ILog} implementation.
 * <p>
 */
public interface LogResolver {
    / * * *@param clazz the class is showed in log message.
     * @return {@link ILog} implementation.
     */
    ILog getLogger(Class
        clazz);

    / * * *@param clazz the class is showed in log message.
     * @return {@link ILog} implementation.
     */
    ILog getLogger(String clazz);
}
Copy the code

The LogResolver interface currently provides two implementation classes: PatternLogResolver and JsonLogResolver return PatternLogger and JsonLogger, respectively.

ILog interface implementation class

  • NoopLogger enumeration

NoopLogger directly inherits ILog, NoopLogger only implements ILog interface, and all methods are empty implementations. NoopLogger exists to prevent NullPointerExceptions. Because the caller can set a different LogResolver LogResolver through the setLogResolver method of LogManager, if null, NoopLogger is returned as the default implementation of the ILog interface.

  • AbstractLogger abstract class
    • PatternLogger
    • JsonLogger

AbstractLogger

AbstractLogger Abstract class AbstractLogger abstract class AbstractLogger abstract class AbstractLogger abstract class AbstractLogger abstract class AbstractLogger

  1. It holds the logger class name targetClass;
  2. Check the log level.
  3. Parses the user input message and replaces {} with the corresponding parameter value;
  4. Provides an abstract method for formatting log content, which requires specific subclasses. Currently, pattern and JSON are supported. The default value is Pattern.

Format of the output log

%level %timestamp %thread %class : %msg %throwable
Copy the code

Each represents the following meaning:

%level log level %timestamp timestamp yyyy-mm-dd HH: MM :ss:SSS format %thread thread name % MSG user-specified log information %class class name %throwable exception information %agent_name Agent name.Copy the code

SkyWalking Java Agent provides converter parsing for each entry in the log format, such as ThreadConverter, LevelConverter, and so on.

/** * An abstract class to simplify the real implementation of the loggers. * It hold the class name of the logger, and is responsible for log level check, * message interpolation, etc. */
public abstract class AbstractLogger implements ILog {
    public static final Map<String, Class<? extends Converter>> DEFAULT_CONVERTER_MAP = new HashMap<>();
    protected List<Converter> converters = new ArrayList<>();

    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);
    }
 
    // omit some code....
}    
Copy the code

For example, the ThreadConverter class parses %thread and outputs the name of the current thread.

/** * Just return the Thread.currentThread().getName() */
public class ThreadConverter implements Converter {
    @Override
    public String convert(LogEvent logEvent) {
        return Thread.currentThread().getName();
    }

    @Override
    public String getKey(a) {
        return "thread"; }}Copy the code

Log Format

The AbstractLogger class implements the ILog interface, implements the print log method, and ultimately calls the following generic method

protected void logger(LogLevel level, String message, Throwable e) {
    WriterFactory.getLogWriter().write(this.format(level, message, e));
}
Copy the code

The Logger method internally calls the format method, which is an abstract method that is left to subclasses to implement the format for logging output. Strings that return strings are printed to files or standard output.

 /**
 * The abstract method left for real loggers.
 * Any implementation MUST return string, which will be directly transferred to log destination,
 * i.e. log files OR stdout
 *
 * @param level log level
 * @param message log message, which has been interpolated with user-defined parameters.
 * @param e throwable if exists
 * @return string representation of the log, for example, raw json string for {@link JsonLogger}
 */
protected abstract String format(LogLevel level, String message, Throwable e);
Copy the code

Abstract classes implement methods in interfaces, and each implementation calls an abstract method that lets subclasses implement concrete business logic.

The following is an implementation of the Format method from one of the AbstractLogger abstract classes, the PatternLogger class, which calls the Converter corresponding to the Converter to concatenate the log into a string.

@Override
protected String format(LogLevel level, String message, Throwable t) {
    LogEvent logEvent = new LogEvent(level, message, t, targetClass);
    StringBuilder stringBuilder = new StringBuilder();
    for (Converter converter : this.converters) {
        stringBuilder.append(converter.convert(logEvent));
    }
    return stringBuilder.toString();
}

public void setPattern(String pattern) {
    if (StringUtil.isEmpty(pattern)) {
        pattern = DEFAULT_PATTERN;
    }
    this.pattern = pattern;
    this.converters = new Parser(pattern, DEFAULT_CONVERTER_MAP).parse();
}
Copy the code

The PatternLogger converts the log format pattern to the corresponding Converter, which is implemented in the patternLogger. setPattern method, internally calling the parser. parse method, See Parser class source code for detailed implementation.

WriterFactory

Now that the log has been assembled, it is time to output the log content to the specified location. The WriterFactory factory class is the implementation class that creates the IWriter interface to write the log information to the destination.

public interface IWriter {
    void write(String message);
}
Copy the code

The IWriter interface has two implementations: FileWriter and SystemOutWriter

  • FileWriter: Using a blocking queue ArrayBlockingQueue as a buffer, the thread pool ScheduledExecutorService asynchronously retrieves logs from the queue and writes them to a log file using FileOutputStream. Typical producer-consumer model;

  • SystemOutWriter: Outputs logs to the console.

How FileWriter works

How does FileWriter write logs to log files

  • The FileWriter is responsible for writing log information to the ArrayBlockingQueue queue

  • ScheduledExecutorService a) Writes all log information to a file from ArrayBlockingQueue every second; B) Check whether the file size exceeds the maximum config.logging. MAX_FILE_SIZE; C) Rename the current file if the maximum value is exceeded.

/**
 * Write log to the queue. W/ performance trade off.
 *
 * @param message to log
 */
@Override
public void write(String message) {
    logBuffer.offer(message);
}
Copy the code

Design patterns involved in the logging component:

  • The factory pattern
  • Singleton pattern (lazy pattern, enumerated classes)
  • Producer-consumer model

Well, today’s content is shared here, more details see SkyWalking Java Agent source code

After you see if you can imitate writing a logging component