All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twitter.logging.ThrottledHandler.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2010 Twitter, Inc.
 *
 * Licensed 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 com.twitter.logging

import java.util.concurrent.atomic.AtomicReference
import java.util.{logging => javalog}

import scala.annotation.tailrec
import scala.collection.mutable

import com.twitter.conversions.time._
import com.twitter.util.{Duration, Time}

object ThrottledHandler {
  /**
   * Generates a HandlerFactory that returns a ThrottledHandler
   * NOTE: ThrottledHandler emits plain-text messages regarding any throttling it does.
   * This means that using it to wrap any logger which you expect to produce easily parseable,
   * well-structured logs (as opposed to just plain text logs) will break your format. 
   * Specifically, wrapping ScribeHandler with ThrottledHandler is usually a bug.
   *
   * @param handler
   * Wrapped handler.
   *
   * @param duration
   * Timespan to consider duplicates. After this amount of time, duplicate entries will be logged
   * again.
   *
   * @param maxToDisplay
   * Maximum duplicate log entries to pass before suppressing them.
   */
  def apply(
    handler: HandlerFactory,
    duration: Duration = 0.seconds,
    maxToDisplay: Int = Int.MaxValue
  ) = () => new ThrottledHandler(handler(), duration, maxToDisplay)
}

/**
 * NOTE: ThrottledHandler emits plain-text messages regarding any throttling it does.
 * This means that using it to wrap any logger which you expect to produce easily parseable,
 * well-structured logs (as opposed to just plain text logs) will break your format. 
 * Specifically, DO NOT wrap Thrift Scribing loggers with ThrottledHandler.
 * @param handler
 * Wrapped handler.
 *
 * @param duration
 * Timespan to consider duplicates. After this amount of time, duplicate entries will be logged
 * again.
 *
 * @param maxToDisplay
 * Maximum duplicate log entries to pass before suppressing them.
 */
class ThrottledHandler(
  handler: Handler,
  val duration: Duration,
  val maxToDisplay: Int
) extends ProxyHandler(handler) {

  private class Throttle(startTime: Time, name: String, level: javalog.Level) {
    private[this] var expired = false
    private[this] var count = 0

    override def toString = "Throttle: startTime=" + startTime + " count=" + count

    final def add(record: javalog.LogRecord, now: Time): Boolean = {
      val (shouldPublish, added) = synchronized {
        if (!expired) {
          count += 1
          (count <= maxToDisplay, true)
        } else {
          (false, false)
        }
      }

      if (shouldPublish) doPublish(record)
      added
    }

    final def removeIfExpired(now: Time): Boolean = {
      val didExpire = synchronized {
        expired = (now - startTime >= duration)
        expired
      }

      if (didExpire && count > maxToDisplay) publishSwallowed()

      didExpire
    }

    private[this] def publishSwallowed() {
      val throttledRecord = new javalog.LogRecord(
        level, "(swallowed %d repeating messages)".format(count - maxToDisplay))
      throttledRecord.setLoggerName(name)
      doPublish(throttledRecord)
    }
  }

  private val lastFlushCheck = new AtomicReference(Time.epoch)
  private val throttleMap = new mutable.HashMap[String, Throttle]

  @deprecated("Use flushThrottled() instead", "5.3.13")
  def reset() {
    flushThrottled()
  }

  /**
   * Force printing any "swallowed" messages.
   */
  def flushThrottled() {
    synchronized {
      val now = Time.now
      throttleMap retain {
        case (_, throttle) => !throttle.removeIfExpired(now)
      }
    }
  }

  /**
   * Log a message, with sprintf formatting, at the desired level, and
   * attach an exception and stack trace.
   */
  override def publish(record: javalog.LogRecord) {
    val now = Time.now
    val last = lastFlushCheck.get

    if (now - last > 1.second && lastFlushCheck.compareAndSet(last, now)) {
      flushThrottled()
    }

    val key = record match {
      case r: LazyLogRecordUnformatted => r.preformatted
      case _ => record.getMessage
    }
    @tailrec def tryPublish() {
      val throttle = synchronized {
        throttleMap.getOrElseUpdate(
          key,
          new Throttle(now, record.getLoggerName(), record.getLevel())
        )
      }

      // catch the case where throttle is removed before we had a chance to add
      if (!throttle.add(record, now)) tryPublish()
    }

    tryPublish()
  }

  private def doPublish(record: javalog.LogRecord) = {
    super.publish(record)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy