com.yahoo.log.LogMessage Maven / Gradle / Ivy
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.log;
import com.yahoo.log.event.Event;
import com.yahoo.log.event.MalformedEventException;
import java.time.Instant;
import java.util.Objects;
import java.util.OptionalLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This class implements the common ground log message used by
* the logserver. A LogMessage is immutable. Note that we have
* chosen the name LogMessage to avoid confusion with LogRecord
* which is used in java.util.logging.
*
* @author Bjorn Borud
* @author bjorncs
*/
public class LogMessage
{
private static Logger log = Logger.getLogger(LogMessage.class.getName());
private static Pattern nativeFormat =
Pattern.compile("^(\\d[^\t]+)\t" + // time
"([^\t]+)\t" + // host
"([^\t]+)\t" + // threadProcess
"([^\t]+)\t" + // service
"([^\t]+)\t" + // component
"([^\t]+)\t" + // level
"(.+)$" // payload
);
private Instant time;
private String host;
private long processId;
private long threadId;
private String service;
private String component;
private Level level;
private String payload;
private Event event;
/**
* Private constructor. Log messages should never be instantiated
* directly; only as the result of a static factory method.
*/
private LogMessage (Instant time, String host, long processId, long threadId,
String service, String component, Level level,
String payload)
{
this.time = time;
this.host = host;
this.processId = processId;
this.threadId = threadId;
this.service = service;
this.component = component;
this.level = level;
this.payload = payload;
}
public static LogMessage of(
Instant time, String host, long processId, long threadId,
String service, String component, Level level, String payload) {
return new LogMessage(time, host, processId, threadId, service, component, level, payload);
}
public Instant getTimestamp() {return time;}
public String getHost () {return host;}
public long getProcessId() {return processId;}
public OptionalLong getThreadId() {return threadId > 0 ? OptionalLong.of(threadId) : OptionalLong.empty();}
public String getService () {return service;}
public String getComponent () {return component;}
public Level getLevel () {return level;}
public String getPayload () {return payload;}
/**
* Make a log message from the native format of the logging
* package.
*
* @param msg The log message
* @return Returns a LogMessage instance
* @throws InvalidLogFormatException if the log message
* can not be parsed, ie. is invalid, we throw this
* exception.
*/
public static LogMessage parseNativeFormat(String msg) throws InvalidLogFormatException {
Matcher m = nativeFormat.matcher(msg);
if (! m.matches()) {
throw new InvalidLogFormatException(msg);
}
@SuppressWarnings("deprecation")
Level msgLevel = LogLevel.parse(m.group(6));
Instant timestamp = parseTimestamp(m.group(1));
String threadProcess = m.group(3);
return new LogMessage(timestamp, m.group(2), parseProcessId(threadProcess), parseThreadId(threadProcess),
m.group(4), m.group(5), msgLevel,
m.group(7));
}
private static Instant parseTimestamp(String timeStr) throws InvalidLogFormatException {
try {
int decimalSeparator = timeStr.indexOf('.');
if (decimalSeparator == -1) {
return Instant.ofEpochSecond(Long.parseLong(timeStr));
}
long seconds = Long.parseLong(timeStr.substring(0, decimalSeparator));
long nanoseconds = Long.parseLong(String.format("%1$-9s", timeStr.substring(decimalSeparator + 1)).replace(' ', '0')); // right pad with zeros
return Instant.ofEpochSecond(seconds, nanoseconds);
} catch (NumberFormatException e) {
throw new InvalidLogFormatException(String.format("Failed to parse timestamp: %s. Timestamp string: '%s'", e.getMessage(), timeStr), e);
}
}
private static long parseProcessId(String threadProcess) {
int slashIndex = threadProcess.indexOf('/');
if (slashIndex == -1) {
return Long.parseLong(threadProcess);
}
return Long.parseLong(threadProcess.substring(0, slashIndex));
}
private static long parseThreadId(String threadProcess) {
int slashIndex = threadProcess.indexOf('/');
if (slashIndex == -1) {
return 0;
}
return Long.parseLong(threadProcess.substring(slashIndex + 1));
}
/**
* If the LogMessage was an EVENT then this method can
* be used to get the Event instance representing the
* event. The event instance created the first time
* this method is called and then cached.
*
* TODO: make sure this throws exception!
*
* @return Returns Event instance if this is an event message
* and the payload is correctly formatted. Otherwise
* it will return null
.
*
*/
@SuppressWarnings("deprecation")
public Event getEvent () throws MalformedEventException {
if ((level == LogLevel.EVENT) && (event == null)) {
try {
event = Event.parse(getPayload());
event.setTime(time.toEpochMilli());
}
catch (MalformedEventException e) {
log.log(LogLevel.DEBUG, "Got malformed event: " + getPayload());
throw e;
}
}
return event;
}
/**
* Return valid representation of log message.
*/
public String toString () {
String threadProcess = VespaFormat.formatThreadProcess(processId, threadId);
String timeStr = VespaFormat.formatTime(time);
return new StringBuilder(timeStr.length()
+ host.length()
+ threadProcess.length()
+ service.length()
+ component.length()
+ level.toString().length()
+ payload.length()
+ 7)
.append(timeStr).append("\t")
.append(host).append("\t")
.append(threadProcess).append("\t")
.append(service).append("\t")
.append(component).append("\t")
.append(level.toString().toLowerCase()).append("\t")
.append(payload).append("\n")
.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LogMessage that = (LogMessage) o;
return processId == that.processId &&
threadId == that.threadId &&
Objects.equals(time, that.time) &&
Objects.equals(host, that.host) &&
Objects.equals(service, that.service) &&
Objects.equals(component, that.component) &&
Objects.equals(level, that.level) &&
Objects.equals(payload, that.payload) &&
Objects.equals(event, that.event);
}
@Override
public int hashCode() {
return Objects.hash(time, host, processId, threadId, service, component, level, payload, event);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy