All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.quartzdesk.api.agent.log.log4j2.ClassicLog4j2InterceptionAppender Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 5.0.3
Show newest version
/*
 * 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.agent.Agent;
import com.quartzdesk.agent.IAgent;
import com.quartzdesk.agent.api.domain.model.log.LoggingEvent;
import com.quartzdesk.agent.api.domain.model.log.LoggingEventPriority;
import com.quartzdesk.agent.api.scheduler.common.log.IExecutingJobLoggingInterceptor;
import com.quartzdesk.api.agent.log.WorkerThreadLoggingInterceptorRegistry;

import org.apache.logging.log4j.Level;
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.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 ====
 * ...
 *
 * <QuartzDeskClassic name="QUARTZDESK_JVM_AGENT">
 *   <PatternLayout pattern="[%d{ISO8601}] %-5p [%t] [%C:%L] - %m%n"/>
 *   <filters>
 *     <ThresholdFilter level="trace"/>
 *   </filters>
 * </QuartzDeskClassic>
 *
 * ...
 *
 * <root level="warn">
 *   ...
 *   <appender-ref ref="QUARTZDESK_JVM_AGENT"/>
 * </root>
 * 
* * This implementation is statically bound to the QuartzDesk JVM Agent API and to the QuartzDesk domain object * API. Therefore this appender requires a JVM with an installed QuartzDesk JVM Agent. * * @version $Id:$ * @see IExecutingJobLoggingInterceptor */ // // JMO: Commented out so as to prevent ClassicLog4j2InterceptionAppender from being included in the // Log4j2Plugins.dat configuration file. The problem is that Log4j2 eagerly loads all plugin classes // and if the QuartzDesk JVM Agent does not happen to be on the classpath, then a ClassNotFoundException // is thrown during application startup. // // Therefore this appender ic currently unusable. // //@Plugin( // name = "QuartzDeskJvmAgent", // category = Core.CATEGORY_NAME, // elementType = Appender.ELEMENT_TYPE //) @Deprecated public class ClassicLog4j2InterceptionAppender 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. */ private static final LoggingEventPriority DEFAULT_LOGGING_INTERCEPTOR_EVENT_PRIORITY = LoggingEventPriority.INFO; /** * 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 IExecutingJobLoggingInterceptor loggingInterceptor; /** * Creates a new {@link ClassicLog4j2InterceptionAppender} 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 ClassicLog4j2InterceptionAppender( String name, Filter filter, Layout layout, boolean ignoreExceptions ) { super( name, filter, layout, ignoreExceptions ); try { // LogEvent.getThreadId() method is available in Log4j2 API >= 2.6 LogEvent.class.getMethod( "getThreadId" ); eventHasGetThreadIdMethod = true; } catch ( NoSuchMethodException e ) { eventHasGetThreadIdMethod = false; } IAgent agent = Agent.getInstance(); LOGGER.info( "Starting " + ClassicLog4j2InterceptionAppender.class.getName() + " for " + agent ); loggingInterceptor = agent.getRuntime().getExecutingJobLoggingInterceptor(); } @PluginFactory public static ClassicLog4j2InterceptionAppender createAppender( @PluginAttribute( "name" ) String name, @PluginAttribute( "ignoreExceptions" ) boolean ignoreExceptions, @PluginElement( "layout" ) Layout 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 ClassicLog4j2InterceptionAppender( 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 ) ) { LoggingEvent loggingInterceptorEvent = new LoggingEvent() .withPriority( 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 ); loggingInterceptorEvent.setMessage( new String( eventBytes, charset ) ); loggingInterceptor.intercept( threadId, loggingInterceptorEvent ); } else { LOGGER.error( "Layout in QuartzDeskAppender must extend AbstractStringLayout. It is typically PatternLayout." ); } } } /** * Returns the {@link LoggingEventPriority} for the specified Log4J level. * * @param level a log4j level. * @return the {@link LoggingEventPriority} for the specified Log4J level. */ protected LoggingEventPriority convertLevel2Priority( Level level ) { // OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL; if ( level.equals( Level.TRACE ) ) return LoggingEventPriority.TRACE; else if ( level.equals( Level.DEBUG ) ) return LoggingEventPriority.DEBUG; else if ( level.equals( Level.INFO ) ) return LoggingEventPriority.INFO; else if ( level.equals( Level.WARN ) ) return LoggingEventPriority.WARN; else if ( level.equals( Level.ERROR ) ) return LoggingEventPriority.ERROR; else if ( level.equals( Level.FATAL ) ) // FATAL mapped to ERROR return LoggingEventPriority.ERROR; else { LOGGER.warn( "Cannot map logging level: " + level + ". Using default logging interceptor event priority: " + DEFAULT_LOGGING_INTERCEPTOR_EVENT_PRIORITY ); return DEFAULT_LOGGING_INTERCEPTOR_EVENT_PRIORITY; } } /** * Returns the {@link Charset} used by the specified layout to format log events and produce their byte array * representations. * * @param layout a layout. * @param the layout type. * @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(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy