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