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