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.core.AbstractLifeCycle; 026import org.apache.logging.log4j.core.Core; 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; 035 036/** 037 * Policy is purging appenders that were not in use specified time in minutes 038 */ 039@Plugin(name = "IdlePurgePolicy", category = Core.CATEGORY_NAME, printObject = true) 040@Scheduled 041public class IdlePurgePolicy extends AbstractLifeCycle implements PurgePolicy, Runnable { 042 043 private final long timeToLive; 044 private final long checkInterval; 045 private final ConcurrentMap<String, Long> appendersUsage = new ConcurrentHashMap<>(); 046 private RoutingAppender routingAppender; 047 private final ConfigurationScheduler scheduler; 048 private volatile ScheduledFuture<?> future; 049 050 public IdlePurgePolicy(final long timeToLive, final long checkInterval, final ConfigurationScheduler scheduler) { 051 this.timeToLive = timeToLive; 052 this.checkInterval = checkInterval; 053 this.scheduler = scheduler; 054 } 055 056 @Override 057 public void initialize(@SuppressWarnings("hiding") final RoutingAppender routingAppender) { 058 this.routingAppender = routingAppender; 059 } 060 061 @Override 062 public boolean stop(final long timeout, final TimeUnit timeUnit) { 063 setStopping(); 064 final boolean stopped = stop(future); 065 setStopped(); 066 return stopped; 067 } 068 069 /** 070 * Purging appenders that were not in use specified time 071 */ 072 @Override 073 public void purge() { 074 final long createTime = System.currentTimeMillis() - timeToLive; 075 for (final Entry<String, Long> entry : appendersUsage.entrySet()) { 076 long entryValue = entry.getValue(); 077 if (entryValue < createTime) { 078 if (appendersUsage.remove(entry.getKey(), entryValue)) { 079 LOGGER.debug("Removing appender {}", entry.getKey()); 080 routingAppender.deleteAppender(entry.getKey()); 081 } 082 } 083 } 084 } 085 086 @Override 087 public void update(final String key, final LogEvent event) { 088 final 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 updateTime = Long.MAX_VALUE; 108 for (final Entry<String, Long> entry : appendersUsage.entrySet()) { 109 if (entry.getValue() < updateTime) { 110 updateTime = entry.getValue(); 111 } 112 } 113 114 if (updateTime < Long.MAX_VALUE) { 115 final long interval = timeToLive - (System.currentTimeMillis() - updateTime); 116 future = scheduler.schedule(this, interval, TimeUnit.MILLISECONDS); 117 } else { 118 // reset to initial state - in case of all appenders already purged 119 future = scheduler.schedule(this, checkInterval, TimeUnit.MILLISECONDS); 120 } 121 } 122 123 /** 124 * Create the PurgePolicy 125 * 126 * @param timeToLive the number of increments of timeUnit before the Appender should be purged. 127 * @param checkInterval when all appenders purged, the number of increments of timeUnit to check if any appenders appeared 128 * @param timeUnit the unit of time the timeToLive and the checkInterval is expressed in. 129 * @return The Routes container. 130 */ 131 @PluginFactory 132 public static PurgePolicy createPurgePolicy( 133 @PluginAttribute("timeToLive") final String timeToLive, 134 @PluginAttribute("checkInterval") final String checkInterval, 135 @PluginAttribute("timeUnit") final String timeUnit, 136 @PluginConfiguration final Configuration configuration) { 137 138 if (timeToLive == null) { 139 LOGGER.error("A timeToLive value is required"); 140 return null; 141 } 142 TimeUnit units; 143 if (timeUnit == null) { 144 units = TimeUnit.MINUTES; 145 } else { 146 try { 147 units = TimeUnit.valueOf(timeUnit.toUpperCase()); 148 } catch (final Exception ex) { 149 LOGGER.error("Invalid timeUnit value {}. timeUnit set to MINUTES", timeUnit, ex); 150 units = TimeUnit.MINUTES; 151 } 152 } 153 154 long ttl = units.toMillis(Long.parseLong(timeToLive)); 155 if (ttl < 0) { 156 LOGGER.error("timeToLive must be positive. timeToLive set to 0"); 157 ttl = 0; 158 } 159 160 long ci; 161 if (checkInterval == null) { 162 ci = ttl; 163 } else { 164 ci = units.toMillis(Long.parseLong(checkInterval)); 165 if (ci < 0) { 166 LOGGER.error("checkInterval must be positive. checkInterval set equal to timeToLive = {}", ttl); 167 ci = ttl; 168 } 169 } 170 171 return new IdlePurgePolicy(ttl, ci, configuration.getScheduler()); 172 } 173 174 @Override 175 public String toString() { 176 return "timeToLive=" + timeToLive; 177 } 178 179}