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.filter;
18  
19  import java.time.Duration;
20  import java.time.Instant;
21  import java.time.LocalDate;
22  import java.time.LocalTime;
23  import java.time.ZoneId;
24  import java.time.ZonedDateTime;
25  import java.time.format.DateTimeFormatter;
26  
27  import org.apache.logging.log4j.Level;
28  import org.apache.logging.log4j.Marker;
29  import org.apache.logging.log4j.core.Filter;
30  import org.apache.logging.log4j.core.LogEvent;
31  import org.apache.logging.log4j.core.Logger;
32  import org.apache.logging.log4j.core.config.Node;
33  import org.apache.logging.log4j.core.config.plugins.Plugin;
34  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
35  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
36  import org.apache.logging.log4j.core.util.Clock;
37  import org.apache.logging.log4j.core.util.ClockFactory;
38  import org.apache.logging.log4j.message.Message;
39  import org.apache.logging.log4j.util.PerformanceSensitive;
40  
41  /**
42   * Filters events that fall within a specified time period in each day.
43   */
44  @Plugin(name = "TimeFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
45  @PerformanceSensitive("allocation")
46  public final class TimeFilter extends AbstractFilter {
47      private static final Clock CLOCK = ClockFactory.getClock();
48      private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
49  
50      /**
51       * Length of hour in milliseconds.
52       */
53      private static final long HOUR_MS = 3600000;
54  
55      private static final long DAY_MS = HOUR_MS * 24;
56  
57      /**
58       * Starting offset from midnight in milliseconds.
59       */
60      private volatile long start;
61      private final LocalTime startTime;
62  
63      /**
64       * Ending offset from midnight in milliseconds.
65       */
66      private volatile long end;
67      private final LocalTime endTime;
68  
69      private final long duration;
70      
71      /**
72       * Timezone.
73       */
74      private final ZoneId timeZone;
75  
76      /*
77       * Expose for unit testing.
78       */
79      TimeFilter(final LocalTime start, final LocalTime end, final ZoneId timeZone, final Result onMatch,
80              final Result onMismatch, LocalDate now) {
81          super(onMatch, onMismatch);
82          this.startTime = start;
83          this.endTime = end;
84          this.timeZone = timeZone;
85          this.start = ZonedDateTime.of(now, startTime, timeZone).withEarlierOffsetAtOverlap().toInstant().toEpochMilli();
86          long endMillis = ZonedDateTime.of(now, endTime, timeZone).withEarlierOffsetAtOverlap().toInstant().toEpochMilli();
87          if (end.isBefore(start)) {
88              // End time must be tomorrow.
89              endMillis += DAY_MS;
90          }
91          duration = startTime.isBefore(endTime) ? Duration.between(startTime, endTime).toMillis() :
92              Duration.between(startTime, endTime).plusHours(24).toMillis();
93          long difference = (endMillis - this.start) - duration;
94          if (difference != 0) {
95              // Handle switch from standard time to daylight time and daylight time to standard time.
96              endMillis -= difference;
97          }
98          this.end = endMillis;
99      }
100 
101     private TimeFilter(final LocalTime start, final LocalTime end, final ZoneId timeZone, final Result onMatch,
102                        final Result onMismatch) {
103         this(start, end, timeZone, onMatch, onMismatch, LocalDate.now(timeZone));
104     }
105 
106     private synchronized void adjustTimes(long currentTimeMillis) {
107         if (currentTimeMillis <= end) {
108             return;
109         }
110         LocalDate date = Instant.ofEpochMilli(currentTimeMillis).atZone(timeZone).toLocalDate();
111         this.start = ZonedDateTime.of(date, startTime, timeZone).withEarlierOffsetAtOverlap().toInstant().toEpochMilli();
112         long endMillis = ZonedDateTime.of(date, endTime, timeZone).withEarlierOffsetAtOverlap().toInstant().toEpochMilli();
113         if (endTime.isBefore(startTime)) {
114             // End time must be tomorrow.
115             endMillis += DAY_MS;
116         }
117         long difference = (endMillis - this.start) - duration;
118         if (difference != 0) {
119             // Handle switch from standard time to daylight time and daylight time to standard time.
120             endMillis -= difference;
121         }
122         this.end = endMillis;
123     }
124 
125     /**
126      * Package-protected for tests.
127      *
128      * @param currentTimeMillis the time to compare with the boundaries. May re-initialize the cached midnight
129      *          boundary values.
130      * @return the action to perform
131      */
132     Result filter(final long currentTimeMillis) {
133         if (currentTimeMillis > end) {
134             adjustTimes(currentTimeMillis);
135         }
136         return currentTimeMillis >= start && currentTimeMillis <= end ? onMatch : onMismatch;
137     }
138 
139     @Override
140     public Result filter(final LogEvent event) {
141         return filter(event.getTimeMillis());
142     }
143 
144     private Result filter() {
145         return filter(CLOCK.currentTimeMillis());
146     }
147 
148     @Override
149     public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
150             final Throwable t) {
151         return filter();
152     }
153 
154     @Override
155     public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg,
156             final Throwable t) {
157         return filter();
158     }
159 
160     @Override
161     public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
162             final Object... params) {
163         return filter();
164     }
165 
166     @Override
167     public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
168             final Object p0) {
169         return filter();
170     }
171 
172     @Override
173     public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
174             final Object p0, final Object p1) {
175         return filter();
176     }
177 
178     @Override
179     public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
180             final Object p0, final Object p1, final Object p2) {
181         return filter();
182     }
183 
184     @Override
185     public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
186             final Object p0, final Object p1, final Object p2, final Object p3) {
187         return filter();
188     }
189 
190     @Override
191     public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
192             final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) {
193         return filter();
194     }
195 
196     @Override
197     public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
198             final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5) {
199         return filter();
200     }
201 
202     @Override
203     public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
204             final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
205             final Object p6) {
206         return filter();
207     }
208 
209     @Override
210     public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
211             final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
212             final Object p6, final Object p7) {
213         return filter();
214     }
215 
216     @Override
217     public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
218             final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
219             final Object p6, final Object p7, final Object p8) {
220         return filter();
221     }
222 
223     @Override
224     public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
225             final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
226             final Object p6, final Object p7, final Object p8, final Object p9) {
227         return filter();
228     }
229 
230     @Override
231     public String toString() {
232         final StringBuilder sb = new StringBuilder();
233         sb.append("start=").append(start);
234         sb.append(", end=").append(end);
235         sb.append(", timezone=").append(timeZone.toString());
236         return sb.toString();
237     }
238 
239     /**
240      * Creates a TimeFilter.
241      * @param start The start time.
242      * @param end The end time.
243      * @param tz timezone.
244      * @param match Action to perform if the time matches.
245      * @param mismatch Action to perform if the action does not match.
246      * @return A TimeFilter.
247      */
248     // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder
249     @PluginFactory
250     public static TimeFilter createFilter(
251             @PluginAttribute("start") final String start,
252             @PluginAttribute("end") final String end,
253             @PluginAttribute("timezone") final String tz,
254             @PluginAttribute("onMatch") final Result match,
255             @PluginAttribute("onMismatch") final Result mismatch) {
256         final LocalTime startTime = parseTimestamp(start, LocalTime.MIN);
257         final LocalTime endTime = parseTimestamp(end, LocalTime.MAX);
258         final ZoneId timeZone = tz == null ? ZoneId.systemDefault() : ZoneId.of(tz);
259         final Result onMatch = match == null ? Result.NEUTRAL : match;
260         final Result onMismatch = mismatch == null ? Result.DENY : mismatch;
261         return new TimeFilter(startTime, endTime, timeZone, onMatch, onMismatch);
262     }
263 
264     private static LocalTime parseTimestamp(final String timestamp, final LocalTime defaultValue) {
265         if (timestamp == null) {
266             return defaultValue;
267         }
268 
269         try {
270             return LocalTime.parse(timestamp, FORMATTER);
271         } catch (final Exception e) {
272             LOGGER.warn("Error parsing TimeFilter timestamp value {}", timestamp, e);
273             return defaultValue;
274         }
275     }
276 
277 }