package com.spotify.logging.logback;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.spotify.logging.LoggingConfigurator;
import java.lang.management.ManagementFactory;
import ch.qos.logback.classic.net.SyslogAppender;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.net.SyslogAppenderBase;
/**
* A {@link SyslogAppender} that uses millisecond precision, and that by default configures its
* values for {@link SyslogAppender#syslogHost} and {@link SyslogAppender#port} based on the
* environment variables specified in {@link LoggingConfigurator#SPOTIFY_SYSLOG_HOST} and
* {@link LoggingConfigurator#SPOTIFY_SYSLOG_PORT}. If the configuration explicitly sets the
* {@link SyslogAppenderBase#syslogHost} or {@link SyslogAppenderBase#port} values, the environment
* variables will not be used. Note that logback's configuration support allows you to use
* environment variables in your logback.xml file as well (see
* http://logback.qos.ch/manual/configuration.html#scopes).
*/
@SuppressWarnings("WeakerAccess")
public class SpotifyInternalAppender extends MillisecondPrecisionSyslogAppender {
private String serviceName;
private boolean portConfigured = false;
@Override
public void start() {
Preconditions.checkState(serviceName != null, "serviceName must be configured");
// set up some defaults
setFacility("LOCAL0");
// our internal syslog-ng configuration splits logs up based on service name, and expects the
// format below.
String serviceAndPid = String.format("%s[%s]", serviceName, getMyPid());
setSuffixPattern(serviceAndPid + ": %msg");
setStackTracePattern(serviceAndPid + ": " + CoreConstants.TAB);
if (getSyslogHost() == null) {
setSyslogHost(System.getenv(LoggingConfigurator.SPOTIFY_SYSLOG_HOST));
}
checkSetPort(System.getenv(LoggingConfigurator.SPOTIFY_SYSLOG_PORT));
super.start();
}
private void checkSetPort(String environmentValue) {
if (environmentValue == null || portConfigured) {
return;
}
try {
setPort(Integer.parseInt(environmentValue));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
"unable to parse value for \"" + LoggingConfigurator.SPOTIFY_SYSLOG_PORT + "\" (" +
environmentValue + ") as an int", e);
}
}
@Override
public void setPort(int port) {
portConfigured = true;
super.setPort(port);
}
/**
* The service name you want to include in the logs - this is a mandatory setting, and determines
* where syslog-ng will send the log output (/spotify/log/${serviceName}).
*
* @param serviceName the service name
*/
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
// copied from LoggingConfigurator to avoid making public and exposing externally.
// TODO (bjorn): We probably want to move this to the utilities project.
// Also, the portability of this function is not guaranteed.
@VisibleForTesting
static String getMyPid() {
String pid = "0";
try {
final String nameStr = ManagementFactory.getRuntimeMXBean().getName();
// XXX (bjorn): Really stupid parsing assuming that nameStr will be of the form
// "pid@hostname", which is probably not guaranteed.
pid = nameStr.split("@")[0];
} catch (RuntimeException e) {
// Fall through.
}
return pid;
}
}