blob: 917d69659875f8715a9ca2369ec2540a0aaedc1c [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.hadoop.yarn.util;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
/**
* A {@link CharSequence} appender that considers its {@link #limit} as upper
* bound.
* <p>
* When {@link #limit} would be reached on append, past messages will be
* truncated from head, and a header telling the user about truncation will be
* prepended, with ellipses in between header and messages.
* <p>
* Note that header and ellipses are not counted against {@link #limit}.
* <p>
* An example:
*
* <pre>
* {@code
* // At the beginning it's an empty string
* final Appendable shortAppender = new BoundedAppender(80);
* // The whole message fits into limit
* shortAppender.append(
* "message1 this is a very long message but fitting into limit\n");
* // The first message is truncated, the second not
* shortAppender.append("message2 this is shorter than the previous one\n");
* // The first message is deleted, the second truncated, the third
* // preserved
* shortAppender.append("message3 this is even shorter message, maybe.\n");
* // The first two are deleted, the third one truncated, the last preserved
* shortAppender.append("message4 the shortest one, yet the greatest :)");
* // Current contents are like this:
* // Diagnostic messages truncated, showing last 80 chars out of 199:
* // ...s is even shorter message, maybe.
* // message4 the shortest one, yet the greatest :)
* }
* </pre>
* <p>
* Note that <tt>null</tt> values are {@link #append(CharSequence) append}ed
* just like in {@link StringBuilder#append(CharSequence) original
* implementation}.
* <p>
* Note that this class is not thread safe.
*/
@InterfaceAudience.Public
@InterfaceStability.Unstable
@VisibleForTesting
public class BoundedAppender {
@VisibleForTesting
public static final String TRUNCATED_MESSAGES_TEMPLATE =
"Diagnostic messages truncated, showing last "
+ "%d chars out of %d:%n...%s";
private final int limit;
private final StringBuilder messages = new StringBuilder();
private int totalCharacterCount = 0;
public BoundedAppender(final int limit) {
Preconditions.checkArgument(limit > 0, "limit should be positive");
this.limit = limit;
}
/**
* Append a {@link CharSequence} considering {@link #limit}, truncating
* from the head of {@code csq} or {@link #messages} when necessary.
*
* @param csq the {@link CharSequence} to append
* @return this
*/
public BoundedAppender append(final CharSequence csq) {
appendAndCount(csq);
checkAndCut();
return this;
}
private void appendAndCount(final CharSequence csq) {
final int before = messages.length();
messages.append(csq);
final int after = messages.length();
totalCharacterCount += after - before;
}
private void checkAndCut() {
if (messages.length() > limit) {
final int newStart = messages.length() - limit;
messages.delete(0, newStart);
}
}
/**
* Get current length of messages considering truncates
* without header and ellipses.
*
* @return current length
*/
public int length() {
return messages.length();
}
public int getLimit() {
return limit;
}
/**
* Get a string representation of the actual contents, displaying also a
* header and ellipses when there was a truncate.
*
* @return String representation of the {@link #messages}
*/
@Override
public String toString() {
if (messages.length() < totalCharacterCount) {
return String.format(TRUNCATED_MESSAGES_TEMPLATE, messages.length(),
totalCharacterCount, messages.toString());
}
return messages.toString();
}
}