/*****************************************************************************/ /* calogs.c Consolidate Access LOGS (pronounced the same as the breakfast cereal brand :^) merges multiple HTTP server common and combined format access logs into a single log file with records in time-order. Although specifically intended for per-instance logging (where one log per server process is generated) it can be used to consolidate any set of access logs. Due to the granularity of HTTP server entry timestamps (one second) the records are sorted to the one second but not within the one second. It uses RMS and the VMS sort-merge routines to provide it's basic consolidation functionality. An RMS search matches a wildcard log file specification as the first CLI parameter. Matching files are opened and each record read. The date/time field is parsed and the textual timestamp converted to a binary time in seconds (Unix time) which is prepended to the log record and passed to sort-merge input. When all files have been processed the sort/merge is performed using the integer time value as an efficient sort key. The sorted records are then written to the output file specified as the second CLI parameter (defaults to SYS$OUTPUT) stripping the leading binary time. To allow some limited filtering of records during the merge the /NOWASD qualifier excludes WASD server status records (startup, shutdown, timestamp, etc.), the /NOPROXY qualifier excludes proxy records (producing a log containing only non-proxy request processing), and the /PROXY qualifier excludes non-proxy records (producing a log containing only proxy request processing). USAGE ----- $ CALOGS == "$HT_EXE:CALOGS" $ CALOGS [] [qualifiers] For example: $ CALOGS HT_LOGS:*200205*.LOG 2002_MAY.LOG $ CALOGS /VERBOSE HT_LOGS: $ CALOGS /NOWASD HT_LOGS:*200206*.LOG_* 2002_JUNE.LOG $ CALOGS /PROXY /NOWASD HT_LOGS:*2002*.LOG 2002_PROXY.LOG QUALIFIERS ---------- /DBUG turns on all "if (Debug)" statements /HELP provide some basic usage information /NOPROXY only merge records that are not of proxy access /NOWASD filter out the WASD server status messages /OUTPUT= same as second parameter (for habitual users of /OUT= :^) /PROXY only merge records of proxy access /QUIET no output except error messages /SOFTWAREID display the utility version /VERBOSE provide file-by-file statistics /VERSION display the utility version BUILD DETAILS ------------- See BUILD_CALOGS.COM procedure. COPYRIGHT --------- Copyright (C) 2002,2003 Mark G.Daniel This program, comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under the conditions of the GNU GENERAL PUBLIC LICENSE, version 2. VERSION HISTORY (update SOFTWAREVN as well!) --------------- 23-DEC-2003 MGD v1.0.1, minor conditional mods to support IA64 28-MAY-2002 MGD v1.0.0, initial development */ /*****************************************************************************/ #define SOFTWAREVN "1.0.1" #define SOFTWARENM "CALOGS" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN #endif #ifdef __VAX # define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN #endif /* standard C header files */ #include #include #include #include #include /* VMS-related header files */ #include #include #include #include #include #include #include #include #define BOOL int #define true 1 #define false 0 #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) (!((x) & STS$M_SUCCESS)) /* maximum log line/record length */ #define MAX_LINE_LENGTH 2048 char CopyrightInfo [] = "Copyright (C) 2002,2003 Mark G.Daniel.\n\ This software comes with ABSOLUTELY NO WARRANTY.\n\ This is free software, and you are welcome to redistribute it\n\ under the conditions of the GNU GENERAL PUBLIC LICENSE, version 2."; char Utility [] = "CALOGS"; BOOL Debug, DoNoWasd, DoNoProxy, DoProxyOnly, DoQuiet, DoVerbose; int DiscardRecordCount, FileCount, InputRecordCount, NonProxyRecordCount, OutputRecordCount, ProxyRecordCount, ReadRecordCount, WasdRecordCount; char LogFileSpec [256], MergedFileName [256]; typedef struct { unsigned short type; unsigned short order; unsigned short offset; unsigned short len; } KEY_INFO; struct { unsigned short num; KEY_INFO key [1]; } SortKeys; globalvalue int SOR$M_STABLE; /* required function prototypes */ int GetParameters (); int ShowHelp (); int ReadLogFiles (); int ProcessLogFile (char*); int WriteMergedLog (); int strsame (char*, char*, int); char* SysGetMsg (int); /*****************************************************************************/ /* */ main () { int status, SortLrl, SortOptions; /*********/ /* begin */ /*********/ if (getenv ("CALOGS$DBUG")) Debug = true; GetParameters (); if (DoVerbose) fprintf (stdout, "%%%s-I-VERBOSE, version %s\n", Utility, SOFTWAREID); /* time (Unix seconds) */ SortKeys.key[0].type = DSC$K_DTYPE_LU; SortKeys.key[0].order = 0; SortKeys.key[0].offset = 0; SortKeys.key[0].len = 4; SortKeys.num = 1; SortOptions = SOR$M_STABLE; SortLrl = MAX_LINE_LENGTH + 4; if (VMSnok (status = sor$begin_sort (&SortKeys, &SortLrl, &SortOptions, 0, 0, 0, 0, 0, 0))) { fprintf (stdout, "%%%s-E-SORT, begin\n-%s\n", Utility, SysGetMsg(status)+1); exit (status | STS$M_INHIB_MSG); } if (DoVerbose) fprintf (stdout, "READING ...\n"); ReadLogFiles (); if (DoVerbose) fprintf (stdout, "MERGING ...\n"); if (VMSnok (status = sor$sort_merge (0))) { fprintf (stdout, "%%%s-E-SORT, merge\n-%s\n", Utility, SysGetMsg(status)+1); exit (status | STS$M_INHIB_MSG); } if (InputRecordCount) { if (DoVerbose) fprintf (stdout, "WRITING ...\n"); if (!MergedFileName[0]) strcpy (MergedFileName, "SYS$OUTPUT:"); WriteMergedLog (); } if (VMSnok (status = sor$end_sort (0))) { fprintf (stdout, "%%%s-E-SORT, end\n-%s\n", Utility, SysGetMsg(status)+1); exit (status | STS$M_INHIB_MSG); } if (!DoQuiet || DoVerbose) { fprintf (stdout, "%%%s-%s-STATISTICS, for log consolidation\n\ Log Files: %d\n\ Records Read: %d\n\ Discarded: %d\n", Utility, InputRecordCount == OutputRecordCount ? "I" : "W", FileCount, ReadRecordCount, DiscardRecordCount); if (DoNoProxy || DoProxyOnly) fprintf (stdout, " Proxy: %d\n\ Non-Proxy: %d\n", ProxyRecordCount, NonProxyRecordCount); if (DoNoWasd) fprintf (stdout, " WASD: %d\n", WasdRecordCount); fprintf (stdout, " Merged: %d\n\ Output: %d\n", InputRecordCount, OutputRecordCount); if (InputRecordCount != OutputRecordCount) fprintf (stdout, "-%s-W-COUNT, input and output record count?\n", Utility); } if (InputRecordCount != OutputRecordCount) exit (SS$_BUGCHECK); exit (SS$_NORMAL); } /****************************************************************************/ /* Search against the command line file specification, passing each file found to be processed. */ ReadLogFiles () { int status, FileNameLength, Length; char FileName [256], ExpFileName [256]; struct FAB SearchFab; struct NAM SearchNam; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ReadLogFiles()\n"); /* initialize the file access block */ SearchFab = cc$rms_fab; SearchFab.fab$l_dna = "*.LOG*;"; SearchFab.fab$b_dns = 7; SearchFab.fab$l_fna = LogFileSpec; SearchFab.fab$b_fns = strlen(LogFileSpec); SearchFab.fab$l_nam = &SearchNam; SearchNam = cc$rms_nam; SearchNam.nam$l_esa = ExpFileName; SearchNam.nam$b_ess = sizeof(ExpFileName)-1; SearchNam.nam$l_rsa = FileName; SearchNam.nam$b_rss = sizeof(FileName)-1; if (VMSnok (status = sys$parse (&SearchFab, 0, 0))) { fprintf (stdout, "%%%s-E-PARSE, %s\n-%s\n", Utility, LogFileSpec, SysGetMsg(status)+1); exit (status | STS$M_INHIB_MSG); } while (VMSok (status = sys$search (&SearchFab, 0, 0))) { FileCount++; *SearchNam.nam$l_ver = '\0'; ProcessLogFile (FileName); *SearchNam.nam$l_ver = ';'; } if (VMSnok (status) && status != RMS$_NMF) { SearchNam.nam$l_ver[SearchNam.nam$b_ver] = '\0'; fprintf (stdout, "%%%s-E-SEARCH, %s\n-%s\n", Utility, FileName, SysGetMsg(status)+1); exit (status | STS$M_INHIB_MSG); } } /****************************************************************************/ /* Open the log file, read and process each record from it, close the file! Parse the date/time components from each record, converting them into a binary integer value (Unix time in seconds). Prepend this this to the textual component of the record passing this to the sort-merge utility routines. To reduce unnecessary copying of record contents the buffer is organized so the first four bytes are left available for prepending the integer time. The common and combined formats ... 'client rfc891 authuser [date/time] "request" status bytes' 'client rfc891 authuser [date/time] "request" status bytes referer agent' */ int ProcessLogFile (char *FileName) { static char *MonthAbbrev [] = { NULL, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static $DESCRIPTOR (RecordDsc, ""); int status, day, month, year, hour, min, sec, FileDiscardRecordCount, FileInputRecordCount, FileNonProxyRecordCount, FileProxyRecordCount, FileRecordCount, FileWasdRecordCount, UnixTime; char *cptr, *mptr, *sptr; /* plus four allows for the binary timestamp */ char Line [MAX_LINE_LENGTH+4]; struct tm utm; struct FAB FileFab; struct RAB FileRab; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessLogFile()\n"); if (DoVerbose) fprintf (stdout, "%s\n", FileName); FileFab = cc$rms_fab; FileFab.fab$b_fac = FAB$M_GET; FileFab.fab$l_fna = FileName; FileFab.fab$b_fns = strlen(FileName); FileFab.fab$b_shr = FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD; if (VMSnok (status = sys$open (&FileFab, 0, 0))) { if (status == RMS$_FLK) { /* convert it into a warning */ status &= 0xfffffff8; fprintf (stdout, "%%%s-W-OPEN, %s\n-%s\n", Utility, FileName, SysGetMsg(status)+1); return (status); } fprintf (stdout, "%%%s-E-OPEN, %s\n-%s\n", Utility, FileName, SysGetMsg(status)+1); exit (status | STS$M_INHIB_MSG); } FileRab = cc$rms_rab; FileRab.rab$l_fab = &FileFab; /* 2 buffers and read ahead performance option */ FileRab.rab$b_mbf = 2; FileRab.rab$l_rop = RAB$M_RAH; /* plus four, minus five allows for the binary timestamp */ FileRab.rab$l_ubf = (char*)Line + 4; FileRab.rab$w_usz = sizeof(Line) - 5; if (VMSnok (status = sys$connect (&FileRab, 0, 0))) { fprintf (stdout, "%%%s-E-CONNECT, %s\n-%s\n", Utility, FileName, SysGetMsg(status)+1); exit (status | STS$M_INHIB_MSG); } FileDiscardRecordCount = FileInputRecordCount = FileNonProxyRecordCount = FileProxyRecordCount = FileRecordCount = FileWasdRecordCount = 0; while (VMSok (status = sys$get (&FileRab, 0, 0))) { ReadRecordCount++; FileRecordCount++; cptr = Line + 4; cptr[FileRab.rab$w_rsz] = '\0'; if (DoNoProxy || DoProxyOnly || DoNoWasd) { /* skip over client and remote ident fields */ while (*cptr && !isspace(*cptr)) cptr++; while (*cptr && isspace(*cptr)) cptr++; while (*cptr && !isspace(*cptr)) cptr++; while (*cptr && isspace(*cptr)) cptr++; sptr = cptr; } if (DoNoProxy || DoProxyOnly) { /* skip straight on to the request field */ while (*cptr && *cptr != '\"') cptr++; if (*cptr) *cptr++; /* skip over method */ mptr = cptr; while (*cptr && !isspace(*cptr)) cptr++; if (*cptr) *cptr++; if (*cptr == '/') { NonProxyRecordCount++; FileNonProxyRecordCount++; if (DoProxyOnly) continue; } else if (*mptr == 'C' && !memcmp (mptr, "CONNECT", 7)) { ProxyRecordCount++; FileProxyRecordCount++; if (DoNoProxy) continue; } else { /* look for what passes for a URL scheme */ while (*cptr && *cptr != ':' && *cptr != '/' && *cptr != '\"') cptr++; if (cptr[0] == ':' && cptr[1] == '/' && cptr[2] == '/') { ProxyRecordCount++; FileProxyRecordCount++; if (DoNoProxy) continue; } } /* go back to the 'authorized user' field */ cptr = sptr; } if (DoNoWasd) { if (!memcmp (cptr, "HTTPd ", 6)) { /* authorized user matches, quite possible, look at request */ cptr += 6; /* skip to the start of the request field */ while (*cptr && *cptr != '\"') cptr++; if (*cptr == '\"') { /* looking for: pre-8.2 '"POST ::HTTP:-"' post-8.2 '"POST /::HTTP:-"' */ cptr++; if (!memcmp (cptr, "POST ", 5)) { /* looking more likely */ cptr += 5; while (*cptr && *cptr != ':' && !isspace(*cptr)) cptr++; if (*(unsigned short*)cptr == '::') { /* more and more likely :^) */ cptr += 2; while (*cptr && *cptr != ':' && !isspace(*cptr)) cptr++; if (*cptr == ':') { /* almost certain */ cptr++; if (isdigit(*cptr)) { while (*cptr && isdigit(*cptr)) cptr++; if (*cptr == '-') { /* ok, I'm convinced! */ WasdRecordCount++; FileWasdRecordCount++; continue; } } } } } } } /* go back to the 'authorized user' field */ cptr = sptr; } /* straight to the date field */ while (*cptr && *cptr != '[') cptr++; if (*cptr != '[') { /* doesn't look like the start of a date field to me */ DiscardRecordCount++; FileDiscardRecordCount++; continue; } cptr++; /* date/time */ year = month = day = hour = min = sec = 0; day = atoi(cptr); while (*cptr && *cptr != '/') cptr++; if (*cptr) cptr++; for (month = 1; month <= 12; month++) if (strsame (MonthAbbrev[month], cptr, 3)) break; while (*cptr && *cptr != '/') cptr++; if (*cptr) cptr++; year = atoi(cptr); while (*cptr && *cptr != ':') cptr++; if (*cptr) cptr++; hour = atoi(cptr); while (*cptr && *cptr != ':') cptr++; if (*cptr) cptr++; min = atoi(cptr); while (*cptr && *cptr != ':') cptr++; if (*cptr) cptr++; sec = atoi(cptr); memset (&utm, 0, sizeof(utm)); utm.tm_sec = sec; utm.tm_min = min; utm.tm_hour = hour; utm.tm_mday = day; utm.tm_mon = month - 1; utm.tm_year = year - 1900; UnixTime = mktime (&utm); if (Debug) fprintf (stdout, "%d %d %d %d %d %d = %d\n", year, month, day, hour, min, sec, UnixTime); if (UnixTime == -1) { DiscardRecordCount++; FileDiscardRecordCount++; continue; } /* prepend the binary, longword time to the original buffer */ *(int*)Line = UnixTime; /* plus four allows for the binary timestamp */ RecordDsc.dsc$a_pointer = Line; RecordDsc.dsc$w_length = FileRab.rab$w_rsz + 4; if (VMSnok (status = sor$release_rec (&RecordDsc, 0))) { fprintf (stdout, "%%%s-E-SORT, release record\n-%s\n", Utility, SysGetMsg(status)+1); exit (status | STS$M_INHIB_MSG); } InputRecordCount++; FileInputRecordCount++; } if (status != RMS$_EOF) { fprintf (stdout, "%%%s-E-GET, %s\n-%s\n", Utility, FileName, SysGetMsg(status)+1); exit (status | STS$M_INHIB_MSG); } sys$close (&FileFab, 0, 0); if (DoVerbose) { fprintf (stdout, "records:%d discarded:%d", FileRecordCount, FileDiscardRecordCount); if (DoNoWasd) fprintf (stdout, " WASD:%d", FileWasdRecordCount); if (DoNoProxy || DoProxyOnly) fprintf (stdout, " proxy:%d non-proxy:%d", FileProxyRecordCount, FileNonProxyRecordCount); fprintf (stdout, " input:%d\n", FileInputRecordCount); } return (SS$_NORMAL); } /****************************************************************************/ /* Open an output file for write. Retrieve each record from the sort-merge utility routines. Strip the leading binary time writing the textual component of the record to the output file. */ int WriteMergedLog () { int status; unsigned short ShortLength; char ExpFileName [256], Line [MAX_LINE_LENGTH+4]; struct FAB FileFab; struct NAM FileNam; struct RAB FileRab; $DESCRIPTOR (RecordDsc, Line); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "WriteMergedLog()\n"); FileFab = cc$rms_fab; FileFab.fab$l_dna = ".LOG"; FileFab.fab$b_dns = 4; FileFab.fab$l_fop = FAB$M_DFW | FAB$M_SQO | FAB$M_TEF; FileFab.fab$l_fna = MergedFileName; FileFab.fab$b_fns = strlen(MergedFileName); FileFab.fab$b_fac = FAB$M_PUT; FileFab.fab$l_nam = &FileNam; FileFab.fab$b_rfm = FAB$C_STMLF; FileFab.fab$b_org = FAB$C_SEQ; FileFab.fab$b_rat = FAB$M_CR; FileNam = cc$rms_nam; FileNam.nam$l_esa = ExpFileName; FileNam.nam$b_ess = sizeof(ExpFileName)-1; if (VMSnok (status = sys$create (&FileFab, 0, 0))) { fprintf (stdout, "%%%s-E-CREATE, %s\n-%s\n", Utility, MergedFileName, SysGetMsg(status)+1); exit (status | STS$M_INHIB_MSG); } FileRab = cc$rms_rab; FileRab.rab$l_fab = &FileFab; FileRab.rab$b_mbc = 127; FileRab.rab$b_mbf = 2; FileRab.rab$l_rop = RAB$M_WBH; if (VMSnok (status = sys$connect (&FileRab, 0, 0))) { fprintf (stdout, "%%%s-E-CONNECT, %s\n-%s\n", Utility, MergedFileName, SysGetMsg(status)+1); exit (status | STS$M_INHIB_MSG); } *FileNam.nam$l_ver = '\0'; for (;;) { if (VMSnok (status = sor$return_rec (&RecordDsc, &ShortLength, 0))) { if (status == SS$_ENDOFFILE) break; fprintf (stdout, "%%%s-E-SORT, return record\n-%s\n", Utility, SysGetMsg(status)+1); exit (status | STS$M_INHIB_MSG); } OutputRecordCount++; FileRab.rab$l_rbf = Line + 4; FileRab.rab$w_rsz = ShortLength - 4; if (VMSnok (status = sys$put (&FileRab, 0, 0))) { fprintf (stdout, "%%%s-E-PUT, %s\n-%s\n", Utility, ExpFileName, SysGetMsg(status)+1); exit (status | STS$M_INHIB_MSG); } } sys$close (&FileFab, 0, 0); if (DoVerbose) fprintf (stdout, "%s\nrecords: %d\n", ExpFileName, OutputRecordCount); return (SS$_NORMAL); } /*****************************************************************************/ /* Get "command-line" parameters, whether from the command-line or from a configuration symbol or logical containing the equivalent. */ GetParameters () { static char CommandLine [256]; static unsigned long Flags = 0; int status; unsigned short Length; char ch; char *aptr, *cptr, *clptr, *sptr; $DESCRIPTOR (CommandLineDsc, CommandLine); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetParameters()\n"); if ((clptr = getenv ("CALOGS$PARAM")) == NULL) { /* get the entire command line following the verb */ if (VMSnok (status = lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags))) exit (status); (clptr = CommandLine)[Length] = '\0'; } aptr = NULL; ch = *clptr; for (;;) { if (aptr != NULL && *aptr == '/') *aptr = '\0'; if (!ch) break; *clptr = ch; if (Debug) fprintf (stdout, "clptr |%s|\n", clptr); while (*clptr && isspace(*clptr)) *clptr++ = '\0'; aptr = clptr; if (*clptr == '/') clptr++; while (*clptr && !isspace (*clptr) && *clptr != '/') { if (*clptr != '\"') { clptr++; continue; } cptr = clptr; clptr++; while (*clptr) { if (*clptr == '\"') if (*(clptr+1) == '\"') clptr++; else break; *cptr++ = *clptr++; } *cptr = '\0'; if (*clptr) clptr++; } ch = *clptr; if (*clptr) *clptr = '\0'; if (Debug) fprintf (stdout, "aptr |%s|\n", aptr); if (!*aptr) continue; if (strsame (aptr, "/DBUG", -1)) { Debug = true; continue; } if (strsame (aptr, "/HELP", -1)) { ShowHelp (); exit (SS$_NORMAL); } if (strsame (aptr, "/PROXY", 5)) { DoProxyOnly = true; continue; } if (strsame (aptr, "/NOPROXY", 5)) { DoNoProxy = true; continue; } if (strsame (aptr, "/NOWASD", 5)) { DoNoWasd = true; continue; } if (strsame (aptr, "/OUTPUT=", 4)) { while (*aptr && *aptr != '=') aptr++; if (*aptr) aptr++; strcpy (MergedFileName, aptr); continue; } if (strsame (aptr, "/QUIET", 4)) { DoQuiet = true; continue; } if (strsame (aptr, "/SOFTWAREID", 4) || strsame (aptr, "/VERSION", 5)) { fprintf (stdout, "%%%s-I-SOFTWAREID, %s\n%s\n", Utility, SOFTWAREID, CopyrightInfo); exit (SS$_NORMAL); } if (strsame (aptr, "/VERBOSE", 5)) { DoVerbose = true; continue; } if (*aptr == '/') { fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n", Utility, aptr+1); exit (STS$K_ERROR | STS$M_INHIB_MSG); } if (!LogFileSpec[0]) { sptr = LogFileSpec; for (cptr = aptr; *cptr; *sptr++ = toupper(*cptr++)); *sptr = '\0'; continue; } if (!MergedFileName[0]) { sptr = MergedFileName; for (cptr = aptr; *cptr; *sptr++ = toupper(*cptr++)); *sptr = '\0'; continue; } fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n", Utility, aptr); exit (STS$K_ERROR | STS$M_INHIB_MSG); } } /****************************************************************************/ /* Does a case-insensitive, character-by-character string compare and returns true if two strings are the same, or false if not. If a maximum number of characters are specified only those will be compared, if the entire strings should be compared then specify the number of characters as 0. */ BOOL strsame ( char *sptr1, char *sptr2, int count ) { while (*sptr1 && *sptr2) { if (toupper (*sptr1++) != toupper (*sptr2++)) return (false); if (count) if (!--count) return (true); } if (*sptr1 || *sptr2) return (false); else return (true); } /****************************************************************************/ /* Return a pointer to the textual meaning of the specific VMS status value. */ char* SysGetMsg (int StatusValue) { static char Message [256]; static $DESCRIPTOR (MessageDsc, Message); int status; short int Length; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "SysGetMsg() %%X%08.08X\n", StatusValue); status = sys$getmsg (StatusValue, &Length, &MessageDsc, 0, 0); if (VMSnok (status)) exit (status); Message[Length] = '\0'; return (Message); } /*****************************************************************************/ /* */ ShowHelp () { fprintf (stdout, "Usage for Consolidate Access LOGS (%s)\n\ \n\ Consolidate Access LOGs merges multiple HTTP server common and combined format\n\ access logs into a single log file with records in time-order. Due to the\n\ granularity of HTTP server entry timestamps (one second) the records are sorted\n\ to the one second but not within the one second. The /NOPROXY, /PROXY and\n\ /NOWASD qualifiers allow the particular class of record to be excluded from the\n\ merged file.\n\ \n\ $ CALOGS [] []\n\ \n\ /HELP /NOPROXY /NOWASD /OUTPUT= /PROXY /QUIET /VERBOSE /VERSION\n\ \n\ Usage examples:\n\ \n\ $ CALOGS == \"$dir:CALOGS\"\n\ $ CALOGS HT_LOGS:*200205*.LOG 2002_MAY.LOG\n\ $ CALOGS /VERBOSE HT_LOGS:\n\ $ CALOGS /NOWASD HT_LOGS:*200206*.LOG_* 2002_JUNE.LOG\n\ $ CALOGS /PROXY /NOWASD HT_LOGS:*2002*.LOG 2002_PROXY.LOG\n\ \n", SOFTWAREID); } /*****************************************************************************/