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