/* * Copyright (c) 2012-2014 Spotify AB * * 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.spotify.logging; import org.slf4j.Logger; import java.util.concurrent.atomic.AtomicLong; /** * Utility for emitting log messages in the Spotify log format. */ public class LoggingSupport { protected static final AtomicLong rid = new AtomicLong(); /** * The ident portion of the log line (comes between the record ID and the log message type). * Format of log idents are defined in identities.py in the log-parser git project. */ public static class Ident { public static final Ident EMPTY_IDENT = new Ident(0); private final String ident; /** * Create a new ident. * * @param version Ident version number. Will only be prepended to ident if not equal to 0. * @param identData Objects that will be converted to strings, joined with tab characters, and * placed inside brackets to make up the ident string. */ public Ident(final int version, final Object... identData) { final StringBuilder buf = new StringBuilder(); if (version != 0) { buf.append(version).append(':'); } buf.append('['); boolean first = true; for (final Object part : identData) { if (!first) { buf.append('\t'); } else { first = false; } appendEscaped(part, buf); } buf.append(']'); this.ident = buf.toString(); } public String toString() { return ident; } } /** * Generate a new debug log message according to the Spotify log format. * * @param logger Which Logger to use for writing the log messages. It is assumed that this Logger * is already set up via com.spotify.logging.LoggingConfigurator and a * [service]-log4j.xml configuration so that the time stamp, hostname, service, and * process ID portions of the log message are automatically prepended. * @param type Log message type. Log messages are defined in the messages.py module in the * log-parser git project. When a new message type is added or changed in a * service, it should also be changed/added in messages.py. * @param version Version of the log message. This is incremented by callers and in messages.py if * the format of a given log message type is changed. * @param ident Ident object to give information generally about a client who made the request * that resulted in this message. * @param args Additional arguments that will be converted to strings, escaped, and appended * (tab-separated) after the log message type and version number. */ public static void debug(final Logger logger, final String type, final int version, final Ident ident, final Object... args) { logger.debug(buildLogLine(type, version, ident, args)); } /** * Generate a new info log message according to the Spotify log format. * * @see LoggingSupport#debug for parameter descriptions. */ public static void info(final Logger logger, final String type, final int version, final Ident ident, final Object... args) { logger.info(buildLogLine(type, version, ident, args)); } /** * Generate a new warn log message according to the Spotify log format. * * @see LoggingSupport#debug for parameter descriptions. */ public static void warn(final Logger logger, final String type, final int version, final Ident ident, final Object... args) { logger.warn(buildLogLine(type, version, ident, args)); } /** * Generate a new error log message according to the Spotify log format. * * @see LoggingSupport#debug for parameter descriptions. */ public static void error(final Logger logger, final String type, final int version, final Ident ident, final Object... args) { logger.error(buildLogLine(type, version, ident, args)); } protected static String buildLogLine(final String type, final int version, final Ident ident, final Object... args) { final StringBuilder line = new StringBuilder(); line.append(LoggingSupport.rid.getAndIncrement()).append(' '); if (ident == null) { line.append(Ident.EMPTY_IDENT); } else { line.append(ident); } line.append(' '); line.append(type).append('\t').append(version); for (final Object arg : args) { line.append('\t'); appendEscaped(arg, line); } return line.toString(); } protected static void appendEscaped(final Object o, final StringBuilder out) { if (o == null) { return; } final String s = o.toString(); for (int i = 0; i < s.length(); i++) { final char c = s.charAt(i); if (c == '\t' || c == '\n') { out.append(' '); } else { out.append(c); } } } }