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}