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        return getFilter() != null && Filter.Result.DENY == getFilter().filter(event);
094    }
095
096    @PerformanceSensitive
097    private boolean isFilteredByLevel(final LogEvent event) {
098        return level != null && intLevel < event.getLevel().intLevel();
099    }
100
101    @PerformanceSensitive
102    private boolean isRecursiveCall() {
103        if (recursive.get() != null) {
104            appenderErrorHandlerMessage("Recursive call to appender ");
105            return true;
106        }
107        return false;
108    }
109
110    private String appenderErrorHandlerMessage(final String prefix) {
111        final String result = createErrorMsg(prefix);
112        appender.getHandler().error(result);
113        return result;
114    }
115
116    private void callAppenderPreventRecursion(final LogEvent event) {
117        try {
118            recursive.set(this);
119            callAppender0(event);
120        } finally {
121            recursive.set(null);
122        }
123    }
124
125    private void callAppender0(final LogEvent event) {
126        ensureAppenderStarted();
127        if (!isFilteredByAppender(event)) {
128            tryCallAppender(event);
129        }
130    }
131
132    private void ensureAppenderStarted() {
133        if (!appender.isStarted()) {
134            handleError("Attempted to append to non-started appender ");
135        }
136    }
137
138    private void handleError(final String prefix) {
139        final String msg = appenderErrorHandlerMessage(prefix);
140        if (!appender.ignoreExceptions()) {
141            throw new AppenderLoggingException(msg);
142        }
143    }
144
145    private String createErrorMsg(final String prefix) {
146        return prefix + appender.getName();
147    }
148
149    private boolean isFilteredByAppender(final LogEvent event) {
150        return appender instanceof Filterable && ((Filterable) appender).isFiltered(event);
151    }
152
153    private void tryCallAppender(final LogEvent event) {
154        try {
155            appender.append(event);
156        } catch (final RuntimeException ex) {
157            handleAppenderError(ex);
158        } catch (final Exception ex) {
159            handleAppenderError(new AppenderLoggingException(ex));
160        }
161    }
162
163    private void handleAppenderError(final RuntimeException ex) {
164        appender.getHandler().error(createErrorMsg("An exception occurred processing Appender "), ex);
165        if (!appender.ignoreExceptions()) {
166            throw ex;
167        }
168    }
169
170    // AppenderControl is a helper object whose purpose is to make it
171    // easier for LoggerConfig to manage and invoke Appenders.
172    // LoggerConfig manages Appenders by their name. To facilitate this,
173    // two AppenderControl objects are considered equal if and only
174    // if they have the same appender name.
175    @Override
176    public boolean equals(final Object obj) {
177        if (obj == this) {
178            return true;
179        }
180        if (!(obj instanceof AppenderControl)) {
181            return false;
182        }
183        final AppenderControl other = (AppenderControl) obj;
184        return Objects.equals(appenderName, other.appenderName);
185    }
186
187    @Override
188    public int hashCode() {
189        return appenderName.hashCode();
190    }
191
192    @Override
193    public String toString() {
194        return super.toString() + "[appender=" + appender + ", appenderName=" + appenderName + ", level=" + level
195                + ", intLevel=" + intLevel + ", recursive=" + recursive + ", filter=" + getFilter() + "]";
196    }
197}