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