2 * SPDX-License-Identifier: LGPL-2.1-only
4 * Copyright (C) 2015-2022 EfficiOS Inc.
5 * Copyright (C) 2015 Alexandre Montplaisir <alexmonthy@efficios.com>
6 * Copyright (C) 2014 Christian Babeux <christian.babeux@efficios.com>
9 package org.lttng.ust.agent.log4j2;
11 import java.io.IOException;
12 import java.util.Collection;
14 import java.util.Map.Entry;
15 import java.util.concurrent.TimeUnit;
16 import java.util.concurrent.atomic.AtomicLong;
18 import org.apache.logging.log4j.core.Appender;
19 import org.apache.logging.log4j.core.Core;
20 import org.apache.logging.log4j.core.Filter;
21 import org.apache.logging.log4j.core.LogEvent;
22 import org.apache.logging.log4j.core.appender.AbstractAppender;
23 import org.apache.logging.log4j.core.config.Property;
24 import org.apache.logging.log4j.core.config.plugins.Plugin;
25 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
26 import org.apache.logging.log4j.core.config.plugins.PluginElement;
27 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
28 import org.apache.logging.log4j.message.Message;
29 import org.lttng.ust.agent.ILttngAgent.Domain;
30 import org.lttng.ust.agent.ILttngHandler;
31 import org.lttng.ust.agent.context.ContextInfoSerializer;
34 * LTTng-UST Log4j 2.x log handler.
36 * Applications can attach this appender to their
37 * {@link org.apache.log4j.Logger} to have it generate UST events from logging
38 * events received through the logger.
40 * It sends its events to UST via the JNI library "liblttng-ust-log4j-jni.so".
41 * Make sure this library is available before using this appender.
44 @Plugin(name = LttngLogAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = false)
45 public final class LttngLogAppender extends AbstractAppender implements ILttngHandler {
48 * The name of the appender in the configuration.
50 public static final String PLUGIN_NAME = "Lttng";
52 private static final String SHARED_OBJECT_NAME = "lttng-ust-log4j-jni";
55 * Number of events logged (really sent through JNI) by this handler
57 private final AtomicLong eventCount = new AtomicLong(0);
59 private final LttngLog4j2Agent agent;
64 * @param name The name of the Appender.
65 * @param domain The LTTng-UST agent domain 'LOG4J' / 'LOG4J2'.
66 * @param filter The Filter or null.
67 * @param ignoreExceptions If {@code "true"} exceptions encountered when
68 * appending events are logged; otherwise they are
69 * propagated to the caller.
71 * @throws IOException This handler requires the
72 * lttng-ust-log4j-jni.so native library,
73 * through which it will send the trace events.
74 * This exception is thrown if this library
76 * @throws IllegalArgumentException If the provided domain is unsupported.
77 * @throws SecurityException We will forward any SecurityExcepion that
78 * may be thrown when trying to load the JNI
81 protected LttngLogAppender(String name, LttngLog4j2Agent.Domain domain, Filter filter, boolean ignoreExceptions)
82 throws IOException, IllegalArgumentException, SecurityException {
84 super(name, filter, null, ignoreExceptions, Property.EMPTY_ARRAY);
86 /* Initialize LTTng UST tracer. */
88 System.loadLibrary(SHARED_OBJECT_NAME); // $NON-NLS-1$
89 } catch (UnsatisfiedLinkError e) {
90 throw new IOException(e);
93 /* Register to the relevant agent. */
94 if (domain == LttngLog4j2Agent.Domain.LOG4J) {
95 agent = LttngLog4j2Agent.getInstance();
97 throw new IllegalArgumentException("Unsupported domain '" + domain + "'");
100 agent.registerHandler(this);
104 * Create an LttngLogAppender.
106 * @param name The name of the Appender, null returns null.
107 * @param domain The LTTng-UST agent domain 'LOG4J' / 'LOG4J2'.
108 * @param ignoreExceptions If {@code "true"} (default) exceptions encountered
109 * when appending events are logged; otherwise they are
110 * propagated to the caller.
111 * @param filter The Filter or null.
113 * @return A new LttngLogAppender, null if the name was null or the domain is
117 public static LttngLogAppender createAppender(@PluginAttribute("name") String name,
118 @PluginAttribute("domain") String domain, @PluginAttribute("ignoreExceptions") Boolean ignoreExceptions,
119 @PluginElement("Filters") Filter filter) {
122 LOGGER.error("No name provided for LttngLogAppender");
126 if (domain == null) {
127 LOGGER.error("No domain provided for LttngLogAppender");
131 if (ignoreExceptions == null) {
132 ignoreExceptions = true;
135 /* Parse the domain string */
136 LttngLog4j2Agent.Domain parsedDomain;
138 parsedDomain = LttngLog4j2Agent.Domain.valueOf(domain.toUpperCase());
139 } catch (IllegalArgumentException e) {
140 LOGGER.error("Invalid domain '{}' for LttngLogAppender", domain);
144 /* Create the appender and handle the possible failures. */
145 LttngLogAppender newAppender;
147 newAppender = new LttngLogAppender(name, parsedDomain, filter, ignoreExceptions);
148 } catch (IllegalArgumentException e) {
149 LOGGER.error("Invalid domain '{}' for LttngLogAppender", parsedDomain);
151 } catch (SecurityException e) {
152 LOGGER.error("Security error trying to load '{}' JNI library for LttngLogAppender", SHARED_OBJECT_NAME);
154 } catch (IOException e) {
155 LOGGER.error("Failed to load '{}' JNI library for LttngLogAppender", SHARED_OBJECT_NAME);
163 public synchronized void close() {
164 agent.unregisterHandler(this);
172 getStatusLogger().debug("Appender Lttng stopped");
176 public boolean stop(final long timeout, final TimeUnit timeUnit) {
178 boolean status = super.stop(timeout, timeUnit);
180 getStatusLogger().debug("Appender Lttng stopped with status " + status);
186 * Get the number of events logged by this handler so far. This means the number
187 * of events actually sent through JNI to UST.
189 * @return The number of events logged so far
192 public long getEventCount() {
193 return eventCount.get();
197 public void append(LogEvent event) {
199 * Check if the current message should be logged, according to the UST session
202 String loggername = event.getLoggerName();
203 if (loggername == null || !agent.isEventEnabled(loggername)) {
208 * Default value if the Message is null.
212 Message eventMessage = event.getMessage();
213 if (eventMessage != null) {
214 message = eventMessage.getFormattedMessage();
218 * Default values if the StackTraceElement is null.
220 String classname = "";
221 String methodname = "";
222 String filename = "";
225 StackTraceElement ste = event.getSource();
227 classname = ste.getClassName();
228 methodname = ste.getMethodName();
229 filename = ste.getFileName();
230 line = ste.getLineNumber();
233 /* Retrieve all the requested context information we can find. */
234 Collection<Entry<String, Map<String, Integer>>> enabledContexts = agent.getEnabledAppContexts();
235 ContextInfoSerializer.SerializedContexts contextInfo = ContextInfoSerializer
236 .queryAndSerializeRequestedContexts(enabledContexts);
238 eventCount.incrementAndGet();
240 LttngLog4j2Api.tracepointWithContext(message, loggername, classname, methodname, filename, line,
241 event.getTimeMillis(), event.getLevel().intLevel(), event.getThreadName(),
242 contextInfo.getEntriesArray(), contextInfo.getStringsArray());