/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.nifi.processors.standard.syslog;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Parses a Syslog message from a ByteBuffer into a SyslogEvent instance.
*
* The Syslog regular expressions below were adapted from the Apache Flume project.
*/
public class SyslogParser {
public static final String SYSLOG_MSG_RFC5424_0 =
"(?:\\<(\\d{1,3})\\>)" + // priority
"(?:(\\d)?\\s?)" + // version
/* yyyy-MM-dd'T'HH:mm:ss.SZ or yyyy-MM-dd'T'HH:mm:ss.S+hh:mm or - (null stamp) */
"(?:" +
"(\\d{4}[-]\\d{2}[-]\\d{2}[T]\\d{2}[:]\\d{2}[:]\\d{2}" +
"(?:\\.\\d{1,6})?(?:[+-]\\d{2}[:]\\d{2}|Z)?)|-)" + // stamp
"\\s" + // separator
"(?:([\\w][\\w\\d\\.@\\-]*)|-)" + // host name or - (null)
"\\s" + // separator
"(.*)$"; // body
public static final String SYSLOG_MSG_RFC3164_0 =
"(?:\\<(\\d{1,3})\\>)" +
"(?:(\\d)?\\s?)" + // version
// stamp MMM d HH:mm:ss, single digit date has two spaces
"([A-Z][a-z][a-z]\\s{1,2}\\d{1,2}\\s\\d{2}[:]\\d{2}[:]\\d{2})" +
"\\s" + // separator
"([\\w][\\w\\d(\\.|\\:)@-]*)" + // host
"\\s(.*)$"; // body
public static final Collection<Pattern> MESSAGE_PATTERNS;
static {
List<Pattern> patterns = new ArrayList<>();
patterns.add(Pattern.compile(SYSLOG_MSG_RFC5424_0));
patterns.add(Pattern.compile(SYSLOG_MSG_RFC3164_0));
MESSAGE_PATTERNS = Collections.unmodifiableList(patterns);
}
// capture group positions from the above message patterns
public static final int SYSLOG_PRIORITY_POS = 1;
public static final int SYSLOG_VERSION_POS = 2;
public static final int SYSLOG_TIMESTAMP_POS = 3;
public static final int SYSLOG_HOSTNAME_POS = 4;
public static final int SYSLOG_BODY_POS = 5;
private Charset charset;
public SyslogParser() {
this(StandardCharsets.UTF_8);
}
public SyslogParser(final Charset charset) {
this.charset = charset;
}
/**
* Parses a SyslogEvent from a byte buffer.
*
* @param buffer a byte buffer containing a syslog message
* @return a SyslogEvent parsed from the byte array
*/
public SyslogEvent parseEvent(final ByteBuffer buffer) {
return parseEvent(buffer, null);
}
/**
* Parses a SyslogEvent from a byte buffer.
*
* @param buffer a byte buffer containing a syslog message
* @param sender the hostname of the syslog server that sent the message
* @return a SyslogEvent parsed from the byte array
*/
public SyslogEvent parseEvent(final ByteBuffer buffer, final String sender) {
if (buffer == null) {
return null;
}
if (buffer.position() != 0) {
buffer.flip();
}
byte bytes[] = new byte[buffer.limit()];
buffer.get(bytes, 0, buffer.limit());
return parseEvent(bytes, sender);
}
/**
* Parses a SyslogEvent from a byte array.
*
* @param bytes a byte array containing a syslog message
* @param sender the hostname of the syslog server that sent the message
* @return a SyslogEvent parsed from the byte array
*/
public SyslogEvent parseEvent(final byte[] bytes, final String sender) {
if (bytes == null || bytes.length == 0) {
return null;
}
// remove trailing new line before parsing
int length = bytes.length;
if (bytes[length - 1] == '\n') {
length = length - 1;
}
final String message = new String(bytes, 0, length, charset);
final SyslogEvent.Builder builder = new SyslogEvent.Builder()
.valid(false).fullMessage(message).rawMessage(bytes).sender(sender);
for (Pattern pattern : MESSAGE_PATTERNS) {
final Matcher matcher = pattern.matcher(message);
if (!matcher.matches()) {
continue;
}
final MatchResult res = matcher.toMatchResult();
for (int grp = 1; grp <= res.groupCount(); grp++) {
String value = res.group(grp);
if (grp == SYSLOG_TIMESTAMP_POS) {
builder.timestamp(value);
} else if (grp == SYSLOG_HOSTNAME_POS) {
builder.hostname(value);
} else if (grp == SYSLOG_PRIORITY_POS) {
int pri = Integer.parseInt(value);
int sev = pri % 8;
int facility = pri / 8;
builder.priority(value);
builder.severity(String.valueOf(sev));
builder.facility(String.valueOf(facility));
} else if (grp == SYSLOG_VERSION_POS) {
builder.version(value);
} else if (grp == SYSLOG_BODY_POS) {
builder.msgBody(value);
}
}
builder.valid(true);
break;
}
// either invalid w/original msg, or fully parsed event
return builder.build();
}
public String getCharsetName() {
return charset == null ? StandardCharsets.UTF_8.name() : charset.name();
}
}