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    /**
036     * The empty array.
037     */
038    static final AppenderControl[] EMPTY_ARRAY = {};
039
040    private final ThreadLocal<AppenderControl> recursive = new ThreadLocal<>();
041    private final Appender appender;
042    private final Level level;
043    private final int intLevel;
044    private final String appenderName;
045
046    /**
047     * Constructor.
048     *
049     * @param appender The target Appender.
050     * @param level the Level to filter on.
051     * @param filter the Filter(s) to apply.
052     */
053    public AppenderControl(final Appender appender, final Level level, final Filter filter) {
054        super(filter);
055        this.appender = appender;
056        this.appenderName = appender.getName();
057        this.level = level;
058        this.intLevel = level == null ? Level.ALL.intLevel() : level.intLevel();
059        start();
060    }
061
062    /**
063     * Returns the name the appender had when this AppenderControl was constructed.
064     *
065     * @return the appender name
066     */
067    public String getAppenderName() {
068        return appenderName;
069    }
070
071    /**
072     * Returns the Appender.
073     *
074     * @return the Appender.
075     */
076    public Appender getAppender() {
077        return appender;
078    }
079
080    /**
081     * Call the appender.
082     *
083     * @param event The event to process.
084     */
085    public void callAppender(final LogEvent event) {
086        if (shouldSkip(event)) {
087            return;
088        }
089        callAppenderPreventRecursion(event);
090    }
091
092    private boolean shouldSkip(final LogEvent event) {
093        return isFilteredByAppenderControl(event) || isFilteredByLevel(event) || isRecursiveCall();
094    }
095
096    @PerformanceSensitive
097    private boolean isFilteredByAppenderControl(final LogEvent event) {
098        final Filter filter = getFilter();
099        return filter != null && Filter.Result.DENY == filter.filter(event);
100    }
101
102    @PerformanceSensitive
103    private boolean isFilteredByLevel(final LogEvent event) {
104        return level != null && intLevel < event.getLevel().intLevel();
105    }
106
107    @PerformanceSensitive
108    private boolean isRecursiveCall() {
109        if (recursive.get() != null) {
110            appenderErrorHandlerMessage("Recursive call to appender ");
111            return true;
112        }
113        return false;
114    }
115
116    private String appenderErrorHandlerMessage(final String prefix) {
117        final String result = createErrorMsg(prefix);
118        appender.getHandler().error(result);
119        return result;
120    }
121
122    private void callAppenderPreventRecursion(final LogEvent event) {
123        try {
124            recursive.set(this);
125            callAppender0(event);
126        } finally {
127            recursive.set(null);
128        }
129    }
130
131    private void callAppender0(final LogEvent event) {
132        ensureAppenderStarted();
133        if (!isFilteredByAppender(event)) {
134            tryCallAppender(event);
135        }
136    }
137
138    private void ensureAppenderStarted() {
139        if (!appender.isStarted()) {
140            handleError("Attempted to append to non-started appender ");
141        }
142    }
143
144    private void handleError(final String prefix) {
145        final String msg = appenderErrorHandlerMessage(prefix);
146        if (!appender.ignoreExceptions()) {
147            throw new AppenderLoggingException(msg);
148        }
149    }
150
151    private String createErrorMsg(final String prefix) {
152        return prefix + appender.getName();
153    }
154
155    private boolean isFilteredByAppender(final LogEvent event) {
156        return appender instanceof Filterable && ((Filterable) appender).isFiltered(event);
157    }
158
159    private void tryCallAppender(final LogEvent event) {
160        try {
161            appender.append(event);
162        } catch (final RuntimeException error) {
163            handleAppenderError(event, error);
164        } catch (final Throwable throwable) {
165            handleAppenderError(event, new AppenderLoggingException(throwable));
166        }
167    }
168
169    private void handleAppenderError(final LogEvent event, final RuntimeException ex) {
170        appender.getHandler().error(createErrorMsg("An exception occurred processing Appender "), event, ex);
171        if (!appender.ignoreExceptions()) {
172            throw ex;
173        }
174    }
175
176    // AppenderControl is a helper object whose purpose is to make it
177    // easier for LoggerConfig to manage and invoke Appenders.
178    // LoggerConfig manages Appenders by their name. To facilitate this,
179    // two AppenderControl objects are considered equal if and only
180    // if they have the same appender name.
181    @Override
182    public boolean equals(final Object obj) {
183        if (obj == this) {
184            return true;
185        }
186        if (!(obj instanceof AppenderControl)) {
187            return false;
188        }
189        final AppenderControl other = (AppenderControl) obj;
190        return Objects.equals(appenderName, other.appenderName);
191    }
192
193    @Override
194    public int hashCode() {
195        return appenderName.hashCode();
196    }
197
198    @Override
199    public String toString() {
200        return super.toString() + "[appender=" + appender + ", appenderName=" + appenderName + ", level=" + level
201                + ", intLevel=" + intLevel + ", recursive=" + recursive + ", filter=" + getFilter() + "]";
202    }
203}