1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.appender.routing;
18
19 import java.util.Map.Entry;
20 import java.util.concurrent.ConcurrentHashMap;
21 import java.util.concurrent.ConcurrentMap;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24
25 import org.apache.logging.log4j.Logger;
26 import org.apache.logging.log4j.core.AbstractLifeCycle;
27 import org.apache.logging.log4j.core.LogEvent;
28 import org.apache.logging.log4j.core.config.Configuration;
29 import org.apache.logging.log4j.core.config.ConfigurationScheduler;
30 import org.apache.logging.log4j.core.config.Scheduled;
31 import org.apache.logging.log4j.core.config.plugins.Plugin;
32 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
33 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
34 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
35 import org.apache.logging.log4j.status.StatusLogger;
36
37
38
39
40 @Plugin(name = "IdlePurgePolicy", category = "Core", printObject = true)
41 @Scheduled
42 public class IdlePurgePolicy extends AbstractLifeCycle implements PurgePolicy, Runnable {
43
44 private static final Logger LOGGER = StatusLogger.getLogger();
45 private final long timeToLive;
46 private final ConcurrentMap<String, Long> appendersUsage = new ConcurrentHashMap<>();
47 private RoutingAppender routingAppender;
48 private final ConfigurationScheduler scheduler;
49 private volatile ScheduledFuture<?> future = null;
50
51 public IdlePurgePolicy(final long timeToLive, final ConfigurationScheduler scheduler) {
52 this.timeToLive = timeToLive;
53 this.scheduler = scheduler;
54 }
55
56 @Override
57 public void initialize(final RoutingAppender routingAppender) {
58 this.routingAppender = routingAppender;
59 }
60
61 @Override
62 public void stop() {
63 super.stop();
64 future.cancel(true);
65 }
66
67
68
69
70 @Override
71 public void purge() {
72 final long createTime = System.currentTimeMillis() - timeToLive;
73 for (final Entry<String, Long> entry : appendersUsage.entrySet()) {
74 if (entry.getValue() < createTime) {
75 LOGGER.debug("Removing appender " + entry.getKey());
76 appendersUsage.remove(entry.getKey());
77 routingAppender.deleteAppender(entry.getKey());
78 }
79 }
80 }
81
82 @Override
83 public void update(final String key, final LogEvent event) {
84 final long now = System.currentTimeMillis();
85 appendersUsage.put(key, now);
86 if (future == null) {
87 synchronized (this) {
88 if (future == null) {
89 scheduleNext();
90 }
91 }
92 }
93
94 }
95
96 @Override
97 public void run() {
98 purge();
99 scheduleNext();
100 }
101
102 private void scheduleNext() {
103 long createTime = Long.MAX_VALUE;
104 for (final Entry<String, Long> entry : appendersUsage.entrySet()) {
105 if (entry.getValue() < createTime) {
106 createTime = entry.getValue();
107 }
108 }
109 if (createTime < Long.MAX_VALUE) {
110 final long interval = timeToLive - (System.currentTimeMillis() - createTime);
111 future = scheduler.schedule(this, interval, TimeUnit.MILLISECONDS);
112 }
113 }
114
115
116
117
118
119
120
121
122 @PluginFactory
123 public static PurgePolicy createPurgePolicy(
124 @PluginAttribute("timeToLive") final String timeToLive,
125 @PluginAttribute("timeUnit") final String timeUnit,
126 @PluginConfiguration final 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 (final 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 }