View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
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.core.AbstractLifeCycle;
26  import org.apache.logging.log4j.core.Core;
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  
36  /**
37   * Policy is purging appenders that were not in use specified time in minutes
38   */
39  @Plugin(name = "IdlePurgePolicy", category = Core.CATEGORY_NAME, printObject = true)
40  @Scheduled
41  public class IdlePurgePolicy extends AbstractLifeCycle implements PurgePolicy, Runnable {
42  
43      private final long timeToLive;
44      private final long checkInterval;
45      private final ConcurrentMap<String, Long> appendersUsage = new ConcurrentHashMap<>();
46      private RoutingAppender routingAppender;
47      private final ConfigurationScheduler scheduler;
48      private volatile ScheduledFuture<?> future;
49  
50      public IdlePurgePolicy(final long timeToLive, final long checkInterval, final ConfigurationScheduler scheduler) {
51          this.timeToLive = timeToLive;
52          this.checkInterval = checkInterval;
53          this.scheduler = scheduler;
54      }
55  
56      @Override
57      public void initialize(@SuppressWarnings("hiding") final RoutingAppender routingAppender) {
58          this.routingAppender = routingAppender;
59      }
60  
61      @Override
62      public boolean stop(final long timeout, final TimeUnit timeUnit) {
63          setStopping();
64          final boolean stopped = stop(future);
65          setStopped();
66          return stopped;
67      }
68  
69      /**
70       * Purging appenders that were not in use specified time
71       */
72      @Override
73      public void purge() {
74          final long createTime = System.currentTimeMillis() - timeToLive;
75          for (final Entry<String, Long> entry : appendersUsage.entrySet()) {
76              long entryValue = entry.getValue();
77              if (entryValue < createTime) {
78                  if (appendersUsage.remove(entry.getKey(), entryValue)) {
79                      LOGGER.debug("Removing appender {}", entry.getKey());
80                      routingAppender.deleteAppender(entry.getKey());
81                  }
82              }
83          }
84      }
85  
86      @Override
87      public void update(final String key, final LogEvent event) {
88          final long now = System.currentTimeMillis();
89          appendersUsage.put(key, now);
90          if (future == null) {
91              synchronized (this) {
92                  if (future == null) {
93                      scheduleNext();
94                  }
95              }
96          }
97  
98      }
99  
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 }