blob: 8306dcad9e60ec325b693ae68db88fb6e78b2a7b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.druid.java.util.common.logger;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.timeline.DataSegment;
import org.apache.druid.timeline.SegmentId;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Iterator;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
public class Logger
{
@VisibleForTesting
static final int SEGMENTS_PER_LOG_MESSAGE = 64;
private final org.slf4j.Logger log;
private final boolean stackTraces;
private final Logger noStackTraceLogger;
public Logger(String name)
{
this(LoggerFactory.getLogger(name), true);
}
public Logger(Class clazz)
{
this(LoggerFactory.getLogger(clazz), true);
}
protected Logger(org.slf4j.Logger log, boolean stackTraces)
{
this.log = log;
this.stackTraces = stackTraces;
noStackTraceLogger = stackTraces ? new Logger(log, false) : this;
}
protected org.slf4j.Logger getSlf4jLogger()
{
return log;
}
@Override
public String toString()
{
return StringUtils.format("Logger{name=[%s], class[%s]}", log.getName(), log.getClass());
}
/**
* Returns a copy of this Logger that does not log exception stack traces, unless the log level is DEBUG or lower.
* Useful for writing code like: {@code log.noStackTrace().warn(e, "Something happened.");}
*/
public Logger noStackTrace()
{
return noStackTraceLogger;
}
public void trace(String message, Object... formatArgs)
{
if (log.isTraceEnabled()) {
log.trace(StringUtils.nonStrictFormat(message, formatArgs));
}
}
public void debug(String message, Object... formatArgs)
{
if (log.isDebugEnabled()) {
log.debug(StringUtils.nonStrictFormat(message, formatArgs));
}
}
public void debug(Throwable t, String message, Object... formatArgs)
{
if (log.isDebugEnabled()) {
logException(log::debug, t, StringUtils.nonStrictFormat(message, formatArgs));
}
}
public void info(String message, Object... formatArgs)
{
if (log.isInfoEnabled()) {
log.info(StringUtils.nonStrictFormat(message, formatArgs));
}
}
public void info(Throwable t, String message, Object... formatArgs)
{
if (log.isInfoEnabled()) {
logException(log::info, t, StringUtils.nonStrictFormat(message, formatArgs));
}
}
/**
* Protect against assuming slf4j convention. use `warn(Throwable t, String message, Object... formatArgs)` instead
*
* @param message The string message
* @param t The Throwable to log
*/
@Deprecated
public void warn(String message, Throwable t)
{
warn(t, message);
}
public void warn(String message, Object... formatArgs)
{
log.warn(StringUtils.nonStrictFormat(message, formatArgs));
}
public void warn(Throwable t, String message, Object... formatArgs)
{
logException(log::warn, t, StringUtils.nonStrictFormat(message, formatArgs));
}
public void error(String message, Object... formatArgs)
{
log.error(StringUtils.nonStrictFormat(message, formatArgs));
}
/**
* Protect against assuming slf4j convention. use `error(Throwable t, String message, Object... formatArgs)` instead
*
* @param message The string message
* @param t The Throwable to log
*/
@Deprecated
public void error(String message, Throwable t)
{
error(t, message);
}
public void error(Throwable t, String message, Object... formatArgs)
{
logException(log::error, t, StringUtils.nonStrictFormat(message, formatArgs));
}
public void assertionError(String message, Object... formatArgs)
{
log.error("ASSERTION_ERROR: " + message, formatArgs);
}
public void debugSegments(@Nullable final Collection<DataSegment> segments, @Nullable String preamble)
{
if (log.isDebugEnabled()) {
logSegments(this::debug, segments, preamble);
}
}
public void infoSegments(@Nullable final Collection<DataSegment> segments, @Nullable String preamble)
{
if (log.isInfoEnabled()) {
logSegments(this::info, segments, preamble);
}
}
public void infoSegmentIds(@Nullable final Stream<SegmentId> segments, @Nullable String preamble)
{
if (log.isInfoEnabled()) {
logSegmentIds(this::info, segments, preamble);
}
}
public void warnSegments(@Nullable final Collection<DataSegment> segments, @Nullable String preamble)
{
if (log.isWarnEnabled()) {
logSegments(this::warn, segments, preamble);
}
}
public void errorSegments(@Nullable final Collection<DataSegment> segments, @Nullable String preamble)
{
logSegments(this::error, segments, preamble);
}
public boolean isTraceEnabled()
{
return log.isTraceEnabled();
}
public boolean isDebugEnabled()
{
return log.isDebugEnabled();
}
public boolean isInfoEnabled()
{
return log.isInfoEnabled();
}
private void logException(BiConsumer<String, Throwable> fn, Throwable t, String message)
{
if (stackTraces || log.isDebugEnabled()) {
fn.accept(message, t);
} else {
if (message.isEmpty()) {
fn.accept(t.toString(), null);
} else {
fn.accept(StringUtils.nonStrictFormat("%s (%s)", message, t.toString()), null);
}
}
}
/**
* Logs all the segment ids you could ever want, {@link #SEGMENTS_PER_LOG_MESSAGE} at a time, as a comma separated
* list.
*/
@VisibleForTesting
static void logSegments(
Logger.LogFunction logger,
@Nullable final Collection<DataSegment> segments,
@Nullable String preamble
)
{
if (segments == null || segments.isEmpty()) {
return;
}
logSegmentIds(logger, segments.stream().map(DataSegment::getId), preamble);
}
/**
* Logs all the segment ids you could ever want, {@link #SEGMENTS_PER_LOG_MESSAGE} at a time, as a comma separated
* list.
*/
@VisibleForTesting
static void logSegmentIds(
Logger.LogFunction logger,
@Nullable final Stream<SegmentId> stream,
@Nullable String preamble
)
{
Preconditions.checkNotNull(preamble);
if (stream == null) {
return;
}
final Iterator<SegmentId> iterator = stream.iterator();
if (!iterator.hasNext()) {
return;
}
final String logFormat = preamble + ": %s";
int counter = 0;
StringBuilder sb = null;
while (iterator.hasNext()) {
SegmentId nextId = iterator.next();
if (counter == 0) {
// use segmentId string length of first as estimate for total size of builder for this batch
sb = new StringBuilder(SEGMENTS_PER_LOG_MESSAGE * (2 + nextId.safeUpperLimitOfStringSize())).append("[");
}
sb.append(nextId);
if (++counter < SEGMENTS_PER_LOG_MESSAGE && iterator.hasNext()) {
sb.append(", ");
}
counter = counter % SEGMENTS_PER_LOG_MESSAGE;
if (counter == 0) {
// flush
sb.append("]");
logger.log(logFormat, sb.toString());
}
}
// check for stragglers
if (counter > 0) {
sb.append("]");
logger.log(logFormat, sb.toString());
}
}
@FunctionalInterface
public interface LogFunction
{
void log(String msg, Object... format);
}
}