lnav/src/lnav.cc

2535 lines
57 KiB
C++
Raw Normal View History

2009-09-14 01:07:32 +00:00
#include "config.h"
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <time.h>
#include <glob.h>
#include <curses.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <readline/readline.h>
#include <map>
#include <stack>
#include <vector>
#include <memory>
#include <sstream>
#include <algorithm>
#include <functional>
#include "help.hh"
#include "auto_temp_file.hh"
#include "logfile.hh"
#include "lnav_util.hh"
#include "listview_curses.hh"
#include "statusview_curses.hh"
#include "vt52_curses.hh"
#include "readline_curses.hh"
#include "textview_curses.hh"
#include "logfile_sub_source.hh"
#include "grep_proc.hh"
#include "bookmarks.hh"
#include "hist_source.hh"
#include "top_status_source.hh"
#include "bottom_status_source.hh"
#include "piper_proc.hh"
#include "log_vtab_impl.hh"
#include "db_sub_source.hh"
#include "pcrecpp.h"
#include <soci.h>
#include <sqlite3/soci-sqlite3.h>
using namespace std;
using namespace soci;
typedef enum {
LNM_PAGING,
LNM_COMMAND,
LNM_SEARCH,
LNM_CAPTURE,
LNM_SQL,
} ln_mode_t;
enum {
LNB_SYSLOG,
LNB__MAX,
LNB_ROTATED
};
typedef enum {
LNF_SYSLOG = (1L << LNB_SYSLOG),
LNF_ROTATED = (1L << LNB_ROTATED),
LNF__ALL = (LNF_SYSLOG)
} lnav_flags_t;
static multimap<lnav_flags_t, string> DEFAULT_FILES;
typedef enum {
LNV_LOG,
LNV_HELP,
LNV_HISTOGRAM,
LNV_GRAPH,
LNV_DB,
LNV__MAX
} lnav_view_t;
typedef enum {
LNS_TOP,
LNS_BOTTOM,
LNS__MAX
} lnav_status_t;
typedef enum {
LG_SEARCH,
LG_GRAPH,
LG_CAPTURE,
LG__MAX
} lnav_grep_t;
struct hist_level {
int hl_bucket_size;
int hl_group_size;
};
static struct hist_level HIST_ZOOM_VALUES[] = {
{ 24 * 60 * 60, 7 * 24 * 60 * 60 },
{ 4 * 60 * 60, 24 * 60 * 60 },
{ 60 * 60, 24 * 60 * 60 },
{ 10 * 60, 60 * 60 },
{ 60, 60 * 60 },
};
static const int HIST_ZOOM_LEVELS = sizeof(HIST_ZOOM_VALUES) / sizeof(struct hist_level);
class grep_highlighter {
public:
grep_highlighter(auto_ptr < grep_proc > gp,
string hl_name,
textview_curses::highlight_map_t &hl_map)
: gh_code(gp->get_code()),
gh_grep_proc(gp),
gh_hl_name(hl_name),
gh_hl_map(hl_map) { };
~grep_highlighter()
{
this->gh_hl_map.erase(this->gh_hl_map.find(this->gh_hl_name));
};
grep_proc *get_grep_proc() { return this->gh_grep_proc.get(); };
private:
auto_mem<pcre> gh_code;
auto_ptr<grep_proc> gh_grep_proc;
string gh_hl_name;
textview_curses::highlight_map_t gh_hl_map;
};
class grapher
: public grep_proc_sink,
public hist_source {
public:
grapher()
: gr_highlighter(NULL)
{
this->set_label_source(&this->gr_label_source);
};
grep_line_t at(int row) { return this->gr_lines[row]; };
void set_highlighter(textview_curses::highlighter *hl)
{
this->gr_highlighter = hl;
};
void grep_begin(grep_proc &gp)
{
this->clear();
this->hs_type2role.clear();
this->gr_lines.clear();
this->gr_x = -1;
this->gr_next_field = bucket_type_t(0);
};
void grep_match(grep_proc &gp, grep_line_t line, int start, int end) { };
void grep_capture(grep_proc &gp,
grep_line_t line,
int start,
int end,
char *capture)
{
float amount = 1.0;
if (this->gr_lines.empty() || this->gr_lines.back() != line) {
this->gr_next_field = bucket_type_t(0);
this->gr_x += 1;
this->gr_lines.push_back(line);
}
if (this->gr_highlighter != NULL) {
if (this->hs_type2role.find(this->gr_next_field) ==
this->hs_type2role.end()) {
this->hs_type2role[this->gr_next_field] =
this->gr_highlighter->get_role(this->gr_next_field);
}
}
sscanf(capture, "%f", &amount);
this->add_value(this->gr_x, this->gr_next_field, amount);
++ this->gr_next_field;
};
void grep_end_batch(grep_proc &gp) { this->analyze(); };
void grep_end(grep_proc &gp) { this->analyze(); };
private:
class label_source
: public hist_source::label_source {
public:
label_source() { };
void hist_label_for_bucket(int bucket_start_value,
const hist_source::bucket_t &bucket,
string &label_out)
{
hist_source::bucket_t::const_iterator iter;
float total = 0.0;
for (iter = bucket.begin(); iter != bucket.end(); iter++) {
char buffer[64];
if (iter->second != 0.0) {
snprintf(buffer, sizeof(buffer), " % 10.2f", iter->second);
}
else {
snprintf(buffer, sizeof(buffer), " % 10s", "-");
}
label_out += string(buffer);
}
};
};
label_source gr_label_source;
textview_curses::highlighter *gr_highlighter;
vector<grep_line_t> gr_lines;
int gr_x;
bucket_type_t gr_next_field;
};
static struct {
const char *ld_program_name;
const char *ld_debug_log_name;
set< pair<string, int> > ld_file_names;
sig_atomic_t ld_looping;
sig_atomic_t ld_winched;
unsigned long ld_flags;
WINDOW *ld_window;
ln_mode_t ld_mode;
statusview_curses ld_status[LNS__MAX];
top_status_source ld_top_source;
bottom_status_source ld_bottom_source;
listview_curses::action::broadcaster ld_scroll_broadcaster;
time_t ld_top_time;
time_t ld_bottom_time;
stack<textview_curses *> ld_view_stack;
textview_curses ld_views[LNV__MAX];
readline_curses *ld_rl_view;
logfile_sub_source ld_log_source;
hist_source ld_hist_source;
int ld_hist_zoom;
map<textview_curses *, int> ld_last_user_mark;
grapher ld_graph_source;
hist_source ld_db_source;
db_label_source ld_db_rows;
int ld_max_fd;
fd_set ld_read_fds;
auto_ptr<grep_highlighter> ld_grep_child[LG__MAX];
log_vtab_manager *ld_vtab_manager;
session *ld_sql;
} lnav_data;
class loading_observer
: public logfile_sub_source::observer {
public:
loading_observer()
: lo_last_offset(0),
lo_last_line(0) { };
void logfile_indexing(logfile &lf, off_t off, size_t total)
{
if (abs(off - this->lo_last_offset) > (128 * 1024) || off == total) {
lnav_data.ld_bottom_source.update_loading(off, total);
this->do_update();
this->lo_last_offset = off;
}
if (!lnav_data.ld_looping) {
throw logfile::error(lf.get_filename(), EINTR);
}
};
void logfile_sub_source_filtering(logfile_sub_source &lss,
content_line_t cl,
size_t total)
{
if (abs(cl - this->lo_last_line) > 1024 || cl == (total - 1)) {
lnav_data.ld_bottom_source.update_loading(cl, (total - 1));
this->do_update();
this->lo_last_line = cl;
}
if (!lnav_data.ld_looping) {
throw logfile::error("", EINTR);
}
};
private:
void do_update(void)
{
lnav_data.ld_top_source.update_time();
lnav_data.ld_status[LNS_TOP].do_update();
lnav_data.ld_status[LNS_BOTTOM].do_update();
refresh();
};
off_t lo_last_offset;
content_line_t lo_last_line;
};
static void rebuild_hist(size_t old_count, bool force)
{
textview_curses &hist_view = lnav_data.ld_views[LNV_HISTOGRAM];
textview_curses &log_view = lnav_data.ld_views[LNV_LOG];
logfile_sub_source &lss = lnav_data.ld_log_source;
size_t new_count = lss.text_line_count();
hist_source &hs = lnav_data.ld_hist_source;
int zoom_level = lnav_data.ld_hist_zoom;
time_t old_time;
int lpc;
old_time = hs.value_for_row(hist_view.get_top());
hs.set_bucket_size(HIST_ZOOM_VALUES[zoom_level].hl_bucket_size);
hs.set_group_size(HIST_ZOOM_VALUES[zoom_level].hl_group_size);
if (force) {
hs.clear();
}
for (lpc = old_count; lpc < new_count; lpc++) {
logline *ll = lss.find_line(lss.at(vis_line_t(lpc)));
if (!(ll->get_level() & logline::LEVEL_CONTINUED)) {
hs.add_value(ll->get_time(),
bucket_type_t(ll->get_level() &
~logline::LEVEL__FLAGS));
}
}
hs.analyze();
hist_view.reload_data();
hist_view.set_top(hs.row_for_value(old_time));
}
static void rebuild_indexes(bool force)
{
static loading_observer obs;
logfile_sub_source &lss = lnav_data.ld_log_source;
textview_curses &log_view = lnav_data.ld_views[LNV_LOG];
vis_line_t old_bottom(0), height(0);
unsigned long width;
bool scroll_down;
size_t old_count;
time_t old_time;
old_time = lnav_data.ld_top_time;
log_view.get_dimensions(height, width);
old_bottom = log_view.get_top() + height;
scroll_down = old_bottom > lss.text_line_count();
if (force) {
old_count = 0;
}
else {
old_count = lss.text_line_count();
}
if (lss.rebuild_index(&obs, force)) {
hist_source &hs = lnav_data.ld_hist_source;
size_t new_count = lss.text_line_count();
grep_line_t start_line;
int lpc;
log_view.reload_data();
if (scroll_down && new_count >= height) {
log_view.set_top(vis_line_t(new_count - height + 1));
}
else if (!scroll_down && force) {
log_view.set_top(lss.find_from_time(old_time));
}
rebuild_hist(old_count, force);
start_line = force ? grep_line_t(0) : grep_line_t(-1);
for (lpc = 0; lpc < LG__MAX; lpc++) {
if (lnav_data.ld_grep_child[lpc].get() != NULL) {
lnav_data.ld_grep_child[lpc]->get_grep_proc()->
queue_request(start_line);
lnav_data.ld_grep_child[lpc]->get_grep_proc()->start();
}
}
}
lnav_data.ld_bottom_source.update_filtered(lss);
lnav_data.ld_scroll_broadcaster.invoke(lnav_data.ld_view_stack.top());
}
class plain_text_source
: public text_sub_source {
public:
plain_text_source(string text)
{
size_t start = 0, end;
while ((end = text.find('\n', start)) != string::npos) {
this->tds_lines.push_back(text.substr(start, end - start));
start = end + 1;
}
this->tds_lines.push_back(text.substr(start));
};
size_t text_line_count()
{
return this->tds_lines.size();
};
void text_value_for_line(textview_curses &tc,
int row,
string &value_out,
bool no_scrub)
{
value_out = this->tds_lines[row];
};
private:
vector<string> tds_lines;
};
class time_label_source
: public hist_source::label_source {
public:
time_label_source() { };
void hist_label_for_bucket(int bucket_start_value,
const hist_source::bucket_t &bucket,
string &label_out)
{
hist_source::bucket_t::const_iterator iter;
int total = 0, errors = 0, warnings = 0;
time_t bucket_time = bucket_start_value;
struct tm *bucket_tm;
char buffer[128];
int len;
bucket_tm = gmtime((time_t *)&bucket_time);
if (bucket_tm)
strftime(buffer, sizeof(buffer),
" %a %b %d %H:%M ",
bucket_tm);
else {
fprintf(stderr, "bad time %d\n", bucket_start_value);
buffer[0] = '\0';
}
for (iter = bucket.begin(); iter != bucket.end(); iter++) {
total += (int)iter->second;
switch (iter->first) {
case logline::LEVEL_ERROR:
case logline::LEVEL_CRITICAL:
errors += (int)iter->second;
break;
case logline::LEVEL_WARNING:
warnings += (int)iter->second;
break;
}
}
len = strlen(buffer);
snprintf(&buffer[len], sizeof(buffer) - len,
" %8d total %8d errors",
total, errors);
label_out = string(buffer);
};
};
static string get_current_dir(void)
{
char cwd[FILENAME_MAX];
string retval = ".";
if (getcwd(cwd, sizeof(cwd)) == NULL) {
perror("getcwd");
}
else {
retval = string(cwd);
}
if (retval != "/") {
retval += "/";
}
return retval;
}
static bool change_to_parent_dir(void)
{
bool retval = false;
char cwd[3] = "";
getcwd(cwd, sizeof(cwd));
if (strcmp(cwd, "/") != 0) {
if (chdir("..") == -1) {
perror("chdir('..')");
}
else {
retval = true;
}
}
return retval;
}
static bool append_default_files(lnav_flags_t flag)
{
bool retval = true;
if (lnav_data.ld_flags & flag) {
pair<multimap<lnav_flags_t, string>::iterator,
multimap<lnav_flags_t, string>::iterator> range;
bool found = false;
for (range = DEFAULT_FILES.equal_range(flag);
range.first != range.second && !found;
range.first++) {
string path = range.first->second;
struct stat st;
if (access(path.c_str(), R_OK) == 0) {
path = get_current_dir() + range.first->second;
if (lnav_data.ld_flags & LNF_ROTATED) {
glob_t gl;
memset(&gl, 0, sizeof(gl));
path += "*";
if (glob(path.c_str(), 0, NULL, &gl) == 0) {
int lpc;
for (lpc = 0; lpc < gl.gl_pathc; lpc++) {
lnav_data.ld_file_names.insert(make_pair(gl.gl_pathv[lpc], -1));
}
globfree(&gl);
}
}
else {
lnav_data.ld_file_names.insert(make_pair(path, -1));
}
found = true;
}
else if (stat(path.c_str(), &st) == 0) {
fprintf(stderr,
"error: cannot read -- %s%s\n",
get_current_dir().c_str(),
path.c_str());
retval = false;
}
}
}
return retval;
}
static void sigint(int sig)
{
lnav_data.ld_looping = false;
}
static void sigwinch(int sig)
{
lnav_data.ld_winched = true;
}
static void back_ten(int ten_minute)
{
logfile_sub_source &lss = lnav_data.ld_log_source;
time_t hour = rounddown_offset(lnav_data.ld_top_time,
60 * 60,
ten_minute * 10 * 60);
vis_line_t line = lss.find_from_time(hour);
--line;
lnav_data.ld_view_stack.top()->set_top(line);
}
static bool toggle_view(textview_curses *toggle_tc)
{
textview_curses *tc = lnav_data.ld_view_stack.top();
bool retval = false;
if (tc == toggle_tc) {
lnav_data.ld_view_stack.pop();
}
else {
int lpc;
for (lpc = 0; lpc < LG__MAX; lpc++) {
lnav_data.ld_grep_child[lpc].reset();
}
lnav_data.ld_view_stack.push(toggle_tc);
retval = true;
}
tc = lnav_data.ld_view_stack.top();
tc->set_needs_update();
lnav_data.ld_scroll_broadcaster.invoke(tc);
return retval;
}
static void moveto_cluster(vis_line_t (bookmark_vector::*f)(vis_line_t),
bookmark_type_t *bt,
vis_line_t top)
{
textview_curses *tc = lnav_data.ld_view_stack.top();
if (tc != &lnav_data.ld_views[LNV_LOG]) {
flash();
}
else {
logfile_sub_source &lss = lnav_data.ld_log_source;
bookmarks &bm = tc->get_bookmarks();
vis_line_t vl(-1), last_top(top);
logline::level_t last_level;
bool done = false;
time_t last_time;
logline *ll;
ll = lss.find_line(lss.at(top));
last_time = ll->get_time();
last_level = ll->get_level();
while (vl == -1 && (top = (bm[bt].*f)(top)) != -1) {
ll = lss.find_line(lss.at(top));
if (abs(last_top - top) > 1 ||
ll->get_level() != last_level ||
ll->get_time() != last_time) {
last_time = ll->get_time();
last_level = ll->get_level();
vl = top;
}
last_top = top;
}
while (vl > 0 && !done) {
ll = lss.find_line(lss.at(vis_line_t(vl - 1)));
if (ll->get_level() != last_level || ll->get_time() != last_time) {
done = true;
}
else {
--vl;
}
}
tc->set_top(vl);
}
}
/* XXX For one, this code is kinda crappy. For two, we should probably link
* directly with X so we don't need to have xclip installed and it'll work if
* we're ssh'd into a box.
*/
static void copy_to_xclip(void)
{
textview_curses *tc = lnav_data.ld_view_stack.top();
bookmark_vector &bv = tc->get_bookmarks()[&textview_curses::BM_USER];
bookmark_vector::iterator iter;
FILE *pfile = NULL;
string line;
if ((pfile = popen("xclip -i > /dev/null", "w")) == NULL) {
flash();
return;
}
for (iter = bv.begin(); iter != bv.end(); iter++) {
tc->grep_value_for_line(*iter, line);
fprintf(pfile, "%s\n", line.c_str());
}
fclose(pfile);
pfile = NULL;
}
static void handle_paging_key(int ch)
{
textview_curses *tc = lnav_data.ld_view_stack.top();
logfile_sub_source &lss = lnav_data.ld_log_source;
bookmarks &bm = tc->get_bookmarks();
if (tc->handle_key(ch)) {
return;
}
/* process the command keystroke */
switch (ch) {
case 'q':
case 'Q':
lnav_data.ld_view_stack.pop();
if (lnav_data.ld_view_stack.empty()) {
lnav_data.ld_looping = false;
}
else {
tc = lnav_data.ld_view_stack.top();
tc->set_needs_update();
}
break;
case 'c':
copy_to_xclip();
break;
case 'e':
moveto_cluster(&bookmark_vector::next,
&logfile_sub_source::BM_ERRORS,
tc->get_top());
break;
case 'E':
moveto_cluster(&bookmark_vector::prev,
&logfile_sub_source::BM_ERRORS,
tc->get_top());
break;
case 'w':
moveto_cluster(&bookmark_vector::next,
&logfile_sub_source::BM_WARNINGS,
tc->get_top());
break;
case 'W':
moveto_cluster(&bookmark_vector::prev,
&logfile_sub_source::BM_WARNINGS,
tc->get_top());
break;
case 'n':
tc->set_top(bm[&textview_curses::BM_SEARCH].next(tc->get_top()));
break;
case 'N':
tc->set_top(bm[&textview_curses::BM_SEARCH].prev(tc->get_top()));
break;
case 'f':
if (tc == &lnav_data.ld_views[LNV_LOG]) {
tc->set_top(bm[&logfile_sub_source::BM_FILES].next(tc->get_top()));
}
break;
case 'F':
if (tc == &lnav_data.ld_views[LNV_LOG]) {
tc->set_top(bm[&logfile_sub_source::BM_FILES].prev(tc->get_top()));
}
break;
#if 0
/* XXX superceded by 'I' ? */
case 'j':
if (tc == &lnav_data.ld_views[LNV_HISTOGRAM]) {
time_t top_time;
top_time = lnav_data.ld_hist_source.value_for_row(tc->get_top());
do {
lnav_data.ld_view_stack.pop();
tc = lnav_data.ld_view_stack.top();
} while (tc != &lnav_data.ld_views[LNV_LOG]);
tc->set_top(lss.find_from_time(top_time));
tc->set_needs_update();
}
else if (tc == &lnav_data.ld_views[LNV_GRAPH]) {
grapher &gr = lnav_data.ld_graph_source;
int row = gr.value_for_row(tc->get_top());
do {
lnav_data.ld_view_stack.pop();
tc = lnav_data.ld_view_stack.top();
} while (tc != &lnav_data.ld_views[LNV_LOG]);
tc->set_top(vis_line_t(gr.at(row)));
tc->set_needs_update();
}
break;
#endif
case 'z':
if (tc == &lnav_data.ld_views[LNV_HISTOGRAM]) {
if ((lnav_data.ld_hist_zoom + 1) >= HIST_ZOOM_LEVELS) {
flash();
}
else {
hist_source &hs = lnav_data.ld_hist_source;
lnav_data.ld_hist_zoom += 1;
rebuild_hist(0, true);
}
}
break;
case 'Z':
if (tc == &lnav_data.ld_views[LNV_HISTOGRAM]) {
if (lnav_data.ld_hist_zoom == 0) {
flash();
}
else {
hist_source &hs = lnav_data.ld_hist_source;
lnav_data.ld_hist_zoom -= 1;
rebuild_hist(0, true);
}
}
break;
case 'u':
tc->set_top(tc->get_bookmarks()[&textview_curses::BM_USER].
next(tc->get_top()));
break;
case 'U':
tc->set_top(tc->get_bookmarks()[&textview_curses::BM_USER].
prev(tc->get_top()));
break;
case 'm':
lnav_data.ld_last_user_mark[tc] = tc->get_top();
tc->toggle_user_mark(lnav_data.ld_last_user_mark[tc]);
tc->reload_data();
break;
case 'J':
// TODO: if they scroll up, we should start marking again from the top.
// We should also scroll down as the continue to mark stuff. If they
// move somewhere else in the file, we should also start marking from
// the top again.
if (lnav_data.ld_last_user_mark.find(tc) == lnav_data.ld_last_user_mark.end()) {
lnav_data.ld_last_user_mark[tc] = tc->get_top();
}
else if (lnav_data.ld_last_user_mark[tc] + 1 > tc->get_bottom()) {
flash();
break; // XXX
}
else {
lnav_data.ld_last_user_mark[tc] += 1;
}
tc->toggle_user_mark(lnav_data.ld_last_user_mark[tc]);
tc->reload_data();
break;
case 'K':
// TODO: scroll up with the selection
if (lnav_data.ld_last_user_mark.find(tc) == lnav_data.ld_last_user_mark.end()) {
lnav_data.ld_last_user_mark[tc] = tc->get_top();
}
tc->toggle_user_mark(lnav_data.ld_last_user_mark[tc]);
if (lnav_data.ld_last_user_mark[tc] - 1 < 0) {
flash();
}
else {
lnav_data.ld_last_user_mark[tc] -= 1;
}
tc->reload_data();
break;
case 'M':
if (lnav_data.ld_last_user_mark.find(tc) == lnav_data.ld_last_user_mark.end()) {
flash();
}
else {
int start_line = min((int)tc->get_top(), lnav_data.ld_last_user_mark[tc] + 1);
int end_line = max((int)tc->get_top(), lnav_data.ld_last_user_mark[tc] - 1);
tc->toggle_user_mark(start_line, end_line);
tc->reload_data();
}
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
{
int ten_minute = (ch - '0') * 10 * 60;
time_t hour = rounddown(lnav_data.ld_top_time +
(60 * 60) -
ten_minute +
1,
60 * 60);
vis_line_t line = lss.find_from_time(hour + ten_minute);
tc->set_top(line);
}
break;
case '!':
back_ten(1);
break;
case '@':
back_ten(2);
break;
case '#':
back_ten(3);
break;
case '$':
back_ten(4);
break;
case '%':
back_ten(5);
break;
case '^':
back_ten(6);
break;
case '0':
{
time_t first_time = lnav_data.ld_top_time;
int step = 24 * 60 * 60;
vis_line_t line = lss.find_from_time(roundup(first_time, step));
tc->set_top(line);
}
break;
case ')':
{
time_t day = rounddown(lnav_data.ld_top_time, 24 * 60 * 60);
vis_line_t line = lss.find_from_time(day);
--line;
tc->set_top(line);
}
break;
case 'D':
case 'O':
if (tc->get_top() == 0) {
flash();
}
else {
int step = ch == 'D' ? (24 * 60 * 60) : (60 * 60);
time_t top_time = lnav_data.ld_top_time;
vis_line_t line = lss.find_from_time(top_time - step);
if (line != 0) {
--line;
}
tc->set_top(line);
}
break;
case 'd':
case 'o':
{
int step = ch == 'd' ? (24 * 60 * 60) : (60 * 60);
vis_line_t line = lss.find_from_time(lnav_data.ld_top_time + step);
tc->set_top(line);
}
break;
case 's':
lnav_data.ld_log_source.toggle_scrub();
tc->reload_data();
break;
case ':':
lnav_data.ld_mode = LNM_COMMAND;
lnav_data.ld_rl_view->focus(LNM_COMMAND, ":");
break;
case '/':
lnav_data.ld_mode = LNM_SEARCH;
lnav_data.ld_rl_view->focus(LNM_SEARCH, "/");
break;
case ';':
lnav_data.ld_mode = LNM_SQL;
lnav_data.ld_rl_view->focus(LNM_SQL, ";");
break;
case 'i':
toggle_view(&lnav_data.ld_views[LNV_HISTOGRAM]);
break;
case 'I':
{
time_t tt = lnav_data.ld_top_time;
if (toggle_view(&lnav_data.ld_views[LNV_HISTOGRAM])) {
hist_source &hs = lnav_data.ld_hist_source;
tc = lnav_data.ld_view_stack.top();
tc->set_top(hs.row_for_value(tt));
}
}
break;
case 'g':
toggle_view(&lnav_data.ld_views[LNV_GRAPH]);
break;
case '?':
toggle_view(&lnav_data.ld_views[LNV_HELP]);
break;
case '.':
toggle_view(&lnav_data.ld_views[LNV_DB]);
break;
default:
// fprintf(stderr, "unhandled %d\n", ch);
flash();
break;
}
}
static void handle_rl_key(int ch)
{
switch (ch) {
case KEY_PPAGE:
case KEY_NPAGE:
handle_paging_key(ch);
break;
default:
lnav_data.ld_rl_view->handle_key(ch);
break;
}
}
static string com_unix_time(string cmdline, vector<string> &args)
{
string retval = "error: expecting a unix time value";
if (args.size() == 0) { }
else if (args.size() >= 2) {
char ftime[128] = "";
bool parsed = false;
struct tm log_time;
time_t u_time;
size_t millis;
char *rest;
u_time = time(NULL);
log_time = *localtime( &u_time);
log_time.tm_isdst = -1;
args[1] = cmdline.substr(cmdline.find(args[1]));
if ((millis = args[1].find('.')) != string::npos ||
(millis = args[1].find(',')) != string::npos) {
args[1] = args[1].erase(millis, 4);
}
if (((rest = strptime(args[1].c_str(),
"%b %d %H:%M:%S %Y",
&log_time)) != NULL &&
(rest - args[1].c_str()) >= 20) ||
((rest = strptime(args[1].c_str(),
"%Y-%m-%d %H:%M:%S",
&log_time)) != NULL &&
(rest - args[1].c_str()) >= 19)) {
u_time = mktime(&log_time);
parsed = true;
}
else if (sscanf(args[1].c_str(), "%ld", &u_time)) {
log_time = *localtime( &u_time);
parsed = true;
}
if (parsed) {
int len;
strftime(ftime, sizeof(ftime),
"%a %b %d %H:%M:%S %Y %z %Z",
localtime(&u_time));
len = strlen(ftime);
snprintf(ftime + len, sizeof(ftime) - len,
" -- %ld\n",
u_time);
retval = string(ftime);
}
}
return retval;
}
static string com_current_time(string cmdline, vector<string> &args)
{
char ftime[128];
string retval;
time_t u_time;
int len;
u_time = time(NULL);
strftime(ftime, sizeof(ftime),
"%a %b %d %H:%M:%S %Y %z %Z",
localtime(&u_time));
len = strlen(ftime);
snprintf(ftime + len, sizeof(ftime) - len,
" -- %ld\n",
u_time);
retval = string(ftime);
return retval;
}
static string com_goto(string cmdline, vector<string> &args)
{
string retval = "error: expecting line number/percentage";
if (args.size() == 0) { }
else if (args.size() > 1) {
textview_curses *tc = lnav_data.ld_view_stack.top();
int line_number, consumed;
float value;
if (sscanf(args[1].c_str(), "%f%n", &value, &consumed) == 1) {
if (args[1][consumed] == '%') {
line_number = (int)
((double)tc->get_inner_height() * (value / 100.0));
}
else {
line_number = (int)value;
}
tc->set_top(vis_line_t(line_number));
retval = "";
}
}
return retval;
}
static string com_save_to(string cmdline, vector<string> &args)
{
FILE *outfile = NULL;
FILE *pfile = NULL;
char command[1024];
const char *mode;
if (args.size() == 0) {
args.push_back("filename");
return "";
}
if (args.size() != 2) {
return "error: expecting file name";
}
snprintf(command, sizeof(command), "echo -n %s", args[1].c_str());
if ((pfile = popen(command, "r")) == NULL) {
return "error: unable to compute file name";
}
fgets(command, sizeof(command), pfile);
fclose(pfile);
pfile = NULL;
if (args[0] == "append-to") {
mode = "a";
}
else if (args[0] == "write-to") {
mode = "w";
}
if ((outfile = fopen(command, mode)) == NULL) {
return "error: unable to open file -- " + string(command);
}
textview_curses *tc = lnav_data.ld_view_stack.top();
bookmark_vector &bv = tc->get_bookmarks()[&textview_curses::BM_USER];
bookmark_vector::iterator iter;
string line;
for (iter = bv.begin(); iter != bv.end(); iter++) {
tc->grep_value_for_line(*iter, line);
fprintf(outfile, "%s\n", line.c_str());
}
fclose(outfile);
outfile = NULL;
return "";
}
static string com_highlight(string cmdline, vector<string> &args)
{
string retval = "error: expecting regular expression to highlight";
if (args.size() == 0) { }
else if (args.size() > 1) {
const char *errptr;
pcre *code;
int eoff;
args[1] = cmdline.substr(cmdline.find(args[1]));
if ((code = pcre_compile(args[1].c_str(),
PCRE_CASELESS,
&errptr,
&eoff,
NULL)) == NULL) {
retval = "error: " + string(errptr);
}
else {
textview_curses *tc = lnav_data.ld_view_stack.top();
textview_curses::highlighter hl(code, false);
textview_curses::highlight_map_t &hm = tc->get_highlights();
hm[args[1]] = hl;
retval = "info: highlight pattern now active";
}
}
return retval;
}
static string com_graph(string cmdline, vector<string> &args)
{
string retval = "error: expecting regular expression to graph";
if (args.size() == 0) {
args.push_back("graph");
}
else if (args.size() > 1) {
const char *errptr;
pcre *code;
int eoff;
args[1] = cmdline.substr(cmdline.find(args[1]));
if ((code = pcre_compile(args[1].c_str(),
PCRE_CASELESS,
&errptr,
&eoff,
NULL)) == NULL) {
retval = "error: " + string(errptr);
}
else {
textview_curses &tc = lnav_data.ld_views[LNV_LOG];
textview_curses::highlighter hl(code, true);
textview_curses::highlight_map_t &hm = tc.get_highlights();
hm["(graph"] = hl;
lnav_data.ld_graph_source.set_highlighter(&hm["(graph"]);
auto_ptr<grep_proc> gp(new grep_proc(code,
tc,
lnav_data.ld_max_fd,
lnav_data.ld_read_fds));
gp->queue_request();
gp->start();
gp->set_sink(&lnav_data.ld_graph_source);
auto_ptr<grep_highlighter>
gh(new grep_highlighter(gp, "(graph", hm));
lnav_data.ld_grep_child[LG_GRAPH] = gh;
retval = "";
}
}
return retval;
}
class pcre_filter
: public logfile_filter {
public:
pcre_filter(type_t type, string id, pcre *code)
: logfile_filter(type, id),
pf_code(code) { };
virtual ~pcre_filter() { };
bool matches(string line)
{
2009-09-19 22:36:27 +00:00
static const int MATCH_COUNT = 20 * 3;
2009-09-14 01:07:32 +00:00
int matches[MATCH_COUNT], rc;
bool retval;
rc = pcre_exec(this->pf_code,
NULL,
line.c_str(),
line.size(),
0,
0,
matches,
MATCH_COUNT);
retval = (rc >= 0);
#if 0
fprintf(stderr, " out %d %s\n",
retval,
line.c_str());
#endif
return retval;
};
protected:
auto_mem<pcre> pf_code;
};
static string com_filter(string cmdline, vector<string> &args)
{
string retval = "error: expecting regular expression to filter out";
if (args.size() == 0) {
args.push_back("filter");
}
else if (args.size() > 1) {
const char *errptr;
pcre *code;
int eoff;
args[1] = cmdline.substr(cmdline.find(args[1]));
if ((code = pcre_compile(args[1].c_str(),
0,
&errptr,
&eoff,
NULL)) == NULL) {
retval = "error: " + string(errptr);
}
else {
logfile_sub_source &lss = lnav_data.ld_log_source;
logfile_filter::type_t lt = (args[0] == "filter-out") ?
logfile_filter::EXCLUDE : logfile_filter::INCLUDE;
auto_ptr<pcre_filter> pf(new pcre_filter(lt, args[1], code));
lss.get_filters().push_back(pf.release());
lnav_data.ld_rl_view->
add_possibility(LNM_COMMAND, "enabled-filter", args[1]);
rebuild_indexes(true);
retval = "info: filter now active";
}
}
return retval;
}
static string com_enable_filter(string cmdline, vector<string> &args)
{
string retval = "error: expecting disabled filter to enable";
if (args.size() == 0) {
args.push_back("disabled-filter");
}
else if (args.size() > 1) {
logfile_filter *lf;
bool found = false;
args[1] = cmdline.substr(cmdline.find(args[1]));
lf = lnav_data.ld_log_source.get_filter(args[1]);
if (lf == NULL) {
retval = "error: no such filter -- " + args[1];
}
else if (lf->is_enabled()) {
retval = "info: filter already enabled";
}
else {
lf->enable();
lnav_data.ld_rl_view->
rem_possibility(LNM_COMMAND, "disabled-filter", args[1]);
lnav_data.ld_rl_view->
add_possibility(LNM_COMMAND, "enabled-filter", args[1]);
rebuild_indexes(true);
retval = "info: filter enabled";
}
}
return retval;
}
static string com_disable_filter(string cmdline, vector<string> &args)
{
string retval = "error: expecting enabled filter to disable";
if (args.size() == 0) {
args.push_back("enabled-filter");
}
else if (args.size() > 1) {
logfile_filter *lf;
bool found = false;
args[1] = cmdline.substr(cmdline.find(args[1]));
lf = lnav_data.ld_log_source.get_filter(args[1]);
if (lf == NULL) {
retval = "error: no such filter -- " + args[1];
}
else if (!lf->is_enabled()) {
retval = "info: filter already disabled";
}
else {
lf->disable();
lnav_data.ld_rl_view->
rem_possibility(LNM_COMMAND, "disabled-filter", args[1]);
lnav_data.ld_rl_view->
add_possibility(LNM_COMMAND, "enabled-filter", args[1]);
rebuild_indexes(true);
retval = "info: filter disabled";
}
}
return retval;
}
static string com_capture(string cmdline, vector<string> &args)
{
string retval = "error: expecting table name";
if (args.size() == 2) {
lnav_data.ld_mode = LNM_CAPTURE;
lnav_data.ld_rl_view->focus(LNM_CAPTURE, "index: ");
retval = "";
}
return retval;
}
static readline_context::command_map_t lnav_commands;
static void rl_search(void *dummy, readline_curses *rc)
{
static string last_search[LG__MAX];
string name;
int index;
switch (lnav_data.ld_mode) {
case LNM_SEARCH:
index = LG_SEARCH;
name = "(search";
break;
case LNM_CAPTURE:
index = LG_CAPTURE;
name = "(capture";
break;
case LNM_COMMAND:
return;
case LNM_SQL:
try {
/* XXX need to use the sqlite api */
session &sql = *lnav_data.ld_sql;
sql.prepare << rc->get_value();
}
catch (soci::soci_error &e) {
lnav_data.ld_bottom_source.
grep_error(string("sql error: ") + e.what());
}
return;
default:
assert(0);
break;
}
auto_ptr<grep_highlighter> &gc = lnav_data.ld_grep_child[index];
if ((gc.get() == NULL) || (rc->get_value() != last_search[index])) {
textview_curses *tc = lnav_data.ld_view_stack.top();
const char *errptr;
pcre *code;
int eoff;
if (rc->get_value().empty() && gc.get() != NULL) {
tc->grep_begin(*(gc->get_grep_proc()));
tc->grep_end(*(gc->get_grep_proc()));
}
gc.reset();
fprintf(stderr, "start search for: %s\n", rc->get_value().c_str());
if (rc->get_value().empty()) {
lnav_data.ld_bottom_source.grep_error("");
}
else if ((code = pcre_compile(rc->get_value().c_str(),
PCRE_CASELESS,
&errptr,
&eoff,
NULL)) == NULL) {
lnav_data.ld_bottom_source.
grep_error("regexp error: " + string(errptr));
}
else {
textview_curses::highlighter
hl(code, false, view_colors::VCR_SEARCH);
textview_curses::highlight_map_t &hm = tc->get_highlights();
hm[name] = hl;
auto_ptr<grep_proc> gp(new grep_proc(code,
*tc,
lnav_data.ld_max_fd,
lnav_data.ld_read_fds));
gp->queue_request(grep_line_t(tc->get_top()));
if (tc->get_top() > 0) {
gp->queue_request(grep_line_t(0),
grep_line_t(tc->get_top()));
}
gp->start();
gp->set_sink(lnav_data.ld_view_stack.top());
tc->set_follow_search(true);
auto_ptr<grep_highlighter> gh(new grep_highlighter(gp, name, hm));
gc = gh;
last_search[index] = rc->get_value();
}
}
}
static void rl_callback(void *dummy, readline_curses *rc)
{
switch (lnav_data.ld_mode) {
case LNM_COMMAND:
{
stringstream ss(rc->get_value());
vector<string> args;
string buf, msg;
lnav_data.ld_mode = LNM_PAGING;
while (ss >> buf) {
args.push_back(buf);
}
if (args.size() > 0) {
readline_context::command_map_t::iterator iter;
if ((iter = lnav_commands.find(args[0])) ==
lnav_commands.end()) {
msg = "error: unknown command - " + args[0];
}
else {
msg = iter->second(rc->get_value(), args);
}
}
rc->set_value(msg);
}
break;
case LNM_SEARCH:
case LNM_CAPTURE:
rl_search(dummy, rc);
if (rc->get_value().size() > 0) {
lnav_data.ld_view_stack.top()->set_follow_search(false);
lnav_data.ld_rl_view->
add_possibility(LNM_COMMAND, "filter", rc->get_value());
rc->set_value("search: " + rc->get_value());
}
lnav_data.ld_mode = LNM_PAGING;
break;
case LNM_SQL:
try {
session &sql = *lnav_data.ld_sql;
rowset<row> rs = (sql.prepare << rc->get_value());
db_label_source &dls = lnav_data.ld_db_rows;
hist_source &hs = lnav_data.ld_db_source;
bool header_done = false;
int row_number = 0;
char buffer[128];
hs.clear();
2009-09-19 22:36:27 +00:00
dls.dls_headers.clear();
2009-09-14 01:07:32 +00:00
dls.dls_rows.clear();
for (rowset<row>::const_iterator it = rs.begin();
it != rs.end();
++it, row_number++) {
dls.dls_rows.resize(dls.dls_rows.size() + 1);
for(std::size_t lpc = 0; lpc != it->size(); ++lpc) {
const column_properties & props = it->get_properties(lpc);
double num_value = 0;
if (!header_done) {
fprintf(stderr, "<%s> ", props.get_name().c_str());
fprintf(stderr, " dt %d\n", props.get_data_type());
2009-09-19 22:36:27 +00:00
dls.push_header(props.get_name());
2009-09-14 01:07:32 +00:00
hs.set_role_for_type(bucket_type_t(lpc),
view_colors::singleton().
next_highlight());
}
switch(props.get_data_type())
{
case dt_string:
{
const char *str = it->get<std::string>(lpc).c_str();
int i;
dls.push_column(str);
// XXX captures too much
if (sscanf(str, "%d", &i) == 1) {
num_value = i;
}
else {
sscanf(str, "%f", &num_value);
}
}
break;
case dt_double:
snprintf(buffer, sizeof(buffer),
"%f",
it->get<double>(lpc));
dls.push_column(buffer);
num_value = it->get<double>(lpc);
break;
case dt_integer:
snprintf(buffer, sizeof(buffer),
"%d",
it->get<int>(lpc));
dls.push_column(buffer);
num_value = it->get<int>(lpc);
break;
case dt_unsigned_long:
snprintf(buffer, sizeof(buffer),
"%ld",
it->get<unsigned long>(lpc));
dls.push_column(buffer);
num_value = it->get<unsigned long>(lpc);
break;
case dt_long_long:
snprintf(buffer, sizeof(buffer),
"%lld",
it->get<long long>(lpc));
dls.push_column(buffer);
num_value = it->get<long long>(lpc);
break;
case dt_date:
{
std::tm when = it->get<std::tm>(lpc);
strftime(buffer, sizeof(buffer),
"%a, %d %b %Y %H:%M:%S %z",
&when);
dls.push_column(buffer);
}
break;
default:
dls.push_column("XXX");
break;
}
hs.add_value(row_number, bucket_type_t(lpc), num_value);
}
header_done = true;
}
rc->set_value(""); // XXX
hs.analyze();
lnav_data.ld_views[LNV_DB].reload_data();
2009-09-19 22:36:27 +00:00
if (row_number > 0)
toggle_view(&lnav_data.ld_views[LNV_DB]);
2009-09-14 01:07:32 +00:00
}
catch (soci::soci_error &e) {
rc->set_value(e.what());
}
lnav_data.ld_mode = LNM_PAGING;
break;
}
}
static void usage(void)
{
const char *usage_msg =
"usage: %s [-habfso] [logfile1 logfile2 ...]\n"
"\n"
"A curses-based log file viewer that indexes log messages by type\n"
"and time to make it easier to navigate through files quickly.\n"
"\n"
"Key bindings:\n"
" ? View/leave the online help text.\n"
" q Quit the program.\n"
"\n"
"Options:\n"
" -h Print this message, then exit.\n"
" -s Load the most recent syslog messages file.\n"
" -a Load all of the most recent log file types.\n"
" -r Load older rotated log files as well.\n"
"\n"
"Optional arguments:\n"
" logfile1 The log files to view.\n"
"\n"
"Examples:\n"
" To load and follow the backend log file -\n"
" $ lnav\n"
"\n"
" To interleave the JBoss and backend log files -\n"
" $ lnav -bf\n";
fprintf(stderr, usage_msg, lnav_data.ld_program_name);
}
static pcre *xpcre_compile(const char *pattern, int options = 0)
{
const char *errptr;
pcre *retval;
int eoff;
if ((retval = pcre_compile(pattern,
options,
&errptr,
&eoff,
NULL)) == NULL) {
fprintf(stderr, "internal error: failed to compile -- %s\n", pattern);
fprintf(stderr, "internal error: %s\n", errptr);
exit(1);
}
return retval;
}
static void update_times(void *, listview_curses *lv)
{
if (lv == &lnav_data.ld_views[LNV_LOG] && lv->get_inner_height() > 0) {
logfile_sub_source &lss = lnav_data.ld_log_source;
lnav_data.ld_top_time =
lss.find_line(lss.at(lv->get_top()))->get_time();
lnav_data.ld_bottom_time =
lss.find_line(lss.at(lv->get_bottom()))->get_time();
}
if (lv == &lnav_data.ld_views[LNV_HISTOGRAM] &&
lv->get_inner_height() > 0) {
hist_source &hs = lnav_data.ld_hist_source;
lnav_data.ld_top_time = hs.value_for_row(lv->get_top());
lnav_data.ld_bottom_time = hs.value_for_row(lv->get_bottom());
}
}
static void looper(void)
{
int fd;
fd = open(lnav_data.ld_debug_log_name, O_WRONLY | O_CREAT | O_APPEND, 0666);
dup2(fd, STDERR_FILENO);
close(fd);
fprintf(stderr, "startup\n");
try {
readline_context command_context(&lnav_commands);
struct timeval last_check = { 0, 0 };
readline_context search_context;
readline_context index_context;
2009-09-19 22:36:27 +00:00
readline_context sql_context;
2009-09-14 01:07:32 +00:00
textview_curses *tc;
readline_curses rlc;
int lpc;
listview_curses::action::broadcaster &sb =
lnav_data.ld_scroll_broadcaster;
rlc.add_context(LNM_COMMAND, command_context);
rlc.add_context(LNM_SEARCH, search_context);
rlc.add_context(LNM_CAPTURE, index_context);
2009-09-19 22:36:27 +00:00
rlc.add_context(LNM_SQL, sql_context);
2009-09-14 01:07:32 +00:00
rlc.start();
lnav_data.ld_rl_view = &rlc;
lnav_data.ld_rl_view->
add_possibility(LNM_COMMAND, "graph", "\\d+(?:\\.\\d+)?");
lnav_data.ld_rl_view->
add_possibility(LNM_COMMAND, "graph", "([:= \\t]\\d+(?:\\.\\d+)?)");
2009-09-19 22:36:27 +00:00
{
const char *sql_commands[] = {
"add",
"all",
"alter",
"analyze",
"asc",
"attach",
"begin",
"collate",
"column",
"commit",
"conflict",
"create",
"cross",
"database",
"delete",
"desc",
"detach",
"distinct",
"drop",
"end",
"except",
"explain",
"from",
"group",
"having",
"index",
"indexed",
"inner",
"insert",
"intersect",
"join",
"left",
"limit",
"natural",
"offset",
"order",
"outer",
"pragma",
"reindex",
"rename",
"replace",
"rollback",
"select",
"table",
"transaction",
"trigger",
"union",
"unique",
"update",
"using",
"vacuum",
"view",
"where",
"when",
// XXX do the following dynamically by reading sqlite_master
"access_log",
"syslog_log",
"generic_log",
"strace_log",
"line_number",
"path",
"log_time",
"level",
"raw_line",
"c_ip",
"cs_username",
"cs_method",
"cs_uri_stem",
"cs_uri_query",
"cs_version",
"sc_status",
"sc_bytes",
"cs_referer",
"cs_user_agent",
"funcname",
"result",
"duration",
"arg0",
"arg1",
"arg2",
"arg3",
"arg4",
"arg5",
"arg6",
"arg7",
"arg8",
"arg9",
NULL
};
for (int lpc = 0; sql_commands[lpc]; lpc++) {
lnav_data.ld_rl_view->
add_possibility(LNM_SQL, "*", sql_commands[lpc]);
}
}
2009-09-14 01:07:32 +00:00
(void)signal(SIGINT, sigint);
(void)signal(SIGTERM, sigint);
(void)signal(SIGWINCH, sigwinch);
screen_curses sc;
lnav_data.ld_window = sc.get_window();
keypad(stdscr, TRUE);
(void)nonl();
(void)cbreak();
(void)noecho();
(void)nodelay(lnav_data.ld_window, 1);
define_key("\033Od", KEY_BEG);
define_key("\033Oc", KEY_END);
view_colors::singleton().init();
rlc.set_window(lnav_data.ld_window);
rlc.set_y(-1);
rlc.set_perform_action(readline_curses::action(rl_callback));
rlc.set_timeout_action(readline_curses::action(rl_search));
lnav_data.ld_view_stack.push(&lnav_data.ld_views[LNV_LOG]);
tc = lnav_data.ld_view_stack.top();
for (lpc = 0; lpc < LNV__MAX; lpc++) {
lnav_data.ld_views[lpc].set_window(lnav_data.ld_window);
lnav_data.ld_views[lpc].set_y(1);
lnav_data.ld_views[lpc].
set_height(vis_line_t(-(rlc.get_height() + 1 + 1)));
lnav_data.ld_views[lpc].
set_scroll_action(sb.get_functor());
lnav_data.ld_views[lpc].
set_search_action(&lnav_data.ld_bottom_source.hits_wire);
}
lnav_data.ld_status[LNS_TOP].set_top(0);
for (lpc = 0; lpc < LNS__MAX; lpc++) {
lnav_data.ld_status[lpc].set_window(lnav_data.ld_window);
}
lnav_data.ld_status[LNS_TOP].
set_data_source(&lnav_data.ld_top_source);
lnav_data.ld_status[LNS_BOTTOM].
set_data_source(&lnav_data.ld_bottom_source);
sb.push_back(view_action<listview_curses>(update_times));
sb.push_back(&lnav_data.ld_top_source.marks_wire);
sb.push_back(&lnav_data.ld_top_source.filename_wire);
sb.push_back(&lnav_data.ld_bottom_source.line_number_wire);
sb.push_back(&lnav_data.ld_bottom_source.percent_wire);
sb.push_back(&lnav_data.ld_bottom_source.marks_wire);
{
vis_line_t top(0), height(0);
unsigned long width;
tc->get_dimensions(height, width);
top = vis_line_t(tc->get_inner_height()) - height + vis_line_t(1);
if (top > 0) {
tc->set_top(top);
}
}
{
hist_source &hs = lnav_data.ld_hist_source;
lnav_data.ld_hist_zoom = 2;
hs.set_role_for_type(bucket_type_t(logline::LEVEL_CRITICAL),
view_colors::VCR_ERROR);
hs.set_role_for_type(bucket_type_t(logline::LEVEL_ERROR),
view_colors::VCR_ERROR);
hs.set_role_for_type(bucket_type_t(logline::LEVEL_WARNING),
view_colors::VCR_WARNING);
hs.set_label_source(new time_label_source());
}
{
hist_source &hs = lnav_data.ld_graph_source;
hs.set_bucket_size(1);
hs.set_group_size(100);
}
{
hist_source &hs = lnav_data.ld_db_source;
hs.set_bucket_size(1);
hs.set_group_size(100);
hs.set_label_source(&lnav_data.ld_db_rows);
}
FD_ZERO(&lnav_data.ld_read_fds);
FD_SET(STDIN_FILENO, &lnav_data.ld_read_fds);
lnav_data.ld_max_fd =
max(STDIN_FILENO, rlc.update_fd_set(lnav_data.ld_read_fds));
while (lnav_data.ld_looping) {
fd_set ready_rfds = lnav_data.ld_read_fds;
struct timeval to = { 0, 330000 };
int rc;
lnav_data.ld_top_source.update_time();
for (lpc = 0; lpc < LNV__MAX; lpc++) {
lnav_data.ld_views[lpc]
.set_height(vis_line_t(-(rlc.get_height() + 1)));
}
lnav_data.ld_status[LNS_BOTTOM].set_top(-(rlc.get_height() + 1));
lnav_data.ld_view_stack.top()->do_update();
lnav_data.ld_status[LNS_TOP].do_update();
lnav_data.ld_status[LNS_BOTTOM].do_update();
rlc.do_update();
refresh();
rc = select(lnav_data.ld_max_fd + 1,
&ready_rfds, NULL, NULL,
&to);
if (lnav_data.ld_grep_child[LG_SEARCH].get() != NULL) {
lnav_data.ld_bottom_source.
update_hits(lnav_data.ld_view_stack.top());
}
if (rc < 0) {
switch (errno) {
case EINTR:
break;
default:
fprintf(stderr, "select %s\n", strerror(errno));
lnav_data.ld_looping = false;
break;
}
}
else if (rc == 0) {
rebuild_indexes(false);
}
else {
if (FD_ISSET(STDIN_FILENO, &ready_rfds)) {
int ch;
while ((ch = getch()) != ERR) {
switch (ch) {
case CEOF:
case KEY_RESIZE:
break;
default:
switch (lnav_data.ld_mode) {
case LNM_PAGING:
handle_paging_key(ch);
break;
case LNM_COMMAND:
case LNM_SEARCH:
case LNM_CAPTURE:
case LNM_SQL:
handle_rl_key(ch);
break;
default:
assert(0);
break;
}
break;
}
}
}
for (lpc = 0; lpc < LG__MAX; lpc++) {
auto_ptr<grep_highlighter> &gc =
lnav_data.ld_grep_child[lpc];
if (gc.get() != NULL) {
gc->get_grep_proc()->check_fd_set(ready_rfds);
if (lpc == LG_GRAPH) {
lnav_data.ld_views[LNV_GRAPH].reload_data();
/* XXX */
}
}
}
rlc.check_fd_set(ready_rfds);
}
if (lnav_data.ld_winched) {
struct winsize size;
fprintf(stderr, "WINCHED\n");
if (ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0) {
resizeterm(size.ws_row, size.ws_col);
}
rlc.window_change();
lnav_data.ld_view_stack.top()->set_needs_update();
lnav_data.ld_winched = false;
}
}
}
catch (readline_curses::error & e) {
fprintf(stderr, "error: %s\n", strerror(e.e_err));
}
}
class access_log_table : public log_vtab_impl {
public:
access_log_table()
: log_vtab_impl("access_log"),
alt_regex("([\\w\\.-]+) [\\w\\.-]+ ([\\w\\.-]+) "
"\\[[^\\]]+\\] \"(\\w+) ([^ \\?]+)(\\?[^ ]+)? "
"([\\w/\\.]+)\" (\\d+) "
"(\\d+|-) \"([^\"]+)\" \"([^\"]+)\".*") {
};
void get_columns(vector<vtab_column> &cols) {
cols.push_back(vtab_column("c_ip", "text"));
cols.push_back(vtab_column("cs_username", "text"));
cols.push_back(vtab_column("cs_method", "text"));
cols.push_back(vtab_column("cs_uri_stem", "text"));
cols.push_back(vtab_column("cs_uri_query", "text"));
cols.push_back(vtab_column("cs_version", "text"));
cols.push_back(vtab_column("sc_status", "text"));
cols.push_back(vtab_column("sc_bytes", "int"));
cols.push_back(vtab_column("cs_referer", "text"));
cols.push_back(vtab_column("cs_user_agent", "text"));
};
void extract(const std::string &line,
int column,
sqlite_api::sqlite3_context *ctx) {
using namespace sqlite_api;
string c_ip, cs_username, cs_method, cs_uri_stem, cs_uri_query;
string cs_version, sc_status, cs_referer, cs_user_agent;
2009-09-19 22:36:27 +00:00
string sc_bytes;
2009-09-14 01:07:32 +00:00
if (!this->alt_regex.FullMatch(line,
&c_ip,
&cs_username,
&cs_method,
&cs_uri_stem,
&cs_uri_query,
&cs_version,
&sc_status,
&sc_bytes,
&cs_referer,
&cs_user_agent)) {
2009-09-19 22:36:27 +00:00
fprintf(stderr, "bad match! %d %s\n", column, line.c_str());
2009-09-14 01:07:32 +00:00
}
switch (column) {
case 0:
sqlite3_result_text(ctx,
c_ip.c_str(),
c_ip.length(),
SQLITE_TRANSIENT);
break;
case 1:
sqlite3_result_text(ctx,
cs_username.c_str(),
cs_username.length(),
SQLITE_TRANSIENT);
break;
case 2:
sqlite3_result_text(ctx,
cs_method.c_str(),
cs_method.length(),
SQLITE_TRANSIENT);
break;
case 3:
sqlite3_result_text(ctx,
cs_uri_stem.c_str(),
cs_uri_stem.length(),
SQLITE_TRANSIENT);
break;
case 4:
sqlite3_result_text(ctx,
cs_uri_query.c_str(),
cs_uri_query.length(),
SQLITE_TRANSIENT);
break;
case 5:
sqlite3_result_text(ctx,
cs_version.c_str(),
cs_version.length(),
SQLITE_TRANSIENT);
break;
case 6:
sqlite3_result_text(ctx,
sc_status.c_str(),
sc_status.length(),
SQLITE_TRANSIENT);
break;
case 7:
2009-09-19 22:36:27 +00:00
{
int sc_bytes_int = 0;
sscanf(sc_bytes.c_str(), "%d", &sc_bytes_int);
sqlite3_result_int64(ctx, sc_bytes_int);
}
2009-09-14 01:07:32 +00:00
break;
case 8:
sqlite3_result_text(ctx,
cs_referer.c_str(),
cs_referer.length(),
SQLITE_TRANSIENT);
break;
case 9:
sqlite3_result_text(ctx,
cs_user_agent.c_str(),
cs_user_agent.length(),
SQLITE_TRANSIENT);
break;
}
};
private:
pcrecpp::RE alt_regex;
};
class strace_log_table : public log_vtab_impl {
public:
strace_log_table()
: log_vtab_impl("strace_log"),
slt_regex("[0-9:.]* ([a-zA-Z_][a-zA-Z_0-9]*)\\("
"(.*)\\)"
"\\s+= ([-xa-fA-F\\d\\?]+).*(?:<(\\d+\\.\\d+)>)?") {
};
void get_columns(vector<vtab_column> &cols) {
cols.push_back(vtab_column("funcname", "text"));
cols.push_back(vtab_column("result", "text"));
cols.push_back(vtab_column("duration", "text"));
cols.push_back(vtab_column("arg0", "text"));
cols.push_back(vtab_column("arg1", "text"));
cols.push_back(vtab_column("arg2", "text"));
cols.push_back(vtab_column("arg3", "text"));
cols.push_back(vtab_column("arg4", "text"));
cols.push_back(vtab_column("arg5", "text"));
cols.push_back(vtab_column("arg6", "text"));
cols.push_back(vtab_column("arg7", "text"));
cols.push_back(vtab_column("arg8", "text"));
cols.push_back(vtab_column("arg9", "text"));
};
void extract(const std::string &line,
int column,
sqlite_api::sqlite3_context *ctx) {
using namespace sqlite_api;
string function, args, result, duration = "0";
if (!this->slt_regex.FullMatch(line,
&function,
&args,
&result,
&duration)) {
fprintf(stderr, "bad match! %s\n", line.c_str());
}
switch (column) {
case 0:
sqlite3_result_text(ctx,
function.c_str(),
function.length(),
SQLITE_TRANSIENT);
break;
case 1:
sqlite3_result_text(ctx,
result.c_str(),
result.length(),
SQLITE_TRANSIENT);
break;
case 2:
sqlite3_result_text(ctx,
duration.c_str(),
duration.length(),
SQLITE_TRANSIENT);
break;
default:
{
const char *arg_start = args.c_str();
int in_struct = 0, in_list = 0;
int lpc, argnum, curarg = 0;
bool in_quote = false;
argnum = column - 3;
for (lpc = 0; lpc < args.length(); lpc++) {
switch (args[lpc]) {
case '{':
if (!in_quote)
in_struct += 1;
break;
2009-09-19 22:36:27 +00:00
case '}':
if (!in_quote)
in_struct -= 1;
break;
case '[':
if (!in_quote)
in_list += 1;
break;
case ']':
if (!in_quote)
in_list -= 1;
break;
2009-09-14 01:07:32 +00:00
case '"':
if (!in_quote)
in_quote = true;
else if (lpc > 0 && args[lpc - 1] != '\\')
in_quote = false;
break;
case ',':
2009-09-19 22:36:27 +00:00
if (!in_quote && !in_struct && !in_list) {
2009-09-14 01:07:32 +00:00
if (curarg == argnum) {
sqlite3_result_text(ctx,
arg_start,
&(args.c_str()[lpc]) -
arg_start,
SQLITE_TRANSIENT);
return;
}
curarg += 1;
arg_start = &(args.c_str()[lpc + 1]);
}
break;
}
}
if (curarg == argnum) {
sqlite3_result_text(ctx,
arg_start,
&(args.c_str()[lpc]) -
arg_start,
SQLITE_TRANSIENT);
}
else {
sqlite3_result_text(ctx,
"",
0,
SQLITE_TRANSIENT);
}
}
break;
}
};
private:
pcrecpp::RE slt_regex;
};
int main(int argc, char *argv[])
{
int lpc, c, retval = EXIT_SUCCESS;
auto_ptr<piper_proc> stdin_reader;
session sql(sqlite3, ":memory:");
lnav_data.ld_program_name = argv[0];
lnav_data.ld_sql = &sql;
lnav_data.ld_vtab_manager =
new log_vtab_manager(sql, lnav_data.ld_log_source);
lnav_data.ld_vtab_manager->register_vtab(new log_vtab_impl("syslog_log"));
lnav_data.ld_vtab_manager->register_vtab(new log_vtab_impl("generic_log"));
lnav_data.ld_vtab_manager->register_vtab(new access_log_table());
lnav_data.ld_vtab_manager->register_vtab(new strace_log_table());
DEFAULT_FILES.insert(make_pair(LNF_SYSLOG, string("var/log/messages")));
lnav_commands["unix-time"] = com_unix_time;
lnav_commands["current-time"] = com_current_time;
lnav_commands["goto"] = com_goto;
lnav_commands["graph"] = com_graph;
lnav_commands["highlight"] = com_highlight;
lnav_commands["filter-in"] = com_filter;
lnav_commands["filter-out"] = com_filter;
lnav_commands["append-to"] = com_save_to;
lnav_commands["write-to"] = com_save_to;
lnav_commands["enable-filter"] = com_enable_filter;
lnav_commands["disable-filter"] = com_disable_filter;
lnav_commands["capture-into"] = com_capture;
lnav_data.ld_views[LNV_HELP].
set_sub_source(new plain_text_source(help_text_start));
lnav_data.ld_views[LNV_LOG].
set_sub_source(&lnav_data.ld_log_source);
lnav_data.ld_views[LNV_HISTOGRAM].
set_sub_source(&lnav_data.ld_hist_source);
lnav_data.ld_views[LNV_GRAPH].
set_sub_source(&lnav_data.ld_graph_source);
lnav_data.ld_views[LNV_DB].
set_sub_source(&lnav_data.ld_db_source);
{
textview_curses::highlight_map_t &hm =
lnav_data.ld_views[LNV_LOG].get_highlights();
hm["(sql"] = textview_curses::
highlighter(xpcre_compile("(?: alter | select | insert | update "
"| create "
"| from | where | order by "
"| group by )", PCRE_CASELESS));
hm["(java"] = textview_curses::
highlighter(xpcre_compile("(?:\\w+\\.java:\\d+)"));
hm["(xml"] = textview_curses::
highlighter(xpcre_compile("<(/?[^ >]+)[^>]*>"));
hm["(stringd"] = textview_curses::
highlighter(xpcre_compile("\".*(?<!\\\\)\""));
hm["(strings"] = textview_curses::
highlighter(xpcre_compile("\'.*(?<!\\\\)\'"));
hm["(ip"] = textview_curses::
highlighter(xpcre_compile("\\d+\\.\\d+\\.\\d+\\.\\d+"));
}
lnav_data.ld_looping = true;
lnav_data.ld_mode = LNM_PAGING;
2009-09-19 22:36:27 +00:00
lnav_data.ld_debug_log_name = "/dev/null"; // XXX change to /dev/null
2009-09-14 01:07:32 +00:00
while ((c = getopt(argc, argv, "harsd:")) != -1) {
switch (c) {
case 'h':
usage();
exit(retval);
break;
case 'd':
lnav_data.ld_debug_log_name = optarg;
break;
case 'a':
lnav_data.ld_flags |= LNF__ALL;
break;
case 'r':
lnav_data.ld_flags |= LNF_ROTATED;
break;
case 's':
lnav_data.ld_flags |= LNF_SYSLOG;
break;
default:
retval = EXIT_FAILURE;
break;
}
}
argc -= optind;
argv += optind;
if (isatty(STDIN_FILENO) && argc == 0 && !(lnav_data.ld_flags & LNF__ALL)) {
lnav_data.ld_flags |= LNF_SYSLOG;
}
if (lnav_data.ld_flags != 0) {
char start_dir[FILENAME_MAX];
if (getcwd(start_dir, sizeof(start_dir)) == NULL) {
perror("getcwd");
}
else {
do {
for (lpc = 0; lpc < LNB__MAX; lpc++) {
if (!append_default_files((lnav_flags_t)(1L << lpc))) {
retval = EXIT_FAILURE;
}
}
} while (lnav_data.ld_file_names.empty() && change_to_parent_dir());
if (chdir(start_dir) == -1) {
perror("chdir(start_dir)");
}
}
}
for (lpc = 0; lpc < argc; lpc++) {
lnav_data.ld_file_names.insert(make_pair(argv[lpc], -1));
}
if (!isatty(STDOUT_FILENO)) {
fprintf(stderr, "error: stdout is not a tty.\n");
retval = EXIT_FAILURE;
}
if (!isatty(STDIN_FILENO)) {
stdin_reader = auto_ptr<piper_proc>(new piper_proc(STDIN_FILENO));
lnav_data.ld_file_names.insert(make_pair("stdin",
stdin_reader->get_fd()));
if (dup2(STDOUT_FILENO, STDIN_FILENO) == -1) {
perror("cannot dup stdout to stdin");
}
}
if (lnav_data.ld_file_names.empty()) {
fprintf(stderr, "error: no log files given/found.\n");
retval = EXIT_FAILURE;
}
if (retval != EXIT_SUCCESS) {
usage();
}
else {
try {
set< pair<string, int> >::iterator iter;
for (iter = lnav_data.ld_file_names.begin();
iter != lnav_data.ld_file_names.end();
iter++) {
logfile *lf = new logfile(iter->first, iter->second);
lnav_data.ld_log_source.insert_file(lf);
}
looper();
}
catch (line_buffer::error & e) {
fprintf(stderr, "error: %s\n", strerror(e.e_err));
}
catch (logfile::error & e) {
if (e.e_err != EINTR) {
fprintf(stderr,
"error: %s -- '%s'\n",
strerror(e.e_err),
e.e_filename.c_str());
}
}
}
return retval;
}