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.filter; 018 019import java.time.Duration; 020import java.time.Instant; 021import java.time.LocalDate; 022import java.time.LocalTime; 023import java.time.ZoneId; 024import java.time.ZonedDateTime; 025import java.time.format.DateTimeFormatter; 026 027import org.apache.logging.log4j.Level; 028import org.apache.logging.log4j.Marker; 029import org.apache.logging.log4j.core.Filter; 030import org.apache.logging.log4j.core.LogEvent; 031import org.apache.logging.log4j.core.Logger; 032import org.apache.logging.log4j.core.config.Node; 033import org.apache.logging.log4j.core.config.plugins.Plugin; 034import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 035import org.apache.logging.log4j.core.config.plugins.PluginFactory; 036import org.apache.logging.log4j.core.util.Clock; 037import org.apache.logging.log4j.core.util.ClockFactory; 038import org.apache.logging.log4j.message.Message; 039import org.apache.logging.log4j.util.PerformanceSensitive; 040 041/** 042 * Filters events that fall within a specified time period in each day. 043 */ 044@Plugin(name = "TimeFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) 045@PerformanceSensitive("allocation") 046public final class TimeFilter extends AbstractFilter { 047 private static final Clock CLOCK = ClockFactory.getClock(); 048 private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); 049 050 /** 051 * Length of hour in milliseconds. 052 */ 053 private static final long HOUR_MS = 3600000; 054 055 private static final long DAY_MS = HOUR_MS * 24; 056 057 /** 058 * Starting offset from midnight in milliseconds. 059 */ 060 private volatile long start; 061 private final LocalTime startTime; 062 063 /** 064 * Ending offset from midnight in milliseconds. 065 */ 066 private volatile long end; 067 private final LocalTime endTime; 068 069 private final long duration; 070 071 /** 072 * Timezone. 073 */ 074 private final ZoneId timeZone; 075 076 /* 077 * Expose for unit testing. 078 */ 079 TimeFilter(final LocalTime start, final LocalTime end, final ZoneId timeZone, final Result onMatch, 080 final Result onMismatch, LocalDate now) { 081 super(onMatch, onMismatch); 082 this.startTime = start; 083 this.endTime = end; 084 this.timeZone = timeZone; 085 this.start = ZonedDateTime.of(now, startTime, timeZone).withEarlierOffsetAtOverlap().toInstant().toEpochMilli(); 086 long endMillis = ZonedDateTime.of(now, endTime, timeZone).withEarlierOffsetAtOverlap().toInstant().toEpochMilli(); 087 if (end.isBefore(start)) { 088 // End time must be tomorrow. 089 endMillis += DAY_MS; 090 } 091 duration = startTime.isBefore(endTime) ? Duration.between(startTime, endTime).toMillis() : 092 Duration.between(startTime, endTime).plusHours(24).toMillis(); 093 long difference = (endMillis - this.start) - duration; 094 if (difference != 0) { 095 // Handle switch from standard time to daylight time and daylight time to standard time. 096 endMillis -= difference; 097 } 098 this.end = endMillis; 099 } 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}