001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.logging; 019 020import java.io.Serializable; 021import java.nio.charset.StandardCharsets; 022import java.util.concurrent.atomic.AtomicBoolean; 023import java.util.concurrent.atomic.AtomicLong; 024import org.apache.logging.log4j.core.Appender; 025import org.apache.logging.log4j.core.Core; 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.AbstractOutputStreamAppender; 030import org.apache.logging.log4j.core.appender.ManagerFactory; 031import org.apache.logging.log4j.core.appender.OutputStreamManager; 032import org.apache.logging.log4j.core.appender.rolling.FileSize; 033import org.apache.logging.log4j.core.config.Property; 034import org.apache.logging.log4j.core.config.plugins.Plugin; 035import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; 036import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; 037import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; 038 039/** 040 * Log4j2 appender to be used when running UTs. 041 * <p/> 042 * The main point here is to limit the total output size to prevent eating all the space of our ci 043 * system when something wrong in our code. 044 * <p/> 045 * See HBASE-26947 for more details. 046 */ 047@Plugin(name = HBaseTestAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, 048 elementType = Appender.ELEMENT_TYPE, printObject = true) 049public final class HBaseTestAppender extends AbstractOutputStreamAppender<OutputStreamManager> { 050 051 public static final String PLUGIN_NAME = "HBaseTest"; 052 private static final HBaseTestManagerFactory FACTORY = new HBaseTestManagerFactory(); 053 054 public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B> 055 implements org.apache.logging.log4j.core.util.Builder<HBaseTestAppender> { 056 057 @PluginBuilderAttribute 058 @Required 059 private Target target; 060 061 @PluginBuilderAttribute 062 @Required 063 private String maxSize; 064 065 public B setTarget(Target target) { 066 this.target = target; 067 return asBuilder(); 068 } 069 070 public B setMaxSize(String maxSize) { 071 this.maxSize = maxSize; 072 return asBuilder(); 073 } 074 075 @Override 076 public HBaseTestAppender build() { 077 long size = FileSize.parse(maxSize, -1); 078 if (size <= 0) { 079 LOGGER.error("Invalid maxSize {}", size); 080 } 081 Layout<? extends Serializable> layout = getOrCreateLayout(StandardCharsets.UTF_8); 082 OutputStreamManager manager = 083 OutputStreamManager.getManager(target.name(), FACTORY, new FactoryData(target, layout)); 084 return new HBaseTestAppender(getName(), layout, getFilter(), isIgnoreExceptions(), 085 isImmediateFlush(), getPropertyArray(), manager, size); 086 } 087 } 088 089 /** 090 * Data to pass to factory method.Unable to instantiate 091 */ 092 private static class FactoryData { 093 private final Target target; 094 private final Layout<? extends Serializable> layout; 095 096 public FactoryData(Target target, Layout<? extends Serializable> layout) { 097 this.target = target; 098 this.layout = layout; 099 } 100 } 101 102 /** 103 * Factory to create the Appender. 104 */ 105 private static class HBaseTestManagerFactory 106 implements ManagerFactory<HBaseTestOutputStreamManager, FactoryData> { 107 108 /** 109 * Create an OutputStreamManager. 110 * @param name The name of the entity to manage. 111 * @param data The data required to create the entity. 112 * @return The OutputStreamManager 113 */ 114 @Override 115 public HBaseTestOutputStreamManager createManager(final String name, final FactoryData data) { 116 return new HBaseTestOutputStreamManager(data.target, data.layout); 117 } 118 } 119 120 @PluginBuilderFactory 121 public static <B extends Builder<B>> B newBuilder() { 122 return new Builder<B>().asBuilder(); 123 } 124 125 private final long maxSize; 126 127 private final AtomicLong size = new AtomicLong(0); 128 129 private final AtomicBoolean stop = new AtomicBoolean(false); 130 131 private HBaseTestAppender(String name, Layout<? extends Serializable> layout, Filter filter, 132 boolean ignoreExceptions, boolean immediateFlush, Property[] properties, 133 OutputStreamManager manager, long maxSize) { 134 super(name, layout, filter, ignoreExceptions, immediateFlush, properties, manager); 135 this.maxSize = maxSize; 136 } 137 138 @Override 139 public void append(LogEvent event) { 140 if (stop.get()) { 141 return; 142 } 143 // for accounting, here we always convert the event to byte array first 144 // this will effect performance a bit but it is OK since this is for UT only 145 byte[] bytes = getLayout().toByteArray(event); 146 if (bytes == null || bytes.length == 0) { 147 return; 148 } 149 long sizeAfterAppend = size.addAndGet(bytes.length); 150 if (sizeAfterAppend >= maxSize) { 151 // stop logging if the log size exceeded the limit 152 if (stop.compareAndSet(false, true)) { 153 LOGGER.error("Log size exceeded the limit {}, will stop logging to prevent eating" 154 + " too much disk space", maxSize); 155 } 156 return; 157 } 158 super.append(event); 159 } 160}