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 | 94 | if (domain == LttngLog4j2Agent.Domain.LOG4J) { |
495b8381 MJ |
95 | agent = LttngLog4j2Agent.getLog4j1Instance(); |
96 | } else if (domain == LttngLog4j2Agent.Domain.LOG4J2) { | |
97 | agent = LttngLog4j2Agent.getLog4j2Instance(); | |
37d351b8 MJ |
98 | } else { |
99 | throw new IllegalArgumentException("Unsupported domain '" + domain + "'"); | |
100 | } | |
101 | ||
464c4756 MJ |
102 | agent.registerHandler(this); |
103 | } | |
104 | ||
105 | /** | |
106 | * Create an LttngLogAppender. | |
107 | * | |
108 | * @param name The name of the Appender, null returns null. | |
37d351b8 | 109 | * @param domain The LTTng-UST agent domain 'LOG4J' / 'LOG4J2'. |
464c4756 MJ |
110 | * @param ignoreExceptions If {@code "true"} (default) exceptions encountered |
111 | * when appending events are logged; otherwise they are | |
112 | * propagated to the caller. | |
113 | * @param filter The Filter or null. | |
114 | * | |
37d351b8 MJ |
115 | * @return A new LttngLogAppender, null if the name was null or the domain is |
116 | * null or invalid. | |
464c4756 MJ |
117 | */ |
118 | @PluginFactory | |
119 | public static LttngLogAppender createAppender(@PluginAttribute("name") String name, | |
37d351b8 MJ |
120 | @PluginAttribute("domain") String domain, @PluginAttribute("ignoreExceptions") Boolean ignoreExceptions, |
121 | @PluginElement("Filters") Filter filter) { | |
464c4756 MJ |
122 | |
123 | if (name == null) { | |
124 | LOGGER.error("No name provided for LttngLogAppender"); | |
125 | return null; | |
126 | } | |
127 | ||
37d351b8 MJ |
128 | if (domain == null) { |
129 | LOGGER.error("No domain provided for LttngLogAppender"); | |
130 | return null; | |
131 | } | |
132 | ||
464c4756 MJ |
133 | if (ignoreExceptions == null) { |
134 | ignoreExceptions = true; | |
135 | } | |
136 | ||
37d351b8 MJ |
137 | /* Parse the domain string */ |
138 | LttngLog4j2Agent.Domain parsedDomain; | |
139 | try { | |
140 | parsedDomain = LttngLog4j2Agent.Domain.valueOf(domain.toUpperCase()); | |
141 | } catch (IllegalArgumentException e) { | |
142 | LOGGER.error("Invalid domain '{}' for LttngLogAppender", domain); | |
143 | return null; | |
144 | } | |
145 | ||
146 | /* Create the appender and handle the possible failures. */ | |
147 | LttngLogAppender newAppender; | |
148 | try { | |
149 | newAppender = new LttngLogAppender(name, parsedDomain, filter, ignoreExceptions); | |
150 | } catch (IllegalArgumentException e) { | |
151 | LOGGER.error("Invalid domain '{}' for LttngLogAppender", parsedDomain); | |
152 | newAppender = null; | |
153 | } catch (SecurityException e) { | |
154 | LOGGER.error("Security error trying to load '{}' JNI library for LttngLogAppender", SHARED_OBJECT_NAME); | |
155 | newAppender = null; | |
156 | } catch (IOException e) { | |
157 | LOGGER.error("Failed to load '{}' JNI library for LttngLogAppender", SHARED_OBJECT_NAME); | |
158 | newAppender = null; | |
159 | } | |
160 | ||
161 | return newAppender; | |
464c4756 MJ |
162 | } |
163 | ||
164 | @Override | |
165 | public synchronized void close() { | |
166 | agent.unregisterHandler(this); | |
167 | } | |
168 | ||
169 | @Override | |
170 | public void stop() { | |
171 | close(); | |
172 | super.stop(); | |
173 | ||
174 | getStatusLogger().debug("Appender Lttng stopped"); | |
175 | } | |
176 | ||
177 | @Override | |
178 | public boolean stop(final long timeout, final TimeUnit timeUnit) { | |
179 | close(); | |
180 | boolean status = super.stop(timeout, timeUnit); | |
181 | ||
182 | getStatusLogger().debug("Appender Lttng stopped with status " + status); | |
183 | ||
184 | return status; | |
185 | } | |
186 | ||
187 | /** | |
188 | * Get the number of events logged by this handler so far. This means the number | |
189 | * of events actually sent through JNI to UST. | |
190 | * | |
191 | * @return The number of events logged so far | |
192 | */ | |
193 | @Override | |
194 | public long getEventCount() { | |
195 | return eventCount.get(); | |
196 | } | |
197 | ||
198 | @Override | |
199 | public void append(LogEvent event) { | |
200 | /* | |
201 | * Check if the current message should be logged, according to the UST session | |
202 | * settings. | |
203 | */ | |
8063c7b0 MJ |
204 | String loggername = event.getLoggerName(); |
205 | if (loggername == null || !agent.isEventEnabled(loggername)) { | |
464c4756 MJ |
206 | return; |
207 | } | |
208 | ||
8063c7b0 MJ |
209 | /* |
210 | * Default value if the Message is null. | |
211 | */ | |
212 | String message = ""; | |
213 | ||
214 | Message eventMessage = event.getMessage(); | |
215 | if (eventMessage != null) { | |
216 | message = eventMessage.getFormattedMessage(); | |
217 | } | |
218 | ||
464c4756 MJ |
219 | /* |
220 | * Default values if the StackTraceElement is null. | |
221 | */ | |
222 | String classname = ""; | |
223 | String methodname = ""; | |
224 | String filename = ""; | |
225 | int line = -1; | |
226 | ||
227 | StackTraceElement ste = event.getSource(); | |
228 | if (ste != null) { | |
229 | classname = ste.getClassName(); | |
230 | methodname = ste.getMethodName(); | |
231 | filename = ste.getFileName(); | |
232 | line = ste.getLineNumber(); | |
233 | } | |
234 | ||
235 | /* Retrieve all the requested context information we can find. */ | |
236 | Collection<Entry<String, Map<String, Integer>>> enabledContexts = agent.getEnabledAppContexts(); | |
237 | ContextInfoSerializer.SerializedContexts contextInfo = ContextInfoSerializer | |
238 | .queryAndSerializeRequestedContexts(enabledContexts); | |
239 | ||
240 | eventCount.incrementAndGet(); | |
241 | ||
8063c7b0 MJ |
242 | LttngLog4j2Api.tracepointWithContext(message, loggername, classname, methodname, filename, line, |
243 | event.getTimeMillis(), event.getLevel().intLevel(), event.getThreadName(), | |
495b8381 | 244 | contextInfo.getEntriesArray(), contextInfo.getStringsArray(), agent.getDomain() == Domain.LOG4J); |
464c4756 MJ |
245 | } |
246 | } |