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.appender.db;
018
019import java.io.Serializable;
020import java.util.concurrent.TimeUnit;
021import java.util.concurrent.locks.Lock;
022import java.util.concurrent.locks.ReadWriteLock;
023import java.util.concurrent.locks.ReentrantReadWriteLock;
024
025import org.apache.logging.log4j.LoggingException;
026import org.apache.logging.log4j.core.Filter;
027import org.apache.logging.log4j.core.Layout;
028import org.apache.logging.log4j.core.LogEvent;
029import org.apache.logging.log4j.core.appender.AbstractAppender;
030import org.apache.logging.log4j.core.appender.AppenderLoggingException;
031import org.apache.logging.log4j.core.config.Property;
032
033/**
034 * An abstract Appender for writing events to a database of some type, be it relational or NoSQL. All database appenders
035 * should inherit from this base appender. Three implementations are currently provided:
036 * {@link org.apache.logging.log4j.core.appender.db.jdbc JDBC}, {@link org.apache.logging.log4j.core.appender.db.jpa
037 * JPA}, and {@link org.apache.logging.log4j.core.appender.nosql NoSQL}.
038 *
039 * @param <T> Specifies which type of {@link AbstractDatabaseManager} this Appender requires.
040 */
041public abstract class AbstractDatabaseAppender<T extends AbstractDatabaseManager> extends AbstractAppender {
042
043    public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B> {
044        // empty for now.
045    }
046    
047    public static final int DEFAULT_RECONNECT_INTERVAL_MILLIS = 5000;
048    
049    private final ReadWriteLock lock = new ReentrantReadWriteLock();
050    private final Lock readLock = lock.readLock();
051    private final Lock writeLock = lock.writeLock();
052    private T manager;
053
054    /**
055     * Instantiates the base appender.
056     *
057     * @param name The appender name.
058     * @param filter The filter, if any, to use.
059     * @param ignoreExceptions If {@code true} exceptions encountered when appending events are logged; otherwise
060     *                         they are propagated to the caller.
061     * @param manager The matching {@link AbstractDatabaseManager} implementation.
062     * @deprecated Use {@link #AbstractDatabaseAppender(String, Filter, Layout, boolean, Property[], AbstractDatabaseManager)}.
063     */
064    @Deprecated
065    protected AbstractDatabaseAppender(final String name, final Filter filter, final boolean ignoreExceptions,
066                                       final T manager) {
067        super(name, filter, null, ignoreExceptions, Property.EMPTY_ARRAY);
068        this.manager = manager;
069    }
070
071    /**
072     * Instantiates the base appender.
073     *
074     * @param name The appender name.
075     * @param filter The filter, if any, to use.
076     * @param layout The layout to use to format the event.
077     * @param ignoreExceptions If {@code true} exceptions encountered when appending events are logged; otherwise
078     *                         they are propagated to the caller.
079     * @param manager The matching {@link AbstractDatabaseManager} implementation.
080     */
081    protected AbstractDatabaseAppender(final String name, final Filter filter,
082            final Layout<? extends Serializable> layout, final boolean ignoreExceptions,
083            final Property[] properties, final T manager) {
084        super(name, filter, layout, ignoreExceptions, properties);
085        this.manager = manager;
086    }
087
088    /**
089     * Instantiates the base appender.
090     *
091     * @param name The appender name.
092     * @param filter The filter, if any, to use.
093     * @param layout The layout to use to format the event.
094     * @param ignoreExceptions If {@code true} exceptions encountered when appending events are logged; otherwise
095     *                         they are propagated to the caller.
096     * @param manager The matching {@link AbstractDatabaseManager} implementation.
097     * @deprecated Use {@link #AbstractDatabaseAppender(String, Filter, Layout, boolean, Property[], AbstractDatabaseManager)}
098     */
099    @Deprecated
100    protected AbstractDatabaseAppender(final String name, final Filter filter,
101            final Layout<? extends Serializable> layout, final boolean ignoreExceptions, final T manager) {
102        super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
103        this.manager = manager;
104    }
105
106    @Override
107    public final void append(final LogEvent event) {
108        this.readLock.lock();
109        try {
110            this.getManager().write(event, toSerializable(event));
111        } catch (final LoggingException e) {
112            LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(),
113                    this.getName(), e);
114            throw e;
115        } catch (final Exception e) {
116            LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(),
117                    this.getName(), e);
118            throw new AppenderLoggingException("Unable to write to database in appender: " + e.getMessage(), e);
119        } finally {
120            this.readLock.unlock();
121        }
122    }
123
124    /**
125     * This always returns {@code null}, as database appenders do not use a single layout. The JPA and NoSQL appenders
126     * do not use a layout at all. The JDBC appender has a layout-per-column pattern.
127     *
128     * @return {@code null}.
129     */
130    @Override
131    public final Layout<LogEvent> getLayout() {
132        return null;
133    }
134
135    /**
136     * Returns the underlying manager in use within this appender.
137     *
138     * @return the manager.
139     */
140    public final T getManager() {
141        return this.manager;
142    }
143
144    /**
145     * Replaces the underlying manager in use within this appender. This can be useful for manually changing the way log
146     * events are written to the database without losing buffered or in-progress events. The existing manager is
147     * released only after the new manager has been installed. This method is thread-safe.
148     *
149     * @param manager The new manager to install.
150     */
151    protected final void replaceManager(final T manager) {
152        this.writeLock.lock();
153        try {
154            final T old = this.getManager();
155            if (!manager.isRunning()) {
156                manager.startup();
157            }
158            this.manager = manager;
159            old.close();
160        } finally {
161            this.writeLock.unlock();
162        }
163    }
164
165    @Override
166    public final void start() {
167        if (this.getManager() == null) {
168            LOGGER.error("No AbstractDatabaseManager set for the appender named [{}].", this.getName());
169        }
170        super.start();
171        if (this.getManager() != null) {
172            this.getManager().startup();
173        }
174    }
175
176    @Override
177    public boolean stop(final long timeout, final TimeUnit timeUnit) {
178        setStopping();
179        boolean stopped = super.stop(timeout, timeUnit, false);
180        if (this.getManager() != null) {
181            stopped &= this.getManager().stop(timeout, timeUnit);
182        }
183        setStopped();
184        return stopped;
185    }
186}