// =================================================================================================
// Copyright 2012 Twitter, Inc.
// -------------------------------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this work except in compliance with the License.
// You may obtain a copy of the License in the LICENSE file, or 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.common.logging;
import javax.annotation.Nullable;
import com.google.common.base.Objects;
import com.google.common.base.Throwables;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
/**
* A utility that can format log records to match the format generated by glog:
* <pre>
* I0218 17:36:47.461 (source) (message)
* </pre>
*/
public final class Glog {
/**
* Classifies the importance of a log message.
*/
public enum Level {
/**
* Indicates the message's classification is unknown. This most likely indicates a
* configuration or programming error that can be corrected by mapping the underlying log
* system's level appropriately.
*/
UNKNOWN('U'),
/**
* Indicates the message is for debugging purposes only.
*/
DEBUG('D'),
/**
* Indicates a message of general interest.
*/
INFO('I'),
/**
* Indicates a warning message likely worth of attention.
*/
WARNING('W'),
/**
* Indicates an unexpected error.
*/
ERROR('E'),
/**
* Indicates a fatal exception generally paired with actions to shut down the errored process.
*/
FATAL('F');
final char label;
private Level(char label) {
this.label = label;
}
}
/**
* An object that can provide details of a log record.
*
* @param <T> The type of log record the formatter handles.
*/
public interface Formatter<T> {
/**
* Gets the message contained in the log record.
*
* @param record The record to extract a message from.
* @return The formatted message.
*/
String getMessage(T record);
/**
* Gets the class name of the class that sent the log record for logging.
*
* @param record The record to extract a producing class name from.
* @return The producing class if known; otherwise {@code null}.
*/
@Nullable
String getClassName(T record);
/**
* Gets the name of the method of within the class that sent the log record for logging.
*
* @param record The record to extract a producing method name from.
* @return The producing method name if known; otherwise {@code null}.
*/
@Nullable
String getMethodName(T record);
/**
* Gets the level of the log record.
*
* @param record The record to extract a log level from.
* @return The record's log level. Can be {@code null} or {@link Level#UNKNOWN} if unknown.
*/
@Nullable
Level getLevel(T record);
/**
* Gets the timestamp in milliseconds since the epoch when the log record was generated.
*
* @param record The record to extract a time stamp from.
* @return The log record's birth date.
*/
long getTimeStamp(T record);
/**
* Gets the id of the thread that generated the log record.
*
* @param record The record to extract a thread id from.
* @return The id of the thread that generated the log record.
*/
long getThreadId(T record);
/**
* Gets the exception associated with the log record if any.
*
* @param record The record to extract an exception from.
* @return The exception associated with the log record; may be {@code null}.
*/
@Nullable
Throwable getThrowable(T record);
}
private static final DateTimeFormatter DATE_TIME_FORMATTER =
DateTimeFormat.forPattern("MMdd HH:mm:ss.SSS").withZone(DateTimeZone.UTC);
private static final int BASE_MESSAGE_LENGTH =
1 // Level char.
+ 4 // Month + day
+ 1 // space
+ 12 // Timestamp
+ 1 // space
+ 6 // THREAD
+ 4 // Room for thread ID.
+ 1; // space
/**
* Converts the given log record into a glog format log line using the given formatter.
*
* @param formatter A formatter that understands how to unpack the given log record.
* @param record A structure containing log data.
* @param <T> The type of log record.
* @return A glog formatted log line.
*/
public static <T> String formatRecord(Formatter<T> formatter, T record) {
String message = formatter.getMessage(record);
int messageLength = BASE_MESSAGE_LENGTH
+ 2 // Colon and space
+ message.length();
String className = formatter.getClassName(record);
String methodName = null;
if (className != null) {
messageLength += className.length();
methodName = formatter.getMethodName(record);
if (methodName != null) {
messageLength += 1; // Period between class and method.
messageLength += methodName.length();
}
}
StringBuilder sb = new StringBuilder(messageLength)
.append(Objects.firstNonNull(formatter.getLevel(record), Level.UNKNOWN).label)
.append(DATE_TIME_FORMATTER.print(formatter.getTimeStamp(record)))
.append(" THREAD")
.append(formatter.getThreadId(record));
if (className != null) {
sb.append(' ').append(className);
if (methodName != null) {
sb.append('.').append(methodName);
}
}
sb.append(": ").append(message);
Throwable throwable = formatter.getThrowable(record);
if (throwable != null) {
sb.append('\n').append(Throwables.getStackTraceAsString(throwable));
}
return sb.append('\n').toString();
}
private Glog() {
// utility
}
}