001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.config;
018
019import java.util.Objects;
020
021import org.apache.logging.log4j.Level;
022import org.apache.logging.log4j.core.Appender;
023import org.apache.logging.log4j.core.Filter;
024import org.apache.logging.log4j.core.LogEvent;
025import org.apache.logging.log4j.core.appender.AppenderLoggingException;
026import org.apache.logging.log4j.core.filter.AbstractFilterable;
027import org.apache.logging.log4j.core.filter.Filterable;
028import org.apache.logging.log4j.util.PerformanceSensitive;
029
030/**
031 * Wraps an {@link Appender} with details an appender implementation shouldn't need to know about.
032 */
033public class AppenderControl extends AbstractFilterable {
034
035    private final ThreadLocal<AppenderControl> recursive = new ThreadLocal<>();
036    private final Appender appender;
037    private final Level level;
038    private final int intLevel;
039    private final String appenderName;
040
041    /**
042     * Constructor.
043     *
044     * @param appender The target Appender.
045     * @param level the Level to filter on.
046     * @param filter the Filter(s) to apply.
047     */
048    public AppenderControl(final Appender appender, final Level level, final Filter filter) {
049        super(filter);
050        this.appender = appender;
051        this.appenderName = appender.getName();
052        this.level = level;
053        this.intLevel = level == null ? Level.ALL.intLevel() : level.intLevel();
054        start();
055    }
056
057    /**
058     * Returns the name the appender had when this AppenderControl was constructed.
059     *
060     * @return the appender name
061     */
062    public String getAppenderName() {
063        return appenderName;
064    }
065
066    /**
067     * Returns the Appender.
068     *
069     * @return the Appender.
070     */
071    public Appender getAppender() {
072        return appender;
073    }
074
075    /**
076     * Call the appender.
077     *
078     * @param event The event to process.
079     */
080    public void callAppender(final LogEvent event) {
081        if (shouldSkip(event)) {
082            return;
083        }
084        callAppenderPreventRecursion(event);
085    }
086
087    private boolean shouldSkip(final LogEvent event) {
088        return isFilteredByAppenderControl(event) || isFilteredByLevel(event) || isRecursiveCall();
089    }
090
091    @PerformanceSensitive
092    private boolean isFilteredByAppenderControl(final LogEvent event) {
093        final Filter filter = getFilter();
094        return filter != null && Filter.Result.DENY == filter.filter(event);
095    }
096
097    @PerformanceSensitive
098    private boolean isFilteredByLevel(final LogEvent event) {
099        return level != null && intLevel < event.getLevel().intLevel();
100    }
101
102    @PerformanceSensitive
103    private boolean isRecursiveCall() {
104        if (recursive.get() != null) {
105            appenderErrorHandlerMessage("Recursive call to appender ");
106            return true;
107        }
108        return false;
109    }
110
111    private String appenderErrorHandlerMessage(final String prefix) {
112        final String result = createErrorMsg(prefix);
113        appender.getHandler().error(result);
114        return result;
115    }
116
117    private void callAppenderPreventRecursion(final LogEvent event) {
118        try {
119            recursive.set(this);
120            callAppender0(event);
121        } finally {
122            recursive.set(null);
123        }
124    }
125
126    private void callAppender0(final LogEvent event) {
127        ensureAppenderStarted();
128        if (!isFilteredByAppender(event)) {
129            tryCallAppender(event);
130        }
131    }
132
133    private void ensureAppenderStarted() {
134        if (!appender.isStarted()) {
135            handleError("Attempted to append to non-started appender ");
136        }
137    }
138
139    private void handleError(final String prefix) {
140        final String msg = appenderErrorHandlerMessage(prefix);
141        if (!appender.ignoreExceptions()) {
142            throw new AppenderLoggingException(msg);
143        }
144    }
145
146    private String createErrorMsg(final String prefix) {
147        return prefix + appender.getName();
148    }
149
150    private boolean isFilteredByAppender(final LogEvent event) {
151        return appender instanceof Filterable && ((Filterable) appender).isFiltered(event);
152    }
153
154    private void tryCallAppender(final LogEvent event) {
155        try {
156            appender.append(event);
157        } catch (final RuntimeException ex) {
158            handleAppenderError(event, ex);
159        } catch (final Throwable t) {
160            handleAppenderError(event, new AppenderLoggingException(t));
161        }
162    }
163
164    private void handleAppenderError(final LogEvent event, final RuntimeException ex) {
165        appender.getHandler().error(createErrorMsg("An exception occurred processing Appender "), event, ex);
166        if (!appender.ignoreExceptions()) {
167            throw ex;
168        }
169    }
170
171    // AppenderControl is a helper object whose purpose is to make it
172    // easier for LoggerConfig to manage and invoke Appenders.
173    // LoggerConfig manages Appenders by their name. To facilitate this,
174    // two AppenderControl objects are considered equal if and only
175    // if they have the same appender name.
176    @Override
177    public boolean equals(final Object obj) {
178        if (obj == this) {
179            return true;
180        }
181        if (!(obj instanceof AppenderControl)) {
182            return false;
183        }
184        final AppenderControl other = (AppenderControl) obj;
185        return Objects.equals(appenderName, other.appenderName);
186    }
187
188    @Override
189    public int hashCode() {
190        return appenderName.hashCode();
191    }
192
193    @Override
194    public String toString() {
195        return super.toString() + "[appender=" + appender + ", appenderName=" + appenderName + ", level=" + level
196                + ", intLevel=" + intLevel + ", recursive=" + recursive + ", filter=" + getFilter() + "]";
197    }
198}