blob: dd0e839832e1d21373e670e97dac90cd6b1600e9 [file] [log] [blame]
/*
* 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.cassandra.tools;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import net.openhft.chronicle.core.io.IORuntimeException;
import net.openhft.chronicle.queue.impl.single.SingleChronicleQueueBuilder;
import net.openhft.chronicle.queue.ExcerptTailer;
import net.openhft.chronicle.queue.RollCycles;
import net.openhft.chronicle.queue.impl.single.SingleChronicleQueue;
import net.openhft.chronicle.threads.Pauser;
import net.openhft.chronicle.wire.ReadMarshallable;
import net.openhft.chronicle.wire.WireIn;
import org.apache.cassandra.audit.BinAuditLogger;
import org.apache.cassandra.utils.binlog.BinLog;
/**
* Tool to view the contenst of AuditLog files in human readable format. Default implementation for AuditLog files
* logs audit messages in {@link org.apache.cassandra.utils.binlog.BinLog} format, this tool prints the contens of
* binary audit log files in text format.
*/
public class AuditLogViewer
{
private static final String TOOL_NAME = "auditlogviewer";
private static final String ROLL_CYCLE = "roll_cycle";
private static final String FOLLOW = "follow";
private static final String IGNORE = "ignore";
private static final String HELP_OPTION = "help";
public static void main(String[] args)
{
AuditLogViewerOptions options = AuditLogViewerOptions.parseArgs(args);
try
{
dump(options.pathList, options.rollCycle, options.follow, options.ignoreUnsupported, System.out::print);
}
catch (Exception e)
{
System.err.println(e.getMessage());
System.exit(1);
}
}
static void dump(List<String> pathList, String rollCycle, boolean follow, boolean ignoreUnsupported, Consumer<String> displayFun)
{
//Backoff strategy for spinning on the queue, not aggressive at all as this doesn't need to be low latency
Pauser pauser = Pauser.millis(100);
List<ExcerptTailer> tailers = pathList.stream()
.distinct()
.map(path -> SingleChronicleQueueBuilder.single(new File(path)).readOnly(true).rollCycle(RollCycles.valueOf(rollCycle)).build())
.map(SingleChronicleQueue::createTailer)
.collect(Collectors.toList());
boolean hadWork = true;
while (hadWork)
{
hadWork = false;
for (ExcerptTailer tailer : tailers)
{
while (tailer.readDocument(new DisplayRecord(ignoreUnsupported, displayFun)))
{
hadWork = true;
}
}
if (follow)
{
if (!hadWork)
{
//Chronicle queue doesn't support blocking so use this backoff strategy
pauser.pause();
}
//Don't terminate the loop even if there wasn't work
hadWork = true;
}
}
}
private static class DisplayRecord implements ReadMarshallable
{
private final boolean ignoreUnsupported;
private final Consumer<String> displayFun;
DisplayRecord(boolean ignoreUnsupported, Consumer<String> displayFun)
{
this.ignoreUnsupported = ignoreUnsupported;
this.displayFun = displayFun;
}
public void readMarshallable(WireIn wireIn) throws IORuntimeException
{
int version = wireIn.read(BinLog.VERSION).int16();
if (!isSupportedVersion(version))
{
return;
}
String type = wireIn.read(BinLog.TYPE).text();
if (!isSupportedType(type))
{
return;
}
StringBuilder sb = new StringBuilder();
sb.append("Type: ")
.append(type)
.append(System.lineSeparator())
.append("LogMessage: ")
.append(wireIn.read(BinAuditLogger.AUDITLOG_MESSAGE).text())
.append(System.lineSeparator());
displayFun.accept(sb.toString());
}
private boolean isSupportedVersion(int version)
{
if (version <= BinAuditLogger.CURRENT_VERSION)
{
return true;
}
if (ignoreUnsupported)
{
return false;
}
throw new IORuntimeException("Unsupported record version [" + version
+ "] - highest supported version is [" + BinAuditLogger.CURRENT_VERSION + ']');
}
private boolean isSupportedType(String type)
{
if (BinAuditLogger.AUDITLOG_TYPE.equals(type))
{
return true;
}
if (ignoreUnsupported)
{
return false;
}
throw new IORuntimeException("Unsupported record type field [" + type
+ "] - supported type is [" + BinAuditLogger.AUDITLOG_TYPE + ']');
}
}
private static class AuditLogViewerOptions
{
private final List<String> pathList;
private String rollCycle = "HOURLY";
private boolean follow;
private boolean ignoreUnsupported;
private AuditLogViewerOptions(String[] pathList)
{
this.pathList = Arrays.asList(pathList);
}
static AuditLogViewerOptions parseArgs(String cmdArgs[])
{
CommandLineParser parser = new GnuParser();
Options options = getCmdLineOptions();
try
{
CommandLine cmd = parser.parse(options, cmdArgs, false);
if (cmd.hasOption(HELP_OPTION))
{
printUsage(options);
System.exit(0);
}
String[] args = cmd.getArgs();
if (args.length <= 0)
{
System.err.println("Audit log files directory path is a required argument.");
printUsage(options);
System.exit(1);
}
AuditLogViewerOptions opts = new AuditLogViewerOptions(args);
opts.follow = cmd.hasOption(FOLLOW);
opts.ignoreUnsupported = cmd.hasOption(IGNORE);
if (cmd.hasOption(ROLL_CYCLE))
{
opts.rollCycle = cmd.getOptionValue(ROLL_CYCLE);
}
return opts;
}
catch (ParseException e)
{
errorMsg(e.getMessage(), options);
return null;
}
}
static void errorMsg(String msg, Options options)
{
System.err.println(msg);
printUsage(options);
System.exit(1);
}
static Options getCmdLineOptions()
{
Options options = new Options();
options.addOption(new Option("r", ROLL_CYCLE, true, "How often to roll the log file was rolled. May be necessary for Chronicle to correctly parse file names. (MINUTELY, HOURLY, DAILY). Default HOURLY."));
options.addOption(new Option("f", FOLLOW, false, "Upon reacahing the end of the log continue indefinitely waiting for more records"));
options.addOption(new Option("i", IGNORE, false, "Silently ignore unsupported records"));
options.addOption(new Option("h", HELP_OPTION, false, "display this help message"));
return options;
}
static void printUsage(Options options)
{
String usage = String.format("%s <path1> [<path2>...<pathN>] [options]", TOOL_NAME);
StringBuilder header = new StringBuilder();
header.append("--\n");
header.append("View the audit log contents in human readable format");
header.append("\n--\n");
header.append("Options are:");
new HelpFormatter().printHelp(usage, header.toString(), options, "");
}
}
}