The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

=encoding utf8

=head1 NAME

sc - Splunk Client

=head1 SYNOPSIS

B<sc>
[--host <host>]
[--port <port>]
[--login <login>]
[--password <password>]
[--insecure]
<subcommand>
[<arguments>,...]

=head1 DESCRIPTION

This is remote client for Splunk log search engine based upon L<WWW::Splunk>.
It is currently quite limited in capabilities, but intended and designed to
be extended in future.

=cut

use Getopt::Long qw/GetOptionsFromArray/;
use WWW::Splunk;
use LWP::UserAgent;
use Pod::Usage;

use strict;
use warnings;

our $VERSION = '2.07';

# Subcommand dispatch

my %commands = (
	search	=> \&search,
);

# Command line options

my $host = 'localhost';
my $port = 8089;
my $login = 'admin';
my $password = 'changeme';
my $insecure = 0;

=head1 OPTIONS

=over

=item B<< --host <host> >>

Sets remote server to connect to. Defaults to localhost.

=item B<< --port <port> >>

Sets port of remote server to connect to. Defaults to 8089.
Please note that this is the management port, not the WWW interface port.

=item B<< --login <login> >>

User name of the user to connect to Splunk as. Defaults to admin.
The defaults for username and password will probably (hopefully?)
not suit your configuration.

=item B<< --password <password> >>

Password of the user to connect to Splunk as. Defaults to changeme.

=item B<--insecure>

Tolerate SSL errors.

=item B<< <subcommand> [<arguments>] >>

Subcommand to run. Currently defined is just B<search>.

=back

=cut

new Getopt::Long::Parser (
        config => [qw/require_order/]
)->getoptions (
	"host=s"	=> \$host,
	"port=i"	=> \$port,
	"login=s"	=> \$login,
	"password=s"	=> \$password,
	"insecure"	=> \$insecure,
	"h|help"	=> sub { pod2usage (0) },
	"m|man"		=> sub { pod2usage (-verbose => 2) },
) or die "Could not parse command line";

# Dispatch

my $subcommand = shift @ARGV;
die "Missing subcommand" unless $subcommand;
die "Invalid subcommand" unless exists $commands{$subcommand};

my $splunk = new WWW::Splunk ({
	host	=> $host,
	port	=> $port,
	login	=> $login,
	password => $password,
	unsafe_ssl => $insecure,
});

exit $commands{$subcommand}->(@ARGV);

# Implementation

sub print_results
{
	my $format = shift;
	return unless @_;

	$format ||= exists $_[0]->{_raw} ? 'raw' : 'compact';

	if ($format eq 'compact') {
		foreach my $entry (@_) {
			print join " ", map { "$_=$entry->{$_}" } sort keys %$entry;
			print "\n";
		}
	} elsif ($format eq 'long') {
		foreach my $entry (@_) {
			print "$_=$entry->{$_}\n" foreach sort keys %$entry;
			print "\n";
		}
	} elsif ($format eq 'raw') {
		print join "\n", (map { $_->{_raw} || '(no raw log)' } @_), '';
	} else {
		die "Unknown format: $format";
	}
}

=head1 COMMANDS

=head2 B<search>
[-t|--since <time>]
[-T|--until <time>]
[-f|--format compact|long|raw]
<search string>

Conduct a search, output the raw log data as they are looked up.
Terminate when the search is finished.

=over 4

=item B<-t>, B<--since> B<< <time> >>

Cut off at given time. The time specification is any string understood
by L<Date::Manip>. Most common formats apply as well as human-readable
relative time specifications (see L<EXAMPLES>).

Use C<rt> for real time search, optionally with specifcation of the
search window, such as C<rt-10> for 10-second window, or C<rt-1m>
for one minute.

Defaults to unlimited.

=item B<-T>, B<--until> B<< <time> >>

Do not look for entries newer than given time. The format of the time
specification is the same as for B<--since> option.

If this or I<--since> is C<rt> a real-time search is conducted.

=item B<-f>, B<--format> I<compact>|I<long>|I<raw>

Switch output format style.

=back

=cut

sub search
{
	my ($since, $until, $format);
        GetOptionsFromArray (\@_,
                't|since=s' => \$since,
                'T|until=s' => \$until,
                'f|format=s' => \$format,
        ) or die 'Bad arguments to search';

	if (($since and $since =~ /^rt/) or ($until and $until =~ /^rt/)) {
		# Real time search
		$splunk->rt_search (join (' ', @_), sub {
			print_results ($format => @_);
		}, $since, $until);
	} else {
		my $sid = $splunk->start_search (join (' ', @_), $since, $until);

		until ($splunk->results_read ($sid)) {
			my @results = $splunk->search_results ($sid);
			print_results ($format => @results);
		}
	}

	return 0;
}

=head1 EXAMPLES

=over

=item B<sc --host splunk.example.net --login user --password s1kr3t2 search --since '2 days ago' --until yesterday 'network AND error | head 10'>

Perform a simple search query limited by given time frame.

=item B<sc search --since 'rt-30' 'source=/var/log/httpd/access_log |stats count by http_status_code'>

Perform a simple real-time search.

=back

=head1 SEE ALSO

L<WWW::Splunk>, L<WWW::Splunk::API>

=head1 AUTHORS

Lubomir Rintel, L<< <lkundrak@v3.sk> >>,
Michal Josef Špaček L<< <skim@cpan.org> >>

The code is hosted on GitHub L<http://github.com/tupinek/perl-WWW-Splunk>.
Bug fixes and feature enhancements are always welcome.

=cut