/* * Copyright (c) 2014 EMC Corporation * All Rights Reserved */ package com.emc.storageos.systemservices.impl.logsvc; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.emc.vipr.model.sys.logging.LogSeverity; // Suppress the following two sonar warnings. Passing byte arrays directly for performance considerations @SuppressWarnings({ "pmd:ArrayIsStoredDirectly", "pmd:MethodReturnsInternalArray" }) public class LogMessage { private Status status; private static final byte[] NULL_BYTES = "null".getBytes(); // nodeId and service are not set in the parser, and nodeId doesn't need to be // serialized in LogNetworkWriter. private byte[] nodeId;// node id // nodeName and service are not set in the parser, and nodeName doesn't need to be // serialized in LogNetworkWriter. private byte[] nodeName;// node name // the following fields need to be serialized in LogNetworkWriter private byte[] service; private long time; // class file name // the following fields should be retrieved from firstLine private int fileNameOffset = -1; private int fileNameLen; // running thread private int threadNameOffset = -1; private int threadNameLen; private int level = -1; private int lineNumberOffset = -1; private int lineNumberLen; private int timeBytesOffset = -1; private int timeBytesLen; private int logOffset = -1; private byte[] firstLine; private List<byte[]> followingLines; enum Status { ACCEPTED, CONTINUATION, REJECTED, REJECTED_LAST, HEADER } public final static LogMessage CONTINUATION_LOGMESSAGE = new LogMessage(Status.CONTINUATION); public final static LogMessage REJECTED_LOGMESSAGE = new LogMessage(Status.REJECTED); public final static LogMessage REJECTED_LAST_LOGMESSAGE = new LogMessage(Status.REJECTED_LAST); public boolean isContinuation() { return status == Status.CONTINUATION; } public boolean isRejected() { return status == Status.REJECTED; } public boolean isRejectedLast() { return status == Status.REJECTED_LAST; } public boolean isHeader() { return status == Status.HEADER; } private LogMessage(Status status) { this.status = status; } private LogMessage() { } public LogMessage(long date, byte[] firstline) { this.status = Status.ACCEPTED; this.firstLine = firstline; this.time = date; } public static LogMessage makeHeaderLog(long time) { LogMessage log = new LogMessage(Status.HEADER); log.setLogOffset(0); log.setTime(time); return log; } public byte[] getNodeId() { if (nodeId == null) { return NULL_BYTES; } return nodeId; } public void setNodeId(byte[] nodeId) { this.nodeId = nodeId; } public byte[] getNodeName() { if (nodeName == null) return NULL_BYTES; return nodeName; } public void setNodeName(byte[] nodeName) { this.nodeName = nodeName; } public void setService(byte[] service) { this.service = service; } public byte[] getService() { if (service == null) { return NULL_BYTES; } return this.service; } public void setTimeBytes(int offset, int len) { timeBytesOffset = offset; timeBytesLen = len; } public int getTimeBytesOffset() { return timeBytesOffset; } public int getTimeBytesLen() { return timeBytesLen; } public void setThreadName(int offset, int len) { threadNameOffset = offset; threadNameLen = len; } public int getThreadNameOffset() { return threadNameOffset; } public int getThreadNameLen() { return threadNameLen; } public void setLevel(int level) { this.level = level; } public void setFileName(int offset, int len) { fileNameOffset = offset; fileNameLen = len; } public int getFileNameOffset() { return fileNameOffset; } public int getFileNameLen() { return fileNameLen; } public void setLineNumber(int offset, int len) { lineNumberOffset = offset; lineNumberLen = len; } public int getLineNumberOffset() { return lineNumberOffset; } public int getLineNumberLen() { return lineNumberLen; } public void setLogOffset(int offset) { logOffset = offset; } public long getTime() { return time; } public void setTime(long t) { time = t; } public byte[] getFileName() { if (fileNameOffset == -1 || fileNameLen == 0) { return NULL_BYTES; } return Arrays.copyOfRange(firstLine, fileNameOffset, fileNameOffset + fileNameLen); } public byte[] getThreadName() { if (threadNameOffset == -1 || threadNameLen == 0) { return NULL_BYTES; } return Arrays.copyOfRange(firstLine, threadNameOffset, threadNameOffset + threadNameLen); } public byte[] getLevel() { if (level < 0 || level >= LogSeverity.MAX_LEVEL) { return NULL_BYTES; } return LogSeverity.values()[level].name().getBytes(); } public byte[] getLineNumber() { if (lineNumberOffset == -1 || lineNumberLen == 0) { return "-1".getBytes(); } return Arrays.copyOfRange(firstLine, lineNumberOffset, lineNumberOffset + lineNumberLen); } public byte[] getTimeBytes() { if (timeBytesOffset == -1 || timeBytesLen == 0) { return NULL_BYTES; } return Arrays.copyOfRange(firstLine, timeBytesOffset, timeBytesOffset + timeBytesLen); } public byte[] getRawLogContent() { return getLogContent(0); } public byte[] getLogContent() { return getLogContent(logOffset); } private byte[] getLogContent(int offset) { int sum = 0; if (firstLine != null) { sum = firstLine.length - offset; } if (followingLines != null && !followingLines.isEmpty()) { for (byte[] line : followingLines) { // trailing \n sum += line.length + 1; } } byte[] result = new byte[sum]; if (firstLine != null && offset < firstLine.length) { sum = firstLine.length - offset; System.arraycopy(firstLine, offset, result, 0, firstLine.length - offset); } else { sum = 0; } if (followingLines != null && !followingLines.isEmpty()) { for (byte[] line : followingLines) { result[sum++] = (byte) '\n'; System.arraycopy(line, 0, result, sum, line.length); sum += line.length; } } return result; } public void setFirstLine(byte[] firstLine) { this.firstLine = firstLine; } public byte[] getFirstLine() { return firstLine; } public void appendMessage(byte[] msg) { if (msg == null || msg.length == 0) { return; } if (followingLines == null) { followingLines = new ArrayList<>(); } followingLines.add(msg); } /** * Write the LogMessage object to a DataOutputStream * The serialized form of the object is: * =============START============== * service length (byte) * service (byte[]) * file name offset (short) * file name length (short) * thread name offset (short) * thread name length (short) * level (byte) * line number offset (short) * line number length (byte) * time (long) * timeBytes offset (short) * timeBytes length (byte) * message offset (short) * message length (int) * message (byte[]) * ==============END=============== * * Note that node id is set in LogNetworkReader, which is above network serialization. * * @param outputStream */ public void write(final DataOutputStream outputStream) throws IOException { // 1 service if (service != null && service.length != 0) { outputStream.write((byte) service.length); outputStream.write(service); } else { outputStream.write((byte) 0); } // 2 file name outputStream.writeShort((short) fileNameOffset); outputStream.writeShort((short) fileNameLen); // 3 thread name outputStream.writeShort((short) threadNameOffset); outputStream.writeShort((short) threadNameLen); // 4 level value outputStream.write((byte) level); // 5 lineNumber outputStream.writeShort((short) lineNumberOffset); outputStream.write((byte) lineNumberLen); // 6 time outputStream.writeLong(time); // 7 timeBytes outputStream.writeShort((short) timeBytesOffset); outputStream.write((byte) timeBytesLen); // 8 msg combined together outputStream.writeShort((short) logOffset); byte[] combinedMsg = getRawLogContent(); if (combinedMsg != null && combinedMsg.length != 0) { outputStream.writeInt(combinedMsg.length); outputStream.write(combinedMsg); } else { outputStream.writeInt(0); } } /** * read object back from buffer * refer to the serialization format in the write method. * * @param inputStream * @return */ public static LogMessage read(final DataInputStream inputStream) throws IOException { LogMessage entry = new LogMessage(); // 1 service int svclen = inputStream.read(); if (svclen > 0) { byte[] svcArr = new byte[svclen]; inputStream.readFully(svcArr); entry.setService(svcArr); } // 2 file name int fnameOffset = inputStream.readShort(); int fnameLen = inputStream.readShort(); entry.setFileName(fnameOffset, fnameLen); // 3 thread name int tnameOffset = inputStream.readShort(); int tnameLen = inputStream.readShort(); entry.setThreadName(tnameOffset, tnameLen); // 4 level value entry.setLevel(inputStream.read()); // 5 lineNumber int lineNoOffset = inputStream.readShort(); int lineNoLen = inputStream.read(); entry.setLineNumber(lineNoOffset, lineNoLen); // 6 time entry.setTime(inputStream.readLong()); // 7 timeBytes int timeOffset = inputStream.readShort(); int timeLen = inputStream.read(); entry.setTimeBytes(timeOffset, timeLen); // 8 msg, all saved to the firstline entry.setLogOffset(inputStream.readShort()); int msglen = inputStream.readInt(); if (msglen > 0) { byte[] msgArr = new byte[msglen]; inputStream.readFully(msgArr); entry.setFirstLine(msgArr); } return entry; } public String toStringForTest() { return "[" + getThreadName() + "]" + " " + getLevel() + " " + getFileName() + " " + getLineNumber() + " " + getLogContent(); } /** * Return original format * Attention: not exactly same. The begging tab is different * when first line firstLine is "", then returned string's first line firstLine is * the second line content if applicable * * @return */ public String toStringOriginalFormat() { StringBuilder sb = new StringBuilder(); sb.append(getTimeBytes()).append(" ").append("[").append(getThreadName()).append("]") .append(" ").append(getLevel()).append(" ") .append(getFileName()).append(" ").append("(line ").append(getLineNumber()) .append(") ").append(" service ").append(getService()).append(" ") .append(getLogContent()); return sb.toString(); } /** * Return original format * Attention: not exactly same. The begging tab is different * when first line firstLine is "", then returned string's first line firstLine is * the second line content if applicable * * @return */ public String toStringOriginalFormatSysLog() { StringBuilder sb = new StringBuilder(); sb.append(getTimeBytes()).append(" ").append("[").append(getThreadName()).append("]") .append(" ").append(getLevel()).append(" ").append(getLogContent()); return sb.toString(); } }