com.quartzdesk.api.agent.log.log4j2.Log4j2InterceptionAppender Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of quartzdesk-api Show documentation
Show all versions of quartzdesk-api Show documentation
QuartzDesk Public API library required for QuartzDesk Standard and Enterprise edition installations. This library must be placed on the classpath of the Quartz scheduler based application that is managed by QuartzDesk. It is important that this library is loaded by the same classloader that loads the Quartz scheduler API used by the application.
/*
* Copyright (c) 2013-2019 QuartzDesk.com. All Rights Reserved.
* QuartzDesk.com PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.quartzdesk.api.agent.log.log4j2;
import com.quartzdesk.api.agent.log.LoggingInterceptorWrapper;
import com.quartzdesk.api.agent.log.LoggingInterceptorWrapperException;
import com.quartzdesk.api.agent.log.WorkerThreadLoggingInterceptorRegistry;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.logging.log4j.core.layout.PatternLayout;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* Implementation of a Log4j2 appender that passes the log events to the QuartzDesk JVM agent that intercepts log
* messages produced by executed jobs.
*
* ==== Example log4j2.xml ====
* ...
*
* <QuartzDesk name="QUARTZDESK_JVM_AGENT">
* <PatternLayout pattern="[%d{ISO8601}] %-5p [%t] [%C:%L] - %m%n"/>
* <filters>
* <ThresholdFilter level="trace"/>
* </filters>
* </QuartzDesk>
*
* ...
*
* <root level="warn">
* ...
* <appender-ref ref="QUARTZDESK_JVM_AGENT"/>
* </root>
*
*
* This implementation is not statically bound to the QuartzDesk JVM Agent API, nor to the domain object API.
* Therefore it is safe to use this appender on JVMs running without an installed QuartzDesk JVM Agent.
*
* @version $Id:$
* @see LoggingInterceptorWrapper
*/
@Plugin(
name = "QuartzDeskJvmAgent",
category = Core.CATEGORY_NAME,
elementType = Appender.ELEMENT_TYPE
)
public class Log4j2InterceptionAppender
extends AbstractAppender
{
/*
* !!! IMPORTANT NOTE !!!
*
* Log4j2 appenders must be registered in org.apache.logging.log4j.core.config.plugins.Log4j2Plugins.dat
* resources located on the classpath. Log4j2Plugins.dat is a binary file that can be generated using
* Maven and the org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor annotation processor.
*/
/**
* Flag indicating if the LogEvent class in the Log4j2 API provides the getThreadId() method. This method is
* available since version 2.6.
*/
private boolean eventHasGetThreadIdMethod;
private LoggingInterceptorWrapper loggingInterceptor;
/**
* Creates a new {@link Log4j2InterceptionAppender} instance.
*
* @param name The Appender name.
* @param filter The Filter to associate with the Appender.
* @param layout The layout to use to format the event.
* @param ignoreExceptions If true, exceptions will be logged and suppressed. If false errors will be logged and
* then passed to the application.
*/
protected Log4j2InterceptionAppender( String name, Filter filter, Layout extends Serializable> layout,
boolean ignoreExceptions )
{
super( name, filter, layout, ignoreExceptions );
LOGGER.info( "Starting " + Log4j2InterceptionAppender.class.getName() + ", name=" + name );
try
{
// LogEvent.getThreadId() method is available in Log4j2 API >= 2.6
LogEvent.class.getMethod( "getThreadId" );
eventHasGetThreadIdMethod = true;
}
catch ( NoSuchMethodException e )
{
eventHasGetThreadIdMethod = false;
}
try
{
loggingInterceptor = LoggingInterceptorWrapper.create();
}
catch ( LoggingInterceptorWrapperException e )
{
// LOGGER.warn( "Cannot initialize " + LoggingInterceptorWrapper.class.getName() + ". Appender " + getName() +
// " will be disabled." + Constants.LINE_SEP, e );
LOGGER.warn( "Cannot initialize " + LoggingInterceptorWrapper.class.getName() + ". Appender " + getName() +
" will be disabled. Cause: " + e.getMessage() );
}
}
@PluginFactory
public static Log4j2InterceptionAppender createAppender(
@PluginAttribute( "name" ) String name,
@PluginAttribute( "ignoreExceptions" ) boolean ignoreExceptions,
@PluginElement( "layout" ) Layout extends Serializable> layout,
@PluginElement( "filters" ) Filter filter )
{
if ( name == null )
{
LOGGER.error( "No name provided for QuartzDeskAppender appender." );
return null;
}
if ( layout == null )
{
//noinspection unchecked
layout = PatternLayout.createDefaultLayout();
}
return new Log4j2InterceptionAppender( name, filter, layout, ignoreExceptions );
}
@Override
public void append( LogEvent event )
{
Long threadId = getThreadId( event );
/*
* The emitter thread can be a worker thread (a thread created / used by the main job thread).
*/
Long jobThreadId = WorkerThreadLoggingInterceptorRegistry.INSTANCE.getJobThreadForWorkerThread( threadId );
if ( jobThreadId != null )
{
threadId = jobThreadId;
}
if ( loggingInterceptor != null && loggingInterceptor.isIntercepting( threadId ) )
{
LoggingInterceptorWrapper.MessagePriority priority = convertLevel2Priority( event.getLevel() );
Layout> layout = getLayout();
if ( layout instanceof AbstractStringLayout )
{
byte[] eventBytes = layout.toByteArray( event );
// we need to determine the charset of the layout that was used to extract bytes from the formatted log event
Charset charset = getLayoutCharset( (AbstractStringLayout) layout );
String message = new String( eventBytes, charset );
loggingInterceptor.intercept( threadId, priority, message );
}
else
{
LOGGER.error( "Layout in QuartzDeskAppender must extend AbstractStringLayout. It is typically PatternLayout." );
}
}
}
/**
* Returns the {@link LoggingInterceptorWrapper.MessagePriority} for the specified Log4J level.
*
* @param level a log4j level.
* @return the {@link LoggingInterceptorWrapper.MessagePriority} for the specified Log4J level.
*/
protected LoggingInterceptorWrapper.MessagePriority convertLevel2Priority( Level level )
{
// OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL;
if ( level.equals( Level.TRACE ) )
return LoggingInterceptorWrapper.MessagePriority.TRACE;
else if ( level.equals( Level.DEBUG ) )
return LoggingInterceptorWrapper.MessagePriority.DEBUG;
else if ( level.equals( Level.INFO ) )
return LoggingInterceptorWrapper.MessagePriority.INFO;
else if ( level.equals( Level.WARN ) )
return LoggingInterceptorWrapper.MessagePriority.WARN;
else if ( level.equals( Level.ERROR ) )
return LoggingInterceptorWrapper.MessagePriority.ERROR;
else if ( level.equals( Level.FATAL ) ) // FATAL mapped to ERROR
return LoggingInterceptorWrapper.MessagePriority.ERROR;
else
{
LOGGER.warn( "Cannot map logging level: " + level + ". Using default message priority: " +
LoggingInterceptorWrapper.DEFAULT_MESSAGE_PRIORITY );
return LoggingInterceptorWrapper.DEFAULT_MESSAGE_PRIORITY;
}
}
/**
* Returns the {@link Charset} used by the specified layout to format log events and produce their byte array
* representations.
*
* @param layout a layout.
* @return the {@link Charset}.
*/
protected Charset getLayoutCharset( L layout )
{
try
{
// getCharset method is protected
Method method = getCharsetMethod( layout.getClass() );
if ( method != null )
{
method.setAccessible( true );
Object result = method.invoke( layout );
if ( result instanceof Charset )
{
return (Charset) result;
}
}
}
catch ( InvocationTargetException | IllegalAccessException e )
{
// should not happen
}
// fall-back charset
return StandardCharsets.UTF_8;
}
/**
* Returns the {@code getCharset} method from the specified class, or null if not found.
*
* @param clazz a class.
* @return the {@code getCharset} method, or null if not found.
*/
private Method getCharsetMethod( Class> clazz )
{
while ( clazz != null )
{
try
{
return clazz.getDeclaredMethod( "getCharset" );
}
catch ( NoSuchMethodException e )
{
// search in super-class
clazz = clazz.getSuperclass();
}
}
return null;
}
/**
* Returns the thread ID of the thread that produced the specified log event.
*
* @param event the log event.
* @return the thread ID of the thread that produced the specified log event.
*/
private long getThreadId( LogEvent event )
{
// Log4j2 API provides LogEvent.getThreadId() method from version 2.6
return eventHasGetThreadIdMethod ? event.getThreadId() : Thread.currentThread().getId();
}
}