/*
mergelogs.c
Copyright (C) 2001-2002 Ulric Eriksson <ulric@siag.nu>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <stdarg.h>
#include <unistd.h>
#include <time.h>
#define KEEP_MAX 100 /* how much to keep from the URI */
typedef struct {
char *a; /* server name */
char *fn; /* log file name */
FILE *fp; /* file pointer, or NULL if eof */
time_t t; /* time stamp from last line */
char cli[1024]; /* client address */
char tim[1024]; /* time string */
char uri[1024]; /* uri */
char b1[1024], b2[1024], b3[1024]; /* misc text */
} server;
static server *servers;
static int nservers;
static int debuglevel = 0;
static char *pfile;
static int jitter = 600; /* 10 minutes */
static FILE *pfp;
static int tz = 0;
static int cache_penlog = 1;
static char *months[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static void debug(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
}
static void error(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
exit(1);
}
static void *pen_malloc(size_t n)
{
char *q = malloc(n);
if (!q) error("Can't allocate %ld bytes", (long)n);
return q;
}
static void *pen_calloc(size_t n, size_t s)
{
char *q = calloc(n, s);
if (!q) error("Can't allocate %ld bytes", (long)n*s);
return q;
}
static void *pen_realloc(void *p, size_t n)
{
char *q = realloc(p, n);
if (!q) error("Can't allocate %ld bytes", (long)n);
return q;
}
static char *pen_strdup(char *p)
{
char *q = pen_malloc(strlen(p)+1);
return strcpy(q, p);
}
static void usage(void)
{
printf("Usage: mergelogs -p penlog [-j jitter] \\\n"
" server1:logfile1 [server2:logfile2 ...]\n\n"
" -c Do not use penlog cache\n"
" -d Debugging (repeat for more)\n"
" -p penlog Log file from pen\n"
" -j jitter Jitter in seconds [2]\n"
" -r filename Where to put rejects\n"
" -t seconds Timezone\n"
" server:logfile Web server address and name of logfile\n");
exit(0);
}
static int options(int argc, char **argv)
{
int c;
while ((c = getopt(argc, argv, "p:a:j:t:cd")) != -1) {
switch (c) {
case 'p':
pfile = optarg;
break;
case 'j':
jitter = atoi(optarg);
break;
case 't':
tz = atoi(optarg);
break;
case 'c':
cache_penlog = 0;
break;
case 'd':
debuglevel++;
break;
default:
usage();
}
}
return optind;
}
static int mon2num(char *p)
{
int i;
for (i = 0; i < 12; i++)
if (!strcmp(p, months[i])) return i;
return -1;
}
static char *num2mon(int m)
{
if (m < 0 || m > 11) return "no such month";
return months[m];
}
static void thenp(char *b, time_t t)
{
struct tm *tms = localtime(&t);
sprintf(b, "%02d/%s/%04d:%02d:%02d:%02d +0000",
tms->tm_mday, num2mon(tms->tm_mon), tms->tm_year+1900,
tms->tm_hour, tms->tm_min, tms->tm_sec);
}
/*
Time format: 09/Jan/2002:00:27:15 +0100
*/
static time_t whenp(char *p, struct tm *tms)
{
char dd[100], mm[100], yy[100];
char hh[100], mi[100], ss[100], tz[100];
time_t t;
tz[0] = '\0';
sscanf(p, "%[^/]/%[^/]/%[^:]:%[^:]:%[^:]:%s %s",
dd, mm, yy, hh, mi, ss, tz);
tms->tm_sec = atoi(ss);
tms->tm_min = atoi(mi);
tms->tm_hour = atoi(hh);
tms->tm_mday = atoi(dd);
tms->tm_mon = mon2num(mm);
tms->tm_year = atoi(yy)-1900;
t = mktime(tms);
if (t != -1 && strlen(tz) == 5) {
int d = 60*atoi(tz+3);
tz[3] = '\0';
d += 3600*atoi(tz+1);
if (tz[0] == '+') t -= d;
else t += d;
}
return t;
}
static void read_server_line(int s)
{
char b[1024];
struct tm tms;
int n;
do {
if (fgets(b, sizeof b, servers[s].fp) == NULL) {
fclose(servers[s].fp);
servers[s].fp = NULL;
return;
}
n = sscanf(b, "%[^ ] %[^[][%[^]]]%[^\"]\"%[^\"]\"%[^\n]",
servers[s].cli, servers[s].b1, servers[s].tim,
servers[s].b2, servers[s].uri, servers[s].b3);
if (n == 6) {
servers[s].t = whenp(servers[s].tim, &tms);
} else if (debuglevel) {
debug("Read %d fields", n);
}
} while (n != 6);
}
static int oldest_server(void)
{
int i, n = -1;
time_t t = -1;
for (i = 0; i < nservers; i++) {
if (servers[i].fp) {
if (t == -1 || servers[i].t < t) {
n = i;
t = servers[i].t;
}
}
}
return n;
}
typedef struct {
char *cli; /* client address */
int ser; /* server index (in servers array) */
time_t t; /* time stamp */
char *uri; /* uri */
} pencache;
static pencache *pc;
static int npc;
static int server2num(char *s)
{
int i;
for (i = 0; i < nservers; i++) {
if (!strcmp(servers[i].a, s)) return i;
}
return -1;
}
/* cache relevant penlog lines to speed up the search */
static void best_client1(char *p, char *s, long t, char *u)
{
char b[1024], from[1024], to[1024], uri[1024];
long when, td, ntd;
int i, j;
int ser = server2num(s);
/* first remove all entries that are older than (t-jitter) */
for (i = 0; i < npc; i++) {
if (pc[i].t >= (t-jitter)) break;
}
if (i) {
if (debuglevel) debug("uncache %d lines\n", i);
for (j = 0; j < i; j++) {
if (debuglevel >= 2) {
debug("uncache '%s %ld %d %s'",
pc[j].cli, pc[j].t,
pc[j].ser, pc[j].uri);
}
free(pc[j].cli);
free(pc[j].uri);
}
while (j < npc) {
pc[j-i] = pc[j];
j++;
}
npc -= i;
}
/* then add entries until eof or newer than (t+jitter) */
for (;;) {
if (npc > 0 && pc[npc-1].t > (t+jitter)) break;
if (feof(pfp)) break;
if (fgets(b, sizeof b, pfp) == NULL) break;
pc = pen_realloc(pc, (npc+1)*sizeof *pc);
if (debuglevel) debug("pc = %p, b = '%s'", pc, b);
if (sscanf(b, "%s %ld %s %[^\n]", from, &when, to, uri) != 4) {
continue;
}
pc[npc].cli = pen_strdup(from);
pc[npc].ser = server2num(to);
pc[npc].t = when-tz;
pc[npc].uri = pen_strdup(uri);
if (debuglevel >= 2) {
debug("cache '%s %ld %d %s'",
pc[npc].cli, pc[npc].t,
pc[npc].ser, pc[npc].uri);
}
npc++;
}
/* now search the cache for a best match */
strcpy(p, s); /* default is client = server */
td = LONG_MAX;
for (i = 0; i < npc; i++) {
if (ser != pc[i].ser) continue;
if (strcmp(u, pc[i].uri)) continue;
ntd = labs(t-pc[i].t);
if (ntd < td) {
td = ntd;
strcpy(p, pc[i].cli);
}
}
}
/* same again, this time without the cache */
static void best_client0(char *p, char *s, long t, char *u)
{
char b[1024], from[1024], to[1024], uri[1024];
long when, td, ntd;
rewind(pfp);
strcpy(p, s); /* default is client = server */
td = LONG_MAX;
while (fgets(b, sizeof b, pfp)) {
if (sscanf(b, "%s %ld %s %[^\n]", from, &when, to, uri) != 4) {
continue;
}
if (strcmp(s, to)) continue;
if (strcmp(u, uri)) continue;
when -= tz;
ntd = labs(t-when);
if (ntd < td) {
td = ntd;
strcpy(p, from);
}
}
if (debuglevel && td > 600) {
debug("Warning: time difference %ld", td);
}
}
static void best_client(char *p, char *s, long t, char *u)
{
if (cache_penlog) best_client1(p, s, t, u);
else best_client0(p, s, t, u);
}
int main(int argc, char **argv)
{
int i, n, s;
n = options(argc, argv);
argc -= n;
argv += n;
if (argc < 1) {
usage();
}
if (pfile == NULL) error("pfile null");
pfp = fopen(pfile, "r");
if (pfp == NULL) error("pfp null");
s = 0;
servers = pen_calloc(argc, sizeof *servers);
for (i = 0; i < argc; i++) {
servers[s].a = pen_strdup(argv[i]);
servers[s].fn = strchr(servers[s].a, ':');
if (servers[s].fn == NULL)
error("Bogus server '%s'\n", argv[i]);
*servers[s].fn++ = '\0';
servers[s].fp = fopen(servers[s].fn, "r");
if (servers[s].fp == NULL)
error("Can't open logfile '%s'\n", servers[s].fn);
read_server_line(s);
nservers++;
s++;
}
/*
Example log lines:
10.0.18.6 - - [09/Jan/2002:00:28:50 +0100] "GET /robots.txt HTTP/1.0" 404 268
10.0.18.6 - - [09/Jan/2002:00:28:50 +0100] "GET /news.html HTTP/1.0" 200 4017
That is: client, whatever, whatever, [timestamp], "URI", code, size.
*/
while ((s = oldest_server()) != -1) {
char cli[1024], tim[1024];
thenp(tim, servers[s].t);
best_client(cli, servers[s].a, servers[s].t, servers[s].uri);
if (debuglevel >= 2) {
debug("\tclient = '%s' => '%s'",
servers[s].cli, cli);
debug("\ttime = '%s' => %d => '%s'",
servers[s].cli, cli, servers[s].tim,
(int)servers[s].t, tim);
debug("\turi = '%s'",
servers[s].uri);
}
printf("%s %s[%s]%s\"%s\"%s\n",
cli, servers[s].b1, tim,
servers[s].b2, servers[s].uri, servers[s].b3);
read_server_line(s);
}
fclose(pfp);
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1