/* * Copyright 2014-present Facebook, 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.facebook.buck.log; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Throwables; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.LogRecord; import javax.annotation.Nullable; public class LogFormatter extends java.util.logging.Formatter { private static final int ERROR_LEVEL = Level.SEVERE.intValue(); private static final int WARN_LEVEL = Level.WARNING.intValue(); private static final int INFO_LEVEL = Level.INFO.intValue(); private static final int DEBUG_LEVEL = Level.FINE.intValue(); private static final int VERBOSE_LEVEL = Level.FINER.intValue(); private final ThreadIdToCommandIdMapper mapper; private final ThreadLocal<SimpleDateFormat> simpleDateFormat; public LogFormatter() { this( GlobalStateManager.singleton().getThreadIdToCommandIdMapper(), Locale.US, TimeZone.getDefault()); } @VisibleForTesting LogFormatter(ThreadIdToCommandIdMapper mapper, final Locale locale, final TimeZone timeZone) { this.mapper = mapper; simpleDateFormat = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { SimpleDateFormat format = new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss.SSS]", locale); format.setTimeZone(timeZone); return format; } }; } @Override public String format(LogRecord record) { String timestamp = simpleDateFormat.get().format(new Date(record.getMillis())); // We explicitly don't use String.format here because this code is very // performance-critical: http://stackoverflow.com/a/1281651 long tid = record.getThreadID(); @Nullable String command = mapper.threadIdToCommandId(tid); StringBuilder sb = new StringBuilder(255) .append(timestamp) .append(formatRecordLevel(record.getLevel())) .append("[command:") .append(command) .append("][tid:"); // Zero-pad on the left. We're currently assuming we have less than 100 threads. if (tid < 10) { sb.append("0").append(tid); } else { sb.append(tid); } sb.append("][").append(record.getLoggerName()).append("] "); if (record instanceof AppendableLogRecord) { // Avoid allocating then throwing away the formatted message and // params; just format directly to the StringBuilder. ((AppendableLogRecord) record).appendFormattedMessage(sb); } else { sb.append(formatMessage(record)); } sb.append("\n"); Throwable t = record.getThrown(); if (t != null) { sb.append(Throwables.getStackTraceAsString(t)).append("\n"); } return sb.toString(); } private static String formatRecordLevel(Level level) { int l = level.intValue(); if (l == ERROR_LEVEL) { return "[error]"; } else if (l == WARN_LEVEL) { return "[warn ]"; } else if (l == INFO_LEVEL) { return "[info ]"; } else if (l == DEBUG_LEVEL) { return "[debug]"; } else if (l == VERBOSE_LEVEL) { return "[vrbos]"; } else { // We don't expect this to happen, so meh, let's use String.format for simplicity. return String.format("[%-5d]", l); } } }