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.routing; 018 019import java.util.Map.Entry; 020import java.util.concurrent.ConcurrentHashMap; 021import java.util.concurrent.ConcurrentMap; 022import java.util.concurrent.ScheduledFuture; 023import java.util.concurrent.TimeUnit; 024 025import org.apache.logging.log4j.Logger; 026import org.apache.logging.log4j.core.AbstractLifeCycle; 027import org.apache.logging.log4j.core.LogEvent; 028import org.apache.logging.log4j.core.config.Configuration; 029import org.apache.logging.log4j.core.config.ConfigurationScheduler; 030import org.apache.logging.log4j.core.config.Scheduled; 031import org.apache.logging.log4j.core.config.plugins.Plugin; 032import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 033import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 034import org.apache.logging.log4j.core.config.plugins.PluginFactory; 035import org.apache.logging.log4j.status.StatusLogger; 036 037/** 038 * 039 * Policy is purging appenders that were not in use specified time in minutes 040 * 041 */ 042@Plugin(name = "IdlePurgePolicy", category = "Core", printObject = true) 043@Scheduled 044public class IdlePurgePolicy extends AbstractLifeCycle implements PurgePolicy, Runnable { 045 046 private static final Logger LOGGER = StatusLogger.getLogger(); 047 private static final long serialVersionUID = 7481062062560624564L; 048 private final long timeToLive; 049 private final ConcurrentMap<String, Long> appendersUsage = new ConcurrentHashMap<>(); 050 private RoutingAppender routingAppender; 051 private final ConfigurationScheduler scheduler; 052 private volatile ScheduledFuture<?> future = null; 053 054 public IdlePurgePolicy(long timeToLive, ConfigurationScheduler scheduler) { 055 this.timeToLive = timeToLive; 056 this.scheduler = scheduler; 057 } 058 059 @Override 060 public void initialize(RoutingAppender routingAppender) { 061 this.routingAppender = routingAppender; 062 } 063 064 @Override 065 public void stop() { 066 super.stop(); 067 future.cancel(true); 068 } 069 070 /** 071 * Purging appenders that were not in use specified time 072 * 073 */ 074 @Override 075 public void purge() { 076 long createTime = System.currentTimeMillis() - timeToLive; 077 for (Entry<String, Long> entry : appendersUsage.entrySet()) { 078 if (entry.getValue() < createTime) { 079 LOGGER.debug("Removing appender " + entry.getKey()); 080 appendersUsage.remove(entry.getKey()); 081 routingAppender.deleteAppender(entry.getKey()); 082 } 083 } 084 } 085 086 @Override 087 public void update(String key, LogEvent event) { 088 long now = System.currentTimeMillis(); 089 appendersUsage.put(key, now); 090 if (future == null) { 091 synchronized(this) { 092 if (future == null) { 093 scheduleNext(); 094 } 095 } 096 } 097 098 } 099 100 @Override 101 public void run() { 102 purge(); 103 scheduleNext(); 104 } 105 106 private void scheduleNext() { 107 long createTime = Long.MAX_VALUE; 108 for (Entry<String, Long> entry : appendersUsage.entrySet()) { 109 if (entry.getValue() < createTime) { 110 createTime = entry.getValue(); 111 } 112 } 113 if (createTime < Long.MAX_VALUE) { 114 long interval = timeToLive - (System.currentTimeMillis() - createTime); 115 future = scheduler.schedule(this, interval, TimeUnit.MILLISECONDS); 116 } 117 } 118 119 /** 120 * Create the PurgePolicy 121 * @param timeToLive the number of increments of timeUnit before the Appender should be purged. 122 * @param timeUnit the unit of time the timeToLive is expressed in. 123 * @return The Routes container. 124 */ 125 @PluginFactory 126 public static PurgePolicy createPurgePolicy( 127 @PluginAttribute("timeToLive") final String timeToLive, 128 @PluginAttribute("timeUnit") final String timeUnit, 129 @PluginConfiguration Configuration configuration) { 130 131 if (timeToLive == null) { 132 LOGGER.error("A timeToLive value is required"); 133 return null; 134 } 135 TimeUnit units; 136 if (timeUnit == null) { 137 units = TimeUnit.MINUTES; 138 } else { 139 try { 140 units = TimeUnit.valueOf(timeUnit.toUpperCase()); 141 } catch(Exception ex) { 142 LOGGER.error("Invalid time unit {}", timeUnit); 143 units = TimeUnit.MINUTES; 144 } 145 } 146 147 final long ttl = units.toMillis(Long.parseLong(timeToLive)); 148 149 150 return new IdlePurgePolicy(ttl, configuration.getScheduler()); 151 } 152 153 @Override 154 public String toString() { 155 return "timeToLive=" + timeToLive; 156 } 157 158}