| /* |
| * 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 java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.text.SimpleDateFormat; |
| import java.util.Date; |
| import java.util.Locale; |
| import java.util.TimeZone; |
| import java.util.concurrent.ConcurrentMap; |
| 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 ConcurrentMap<Long, String> threadIdToCommandId; |
| private final ThreadLocal<SimpleDateFormat> simpleDateFormat; |
| |
| public LogFormatter() { |
| this( |
| GlobalState.THREAD_ID_TO_COMMAND_ID, |
| Locale.US, |
| TimeZone.getDefault()); |
| } |
| |
| @VisibleForTesting |
| LogFormatter( |
| ConcurrentMap<Long, String> threadIdToCommandId, |
| final Locale locale, |
| final TimeZone timeZone) { |
| this.threadIdToCommandId = threadIdToCommandId; |
| 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 = threadIdToCommandId.get(tid); |
| StringBuilder sb = new StringBuilder(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("] ") |
| .append(formatMessage(record)) |
| .append("\n"); |
| Throwable t = record.getThrown(); |
| if (t != null) { |
| StringWriter sw = new StringWriter(); |
| PrintWriter pw = new PrintWriter(sw); |
| t.printStackTrace(pw); |
| sb.append(sw) |
| .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); |
| } |
| } |
| } |