blob: 619d17598811c4a043808c79c02cb2496be7824c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache license, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the license for the specific language governing permissions and
* limitations under the license.
*/
package org.apache.logging.log4j.core.appender.routing;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.core.AbstractLifeCycle;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationScheduler;
import org.apache.logging.log4j.core.config.Scheduled;
import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Policy is purging appenders that were not in use specified time in minutes
*/
@Plugin(name = "IdlePurgePolicy", category = Core.CATEGORY_NAME, printObject = true)
@Scheduled
public class IdlePurgePolicy extends AbstractLifeCycle implements PurgePolicy, Runnable {
private final long timeToLive;
private final long checkInterval;
private final ConcurrentMap<String, Long> appendersUsage = new ConcurrentHashMap<>();
private RoutingAppender routingAppender;
private final ConfigurationScheduler scheduler;
private volatile ScheduledFuture<?> future;
public IdlePurgePolicy(final long timeToLive, final long checkInterval, final ConfigurationScheduler scheduler) {
this.timeToLive = timeToLive;
this.checkInterval = checkInterval;
this.scheduler = scheduler;
}
@Override
public void initialize(@SuppressWarnings("hiding") final RoutingAppender routingAppender) {
this.routingAppender = routingAppender;
}
@Override
public boolean stop(final long timeout, final TimeUnit timeUnit) {
setStopping();
final boolean stopped = stop(future);
setStopped();
return stopped;
}
/**
* Purging appenders that were not in use specified time
*/
@Override
public void purge() {
final long createTime = System.currentTimeMillis() - timeToLive;
for (final Entry<String, Long> entry : appendersUsage.entrySet()) {
final long entryValue = entry.getValue();
if (entryValue < createTime) {
if (appendersUsage.remove(entry.getKey(), entryValue)) {
LOGGER.debug("Removing appender {}", entry.getKey());
routingAppender.deleteAppender(entry.getKey());
}
}
}
}
@Override
public void update(final String key, final LogEvent event) {
final long now = System.currentTimeMillis();
appendersUsage.put(key, now);
if (future == null) {
synchronized (this) {
if (future == null) {
scheduleNext();
}
}
}
}
@Override
public void run() {
purge();
scheduleNext();
}
private void scheduleNext() {
long updateTime = Long.MAX_VALUE;
for (final Entry<String, Long> entry : appendersUsage.entrySet()) {
if (entry.getValue() < updateTime) {
updateTime = entry.getValue();
}
}
if (updateTime < Long.MAX_VALUE) {
final long interval = timeToLive - (System.currentTimeMillis() - updateTime);
future = scheduler.schedule(this, interval, TimeUnit.MILLISECONDS);
} else {
// reset to initial state - in case of all appenders already purged
future = scheduler.schedule(this, checkInterval, TimeUnit.MILLISECONDS);
}
}
/**
* Create the PurgePolicy
*
* @param timeToLive the number of increments of timeUnit before the Appender should be purged.
* @param checkInterval when all appenders purged, the number of increments of timeUnit to check if any appenders appeared
* @param timeUnit the unit of time the timeToLive and the checkInterval is expressed in.
* @return The Routes container.
*/
@PluginFactory
public static PurgePolicy createPurgePolicy(
@PluginAttribute final String timeToLive,
@PluginAttribute final String checkInterval,
@PluginAttribute final String timeUnit,
@PluginConfiguration final Configuration configuration) {
if (timeToLive == null) {
LOGGER.error("A timeToLive value is required");
return null;
}
TimeUnit units;
if (timeUnit == null) {
units = TimeUnit.MINUTES;
} else {
try {
units = TimeUnit.valueOf(timeUnit.toUpperCase());
} catch (final Exception ex) {
LOGGER.error("Invalid timeUnit value {}. timeUnit set to MINUTES", timeUnit, ex);
units = TimeUnit.MINUTES;
}
}
long ttl = units.toMillis(Long.parseLong(timeToLive));
if (ttl < 0) {
LOGGER.error("timeToLive must be positive. timeToLive set to 0");
ttl = 0;
}
long ci;
if (checkInterval == null) {
ci = ttl;
} else {
ci = units.toMillis(Long.parseLong(checkInterval));
if (ci < 0) {
LOGGER.error("checkInterval must be positive. checkInterval set equal to timeToLive = {}", ttl);
ci = ttl;
}
}
return new IdlePurgePolicy(ttl, ci, configuration.getScheduler());
}
@Override
public String toString() {
return "timeToLive=" + timeToLive;
}
}