Commit | Line | Data |
---|---|---|
464c4756 MJ |
1 | /* |
2 | * SPDX-License-Identifier: LGPL-2.1-only | |
3 | * | |
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> | |
7 | */ | |
8 | ||
9 | package org.lttng.ust.agent.log4j2; | |
10 | ||
11 | import java.io.IOException; | |
12 | import java.util.Collection; | |
13 | import java.util.Map; | |
14 | import java.util.Map.Entry; | |
15 | import java.util.concurrent.TimeUnit; | |
16 | import java.util.concurrent.atomic.AtomicLong; | |
17 | ||
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; | |
8063c7b0 | 28 | import org.apache.logging.log4j.message.Message; |
37d351b8 | 29 | import org.lttng.ust.agent.ILttngAgent.Domain; |
464c4756 MJ |
30 | import org.lttng.ust.agent.ILttngHandler; |
31 | import org.lttng.ust.agent.context.ContextInfoSerializer; | |
32 | ||
33 | /** | |
34 | * LTTng-UST Log4j 2.x log handler. | |
35 | * | |
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. | |
39 | * | |
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. | |
42 | * | |
43 | */ | |
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 { | |
46 | ||
47 | /** | |
48 | * The name of the appender in the configuration. | |
49 | */ | |
50 | public static final String PLUGIN_NAME = "Lttng"; | |
51 | ||
52 | private static final String SHARED_OBJECT_NAME = "lttng-ust-log4j-jni"; | |
53 | ||
54 | /** | |
55 | * Number of events logged (really sent through JNI) by this handler | |
56 | */ | |
57 | private final AtomicLong eventCount = new AtomicLong(0); | |
58 | ||
59 | private final LttngLog4j2Agent agent; | |
60 | ||
61 | /** | |
62 | * Constructor | |
63 | * | |
64 | * @param name The name of the Appender. | |
37d351b8 | 65 | * @param domain The LTTng-UST agent domain 'LOG4J' / 'LOG4J2'. |
464c4756 | 66 | * @param filter The Filter or null. |
37d351b8 MJ |
67 | * @param ignoreExceptions If {@code "true"} exceptions encountered when |
68 | * appending events are logged; otherwise they are | |
464c4756 MJ |
69 | * propagated to the caller. |
70 | * | |
37d351b8 MJ |
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 | |
75 | * cannot be found. | |
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 | |
79 | * library. | |
464c4756 | 80 | */ |
37d351b8 MJ |
81 | protected LttngLogAppender(String name, LttngLog4j2Agent.Domain domain, Filter filter, boolean ignoreExceptions) |
82 | throws IOException, IllegalArgumentException, SecurityException { | |
464c4756 MJ |
83 | |
84 | super(name, filter, null, ignoreExceptions, Property.EMPTY_ARRAY); | |
85 | ||
86 | /* Initialize LTTng UST tracer. */ | |
87 | try { | |
88 | System.loadLibrary(SHARED_OBJECT_NAME); // $NON-NLS-1$ | |
89 | } catch (UnsatisfiedLinkError e) { | |
90 | throw new IOException(e); | |
91 | } | |
92 | ||
93 | /* Register to the relevant agent. */ | |
37d351b8 MJ |
94 | if (domain == LttngLog4j2Agent.Domain.LOG4J) { |
95 | agent = LttngLog4j2Agent.getInstance(); | |
96 | } else { | |
97 | throw new IllegalArgumentException("Unsupported domain '" + domain + "'"); | |
98 | } | |
99 | ||
464c4756 MJ |
100 | agent.registerHandler(this); |
101 | } | |
102 | ||
103 | /** | |
104 | * Create an LttngLogAppender. | |
105 | * | |
106 | * @param name The name of the Appender, null returns null. | |
37d351b8 | 107 | * @param domain The LTTng-UST agent domain 'LOG4J' / 'LOG4J2'. |
464c4756 MJ |
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. | |
112 | * | |
37d351b8 MJ |
113 | * @return A new LttngLogAppender, null if the name was null or the domain is |
114 | * null or invalid. | |
464c4756 MJ |
115 | */ |
116 | @PluginFactory | |
117 | public static LttngLogAppender createAppender(@PluginAttribute("name") String name, | |
37d351b8 MJ |
118 | @PluginAttribute("domain") String domain, @PluginAttribute("ignoreExceptions") Boolean ignoreExceptions, |
119 | @PluginElement("Filters") Filter filter) { | |
464c4756 MJ |
120 | |
121 | if (name == null) { | |
122 | LOGGER.error("No name provided for LttngLogAppender"); | |
123 | return null; | |
124 | } | |
125 | ||
37d351b8 MJ |
126 | if (domain == null) { |
127 | LOGGER.error("No domain provided for LttngLogAppender"); | |
128 | return null; | |
129 | } | |
130 | ||
464c4756 MJ |
131 | if (ignoreExceptions == null) { |
132 | ignoreExceptions = true; | |
133 | } | |
134 | ||
37d351b8 MJ |
135 | /* Parse the domain string */ |
136 | LttngLog4j2Agent.Domain parsedDomain; | |
137 | try { | |
138 | parsedDomain = LttngLog4j2Agent.Domain.valueOf(domain.toUpperCase()); | |
139 | } catch (IllegalArgumentException e) { | |
140 | LOGGER.error("Invalid domain '{}' for LttngLogAppender", domain); | |
141 | return null; | |
142 | } | |
143 | ||
144 | /* Create the appender and handle the possible failures. */ | |
145 | LttngLogAppender newAppender; | |
146 | try { | |
147 | newAppender = new LttngLogAppender(name, parsedDomain, filter, ignoreExceptions); | |
148 | } catch (IllegalArgumentException e) { | |
149 | LOGGER.error("Invalid domain '{}' for LttngLogAppender", parsedDomain); | |
150 | newAppender = null; | |
151 | } catch (SecurityException e) { | |
152 | LOGGER.error("Security error trying to load '{}' JNI library for LttngLogAppender", SHARED_OBJECT_NAME); | |
153 | newAppender = null; | |
154 | } catch (IOException e) { | |
155 | LOGGER.error("Failed to load '{}' JNI library for LttngLogAppender", SHARED_OBJECT_NAME); | |
156 | newAppender = null; | |
157 | } | |
158 | ||
159 | return newAppender; | |
464c4756 MJ |
160 | } |
161 | ||
162 | @Override | |
163 | public synchronized void close() { | |
164 | agent.unregisterHandler(this); | |
165 | } | |
166 | ||
167 | @Override | |
168 | public void stop() { | |
169 | close(); | |
170 | super.stop(); | |
171 | ||
172 | getStatusLogger().debug("Appender Lttng stopped"); | |
173 | } | |
174 | ||
175 | @Override | |
176 | public boolean stop(final long timeout, final TimeUnit timeUnit) { | |
177 | close(); | |
178 | boolean status = super.stop(timeout, timeUnit); | |
179 | ||
180 | getStatusLogger().debug("Appender Lttng stopped with status " + status); | |
181 | ||
182 | return status; | |
183 | } | |
184 | ||
185 | /** | |
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. | |
188 | * | |
189 | * @return The number of events logged so far | |
190 | */ | |
191 | @Override | |
192 | public long getEventCount() { | |
193 | return eventCount.get(); | |
194 | } | |
195 | ||
196 | @Override | |
197 | public void append(LogEvent event) { | |
198 | /* | |
199 | * Check if the current message should be logged, according to the UST session | |
200 | * settings. | |
201 | */ | |
8063c7b0 MJ |
202 | String loggername = event.getLoggerName(); |
203 | if (loggername == null || !agent.isEventEnabled(loggername)) { | |
464c4756 MJ |
204 | return; |
205 | } | |
206 | ||
8063c7b0 MJ |
207 | /* |
208 | * Default value if the Message is null. | |
209 | */ | |
210 | String message = ""; | |
211 | ||
212 | Message eventMessage = event.getMessage(); | |
213 | if (eventMessage != null) { | |
214 | message = eventMessage.getFormattedMessage(); | |
215 | } | |
216 | ||
464c4756 MJ |
217 | /* |
218 | * Default values if the StackTraceElement is null. | |
219 | */ | |
220 | String classname = ""; | |
221 | String methodname = ""; | |
222 | String filename = ""; | |
223 | int line = -1; | |
224 | ||
225 | StackTraceElement ste = event.getSource(); | |
226 | if (ste != null) { | |
227 | classname = ste.getClassName(); | |
228 | methodname = ste.getMethodName(); | |
229 | filename = ste.getFileName(); | |
230 | line = ste.getLineNumber(); | |
231 | } | |
232 | ||
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); | |
237 | ||
238 | eventCount.incrementAndGet(); | |
239 | ||
8063c7b0 MJ |
240 | LttngLog4j2Api.tracepointWithContext(message, loggername, classname, methodname, filename, line, |
241 | event.getTimeMillis(), event.getLevel().intLevel(), event.getThreadName(), | |
464c4756 MJ |
242 | contextInfo.getEntriesArray(), contextInfo.getStringsArray()); |
243 | } | |
244 | } |