| 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 | */ |
| 7 | |
| 8 | package org.lttng.ust.agent.log4j2; |
| 9 | |
| 10 | import java.util.Collection; |
| 11 | import java.util.Map; |
| 12 | import java.util.Set; |
| 13 | import java.util.TreeSet; |
| 14 | |
| 15 | import org.apache.logging.log4j.LogManager; |
| 16 | import org.apache.logging.log4j.core.Appender; |
| 17 | import org.apache.logging.log4j.core.Logger; |
| 18 | import org.apache.logging.log4j.core.LoggerContext; |
| 19 | import org.apache.logging.log4j.core.impl.Log4jContextFactory; |
| 20 | import org.apache.logging.log4j.core.selector.ContextSelector; |
| 21 | import org.apache.logging.log4j.spi.LoggerContextFactory; |
| 22 | import org.apache.logging.log4j.status.StatusLogger; |
| 23 | import org.lttng.ust.agent.AbstractLttngAgent; |
| 24 | |
| 25 | /** |
| 26 | * Agent implementation for Log4j 2.x. |
| 27 | */ |
| 28 | class LttngLog4j2Agent extends AbstractLttngAgent<LttngLogAppender> { |
| 29 | |
| 30 | private static LttngLog4j2Agent log4j2_instance = null; |
| 31 | private static LttngLog4j2Agent log4j1_instance = null; |
| 32 | |
| 33 | private LttngLog4j2Agent(Domain domain) { |
| 34 | super(domain); |
| 35 | } |
| 36 | |
| 37 | public static synchronized LttngLog4j2Agent getLog4j1Instance() { |
| 38 | if (log4j1_instance == null) { |
| 39 | log4j1_instance = new LttngLog4j2Agent(Domain.LOG4J); |
| 40 | } |
| 41 | return log4j1_instance; |
| 42 | } |
| 43 | |
| 44 | public static synchronized LttngLog4j2Agent getLog4j2Instance() { |
| 45 | if (log4j2_instance == null) { |
| 46 | log4j2_instance = new LttngLog4j2Agent(Domain.LOG4J2); |
| 47 | } |
| 48 | return log4j2_instance; |
| 49 | } |
| 50 | |
| 51 | @Override |
| 52 | public Collection<String> listAvailableEvents() { |
| 53 | Set<String> eventNames = new TreeSet<>(); |
| 54 | |
| 55 | LoggerContextFactory contextFactory = LogManager.getFactory(); |
| 56 | if (!(contextFactory instanceof Log4jContextFactory)) { |
| 57 | /* Using a custom ContextFactory is not supported. */ |
| 58 | StatusLogger.getLogger().error("Can't list events with custom ContextFactory"); |
| 59 | return eventNames; |
| 60 | } |
| 61 | |
| 62 | ContextSelector selector = ((Log4jContextFactory) contextFactory).getSelector(); |
| 63 | |
| 64 | for (LoggerContext logContext : selector.getLoggerContexts()) { |
| 65 | Collection<? extends Logger> loggers = logContext.getLoggers(); |
| 66 | for (Logger logger : loggers) { |
| 67 | /* |
| 68 | * Check if that logger has at least one LTTng log4j appender attached. |
| 69 | */ |
| 70 | if (hasLttngAppenderAttached(logger)) { |
| 71 | eventNames.add(logger.getName()); |
| 72 | } |
| 73 | } |
| 74 | } |
| 75 | return eventNames; |
| 76 | } |
| 77 | |
| 78 | /* |
| 79 | * Check if a logger has an LttngLogAppender attached. |
| 80 | * |
| 81 | * @param logger the Logger to check, null returns false |
| 82 | * @return true if the logger or its parent has at least one LttngLogAppender attached |
| 83 | */ |
| 84 | private static boolean hasLttngAppenderAttached(Logger logger) { |
| 85 | |
| 86 | if (logger == null) { |
| 87 | return false; |
| 88 | } |
| 89 | |
| 90 | /* |
| 91 | * Check all the appenders associated with the logger and return true if one of |
| 92 | * them is an LttngLogAppender. |
| 93 | */ |
| 94 | Map<String, Appender> appenders = logger.getAppenders(); |
| 95 | for (Map.Entry<String, Appender> appender : appenders.entrySet()) { |
| 96 | if (appender.getValue() instanceof LttngLogAppender) { |
| 97 | return true; |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | /* |
| 102 | * A parent logger, if any, may be connected to an LTTng handler. In this case, |
| 103 | * we will want to include this child logger in the output, since it will be |
| 104 | * accessible by LTTng. |
| 105 | * |
| 106 | * Despite the doc, getParent can return null based on the implementation as of |
| 107 | * log4j 2.17.1. |
| 108 | * |
| 109 | * The getParent function is there as a backward compat for 1.x. It is not clear |
| 110 | * in which context it should be used. The cost of doing the lookup is minimal |
| 111 | * and mimics what was done for the 1.x agent. |
| 112 | */ |
| 113 | return hasLttngAppenderAttached(logger.getParent()); |
| 114 | |
| 115 | } |
| 116 | } |