logapp-0.15/0000755000175000017500000000000011506066153012512 5ustar michaelmichaellogapp-0.15/logapp.10000644000175000017500000005167011506065567014077 0ustar michaelmichael.\" t -*- coding: us-ascii -*- .if \n(.g .ds T< \\FC .if \n(.g .ds T> \\F[\n[.fam]] .de URL \\$2 \(la\\$1\(ra\\$3 .. .if \n(.g .mso www.tmac .TH logapp 1 "December 2010" "logapp 0.15" "" .SH NAME logapp \- An application output supervisor. .SH SYNOPSIS 'nh .fi .ad l \fBlogapp\fR \kx .if (\nx>(\n(.l/2)) .nr x (\n(.l/5) 'in \n(.iu+\nxu [\fIoption\fR]\&... \fIapplication\fR [\fB\-\-logapp_\fIoption\fB\fR]\&... [\fIapp.-argument\fR]\&... 'in \n(.iu-\nxu .ad b 'hy .PP 'nh .fi .ad l \fBapplicationsymlink\fR \kx .if (\nx>(\n(.l/2)) .nr x (\n(.l/5) 'in \n(.iu+\nxu [\fB\-\-logapp_\fIoption\fB\fR]\&... [\fIapplication-argument\fR]\&... 'in \n(.iu-\nxu .ad b 'hy .PP Instead of calling logapp directly you can also create a symlink with the name of the application pointing to logapp. Logapp will automatically start the application the name points to. It will also work if the symlink name is prefixed with \fIlog\fR. .SH DESCRIPTION Logapp is a wrapper utility that helps supervise the execution of applications that produce heavy console output (e.g. make, CVS and Subversion). It does this by logging, trimming, and coloring each line of the output before displaying it. It can be called instead of the executable that should be monitored; it then starts the application and logs all of its console output to a file. The output shown in the terminal is preprocessed, e.g. to limit the length of printed lines and to show the stderr output in a different color. It is also possible to automatically highlight lines that match a certain regular expression. The output is therefore reduced to the necessary amount, and all important lines are easy to identify. .SH OPTIONS The options provided before the \fIapplication\fR argument are processed directly by logapp. Options provided after the \fIapplication\fR argument are only parsed if they are prefixed with \*(T<\fB\-\-logapp_\fR\*(T> (long option names only) otherwise they are passed to the application. If logapp is called via a symlink all unprefixed options are passed to the application. .PP Every application usually uses two independent output streams: \fIstdout\fR for normal output and \fIstderr\fR for errors and important messages. Both of them are handled independently by logapp, therefore many options are available for both streams. .PP Bool options are accepting \fI1/0\fR and \fItrue/false\fR as value. For long boolean options the value can be omitted, in that case it will be assumed to be 'true'. .SS "GENERAL OPTIONS" .TP \*(T<\fB\-?\fR\*(T>, \*(T<\fB\-\-help\fR\*(T> Show a short overview over all available options. .TP \*(T<\fB\-\-version\fR\*(T> Show version information. .TP \*(T<\fB\-\-configfile=\fR\*(T>\fIFILE\fR Use a specific configuration file instead of searching the configuration search paths. .TP \*(T<\fB\-\-showconfig\fR\*(T> Print the current configuration of logapp and exit before the application is executed. This can be used this to check if all configuration options are setup correctly if something doesn't work as expected. .TP \*(T<\fB\-\-configsection=\fR\*(T>\fINAME\fR Enable a specific section in the configuration file. If this option is not provided the application name is used as default. .TP \*(T<\fB\-\-disable\fR\*(T> This disables logapp data handling completely. The application is still started, but logapp won't touch the data streams coming from the application. Neither logging nor output formating is performed, only the execution time and the exit state tracked. This is useful if logapp won't be able to deal with expected data correctly, for example when starting curses based applications. Have a look at \*(T<\fB\-\-disable_keywords\fR\*(T> to see how this option can be enabled automatically. .TP \*(T<\fB\-\-disable_keywords=\fR\*(T>\fIkeywordlist\fR With this option a list of comma separated keywords can be provided which will cause the \*(T<\fB\-\-disable\fR\*(T> to be enabled automatically if found in the applications option list. This is useful if an application usually provides line-based output, but creates binary data or uses a curses based frontend if called with a specific parameter. You can also use the \*(T<\fB\-\-detectescape\fR\*(T> option for another way to do this without disabling the logging functionality. .TP \*(T<\fB\-\-detectescape=\fR\*(T>\fIbool\fR This option can be used to switch escape-sequence detection on or off. With escape-sequence detection logapp will automatically enable char-based stream handling as soon as an escape-sequence is part of the specific stream. This behavior can be useful if you are working with an application that is usually line-based, but starts other applications which may be using escape sequences to format the screen. This option will prevent the terminal from being messed up in that case. .TP \*(T<\fB\-\-dumbterm=\fR\*(T>\fIbool\fR With this option set to true there will be no terminal output coloring for \fIstdout\fR and \fIstderr\fR. Normally this option is disabled and logapp tries to detect "dumb" terminals itself. .TP \*(T<\fB\-\-usepty=\fR\*(T>\fIbool\fR This option is only available if logapp has been compiled with PTY support. If PTY support is enabled with this option set to true, logapp will open a \fIpseudo terminal\fR for \fIstdout\fR. This helps wenn running logapp with applications that usually need a real terminal for output. You can disable this option for most line based applications like make, CVS or Subversion. Other applications like telnet or picocom may produce strange results when used without PTY support. .TP \*(T<\fB\-\-ptyremovecr=\fR\*(T>\fIbool\fR This option is only available if logapp has been compiled with PTY support. When using a pseudo terminal for getting the application output you will always get CR-LF line endings, which is usually not desired when working in UNIX environments. With this option enabled, logapp will automatically translate all CR-LF line endings in LF line endings. This option is enabled as default. .TP \*(T<\fB\-\-stdout_blen=\fR\*(T>\fIbytes\fR .TP \*(T<\fB\-\-stderr_blen=\fR\*(T>\fIbytes\fR The line buffer size can be adjusted for \fIstdout\fR and \fIstderr\fR independently with this option. If the value is too small, lines will be split up if the buffer is full. The default is \fI2048 byte\fR which should be big enough for most applications. .TP \*(T<\fB\-\-stdout_charbased=\fR\*(T>\fIbool\fR .TP \*(T<\fB\-\-stderr_charbased=\fR\*(T>\fIbool\fR If you want to use logapp with applications that do not produce line based output you can enable this options for \fIstdout\fR and \fIstderr\fR independently. With this option enabled logapp won't expect complete lines and will handle data as it comes in. By default all single data packets are written to a new line if this option is enabled, this can be changed with the \*(T<\fB\-\-alignlog\fR\*(T> option. If the result will be usable depends on what kind of data is generated by the application. .TP \*(T<\fB\-\-extended\-regexp=\fR\*(T>\fIbool\fR If this option is enabled logapp will interpret provided regular expression patterns as extended regular expressions. The default is to use basic regular expressions. .SS "LOGGING OPTIONS" This section contains options that affect the logfile. .TP \*(T<\fB\-l\fR\*(T>, \*(T<\fB\-\-logfile=\fR\*(T>\fIfile\fR This option can be used to change the file that is used for storing the logged application data. If an empty string is provided, logging is disabled and no logfile will be created. The default is that logapp creates a logfile called \fIlogapp.log\fR in the current directory. .TP \*(T<\fB\-a\fR\*(T>, \*(T<\fB\-\-appendlog=\fR\*(T>\fIbool\fR This option specifies if the logfile will be truncated or if the data will be appended to an existing file on logapp startup. .TP \*(T<\fB\-\-maxlogsize=\fR\*(T>\fIkibyte\fR To limit the maximum size of the logfile you can set this option to a value between \fI10 and 4000000 kiBytes\fR. The default is \fI0\fR which disables the logfile size limit. There are different ways implemented how the logfile is limited. Have a look at the options \*(T<\fB\-\-logrename\fR\*(T> and \*(T<\fB\-\-circularlog\fR\*(T> to learn more. The default way is that the extension \*(T<.old\*(T> is added to the logfile and a new logfile is started. .TP \*(T<\fB\-\-logrename=\fR\*(T>\fIbool\fR This option specifies the behavior when a logfile is to be truncated. If \*(T<\fB\-\-logrename\fR\*(T> is enabled the logfile is renamed. The new filename will be the same as before with the extension defined with \*(T<\fB\-\-oldlogext\fR\*(T> added. The default extension is \*(T<.old\*(T>. This option is used together with the value of \*(T<\fB\-\-appendlog\fR\*(T> and \*(T<\fB\-\-maxlogsize\fR\*(T> .TP \*(T<\fB\-\-circularlog=\fR\*(T>\fIbool\fR If this option is enabled together with a logfile size limit set with \*(T<\fB\-\-maxlogsize\fR\*(T>, the logfile will be used in a circular way. This means if the maximum size is reached, the file pointer is set to the beginning of the file and the old content is overwritten from the beginning. There are tags added to the logfile to help navigating in the file. .TP \*(T<\fB\-\-oldlogext=\fR\*(T>\fIextension\fR This defines the extion that is used when logapp is renaming a logfile. The \*(T<\fB\-\-logrename\fR\*(T> option defines if logapp will rename the file and the default extension is \*(T<.old\*(T>. .TP \*(T<\fB\-\-locklogfile=\fR\*(T>\fIbool\fR With this option active the logfile is locked in order to prevent it to be overwritten by another task. This is useful if otherwise an unreadable mix up of different contents would be the result. Depending on the value of the \*(T<\fB\-\-maxaltlogfiles\fR\*(T> option another logfile is chosen with the same name and a number added. Logfile locking is activated by default. .TP \*(T<\fB\-\-warnlogfilelock=\fR\*(T>\fIbool\fR This options defines if there should be a warning printed to the console if the chosen logfile is already locked or in other means not accessible. In this case there will be a message before the application is started and directly after its execution where the name of the alternative logfile is mentioned. This option is enabled by default. Also have a look at the \*(T<\fB\-\-printlogname\fR\*(T> where you can define to always get the current logfile reported. .TP \*(T<\fB\-\-printlogname=\fR\*(T>\fIbool\fR This option defines if the name of the used logfile should be printed after the application has finished its execution. This option is disabled by default. Also have a look at the \*(T<\fB\-\-warnlogfilelock\fR\*(T> where you can enable/disable a warning if the logfile name is changed because of a locked logfile. .TP \*(T<\fB\-\-maxaltlogfiles=\fR\*(T>\fInumber\fR This options defines the maximum number that can be added to the logfile name, if the original file is not accessible. On logapp startup it will be checked if the currently defined logfile is writeable, if this is not the case automatically a number is added to the filename. If the alternative file is also not accessible this number is increased until a file is writable or the value of \fImaxaltlogfiles\fR is reached. In the latter case the application will exit with an error. If a value of 0 is used only the original logfile name is tried. Also have a look at the \*(T<\fB\-\-warnlogfilelock\fR\*(T> and \*(T<\fB\-\-printlogname\fR\*(T> options to define if there should be messages about the currently used logfile. .TP \*(T<\fB\-\-alignlog=\fR\*(T>\fIbool\fR This option is used together with \*(T<\fB\-\-stdout_charbased\fR\*(T> and \*(T<\fB\-\-stderr_charbased\fR\*(T> and defines if data packets are written to the logfile as they come or if they are each written to a new line. The default is that each data packet is written to a new line, set this option to false to disable it. .TP \*(T<\fB\-\-alignlinebreaks=\fR\*(T>\fIbool\fR This option is used together with \*(T<\fB\-\-stdout_charbased\fR\*(T> and \*(T<\fB\-\-stderr_charbased\fR\*(T> and aligns the lines to the left in the logfile with regard to prefix and timestamp. This option is enabled by default. .TP \*(T<\fB\-\-jointimeout=\fR\*(T>\fItime\fR This option is used together with \*(T<\fB\-\-stdout_charbased\fR\*(T> and \*(T<\fB\-\-stderr_charbased\fR\*(T> and defines a ms timeout for joining single packets to one. This means if for example two chars get written within the timeout, they are treated as one packet. This is best used together with \*(T<\fB\-\-alignlog\fR\*(T> and \*(T<\fB\-\-logtime\fR\*(T>. Use this option if the data packets have lost their coherency for some reason (e.g. if the data comes through a serial line). This feature is disabled by default and can be enabled by setting \fItime\fR to a value bigger than 0 ms. .TP \*(T<\fB\-t\fR\*(T>, \*(T<\fB\-\-logtime=\fR\*(T>\fIbool\fR This option can be enabled to add a ms timestamp to each line of the logfile. Normally the time since the application start is used, but this can be changed with the \*(T<\fB\-\-logreltime\fR\*(T> option. .TP \*(T<\fB\-\-logreltime=\fR\*(T>\fIbool\fR If this option is set this to true, the \*(T<\fB\-\-logreltime\fR\*(T> option will use the relative time since the last line for the logged timestamps. .TP \*(T<\fB\-\-logenv=\fR\*(T>\fIbool\fR With this option set to true logapp will add a list of all active environment variables to the logfile. This option is disabled by default. .TP \*(T<\fB\-p\fR\*(T>, \*(T<\fB\-\-stdout_lineprefix=\fR\*(T>\fIprefix\fR .TP \*(T<\fB\-P\fR\*(T>, \*(T<\fB\-\-stderr_lineprefix=\fR\*(T>\fIprefix\fR To be able to distinguish \fIstdout\fR and \fIstderr\fR output in the logfile logapp can prefix each line with a string that indicates if the line belongs to a specific data stream. Those strings can be changed with this option. The default is that \fIstdout\fR does not have a prefix and \fIstderr\fR is prefixed with \fISTDERR:\fR. .SS "CONSOLE OUTPUT OPTIONS" This section contains options that affect the visual output on the console. .TP \*(T<\fB\-\-dumbterm=\fR\*(T>\fIbool\fR This option disables output coloring. This is usually done automatically if a \fIdumb\fR terminal is detected. .TP \*(T<\fB\-s\fR\*(T>, \*(T<\fB\-\-print_summary=\fR\*(T>\fIbool\fR If this option is set to true, then a short summary will be printed after the application has terminated. This option is disabled by default. .TP \*(T<\fB\-f\fR\*(T>, \*(T<\fB\-\-stdout_fgcol=\fR\*(T>\fIcolor\fR .TP \*(T<\fB\-F\fR\*(T>, \*(T<\fB\-\-stderr_fgcol=\fR\*(T>\fIcolor\fR This options define the foreground color for the specific data stream. The value can be one of the entries in the \fIconsole color table\fR at the end of this section. .TP \*(T<\fB\-b\fR\*(T>, \*(T<\fB\-\-stdout_bold=\fR\*(T>\fIbool\fR .TP \*(T<\fB\-B\fR\*(T>, \*(T<\fB\-\-stderr_bold=\fR\*(T>\fIbool\fR This options define if the font for the specific data stream should be printed bold. .TP \*(T<\fB\-r\fR\*(T>, \*(T<\fB\-\-stdout_regexp=\fR\*(T>\fIregular expression\fR .TP \*(T<\fB\-R\fR\*(T>, \*(T<\fB\-\-stderr_regexp=\fR\*(T>\fIregular expression\fR The regular expression that can be defined with this option is applied to every line of the specific data stream. On a match the background color changes to the value provided with the \*(T<\fB\-\-stdout_regexp_bgcol\fR\*(T> respectively \*(T<\fB\-\-stderr_regexp_bgcol\fR\*(T> option. .TP \*(T<\fB\-\-stdout_regexp_bgcol=\fR\*(T>\fIcolor\fR .TP \*(T<\fB\-\-stderr_regexp_bgcol=\fR\*(T>\fIcolor\fR This options define the background color for the specific data stream for the case that the appropriate regular expression provided with \*(T<\fB\-\-stdout_regexp\fR\*(T> or \*(T<\fB\-\-stderr_regexp\fR\*(T> matches. The value can be one of the entries in the \fIconsole color table\fR at the end of this section. .TP \*(T<\fB\-c\fR\*(T>, \*(T<\fB\-\-stdout_clip=\fR\*(T>\fIwidth\fR .TP \*(T<\fB\-C\fR\*(T>, \*(T<\fB\-\-stderr_clip=\fR\*(T>\fIwidth\fR This options define at which column the output should be clipped for the specific stream to reduce the amount of data written to the console. If a value of \fI\-1\fR is provided clipping is disabled for the stream. A value of \fI\-2\fR sets the clipping to the current console width. It is also possible to use \fIdisable\fR and \fIauto\fR instead of the numeric values. The default is that \fIstdout\fR is limited to the console width and that clipping is deactivated for \fIstderr\fR. \fBConsole color table\fR .TS allbox ; l | l. T{ # T} T{ color T} .T& l | l. T{ \-1 T} T{ (console) default T} T{ 0 T} T{ black T} T{ 1 T} T{ red T} T{ 2 T} T{ green T} T{ 3 T} T{ brown T} T{ 4 T} T{ blue T} T{ 5 T} T{ magenta T} T{ 6 T} T{ cyan T} T{ 7 T} T{ white T} .TE .SS "COMMAND EXECUTION OPTIONS" This section contains options that configure the execution of commands on regular expression matches. .TP \*(T<\fB\-\-exitonexecfail=\fR\*(T>\fIBOOL\fR This option defines if logapp should exit and end the wrapped application if the return value of an executed command indicates a failure. As default this option is disabled and logapp ignores the return state of executed commands. .TP \*(T<\fB\-\-preexec=\fR\*(T>\fIcommand\fR The command that can be provided with this option is executed directly before the application is started. At this time the header is already written to the logfile and can be parsed by the command. .TP \*(T<\fB\-\-postexec=\fR\*(T>\fIcommand\fR The command that can be provided with this option is executed directly after the application has exited. At this time the logfile is already closed for writing so all application output and the footer are already included and can be processed by the command. .TP \*(T<\fB\-e\fR\*(T>, \*(T<\fB\-\-stdout_execregexp=\fR\*(T>\fIregular expression\fR .TP \*(T<\fB\-E\fR\*(T>, \*(T<\fB\-\-stderr_execregexp=\fR\*(T>\fIregular expression\fR The regular expression that can be defined with this option is applied to every line of the specific data stream. On a match the command provided with the \*(T<\fB\-\-stdout_execcommand\fR\*(T> respectively \*(T<\fB\-\-stderr_execcomand\fR\*(T> option is executed. An empty value for this option disables the regular expression matching. .TP \*(T<\fB\-x\fR\*(T>, \*(T<\fB\-\-stdout_execcommand=\fR\*(T>\fIcommand\fR .TP \*(T<\fB\-X\fR\*(T>, \*(T<\fB\-\-stderr_execcommand=\fR\*(T>\fIcommand\fR This option defines the command that is executed on a regular expression match. The regular expression can be defined separately for the \fIstdout\fR and \fIstderr\fR stream with the \*(T<\fB\-\-stdout_execregexp\fR\*(T> respectively \*(T<\fB\-\-stderr_execregexp\fR\*(T> option. .SH "REGULAR EXPRESSIONS" Regular expressions are patterns that describe strings. Logapp uses this patterns to execute actions based on strings found in the data stream. The implementation is identical to the one that is used by \fIgrep\fR. .PP Logapp understands the "basic" and "extended" syntax of regular expressions as defined by POSIX. The default is to use the basic set, but you can switch to extended patterns with the \*(T<\fB\-\-extended\-regexp\fR\*(T> parameter. Please have a look at the \fBgrep\fR(1) and \fBregex\fR(7) manpage for detailed information. .SS EXAMPLES .TP \*(T<\fBString\fR\*(T> Matches "String" .TP \*(T<\fB^String\fR\*(T> Matches "String" at the beginning of a line .TP \*(T<\fBString$\fR\*(T> Matches "String" at the end of a line .TP \*(T<\fB^String$\fR\*(T> Line contains only "String" .TP \*(T<\fB[Ss]tring\fR\*(T> Matches "String" or "string" .TP \*(T<\fBStr.ng\fR\*(T> The dot matches all characters, so this matches for example "String" or "Strong" .TP \*(T<\fBStr.*ng\fR\*(T> The dot together with star matches any number of characters, so this matches for example "String" or "Streaming" .TP \*(T<\fB^[A\-Z] *\fR\*(T> Matches any one of the characters from A to Z at the beginning of a line followed by zero or any number of spaces .TP \*(T<\fBString\e|Word\fR\*(T> Matches "String" or "Word" when working with \fIbasic regular expressions\fR .TP \*(T<\fBString|Word\fR\*(T> Matches "String" or "Word" when working with \fIextended regular expressions\fR .SH ENVIRONMENT .TP \fBTERM\fR This variable is checked to see which type of console logapp is running in. Currently only the value \fIdumb\fR is handled in a special way \(em by disabling console colors. If the \fBTERM\fR variable is missing also a dumb terminal is assumed. The setting can be overridden by enabling/disabling the dumb terminal mode using the \*(T<\fB\-\-dumbterm\fR\*(T> option. .SH FILES .TP \*(T<\fI~/.logapprc\fR\*(T>, \*(T<\fI/etc/logapp.conf\fR\*(T>, \*(T<\fI/etc/logapp/logapp.conf\fR\*(T> Configuration file locations that are tried if no \*(T<\fB\-\-configfile\fR\*(T> option is provided. .SH BUGS See the \*(T<\fITODO\fR\*(T> file included in the source package. .SH "SEE ALSO" \fBgrep\fR(1), \fBregex\fR(7) .SH AUTHOR Michael Brunner .SH COPYING Copyright (C) 2007\(en2010 Michael Brunner .PP 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 of the License, or (at your option) any later version. .PP 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 General Public License for more details. logapp-0.15/logfile.h0000644000175000017500000000163411206032076014302 0ustar michaelmichael/* * logfile.h: logapp logfile handling * * * Copyright (C) 2007-2009 Michael Brunner * * 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 * of the License, 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 * General Public License for more details. */ #ifndef __LOGFILE_H__ #define __LOGFILE_H__ #include "logapp.h" #include #include "configuration.h" extern int logfile_write(logfile_t* logfile, pipe_t* pipe); extern int logfile_open(char* filename, logfile_t* logfile); extern int logfile_close(logfile_t* logfile); #endif /* __LOGFILE_H__ */ logapp-0.15/COPYING0000644000175000017500000004310310563200452013540 0ustar michaelmichael GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. logapp-0.15/capture.c0000644000175000017500000003623611501132662014325 0ustar michaelmichael/* * capture.c: capture data from stdout and stderr * * * Copyright (C) 2007-2010 Michael Brunner * * 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 * of the License, 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 * General Public License for more details. */ #include "logapp.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "configuration.h" #include "logfile.h" #include "capture.h" int handle_cbstream(pipe_t* pipe); void reset_console(void) { int err = 0;; if (!config.dumbterm) { err = write(app.pstdout->cfhno, app.pstdout->escreset, app.pstdout->escresetlen); err = write(app.pstderr->cfhno, app.pstderr->escreset, app.pstderr->escresetlen); } if (app.ptytermios_bak != NULL) tcsetattr(STDIN_FILENO, TCSADRAIN, app.ptytermios_bak); tcdrain(STDOUT_FILENO); tcdrain(STDERR_FILENO); if (err == -1) { warning("Problem reseting console..."); } } static int recalc_cliplen(pipe_t *pipe) { int i; int length=0; int checklen; if (pipe->bfill>pipe->clip) { checklen = pipe->clip; } else { checklen = pipe->bfill; } for (i=0; ibfill; i++) { switch(pipe->buf[i]) { case '\t': length += (8-length%8); break; default: length++; break; } if (length>pipe->clip) return i--; } return pipe->clip; } int print_stream(pipe_t *pipe) { static pipe_t *pipe_old; static unsigned offset; char* buf; unsigned ccount; unsigned esclength = 0; int regmatch = 0; if (pipe->bfill < 1) return 1; if (!config.dumbterm) { /* Check for regular expression match */ if (pipe->regexp) { if (!(regexec(&pipe->preg, pipe->buf, 0, NULL, 0))) { regmatch = 1; } } /* Change font style if we are switching the stream */ if (((void*)pipe_old != (void*)pipe)||(regmatch)) { offset = 0; buf = pipe->esccolor; esclength = pipe->buf - pipe->esccolor; if (pipe->bgesccolor) { if (!regmatch) { *pipe->bgesccolor = pipe->bgcol + '0'; pipe_old = pipe; } else { *pipe->bgesccolor = pipe->regbgcol + '0'; pipe_old = NULL; } } else { pipe_old = pipe; } } else { buf = pipe->buf; } } else { buf = pipe->buf; } /* Clip output if configured */ if (pipe->eclip) { unsigned int clip; /* clip len should be unsigned by now */ /* recalculate clipping if there are tabs in the stream */ if (pipe->recalclen) { clip = recalc_cliplen(pipe); } else { clip = pipe->clip; } if (offset >= clip) return 0; if ((pipe->bfill + offset) > clip) { ccount = clip - offset; pipe->buf[ccount] = '\n'; ccount++; offset = 0; } else { ccount = pipe->bfill; if (pipe->buf[ccount - 1] == '\n') { offset = 0; } else { offset += ccount; } } } else { ccount = pipe->bfill; } if (regmatch && !config.dumbterm) { /* Prevent colorizing the next line if regexp matches */ if (pipe->buf[ccount-1] == '\n') { int rlen = pipe->escresetlen + 1; strcpy(&pipe->buf[ccount-1], pipe->escreset); strcpy(&pipe->buf[ccount-1 + rlen], "\n"); esclength+=rlen; regmatch = 1; } else { regmatch = 0; } } if (write(pipe->cfhno, buf, ccount + esclength) == -1) return -1; if (regmatch && !config.dumbterm) { /* Restore \n */ if (regmatch) { pipe->buf[ccount-1] = '\n'; } } pipe->recalclen = 0; return 1; } void print_summary(void) { time_t sec; unsigned int executiontime = 0; time(&sec); if ((app.starttime > -1)&&(app.starttime <= sec)) { executiontime = (unsigned int)(sec - app.starttime); } else { error("Error calculating execution time\n"); } fprintf(stderr, "\nLogapp exited after %us; ", executiontime); if (!app.pstdout->charbased) { fprintf(stderr, "%u", app.pstdout->linecount); } else { fprintf(stderr, "?"); } fprintf(stderr, " stdout, "); if (!app.pstderr->charbased) { fprintf(stderr, "%u", app.pstderr->linecount); } else { fprintf(stderr, "?"); } fprintf(stderr, " stderr lines;"); if (WIFEXITED(app.exit_state)) { fprintf(stderr, " exit state %u\n", WEXITSTATUS(app.exit_state)); } else if (WIFSIGNALED(app.exit_state)) { fprintf(stderr, " signal %d\n", WTERMSIG(app.exit_state)); } else { fprintf(stderr, " unknown exit state\n"); } /* Show a message if we changed the logfile name */ if ((logfile.appendnr && config.warnlogfilelock) || config.printlogname) fprintf(stderr, "Logapp logfile is: %s\n", logfile.name); } inline int is_rdbuf_empty(pipe_t *pipe) { return (pipe->rdbuf.read >= pipe->rdbuf.end); } int buf_get_char(pipe_t *pipe, char* buf) { int i; /* Fill buffer if it is empty */ if (is_rdbuf_empty(pipe)) { pipe->rdbuf.read = pipe->rdbuf.start; if (pipe->switchtocbmode) { pipe->charbased = 1; pipe->handler = (int(*)(void*)) handle_cbstream; return 0; } i = read(pipe->fh, pipe->rdbuf.start, pipe->rdbuf.len); if (i < 1) { pipe->rdbuf.end = pipe->rdbuf.start; return i; } pipe->rdbuf.end = pipe->rdbuf.start + i; } /* Copy one char into the provided buffer and increase read pointer */ *buf = *pipe->rdbuf.read; pipe->rdbuf.read++; return 1; } int read_pipe(pipe_t *pipe) { int i = 0; if (pipe->state > 0) { pipe->recalclen = 0; pipe->bfill = 0; } /* If a CR is left from the last read, put it into the buffer again */ if (CONFIG_SUPPORT_PTY && pipe->memcr) { *pipe->buf = '\r'; pipe->bfill++; pipe->memcr = 0; } while(1) { i = buf_get_char(pipe, &pipe->buf[pipe->bfill]); if (i < 1) return(pipe->bfill); if (pipe->buf[pipe->bfill] == '\t') pipe->recalclen = 1; if (pipe->buf[pipe->bfill] == '\n' || ( i + pipe->bfill >= pipe->blen) || ((pipe->buf[pipe->bfill] == 27) && pipe->detectescape) ) { if ((pipe->buf[pipe->bfill] == '\n') && (i > 0)) { /* Change CR-LF into LF if configured */ if (CONFIG_SUPPORT_PTY && pipe->ptyremovecr && (pipe->bfill > 0)) { if (pipe->buf[pipe->bfill-1] =='\r'){ pipe->bfill--; pipe->buf[pipe->bfill]='\n'; } } pipe->bfill++; pipe->linecount++; } else if ((pipe->buf[pipe->bfill] == 27) && (pipe->detectescape) && (i > 0)) { pipe->bfill++; /* Switch to charbased stream handling as we * just detected an escape sequence */ pipe->switchtocbmode = 1; } else if ( i + pipe->bfill >= pipe->blen) { pipe->bfill++; } /* If there is a lonely CR at the end of the buffer, * ignore it this time until the next read */ if (pipe->buf[pipe->bfill-1] == '\r') { pipe->memcr = 1; pipe->bfill--; } /* NULL terminate string, as needed for regexec */ pipe->buf[pipe->bfill] = '\0'; return(pipe->bfill); }; if (pipe->bfill < pipe->blen) pipe->bfill++; } return 0; } int prepare_buffer(pipe_t* pipe) { if (pipe->blen) { /* length = buffer length + escape prefix + escape postfix */ int len = pipe->blen + pipe->esccolorlen + 1; if (!(pipe->dbuf = (char*)malloc(len))) { error_outofmemory(); error("trying to allocate %d byte " "%s buffer\n", len, pipe->name); return -1; } /* in line-based mode an intermediate ring-buffer is used for * faster data processing */ if (!pipe->charbased) { struct stat statbuf; /* get pipe stats to determine optimal buffer size*/ if (fstat(pipe->fh, &statbuf)) { error("unable to get io buffer stats for %s\n", pipe->name); free(pipe->dbuf); pipe->dbuf = NULL; return -1; } pipe->rdbuf.len = statbuf.st_blksize; pipe->rdbuf.start = (char*)malloc(pipe->rdbuf.len); if (!pipe->rdbuf.start) { error_outofmemory(); error("trying to allocate %d " "byte %s read buffer\n", pipe->rdbuf.len, pipe->name); free(pipe->buf); pipe->buf = NULL; return -1; } pipe->rdbuf.read = pipe->rdbuf.start; pipe->rdbuf.end = pipe->rdbuf.start; } /* We put the color escape sequence directly before the line * buffer memory to be able to squeeze it all out at once. * The reserved space at the end is for a console reset * sequence to end the regexp match background color */ strcpy(pipe->dbuf, pipe->esccolor); free(pipe->esccolor); if (pipe->bgesccolor) { pipe->bgesccolor = pipe->dbuf + (pipe->bgesccolor - pipe->esccolor); /* fixup bg color pointer */ } pipe->esccolor = pipe->dbuf; pipe->buf = pipe->dbuf + pipe->esccolorlen; }; return 0; } void free_buffer(pipe_t* pipe) { if (pipe->dbuf) { free(pipe->dbuf); if (pipe->dbuf == pipe->esccolor) pipe->esccolor = NULL; pipe->dbuf = NULL; pipe->buf = NULL; } if (pipe->rdbuf.start) { free(pipe->rdbuf.start); pipe->rdbuf.start = NULL; pipe->rdbuf.read = NULL; pipe->rdbuf.end = NULL; } } int handle_cbstream(pipe_t* pipe) { /* If a CR is left from the last read, put it into the buffer again */ if (CONFIG_SUPPORT_PTY && pipe->memcr) { *pipe->buf = '\r'; } if ((pipe->bfill = read(pipe->fh, pipe->buf+pipe->memcr, pipe->blen-pipe->memcr)) > 0) { pipe->bfill += pipe->memcr; /* Change CR-LF into LF if configured */ if (CONFIG_SUPPORT_PTY && pipe->ptyremovecr) { int i; int lbo = 0; char* bufa = pipe->buf; char* bufb = pipe->buf; for (i=0; i<(pipe->bfill-1); i++) { *bufb = *bufa; bufa++; bufb++; if (*bufa == '\n') { if (*(bufa-1) == '\r') bufb--; if ((config.alignlinebreaks) && (lbolinebreakpos[lbo++] = bufb - pipe->buf + 1; } } } pipe->bfill -= (bufa-bufb); *bufb = *bufa; /* linebreakpos buffer is defined size + 1 for the 0 */ pipe->linebreakpos[lbo] = 0; /* if there is no linebreak detected so far, add * length of the packet to the array */ if (!pipe->linebreakpos[0]) { pipe->linebreakpos[0] = pipe->bfill; pipe->linebreakpos[1] = 0; } /* If there is a lonely CR at the end of the buffer, * ignore it this time until the next read */ if (pipe->buf[pipe->bfill-1] == '\r') { pipe->memcr = 1; pipe->bfill--; if (pipe->bfill <= 0) { return 0; } } else { pipe->memcr = 0; } } /* Write to console */ if (write(pipe->cfhno, pipe->buf, pipe->bfill) == -1) { error("Problem writing data to screen for %s\n", pipe->name); return -1; } /* Write to logfile */ if (logfile_write(app.logfile, pipe)) { error("error writing logfile\n"); return -1; } } else { /* Pipe didn't provide data - exit if * application isn't active anymore */ if (!app.active) { /* Print remembered CR before exiting the * application */ if (CONFIG_SUPPORT_PTY && pipe->memcr) { pipe->bfill = 1; /* Write to console */ if (write(pipe->cfhno, pipe->buf, pipe->bfill) == -1) { error("Problem writing data to screen " "for %s\n", pipe->name); return -1; } /* Write to logfile */ if (logfile_write(app.logfile, pipe)) { error("error writing logfile\n"); return -1; } } return 1; } return 2; } return 0; } int handle_lbstream(pipe_t* pipe) { if ((pipe->state = read_pipe(pipe)) > 0) { int clearbuf; if (logfile_write(app.logfile, pipe)) { error("error writing logfile\n"); return -1; } if ((clearbuf = print_stream(pipe)) < 0) { error("error printing %s output\n", pipe->name); return -1; } /* Reset buffer len after printing and logging one line */ if (clearbuf) pipe->bfill = 0; /* Check if we should execute a command */ if (pipe->execregexp) { if (!regexec(&pipe->pexecreg, pipe->buf, pipe->bfill, NULL, 0)) { int ret = execcmd(pipe->execcommand); if (ret) { print(config.exitonexecfail ?ERROR:WARNING, "command \"%s\" returned 0x%x\n", pipe->execcommand, ret); if (config.exitonexecfail) { exit(EXIT_FAILURE); } } } } } else { /* Don't exit if there is data still in the buffer */ if (!is_rdbuf_empty(pipe)) return 0; /* Pipe didn't provide data - exit if * application isn't active anymore */ if (!app.active) return 1; return 2; } return 0; } void* capture_thread(void* do_pipe) { fd_set fd_pipe; pipe_t* pipe = (pipe_t*) do_pipe; FD_ZERO(&fd_pipe); FD_SET(pipe->fh, &fd_pipe); if (pipe->charbased) { pipe->handler = (int(*)(void*)) handle_cbstream; } else { pipe->handler = (int(*)(void*)) handle_lbstream; } while(1) { if (select(pipe->fh + 1, &fd_pipe, NULL, NULL, NULL) == 1) { switch (pipe->handler(pipe)) { case -1: reset_console(); exit(EXIT_FAILURE); break; case 1: if (CONFIG_USE_THREADS) pthread_exit(NULL); break; case 2: usleep(10000); break; default: break; } } else { if (CONFIG_USE_THREADS) pthread_exit(NULL); } } } int capture_loop(pipe_t* pipe_stdout, pipe_t* pipe_stderr) { fd_set rdfs; long flags = 0; int do_stdout_loop = 1; int do_stderr_loop = 1; if (pipe_stdout->charbased) { pipe_stdout->handler = (int(*)(void*)) handle_cbstream; } else { pipe_stdout->handler = (int(*)(void*)) handle_lbstream; } if (pipe_stderr->charbased) { pipe_stderr->handler = (int(*)(void*)) handle_cbstream; } else { pipe_stderr->handler = (int(*)(void*)) handle_lbstream; } fcntl(pipe_stdout->fh, F_GETFL, flags); fcntl(pipe_stdout->fh, F_SETFL, flags | O_NONBLOCK); fcntl(pipe_stderr->fh, F_GETFL, flags); fcntl(pipe_stderr->fh, F_SETFL, flags | O_NONBLOCK); while (do_stdout_loop || do_stderr_loop) { FD_ZERO(&rdfs); FD_SET(pipe_stdout->fh, &rdfs); FD_SET(pipe_stderr->fh, &rdfs); if (select(FD_SETSIZE, &rdfs, NULL, NULL, NULL) > 0) { if (FD_ISSET(pipe_stdout->fh, &rdfs)) { switch (pipe_stdout->handler(pipe_stdout)) { case -1: reset_console(); do_stdout_loop = 0; break; case 1: do_stdout_loop = 0; break; case 2: default: break; } } if (FD_ISSET(pipe_stderr->fh, &rdfs)) { switch (pipe_stdout->handler(pipe_stderr)) { case -1: reset_console(); do_stderr_loop = 0; break; case 1: do_stderr_loop = 0; break; case 2: default: break; } } } else { do_stdout_loop = 0; do_stderr_loop = 0; } } return 0; } int capture_start(void) { if (prepare_buffer(app.pstdout)) { exit(EXIT_FAILURE); } if (prepare_buffer(app.pstderr)) { exit(EXIT_FAILURE); } if (CONFIG_USE_THREADS) { pthread_create(&app.pstderr->ct, NULL, capture_thread, app.pstderr); pthread_create(&app.pstdout->ct, NULL, capture_thread, app.pstdout); } else { return capture_loop(app.pstdout, app.pstderr); } return 0; } int capture_end(void) { if (CONFIG_USE_THREADS) { /* wait for capture threads to exit */ pthread_join (app.pstderr->ct, NULL); pthread_join (app.pstdout->ct, NULL); } free_buffer(app.pstdout); free_buffer(app.pstderr); return 0; } logapp-0.15/configuration.h0000644000175000017500000001677211445366426015557 0ustar michaelmichael/* * configuration.h: logapp configuration header * * * Copyright (C) 2007-2010 Michael Brunner * * 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 * of the License, 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 * General Public License for more details. */ #ifndef __CONFIGURATION_H__ #define __CONFIGURATION_H__ #include "logapp.h" #include #include #include #include typedef struct { char* argprefix; /* always handle arguments starting with this prefix */ char* strip_prefix; /* strip prefix from called executable filename */ char* executable; /* logapp executable filename */ int usepty; /* use ptys to redirect streams */ int dumbterm; /* term is not able to handle escape codes */ int detectescape; /* this option enables escape sequence detection for stdout and stderr */ int ptyremovecr; /* remove CR read from PTY */ int appendlog; /* append to log file */ int printsummary; /* print execution summary */ int printlogname; /* show used logfile after execution */ int disable; /* disable output handling */ char* disable_keywords; /* keywords the disable output handling when found in the application parameter list */ char* logname; /* application output log */ int logtime; /* add timestamp to each logged line */ int logreltime; /* log relative times */ int logenv; /* add environment variables to logfile */ int maxlogsize; /* maximum logfile size in KiB */ int locklogfile; /* lock logfile when opening it */ int warnlogfilelock; /* show message if logfile is locked */ int maxlogfiles; /* maximum number of logfiles open at a time */ int circularlog; /* reuse same logfile in a circular way */ int logrename; /* rename logfile before truncating it */ int alignlog; /* start a new line for every logged item even in charbased mode */ int jointimeout; /* join timeout for char based packets */ int alignlinebreaks; /* Align lines to the left in the logfile if timestamps are added */ char* configfile; /* current config file */ char* configsection; /* manually selected section in config file */ char* custconfigfile; /* custom config file */ char* preexeccmd; /* command to be executed before application is started */ char* postexeccmd; /* command to be executed after application is started */ int exitonexecfail; /* exit if execution of command fails */ int extregexp; /* use extended regexp syntax */ } config_t; extern config_t config; typedef struct { char* start; int len; char* end; char* read; } readbuf_t; typedef struct { int fh; /* pipe file handle */ int cfhno; /* numerical console file handle */ char* name; /* visible pipe name */ pthread_t ct; /* capture thread */ char* dbuf; /* physical data buffer pointed to by other buffers */ char* buf; /* char buffer */ readbuf_t rdbuf; /* used in linebased mode for faster read */ int state; /* read return state */ int charbased; /* work character based instead of linebased */ int (*handler)(void*); /* Pointer to stream handler */ int detectescape; /* switch stream to charbased mode if an escape sequence is detected */ int switchtocbmode; /* escape has bin detected, now switch to charbased mode as soon as possible */ int memcr; /* temporary stored CR */ int ptyremovecr; /* remove carriage return from CR-LF */ int blen; /* buffer length (1 will be added for overhead) */ int bfill; /* number of characters currently buffered */ int recalclen; /* recalculate line length */ int fgcol; /* foreground console text color */ int bgcol; /* background console text color */ int bold; /* bold console font */ int clip; /* clip console output after x chars */ int eclip; /* enable clipping: 0 disable, 1 enable, 2 auto */ char* escreset; /* Escape sequence to reset the console */ int escresetlen; char* esccolor; /* Escape sequence to set color attribute */ int esccolorlen; int linecount; /* count line passed to stream */ char* lineprefix; /* prefix for each line in logfile */ int lineprefixlen; #define LINEBREAKBUFSIZE 8 int linebreakpos[LINEBREAKBUFSIZE + 1]; /* buffer to store already detected linebreaks */ char* regexp; /* regular expression */ regex_t preg; /* preprocessed regular expression */ int regbgcol; /* background console text color for regexp */ char* bgesccolor; /* Pointer to escape bg color attribute */ char* execregexp; /* regular expression for command execution */ regex_t pexecreg; /* preprocessed regular expression for command execution */ char* execcommand; /* command to be executed after regexp match */ } pipe_t; extern pipe_t pstdout; extern pipe_t pstderr; typedef struct { int fh; /* file handle */ char* name; /* file name */ unsigned sizelimit; /* max size */ char* oldext; /* extension to add when renaming logfile */ int appendnr; /* number added to filename (is increased if file is locked by another process; 0 means no number is added) */ char* head; /* prefix for logfile header lines */ char* split; /* prefix for logfile split marker lines */ char* foot; /* prefix for logfile footer lines */ int addnewline; /* add newline before next log write */ char* indent; /* prefix for indenting logfile lines */ } logfile_t; extern logfile_t logfile; typedef struct { char* exe; /* application to be run */ char** argv; /* application argv array */ int argc; /* application arg count */ pid_t pid; /* child application pid */ int active; /* application active */ int doexit; /* application has received signal to exit */ int exit_state; /* exit state after application terminated */ time_t starttime; /* time the application has been started */ struct timeval toffset; /* time offset for timestamp calculation */ pipe_t* pstdout; /* application stdout pipe */ pipe_t* pstderr; /* application stderr pipe */ struct winsize* ptysize; /* PTY window size */ struct termios* ptytermios; /* PTY terminal io attributes */ struct termios* ptytermios_bak; /* orig. PTY terminal io attributes */ logfile_t* logfile; /* logfile handle */ } app_t; typedef struct { char* string; int value; } stringvalue_t; extern app_t app; extern int show_usage; extern int show_version; extern int show_config; /* possible argument values */ typedef enum { TBOOL, TCLIP, TCOLOR, TINT, TNONE, TSTRING, TUINT, } t_vartype; typedef struct { char shrt; /* one char argument */ char* lng; /* long argument name */ char* parm; /* value description */ t_vartype type; /* value type */ void* var; /* pointer to config variable */ char* desc; /* argument description */ int set; /* provided as commandline argument */ } arglist_t; extern arglist_t arglist[]; extern const int arglistsize; extern int parse_args(int argc, char* argv[]); extern void show_configuration(void); extern int get_config(void); extern int fixup_config(void); extern void cleanup_config(void); extern int get_display_parameters(void); extern int adjust_clipping(void); extern int check_for_disable_keywords(void); extern char* get_longpath(const char* filename); #endif /* __CONFIGURATION_H__ */ logapp-0.15/main.c0000644000175000017500000002675411506057273013623 0ustar michaelmichael/* * main.c: logapp main file * * * Copyright (C) 2007-2010 Michael Brunner * * 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 * of the License, 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 * General Public License for more details. */ #include "logapp.h" #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__FreeBSD__) #include #include #include #elif defined(__OpenBSD__) || defined(__NetBSD__) #include #include #elif defined(__APPLE__) #include #else #include #endif #include "configuration.h" #include "logfile.h" #include "capture.h" const char string_outofmemory[] = EXECUTABLE " error: out of memory\n"; void error_outofmemory(void) { int ret; ret = write(STDERR_FILENO, string_outofmemory, sizeof(string_outofmemory)); } #define max(a, b) ((a) > (b) ? (a) : (b)) void print(t_printtype type, const char *format,...) { va_list argptr; int format_len; int fmt_len; int exec_len; int warn_len; int err_len; char *fmtstring; format_len = strlen(format); exec_len = strlen(EXECUTABLE); warn_len = strlen(STRING_WARNING); err_len = strlen(STRING_ERROR); fmt_len = format_len + exec_len + max(warn_len, err_len) + 1; fmtstring = malloc(fmt_len); if (!fmtstring) { error_outofmemory(); exit(EXIT_FAILURE); } va_start(argptr, format); if (type!=MESSAGE) { memcpy(fmtstring, EXECUTABLE, exec_len); fmt_len = exec_len; } else { fmt_len = 0; *fmtstring = '\0'; } switch (type) { case WARNING: memcpy(fmtstring + exec_len, STRING_WARNING, warn_len); fmt_len+=warn_len; break; case ERROR: memcpy(fmtstring + exec_len, STRING_ERROR, err_len); fmt_len+=err_len; break; default: break; } memcpy(fmtstring + fmt_len, format, format_len); *(fmtstring + fmt_len + format_len) = '\0'; if (vfprintf(stderr, fmtstring, argptr) < 0) { exit(EXIT_FAILURE); } va_end(argptr); free(fmtstring); } int execcmd(char* cmd) { return system(cmd); } #define LINELENGTH 80 #define DESCOFFSET 33 void usage(void) { int i; unsigned int j; int offset; char linebuf[LINELENGTH + 1]; message("%s %s\n\n", EXECUTABLE, VERSION); message("Usage: %s [OPTION]... APPLICATION...\n", EXECUTABLE); /* omit rest of the usage instructions if it has not been requested * with the --help parameter */ if (!show_usage) { message("\nTry \"%s --help\" for more options.\n", EXECUTABLE); return; } for (i=0; i desclen)) { if ((offset + (j - descoffset)) >= LINELENGTH) { linebuf[offset] = '\n'; linebuf[offset+1] = '\0'; /* Indent further description lines */ offset = DESCOFFSET + 1; message("%s", linebuf); memset(linebuf, ' ', offset); } memcpy(&linebuf[offset], &arglist[i].desc[descoffset], j - descoffset); offset += j - descoffset; descoffset = j; linebuf[offset] = '\0'; } } message("%s\n", linebuf); } message("\nPlease send bug reports and feature requests to %s\n", AUTHOR); } void version(void) { message("%s %s\n", EXECUTABLE, VERSION); message("Build date: %s %s\n", __DATE__, __TIME__); if (strlen(SVN_REVISION)) message("SVN revision: %s\n\n", SVN_REVISION); message("\nCompiletime configuration:\n"); message(" PTY support %s\n", CONFIG_SUPPORT_PTY?"enabled":"disabled"); message(" Thread usage %s\n\n", CONFIG_USE_THREADS?"enabled":"disabled"); } static void sig_handler(int signr) { switch (signr) { case SIGWINCH: adjust_clipping(); break; case SIGCONT: app.active = 1; break; case SIGCHLD: case SIGTERM: case SIGQUIT: case SIGINT: default: app.active = 0; app.doexit = 1; break; } } int fork_child() { int pipe_stdout[2]; int pipe_stderr[2]; if (!config.disable) { get_display_parameters(); if (app.ptytermios != NULL) { memcpy(app.ptytermios_bak, app.ptytermios, sizeof(struct termios)); } if (CONFIG_SUPPORT_PTY && config.usepty) { if (openpty(&pipe_stdout[0], &pipe_stdout[1], NULL, app.ptytermios, app.ptysize) == -1) { error("opening PTY for %s failed with error " "%d\n", app.pstdout->name, errno); /* no need to remove CRs if PTY cannot be * opened */ app.pstdout->ptyremovecr = 0; } } else { if (pipe(pipe_stdout) < 0) { error("creating stdout pipe handler\n"); return -1; } } if (pipe(pipe_stderr) < 0) { error("creating stderr pipe handler\n"); return -1; } } signal(SIGCONT, sig_handler); /* flush all streams before forking */ fflush(NULL); if ((app.pid = fork()) < 0) { error("fork error\n"); return -1; }; if (app.pid> 0) { if (!config.disable) { close(pipe_stdout[1]); close(pipe_stderr[1]); app.pstdout->fh = pipe_stdout[0]; app.pstderr->fh = pipe_stderr[0]; } /* synchronize with child */ while (!app.active && !app.doexit) /* wait for child */ usleep(10000); kill(app.pid, SIGCONT); /* tell the child we are ready */ /* Store current time */ time(&app.starttime); gettimeofday(&app.toffset, NULL); } else if (app.pid==0) { if (!config.disable) { close(pipe_stdout[0]); close(pipe_stderr[0]); if (pipe_stdout[1] != STDOUT_FILENO) { if (dup2(pipe_stdout[1], STDOUT_FILENO) != STDOUT_FILENO) error("redirecting stdout failed"); close(pipe_stdout[1]); } if (pipe_stderr[1] != STDERR_FILENO) { if (dup2(pipe_stderr[1], STDERR_FILENO) != STDERR_FILENO) error("redirecting stderr failed"); close(pipe_stderr[1]); } if (app.ptytermios != NULL) { tcsetattr(STDIN_FILENO, TCSADRAIN, app.ptytermios); } } /* synchronize with parent */ kill(getppid(), SIGCONT); /* tell the parent we are ready */ while (!app.active && !app.doexit) /* wait for parent */ usleep(10000); if (execvp(app.exe, app.argv) < 0) { error("executing application %s failed\n", app.exe); exit(EXIT_FAILURE); } } else { error("forking process failed\n"); return -1; }; return 0; } int main(int argc, char *argv[]) { char* argv0; int ret; char* env; /* check if we are executed from a dumb terminal */ env = getenv("TERM"); if (env) { if (!strcmp(env, "dumb")) { config.dumbterm = 1; } } else { /* Terminal environment variable not set, so assuming a dumb * terminal */ config.dumbterm = 1; } /* check if the called filename defines an application to run */ argv0 = strrchr(argv[0], '/'); if (argv0) argv0++; else argv0 = argv[0]; if (strcmp(argv0, EXECUTABLE)) { app.exe = strstr(argv0, config.strip_prefix); if (app.exe == argv0) { if (strlen(app.exe)>strlen(config.strip_prefix)) app.exe += strlen(config.strip_prefix); } else { app.exe = argv0; } } if ((ret = parse_args(argc, argv))) { error("error parsing " EXECUTABLE " argument %d\n", ret); exit(EXIT_FAILURE); } if (show_usage) { usage(); exit(EXIT_SUCCESS); } if (show_version) { version(); exit(EXIT_SUCCESS); } /* also show usage if no application has been provided */ if (!app.exe && !show_config) { message("%s: No application parameter has been provided.\n", EXECUTABLE); usage(); exit(EXIT_SUCCESS); } /* get default configuration from file */ if (get_config()) { error("problem while loading default config from file\n"); exit(EXIT_FAILURE); } if (fixup_config()) { error("problem processing configuration\n"); exit(EXIT_FAILURE); } if (check_for_disable_keywords()) { error("failed to check for disable keywords\n"); exit(EXIT_FAILURE); } if (show_config) { show_configuration(); exit(EXIT_SUCCESS); } /* Get window size changes */ if ((app.pstderr->eclip == 2) || (app.pstdout->eclip == 2)) { if (signal(SIGWINCH, sig_handler) == SIG_ERR) { error("creating signal handler\n"); return -1; } adjust_clipping(); } if (signal(SIGINT, sig_handler) == SIG_ERR) { error("creating signal handler\n"); exit(EXIT_FAILURE); } if (signal(SIGQUIT, sig_handler) == SIG_ERR) { error("creating signal handler\n"); exit(EXIT_FAILURE); } if (signal(SIGTERM, sig_handler) == SIG_ERR) { error("creating signal handler\n"); exit(EXIT_FAILURE); } if (signal(SIGCHLD, sig_handler) == SIG_ERR) { error("creating signal handler\n"); exit(EXIT_FAILURE); } /* Start the application */ if (fork_child()) { exit(EXIT_FAILURE); } /* Open logfile */ if (logfile_open(config.logname, app.logfile)) { error("unable to open logfile %s\n", config.logname); exit(EXIT_FAILURE); } /* execute preexec command */ if (config.preexeccmd) { int ret = execcmd(config.preexeccmd); if (ret) { print(config.exitonexecfail?ERROR:WARNING, "preexec command \"%s\" returned 0x%x\n", config.preexeccmd, ret); if (config.exitonexecfail) { exit(EXIT_FAILURE); } } } /* Start the capture threads for stdin and stderr */ if (!config.disable) { if (capture_start()) { error("unable to capture streams\n"); exit(EXIT_FAILURE); } } /* Wait for application to exit get its exit status */ waitpid(app.pid, &app.exit_state, 0); /* Wait for capture threads to exit */ if (!config.disable) capture_end(); if (logfile_close(app.logfile)) { error("problem closing logfile"); } /* execute postexec command */ if (config.postexeccmd) { int ret = execcmd(config.postexeccmd); if (ret) { print(config.exitonexecfail?ERROR:WARNING, "postexec command \"%s\" returned 0x%x\n", config.postexeccmd, ret); } } /* Be sure the console is not messed up when we exit */ if (!config.disable) reset_console(); /* flush all streams before we write the summary */ fflush(NULL); /* Print short summary */ if (config.printsummary) print_summary(); /* free data allocated for configuration */ cleanup_config(); /* We are returning the exit status of the application if possible */ if (WIFEXITED(app.exit_state)) exit(WEXITSTATUS(app.exit_state)); else exit(EXIT_FAILURE); } logapp-0.15/logapp.h0000644000175000017500000000300211362565645014152 0ustar michaelmichael/* * logapp.h: Logapp main header file * * * Copyright (C) 2007-2010 Michael Brunner * * 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 * of the License, 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 * General Public License for more details. */ #ifndef __LOGAPP_H__ #define __LOGAPP_H__ #define AUTHOR "Michael Brunner " typedef enum { MESSAGE, WARNING, ERROR } t_printtype; #define message(x...) print(MESSAGE, x) #define warning(x...) print(WARNING, x) #define error(x...) print(ERROR, x) #define STRING_WARNING " warning: " #define STRING_ERROR " error: " void print(t_printtype type, const char *format,...); void error_outofmemory(void); int execcmd(char* cmd); /* These values should normally be defined by the Makefile */ #ifndef EXECUTABLE # define EXECUTABLE "logapp" #endif #ifndef VERSION # define VERSION "" #endif #ifndef SVN_REVISION # define SVN_REVISION "" #endif #ifndef CONFIG_SUPPORT_PTY # define CONFIG_SUPPORT_PTY 1 #endif #ifndef CONFIG_USE_THREADS # define CONFIG_USE_THREADS 1 #endif #if (CONFIG_USE_THREADS == 1) /* Let the system take care of making errno thread safe. */ #define _REENTRANT #endif #endif /* __LOGAPP_H__ */ logapp-0.15/Makefile0000644000175000017500000000647311506065567014174 0ustar michaelmichael# Makefile for Logapp # # Copyright (C) 2007-2010 Michael Brunner TARGET = logapp MANPAGE = logapp.1 VERSION = 0.15 ### Configuration options: ### # Enable support for pseudo terminal interface on stdout # -> enables --usepty runtime option SUPPORT_PTY = 1 # Use different threads for stdout and stderr handling USE_THREADS = 1 # Build static binary BUILD_STATIC = 0 # Add architecture prefix here or use CROSS_COMPILE environment variable to # specify a cross compiler CROSS_COMPILE ?= # Set this to 1 to create a debug build of logapp (can also be set as # environment variable) DEBUG_BUILD ?= 0 # This defines which symlinks are created during the installation SYMLINKS = logmake logsvn logcvs # Installation directories are defined here PREFIX ?= /usr/local DESTDIR = $(PREFIX)/bin/ MANDIR = $(PREFIX)/share/man/man1/ # Tool definitions INSTALL = install -c ### END OF CONFIGURATION SECTION ### # If diet is defined with the CROSS_COMPILE variable be sure to add a space # so the code is linked against the dietlibc ifeq ($(CROSS_COMPILE), diet) CROSS_COMPILE = diet LINK_DIETLIBC = 1 endif CC = $(CROSS_COMPILE)gcc DEFS = -DSVN_REVISION='"$(shell svnversion -cn . 2>/dev/null \ | sed -e "s/.*://" -e "s/\([0-9]*\).*/\1/")"' \ -DVERSION='"$(VERSION)"' \ -DEXECUTABLE='"$(TARGET)"' \ -DCONFIG_SUPPORT_PTY="$(SUPPORT_PTY)" \ -DCONFIG_USE_THREADS="$(USE_THREADS)" CFLAGS = -Wall -Wextra $(DEFS) # Do not optimize code when creating a debug build ifeq ($(DEBUG_BUILD),1) CFLAGS += -g else CFLAGS += -O2 endif LINK = $(CROSS_COMPILE)gcc LINKFLAGS = ifeq ($(BUILD_STATIC),1) LINKFLAGS += --static endif # Strip unless we are creating a debug build ifneq ($(DEBUG_BUILD),1) LINKFLAGS += -s endif OBJECTS = main.o configuration.o logfile.o capture.o DEPENDENCIES = .dependencies EXTRADEPS = Makefile LIBS = ifeq ($(SUPPORT_PTY),1) ifneq ($(LINK_DIETLIBC),1) LIBS += -lutil endif endif ifeq ($(USE_THREADS),1) LIBS += -lpthread endif all: $(DEPENDENCIES) $(TARGET) $(TARGET): $(OBJECTS) $(LINK) $(LINKFLAGS) -o $(TARGET) $(OBJECTS) $(LIBS) install: all install_links $(INSTALL) -d $(DESTDIR) $(INSTALL) $(TARGET) $(DESTDIR) $(INSTALL) -d $(MANDIR) $(INSTALL) -m 644 $(MANPAGE) $(MANDIR) install_links: for L in $(SYMLINKS); do ln -s -f $(TARGET) $(DESTDIR)$$L ; done uninstall: deinstall deinstall: remove_links rm -f $(DESTDIR)$(TARGET) rm -f $(MANDIR)$(MANPAGE) remove_links: for L in $(SYMLINKS); do rm -f $(DESTDIR)$$L ; done clean: rm -f *.o $(TARGET) $(DEPENDENCIES) @make -C doc clean distclean: clean rm -f *.log tags man: @$(MAKE) -C doc update-main help: @echo 'Generic targets:' @echo ' install - Install logapp to $(DESTDIR)' @echo ' uninstall - Uninstall logapp from $(DESTDIR)' @echo ' clean - Remove generated files from build directory' @echo '' @echo 'Build targets:' @echo ' all - Build logapp' @echo '' @echo 'Environment variables:' @echo ' PREFIX - Install prefix (default: /usr/local)' @echo ' DEBUG_BUILD - Create debug build (default: 0)' @echo ' CROSS_COMPILE - Use cross compile prefix (default: )' $(DEPENDENCIES): @$(CC) -MM *.c | sed -e 's/\([^\]\)$$/\1 $(EXTRADEPS)/' \ > $(DEPENDENCIES) .PHONY: all clean distclean deinstall uninstall install install_links \ remove_links man -include $(DEPENDENCIES) logapp-0.15/TODO0000644000175000017500000000053711425745553013217 0ustar michaelmichaelTODO ---- - Improve line ordering if stdout and stderr switch very fast - Add terminal detection (currently only dumb terminal is handled seperately) - Completely rewrite the manual to make it more readable... - Merge char- and linebased code - Allow to handle stdout and stderr in one stream (fixes line ordering) - Add live throughput/line counter logapp-0.15/doc/0000755000175000017500000000000011506066152013256 5ustar michaelmichaellogapp-0.15/doc/DOC_README0000644000175000017500000000037110623620764014630 0ustar michaelmichaelThis directory contains the DocBook source for the documentation. The manpage is already prebuilt in the root source directory, so normally you shouldn't need to care about files in this directory unless you want to change something in the manual. logapp-0.15/doc/manpage.xml0000644000175000017500000010747311506065567015435 0ustar michaelmichael December 2010 logapp 1 logapp 0.15 logapp An application output supervisor. logapp option application app.-argument applicationsymlink application-argument Instead of calling logapp directly you can also create a symlink with the name of the application pointing to logapp. Logapp will automatically start the application the name points to. It will also work if the symlink name is prefixed with log. DESCRIPTION Logapp is a wrapper utility that helps supervise the execution of applications that produce heavy console output (e.g. make, CVS and Subversion). It does this by logging, trimming, and coloring each line of the output before displaying it. It can be called instead of the executable that should be monitored; it then starts the application and logs all of its console output to a file. The output shown in the terminal is preprocessed, e.g. to limit the length of printed lines and to show the stderr output in a different color. It is also possible to automatically highlight lines that match a certain regular expression. The output is therefore reduced to the necessary amount, and all important lines are easy to identify. OPTIONS The options provided before the application argument are processed directly by logapp. Options provided after the application argument are only parsed if they are prefixed with (long option names only) otherwise they are passed to the application. If logapp is called via a symlink all unprefixed options are passed to the application. Every application usually uses two independent output streams: stdout for normal output and stderr for errors and important messages. Both of them are handled independently by logapp, therefore many options are available for both streams. Bool options are accepting 1/0 and true/false as value. For long boolean options the value can be omitted, in that case it will be assumed to be 'true'. GENERAL OPTIONS Show a short overview over all available options. Show version information. FILE Use a specific configuration file instead of searching the configuration search paths. Print the current configuration of logapp and exit before the application is executed. This can be used this to check if all configuration options are setup correctly if something doesn't work as expected. NAME Enable a specific section in the configuration file. If this option is not provided the application name is used as default. This disables logapp data handling completely. The application is still started, but logapp won't touch the data streams coming from the application. Neither logging nor output formating is performed, only the execution time and the exit state tracked. This is useful if logapp won't be able to deal with expected data correctly, for example when starting curses based applications. Have a look at to see how this option can be enabled automatically. keywordlist With this option a list of comma separated keywords can be provided which will cause the to be enabled automatically if found in the applications option list. This is useful if an application usually provides line-based output, but creates binary data or uses a curses based frontend if called with a specific parameter. You can also use the option for another way to do this without disabling the logging functionality. bool This option can be used to switch escape-sequence detection on or off. With escape-sequence detection logapp will automatically enable char-based stream handling as soon as an escape-sequence is part of the specific stream. This behavior can be useful if you are working with an application that is usually line-based, but starts other applications which may be using escape sequences to format the screen. This option will prevent the terminal from being messed up in that case. bool With this option set to true there will be no terminal output coloring for stdout and stderr. Normally this option is disabled and logapp tries to detect "dumb" terminals itself. bool This option is only available if logapp has been compiled with PTY support. If PTY support is enabled with this option set to true, logapp will open a pseudo terminal for stdout. This helps wenn running logapp with applications that usually need a real terminal for output. You can disable this option for most line based applications like make, CVS or Subversion. Other applications like telnet or picocom may produce strange results when used without PTY support. bool This option is only available if logapp has been compiled with PTY support. When using a pseudo terminal for getting the application output you will always get CR-LF line endings, which is usually not desired when working in UNIX environments. With this option enabled, logapp will automatically translate all CR-LF line endings in LF line endings. This option is enabled as default. bytes bytes The line buffer size can be adjusted for stdout and stderr independently with this option. If the value is too small, lines will be split up if the buffer is full. The default is 2048 byte which should be big enough for most applications. bool bool If you want to use logapp with applications that do not produce line based output you can enable this options for stdout and stderr independently. With this option enabled logapp won't expect complete lines and will handle data as it comes in. By default all single data packets are written to a new line if this option is enabled, this can be changed with the option. If the result will be usable depends on what kind of data is generated by the application. bool If this option is enabled logapp will interpret provided regular expression patterns as extended regular expressions. The default is to use basic regular expressions. LOGGING OPTIONS This section contains options that affect the logfile. file This option can be used to change the file that is used for storing the logged application data. If an empty string is provided, logging is disabled and no logfile will be created. The default is that logapp creates a logfile called logapp.log in the current directory. bool This option specifies if the logfile will be truncated or if the data will be appended to an existing file on logapp startup. kibyte To limit the maximum size of the logfile you can set this option to a value between 10 and 4000000 kiBytes. The default is 0 which disables the logfile size limit. There are different ways implemented how the logfile is limited. Have a look at the options and to learn more. The default way is that the extension .old is added to the logfile and a new logfile is started. bool This option specifies the behavior when a logfile is to be truncated. If is enabled the logfile is renamed. The new filename will be the same as before with the extension defined with added. The default extension is .old. This option is used together with the value of and bool If this option is enabled together with a logfile size limit set with , the logfile will be used in a circular way. This means if the maximum size is reached, the file pointer is set to the beginning of the file and the old content is overwritten from the beginning. There are tags added to the logfile to help navigating in the file. extension This defines the extion that is used when logapp is renaming a logfile. The option defines if logapp will rename the file and the default extension is .old. bool With this option active the logfile is locked in order to prevent it to be overwritten by another task. This is useful if otherwise an unreadable mix up of different contents would be the result. Depending on the value of the option another logfile is chosen with the same name and a number added. Logfile locking is activated by default. bool This options defines if there should be a warning printed to the console if the chosen logfile is already locked or in other means not accessible. In this case there will be a message before the application is started and directly after its execution where the name of the alternative logfile is mentioned. This option is enabled by default. Also have a look at the where you can define to always get the current logfile reported. bool This option defines if the name of the used logfile should be printed after the application has finished its execution. This option is disabled by default. Also have a look at the where you can enable/disable a warning if the logfile name is changed because of a locked logfile. number This options defines the maximum number that can be added to the logfile name, if the original file is not accessible. On logapp startup it will be checked if the currently defined logfile is writeable, if this is not the case automatically a number is added to the filename. If the alternative file is also not accessible this number is increased until a file is writable or the value of maxaltlogfiles is reached. In the latter case the application will exit with an error. If a value of 0 is used only the original logfile name is tried. Also have a look at the and options to define if there should be messages about the currently used logfile. bool This option is used together with and and defines if data packets are written to the logfile as they come or if they are each written to a new line. The default is that each data packet is written to a new line, set this option to false to disable it. bool This option is used together with and and aligns the lines to the left in the logfile with regard to prefix and timestamp. This option is enabled by default. time This option is used together with and and defines a ms timeout for joining single packets to one. This means if for example two chars get written within the timeout, they are treated as one packet. This is best used together with and . Use this option if the data packets have lost their coherency for some reason (e.g. if the data comes through a serial line). This feature is disabled by default and can be enabled by setting time to a value bigger than 0 ms. bool This option can be enabled to add a ms timestamp to each line of the logfile. Normally the time since the application start is used, but this can be changed with the option. bool If this option is set this to true, the option will use the relative time since the last line for the logged timestamps. bool With this option set to true logapp will add a list of all active environment variables to the logfile. This option is disabled by default. prefix prefix To be able to distinguish stdout and stderr output in the logfile logapp can prefix each line with a string that indicates if the line belongs to a specific data stream. Those strings can be changed with this option. The default is that stdout does not have a prefix and stderr is prefixed with STDERR:. CONSOLE OUTPUT OPTIONS This section contains options that affect the visual output on the console. bool This option disables output coloring. This is usually done automatically if a dumb terminal is detected. bool If this option is set to true, then a short summary will be printed after the application has terminated. This option is disabled by default. color color This options define the foreground color for the specific data stream. The value can be one of the entries in the console color table at the end of this section. bool bool This options define if the font for the specific data stream should be printed bold. regular expression regular expression The regular expression that can be defined with this option is applied to every line of the specific data stream. On a match the background color changes to the value provided with the respectively option. color color This options define the background color for the specific data stream for the case that the appropriate regular expression provided with or matches. The value can be one of the entries in the console color table at the end of this section. width width This options define at which column the output should be clipped for the specific stream to reduce the amount of data written to the console. If a value of −1 is provided clipping is disabled for the stream. A value of −2 sets the clipping to the current console width. It is also possible to use disable and auto instead of the numeric values. The default is that stdout is limited to the console width and that clipping is deactivated for stderr. Console color table # color −1 (console) default 0 black 1 red 2 green 3 brown 4 blue 5 magenta 6 cyan 7 white
COMMAND EXECUTION OPTIONS This section contains options that configure the execution of commands on regular expression matches. BOOL This option defines if logapp should exit and end the wrapped application if the return value of an executed command indicates a failure. As default this option is disabled and logapp ignores the return state of executed commands. command The command that can be provided with this option is executed directly before the application is started. At this time the header is already written to the logfile and can be parsed by the command. command The command that can be provided with this option is executed directly after the application has exited. At this time the logfile is already closed for writing so all application output and the footer are already included and can be processed by the command. regular expression regular expression The regular expression that can be defined with this option is applied to every line of the specific data stream. On a match the command provided with the respectively option is executed. An empty value for this option disables the regular expression matching. command command This option defines the command that is executed on a regular expression match. The regular expression can be defined separately for the stdout and stderr stream with the respectively option.
REGULAR EXPRESSIONS Regular expressions are patterns that describe strings. Logapp uses this patterns to execute actions based on strings found in the data stream. The implementation is identical to the one that is used by grep. Logapp understands the "basic" and "extended" syntax of regular expressions as defined by POSIX. The default is to use the basic set, but you can switch to extended patterns with the parameter. Please have a look at the grep1 and regex7 manpage for detailed information. EXAMPLES Matches "String" Matches "String" at the beginning of a line Matches "String" at the end of a line Line contains only "String" Matches "String" or "string" The dot matches all characters, so this matches for example "String" or "Strong" The dot together with star matches any number of characters, so this matches for example "String" or "Streaming" Matches any one of the characters from A to Z at the beginning of a line followed by zero or any number of spaces Matches "String" or "Word" when working with basic regular expressions Matches "String" or "Word" when working with extended regular expressions ENVIRONMENT TERM This variable is checked to see which type of console logapp is running in. Currently only the value dumb is handled in a special way — by disabling console colors. If the TERM variable is missing also a dumb terminal is assumed. The setting can be overridden by enabling/disabling the dumb terminal mode using the option. FILES ~/.logapprc /etc/logapp.conf /etc/logapp/logapp.conf Configuration file locations that are tried if no option is provided. BUGS See the TODO file included in the source package. SEE ALSO grep1, regex7 AUTHOR Michael Brunner Original author COPYING Copyright (C) 2007–2010 Michael Brunner 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 of the License, 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 General Public License for more details.
logapp-0.15/doc/Makefile0000644000175000017500000000167511205275321014723 0ustar michaelmichael# Makefile for Logapp documentation # # Copyright (C) 2007-2009 Michael Brunner HTMLXSL = /usr/share/sgml/docbook/stylesheet/xsl/nwalsh/html MANPAGE = logapp.1 HTMLPAGE = manpage.html TEXTPAGE = manpage.txt SOURCEFILE = manpage.xml MAINMANPAGE = ../$(MANPAGE) EXTRADEPS = Makefile all: html text manpage html: $(HTMLPAGE) $(HTMLPAGE): $(SOURCEFILE) $(EXTRADEPS) xsltproc $(HTMLXSL)/docbook.xsl $(SOURCEFILE) \ | sed -e "s/−/-/g" > $(HTMLPAGE) manpage: $(MANPAGE) $(MANPAGE): $(SOURCEFILE) $(EXTRADEPS) docbook2x-man --to-stdout --encoding "us-ascii" manpage.xml |\ sed "1,1s/'/./" > $(MANPAGE) text: $(TEXTPAGE) $(TEXTPAGE): $(HTMLPAGE) $(EXTRADEPS) lynx -dump $(HTMLPAGE) > $(TEXTPAGE) update-main: manpage $(MAINMANPAGE) $(MAINMANPAGE): $(MANPAGE) install -m 644 $(MANPAGE) $(MAINMANPAGE) clean: rm -f $(MANPAGE) *.log tags *.html *.gz *.txt distclean: clean .PHONY: all clean distclean html manpage text update-main logapp-0.15/configuration.c0000644000175000017500000007544411505137646015550 0ustar michaelmichael/* * configuration.c: logapp configuration handling * * * Copyright (C) 2007-2010 Michael Brunner * * 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 * of the License, 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 * General Public License for more details. */ #include "logapp.h" #include #include #include #include #include #include #include #include #ifndef TIOCGWINSIZE #include #endif #include "configuration.h" #include "logfile.h" int show_usage = 0; int show_version = 0; int show_config = 0; /* general configuration */ config_t config = { .argprefix = "logapp_", .strip_prefix = "log", .executable = EXECUTABLE, .dumbterm = 0, .detectescape = 0, .usepty = 1, .ptyremovecr = 1, .appendlog = 0, .printsummary = 0, .printlogname = 0, .disable = 0, .disable_keywords = NULL, .logname = "./logapp.log", .logtime = 0, .logreltime = 0, .logenv = 0, .circularlog = 0, .maxlogsize = 0, .locklogfile = 1, .warnlogfilelock = 1, .maxlogfiles = 10, .logrename = 1, .alignlog = 1, .alignlinebreaks = 1, .jointimeout = 0, .configfile = NULL, .configsection = NULL, .custconfigfile = NULL, .preexeccmd = NULL, .postexeccmd = NULL, .exitonexecfail = 0, .extregexp = 0, }; /* default search path list for configuration file */ char* configsearchpath[] = { "~/.logapprc", "/etc/logapp.conf", "/etc/logapp/logapp.conf", }; #define CONFIGSEARCHPATH_SIZE (sizeof(configsearchpath)/sizeof(char*)) /* configuration for stdout */ pipe_t pstdout = { .fh = 0, .cfhno = STDOUT_FILENO, .name = "stdout", .buf = NULL, .charbased = 0, .detectescape = 0, .switchtocbmode = 0, .blen = 65540, .bfill = 0, .memcr = 0, .ptyremovecr = 0, .recalclen = 0, .fgcol = -1, .bgcol = -1, .bold = 0, .clip = -2, .eclip = 2, .escreset = "\033[0m", .esccolor = NULL, .linecount = 0, .lineprefix = NULL, .linebreakpos = {0}, .regexp = NULL, .regbgcol = 4, .bgesccolor = NULL, .execregexp = NULL, .execcommand = NULL, }; /* configuration for stderr */ pipe_t pstderr = { .fh = 0, .cfhno = STDERR_FILENO, .name = "stderr", .buf = NULL, .charbased = 0, .detectescape = 0, .switchtocbmode = 0, .blen = 65540, .bfill = 0, .memcr = 0, .ptyremovecr = 0, .recalclen = 0, .fgcol = 1, .bgcol = -1, .bold = 1, .clip = -1, .eclip = 0, .escreset = "\033[0m", .esccolor = NULL, .linecount = 0, .lineprefix = "STDERR: ", .linebreakpos = {0}, .regexp = NULL, .regbgcol = 4, .bgesccolor = NULL, .execregexp = NULL, .execcommand = NULL, }; /* logfile configuration */ logfile_t logfile = { .fh = 0, .name = NULL, .sizelimit = 0, .oldext = ".old", .appendnr = 0, .head = ">> logapp:", .split = "== logapp split:", .foot = "<< logapp end:", .addnewline = 0, }; struct winsize ptysize; struct termios ptytermios, ptytermios_bak; /* application specific properties */ app_t app = { .exe = NULL, .argv = NULL, .argc = 0, .pid = 0, .pstdout = &pstdout, .pstderr = &pstderr, .ptysize = &ptysize, .ptytermios = &ptytermios, .ptytermios_bak = &ptytermios_bak, .active = 0, .doexit = 0, .exit_state = 0, .starttime = 0, .logfile = &logfile, }; const stringvalue_t boolvalues[] = { { "true", 1 }, { "false", 0 }, { "on", 1 }, { "off", 0 }, { NULL, 0 } }; const stringvalue_t colorvalues[] = { { "disable", -1 }, { "default", -1 }, { "black", 0 }, { "red", 1 }, { "green", 2 }, { "brown", 3 }, { "blue", 4 }, { "magenta", 5 }, { "cyan", 6 }, { "white", 7 }, { NULL, 0 } }; const stringvalue_t clipvalues[] = { { "disable", -1 }, { "auto", -2 }, { NULL, 0 } }; const stringvalue_t multprefixes[] = { { "", 1 }, { "k", 1000 }, { "M", 1000000 }, { "G", 1000000000 }, { "ki", 1024 }, { "Mi", 1048576 }, { "Gi", 1073741824 }, { NULL, 0 } }; /* all possible configuration parameters with help message */ arglist_t arglist[] = { /* General options */ { '\0', NULL, NULL, TNONE, NULL, "General options:", 0 }, { '?', "help", NULL, TNONE, &show_usage, "show this help", 0 }, { '\0', "version", NULL, TNONE, &show_version, "show version information", 0 }, { '\0', "configfile", "FILE", TSTRING, &config.custconfigfile, "configuration file", 0 }, { '\0', "showconfig", NULL, TNONE, &show_config, "print current configuration on the screen and exit", 0 }, { '\0', "configsection", "NAME", TSTRING, &config.configsection, "select configuration section", 0 }, { '\0', "disable", NULL, TNONE, &config.disable, "disable output handling", 0 }, { '\0', "disable_keywords", "STRING", TSTRING, &config.disable_keywords, "comma separated keywords to " "disable output handling when found in argument list", 0 }, { '\0', "detectescape", "BOOL", TBOOL, &config.detectescape, "switch to charbased mode if escape sequence is detected", 0 }, #if CONFIG_SUPPORT_PTY { '\0', "usepty", "BOOL", TBOOL, &config.usepty, "use PTY for stream redirection", 0 }, { '\0', "ptyremovecr", "BOOL", TBOOL, &config.ptyremovecr, "translate CR-LF to LF when capturing PTY", 0 }, #else { '\0', "usepty", "BOOL", TBOOL, &config.usepty, " use PTY for stream redirection", 0 }, { '\0', "ptyremovecr", "BOOL", TBOOL, &config.ptyremovecr, " translate CR-LF to LF when capturing PTY", 0 }, #endif { '\0', "stdout_blen", "SIZE", TUINT, &pstdout.blen, "stdout buffer size", 0 }, { '\0', "stderr_blen", "SIZE", TUINT, &pstderr.blen, "stderr buffer size", 0 }, { '\0', "stdout_charbased", "BOOL", TBOOL, &pstdout.charbased, "handle stdout char- instead of line-based", 0 }, { '\0', "stderr_charbased", "BOOL", TBOOL, &pstderr.charbased, "handle stderr char- instead of line-based", 0 }, { '\0', "extended-regexp", "BOOL", TBOOL, &config.extregexp, "interpret regexp patterns as extended regular expressions", 0 }, /* Logging options */ { '\0', NULL, NULL, TNONE, NULL, "Logging options:", 0 }, { 'l', "logfile", "NAME", TSTRING, &config.logname, "application logfile", 0 }, { 'a', "appendlog", "BOOL", TBOOL, &config.appendlog, "append to existing logfile", 0 }, { '\0', "maxlogsize", "SIZE", TUINT, &config.maxlogsize, "max. logfile size in KiB (0=no limit, 10-4000000) the file " "will be truncated if logrename isn't set", 0 }, { '\0', "logrename", "BOOL", TBOOL, &config.logrename, "rename logfile before replacing it", 0 }, { '\0', "circularlog", "BOOL", TBOOL, &config.circularlog, "write log in a circular way, keeping the max. size", 0 }, { '\0', "oldlogext", "STRING", TSTRING, &logfile.oldext, "extension for old logfile", 0 }, { '\0', "locklogfile", "BOOL", TBOOL, &config.locklogfile, "lock logfile when opening it", 0 }, { '\0', "warnlogfilelock", "BOOL", TBOOL, &config.warnlogfilelock, "print a warning if current logfile is locked", 0 }, { 'n', "print_logname", "BOOL", TBOOL, &config.printlogname, "print logfile name after execution", 0 }, { '\0', "maxaltlogfiles", "COUNT", TUINT, &config.maxlogfiles, "max # of alternate logfiles on lock", 0 }, { '\0', "alignlog", "BOOL", TBOOL, &config.alignlog, "use one line for every write in charbased mode", 0 }, { '\0', "jointimeout", "TIME", TUINT, &config.jointimeout, "join timeout for packets with alignlog active", 0 }, { '\0', "alignlinebreaks", "BOOL", TBOOL, &config.alignlinebreaks, "align logged line breaks in charbased mode", 0 }, { 't', "logtime", "BOOL", TBOOL, &config.logtime, "add timestamp to each logged line", 0 }, { '\0', "logreltime", "BOOL", TBOOL, &config.logreltime, "log relative time with --logtime", 0 }, { '\0', "logenv", "BOOL", TBOOL, &config.logenv, "add environment variables to logfile", 0 }, { 'p', "stdout_lineprefix", "STRING", TSTRING, &pstdout.lineprefix, "logfile line prefix for stdout", 0 }, { 'P', "stderr_lineprefix", "STRING", TSTRING, &pstderr.lineprefix, "logfile line prefix for stderr", 0 }, /* Console output options */ { '\0', NULL, NULL, TNONE, NULL, "Console output options:", 0 }, { '\0', "dumbterm", "BOOL", TBOOL, &config.dumbterm, "disable colors for use with dumb terminals", 0 }, { 's', "print_summary", "BOOL", TBOOL, &config.printsummary, "print execution summary", 0 }, { 'f', "stdout_fgcol", "COLOR", TCOLOR, &pstdout.fgcol, "stdout console foreground color (disable=-1, 0-7)", 0 }, { 'F', "stderr_fgcol", "COLOR", TCOLOR, &pstderr.fgcol, "stderr console foreground color (disable=-1, 0-7)", 0 }, { 'b', "stdout_bold", "BOOL", TBOOL, &pstdout.bold, "bold stdout console font", 0 }, { 'B', "stderr_bold", "BOOL", TBOOL, &pstderr.bold, "bold stderr console font", 0 }, { 'r', "stdout_regexp", "STRING", TSTRING, &pstdout.regexp, "regular expression to change stdout background color", 0 }, { 'R', "stderr_regexp", "STRING", TSTRING, &pstderr.regexp, "regular expression to change stderr background color", 0 }, { '\0', "stdout_regexp_bgcol", "COLOR", TCOLOR, &pstdout.regbgcol, "stdout console background color on regexp match (disable=-1, " "0-7)", 0 }, { '\0', "stderr_regexp_bgcol", "COLOR", TCOLOR, &pstderr.regbgcol, "stderr console background color on regexp match (disable=-1, " "0-7)", 0 }, { 'c', "stdout_clip", "LENGTH", TCLIP, &pstdout.clip, "clip stdout console at column LENGTH (disable=-1, auto=-2)", 0 }, { 'C', "stderr_clip", "LENGTH", TCLIP, &pstderr.clip, "clip stderr console at column LENGTH (disable=-1, auto=-2)", 0 }, /* Command execution options */ { '\0', NULL, NULL, TNONE, NULL, "Command execution options:", 0 }, { '\0', "exitonexecfail", "BOOL", TBOOL, &config.exitonexecfail, "exit if execution of command fails", 0 }, { '\0', "preexec", "STRING", TSTRING, &config.preexeccmd, "command executed before application start", 0 }, { '\0', "postexec", "STRING", TSTRING, &config.postexeccmd, "command executed after application exit", 0 }, { 'e', "stdout_execregexp", "STRING", TSTRING, &pstdout.execregexp, "regular expression to execute command", 0 }, { 'E', "stderr_execregexp", "STRING", TSTRING, &pstderr.execregexp, "regular expression to execute command", 0 }, { 'x', "stdout_execcommand", "STRING", TSTRING, &pstdout.execcommand, "command to be executed after stdout regexp match", 0 }, { 'X', "stderr_execregexp", "STRING", TSTRING, &pstderr.execcommand, "command to be executed after stderr regexp match", 0 }, }; const int arglistsize = ((int)(sizeof(arglist)/sizeof(arglist_t))); void show_configuration(void) { unsigned int i; message("%s %s\n\n", EXECUTABLE, VERSION); message("current configuration:\n"); message(" argument prefix %s\n", config.argprefix); message(" executable prefix %s\n", config.strip_prefix); message(" active config file %s\n", config.configfile?config.configfile:""); message(" custom configfile path %s\n", config.custconfigfile?config.custconfigfile:""); for (i=0; i"); message(" application logfile %s\n", config.logname?config.logname:""); message(" append to logfile %i\n", config.appendlog); message(" log timestamps %i\n", config.logtime); message(" log relative time %i\n", config.logreltime); message(" log environment %i\n", config.logenv); message(" align log writes left %i\n", config.alignlog); message(" join timeout %i\n", config.jointimeout); message(" align line breaks %i\n", config.alignlinebreaks); message(" lock logfiles %i\n", config.locklogfile); message(" warning on logfile lock %i\n", config.warnlogfilelock); message(" max. alternate logfiles %u\n", config.maxlogfiles - 1); message(" maximum logsize %u\n", logfile.sizelimit); message(" rename logfiles %i\n", config.logrename); message(" circular logfile %i\n", config.circularlog); message(" extension for old logs %s\n", logfile.oldext); message(" exit on execution fail %i\n", config.exitonexecfail); message("\n application executable %s\n", app.exe?app.exe:""); message(" application argc %d\n", app.argc); for (i=0; i<(unsigned int)app.argc; i++) { message(" application argv[%d] %s\n", i, app.argv[i]); } message("\n"); message(" preexec command %s\n", config.preexeccmd?config.preexeccmd:""); message(" postexec command %s\n", config.postexeccmd?config.postexeccmd:""); message("\n"); if (CONFIG_USE_THREADS) { message(" use threads %i\n", 1); } else { message(" use threads \n"); } if (CONFIG_SUPPORT_PTY) { message(" use PTY %i\n", config.usepty); message(" remove CR for PTY %i\n", config.ptyremovecr); } else { message(" use PTY \n"); message(" remove CR for PTY \n"); } message("\n ext. regexp patterns %i\n", config.extregexp); message("\n dumb terminal mode %i\n", config.dumbterm); message(" print_summary %i\n", config.printsummary); message(" print_logname %i\n", config.printlogname); message(" disable output handling %i\n", config.disable); message(" disable keywords %s\n", config.disable_keywords?config.disable_keywords:""); message("\n stdout buffer length %i\n", app.pstdout->blen); message(" stdout foreground color %i\n", app.pstdout->fgcol); message(" stdout background color %i\n", (app.pstdout->bgcol == 9)? -1:app.pstdout->bgcol); message(" stdout regexp bg color %i\n", (app.pstdout->regbgcol == 9)? -1:app.pstdout->regbgcol); message(" stdout bold font %i\n", app.pstdout->bold); message(" stdout clip at column "); if (app.pstdout->eclip==1) { message("%d\n", app.pstdout->clip); } else { message("%s\n", app.pstdout->eclip?"auto":"disable"); } message(" stdout line prefix %s\n", app.pstdout->lineprefix? app.pstdout->lineprefix:""); message(" stdout regexp %s\n", app.pstdout->regexp? app.pstdout->regexp:""); message(" stdout exec regexp %s\n", app.pstdout->execregexp? app.pstdout->execregexp:""); message(" stdout regexp command %s\n", app.pstdout->execcommand? app.pstdout->execcommand:""); message(" stdout charbased %i\n", app.pstdout->charbased); message(" stdout esc detection %i\n", app.pstdout->detectescape); message("\n stderr buffer length %i\n", app.pstderr->blen); message(" stderr foreground color %i\n", app.pstderr->fgcol); message(" stderr background color %i\n", (app.pstderr->bgcol == 9)? -1:app.pstderr->bgcol); message(" stderr regexp bg color %i\n", (app.pstderr->regbgcol == 9)? -1:app.pstderr->regbgcol); message(" stderr bold font %i\n", app.pstderr->bold); message(" stderr clip at column "); if (app.pstderr->eclip==1) { message("%d\n", app.pstderr->clip); } else { message("%s\n", app.pstderr->eclip?"auto":"disable"); } message(" stderr line prefix %s\n", app.pstderr->lineprefix? app.pstderr->lineprefix:""); message(" stderr regexp %s\n", app.pstderr->regexp? app.pstderr->regexp:""); message(" stderr exec regexp %s\n", app.pstderr->execregexp? app.pstderr->execregexp:""); message(" stderr regexp command %s\n", app.pstderr->execcommand? app.pstderr->execcommand:""); message(" stderr charbased %i\n", app.pstderr->charbased); message(" stderr esc detection %i\n", app.pstderr->detectescape); } char* get_longpath(const char* filename) { char* home; char* longpath; if (!strncmp(filename, "~/", 2)) { home = getenv("HOME"); if (home == NULL) { home = "./"; }; longpath = (char*) malloc(strlen(home) + strlen(filename) + 1); if (longpath != NULL) { strcpy(longpath, home); strcat(longpath, "/"); strcat(longpath, filename + 2); } } else { longpath = strdup(filename); } if (longpath == NULL) { error_outofmemory(); return NULL; } return longpath; } static int fixup_pipe(pipe_t *pipe) { if (config.detectescape) pipe->detectescape = 1; if ((pipe->fgcol < -1) || (pipe->fgcol > 7)) { error("%s foreground color out of range\n", pipe->name); return -1; } if ((pipe->bgcol < -1) || (pipe->bgcol > 7)) { error("%s background color out of range\n", pipe->name); return -1; } if (pipe->regbgcol == -1) pipe->regexp = NULL; if ((pipe->regbgcol < -1) || (pipe->regbgcol > 7)) { error("%s regexp background color out of range\n", pipe->name); return -1; } else if (pipe->bgcol != pipe->regbgcol) { if (pipe->bgcol == -1) pipe->bgcol = 9; if (pipe->regbgcol == -1) pipe->regbgcol = 9; } switch (pipe->clip) { case -1: pipe->eclip = 0; pipe->clip = 80; break; case -2: pipe->eclip = 2; pipe->clip = 80; break; default: if (pipe->clip < 0) pipe->eclip = 0; else pipe->eclip = 1; } if (pipe->lineprefix != NULL) { pipe->lineprefixlen = strlen(pipe->lineprefix); if (pipe->lineprefixlen == 0) { free(pipe->lineprefix); pipe->lineprefix = NULL; } } /* build console escape string for this pipe */ int len; pipe->escresetlen = strlen(pipe->escreset); len = pipe->escresetlen; if (pipe->fgcol >= 0) len += 5; if (pipe->regbgcol >= 0) len += 5; if (pipe->bold) len += 4; pipe->esccolor = malloc(len + 1); pipe->esccolorlen = 0; if (!pipe->esccolor) { error_outofmemory(); return -1; } memcpy(pipe->esccolor, pipe->escreset, pipe->escresetlen); pipe->esccolorlen += pipe->escresetlen; if (pipe->fgcol >= 0) { snprintf(pipe->esccolor + pipe->esccolorlen, 6, "\033[3%dm", pipe->fgcol); pipe->esccolorlen += 5; } if (pipe->regbgcol >= 0) { pipe->bgesccolor = pipe->esccolor + pipe->esccolorlen + 3; snprintf(pipe->esccolor + pipe->esccolorlen, 6, "\033[4%dm", pipe->bgcol); pipe->esccolorlen += 5; } else { pipe->bgesccolor = NULL; } if (pipe->bold) { snprintf(pipe->esccolor + pipe->esccolorlen, 5, "\033[1m"); pipe->esccolorlen += 4; } int regexp_cflags = REG_NOSUB|REG_NEWLINE; if (config.extregexp) regexp_cflags |= REG_EXTENDED; if (pipe->regexp) { if (strlen(pipe->regexp)) { if (regcomp(&pipe->preg, pipe->regexp, regexp_cflags)) { error("unable to process regular expression " "for %s\n", pipe->name); return -1; } } else { pipe->regexp = NULL; } } if (pipe->execregexp) { if (strlen(pipe->execregexp)) { if (regcomp(&pipe->pexecreg, pipe->execregexp, regexp_cflags)) { error("unable to process regular expression " "for %s\n", pipe->name); return -1; } } else { pipe->execregexp = NULL; } /* disable execregexp if there was no command provided */ if (pipe->execcommand == NULL) { pipe->execregexp = NULL; } else { if (strlen(pipe->execcommand) == 0) { pipe->execregexp = NULL; } } } return 0; } int fixup_config(void) { if (strlen(config.logname) == 0) config.logname = NULL; /* We have to add the original logfile to maxlogfiles */ config.maxlogfiles++; if (config.maxlogsize == 0) { logfile.sizelimit = 0; } else if ((config.maxlogsize < 10) || (config.maxlogsize > 4000000)) { error("maximum logsize out of range (0=no limit, 10-4000000 " "KiB)\n"); return -1; } else { logfile.sizelimit = config.maxlogsize * 1024; } if (CONFIG_SUPPORT_PTY && config.usepty) { app.pstdout->ptyremovecr = config.ptyremovecr; } if (fixup_pipe(app.pstdout)) return -1; if (fixup_pipe(app.pstderr)) return -1; /* Prepare indent string */ int maxindentlen = 15; if (app.pstdout->lineprefixlen > app.pstderr->lineprefixlen) maxindentlen+=app.pstdout->lineprefixlen; else maxindentlen+=app.pstderr->lineprefixlen; app.logfile->indent = malloc(maxindentlen+1); if (!app.logfile->indent) { error_outofmemory(); return -1; } memset(app.logfile->indent, ' ', maxindentlen); return 0; } void cleanup_config(void) { if (app.argv) { free(app.argv); app.argv = NULL; } } int get_display_parameters(void) { /* As the terminal settings are not available if the stream is * redirected we try all three standard streams and choose the first * one that works. If none works we continue without the settings... */ /* Get current terminal window size */ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, (char*)app.ptysize) < 0) { if (ioctl(STDERR_FILENO, TIOCGWINSZ, (char*)app.ptysize) < 0) { if (ioctl(STDIN_FILENO, TIOCGWINSZ, (char*)app.ptysize) < 0) { app.ptysize = NULL; app.ptytermios = NULL; return -1; } } } /* Get terminal attributes */ if (tcgetattr(STDOUT_FILENO, app.ptytermios) < 0) { if (tcgetattr(STDERR_FILENO, app.ptytermios) < 0) { if (tcgetattr(STDIN_FILENO, app.ptytermios) < 0) { error("unable to get window termios\n"); app.ptytermios = NULL; return -1; } } } return 0; } int adjust_clipping(void) { if (get_display_parameters()) { /* We don't seem to have a terminal window and therefore can't * get a size for automatic line clipping - disable it! */ if (pstdout.eclip == 2) { pstdout.eclip = 0; } if (pstderr.eclip == 2) { pstderr.eclip = 0; } return -1; } if (pstdout.eclip == 2) pstdout.clip = app.ptysize->ws_col; if (pstderr.eclip == 2) pstderr.clip = app.ptysize->ws_col; /* inform the application that window size changed */ if (app.pid != 0) { if (ioctl(app.pstdout->fh, TIOCSWINSZ, (char*)app.ptysize) < 0) { error("Unable to set window size"); return -1; } } return 0; } int get_argid(char* arg, int isshort) { int i; if (!arg) return -1; for (i=0; istring)) { list++; if (list->string == NULL) break; } if (list->string != NULL) { *value = list->value; return 0; } } errno = 0; tmp_value = (int) strtol(string, &p, 0); if (errno==ERANGE) { error("value out of range\n"); return -1; } if (p==string) { error("invalid parameter value\n"); return -1; } /* check for prefix */ if (prefixes && *p) { error("sdf %d\n", *value); while (strcmp(p, prefixes->string)) { prefixes++; if (prefixes->string == NULL) { break; } } if (prefixes->string != NULL) { signed long long tmp; /* be sure we don't get an integer overflow during * multiplication */ tmp = (signed long long) tmp_value * prefixes->value; if ((tmp<(int)0x80000000)||(tmp>(int)~0x80000000)) { error("value including prefix out of range\n"); return -1; } tmp_value = (int) tmp; } else { error("invalid prefix for numerical value\n"); return -1; } } *value = tmp_value; return 0; } int get_argvalue(int priority, int argid, int* argp, int argc, char* argv[], char* value) { int tmpint; int ret; if (!arglist[argid].var) { return -1; } if (argv && value == &argv[*argp][2]) { /* Long parameter has no value provided - we assume 'true' * for boolean options */ if (arglist[argid].type == TBOOL) { value = boolvalues[0].string; } } else if (value && (arglist[argid].type == TNONE)) { error("no value expected\n"); return -1; } else if (!value && (arglist[argid].type != TNONE)) { if (argp) { if (*argp < argc -1) { (*argp)++; if (argv && argv[*argp][0]!='-') { value = argv[*argp]; } } } if (!value) { error("parameter needs value\n"); return -1; } } switch (arglist[argid].type) { case TNONE: if (arglist[argid].set > priority) break; *((int*)arglist[argid].var) = 1; break; case TCLIP: case TCOLOR: case TINT: case TUINT: switch (arglist[argid].type) { case TCLIP: ret = string2numvalue(value, &tmpint, clipvalues, multprefixes); break; case TCOLOR: ret = string2numvalue(value, &tmpint, colorvalues, multprefixes); break; default: ret = string2numvalue(value, &tmpint, NULL, multprefixes); } if (ret) { return -1; } if ((arglist[argid].type==TUINT)&&(tmpint<0)) { error("negative value not allowed for " "this parameter\n"); return -1; } if (arglist[argid].set > priority) break; *((int*)arglist[argid].var) = tmpint; break; case TSTRING: if (arglist[argid].set > priority) break; if ((*((char**)arglist[argid].var) != NULL) && (arglist[argid].set > 0)) { free(*((char**)arglist[argid].var)); } *((char**)arglist[argid].var) = strdup(value); if (*((char**)arglist[argid].var) == NULL) { error_outofmemory(); return -1; } break; case TBOOL: if (string2numvalue(value, &tmpint, boolvalues, NULL)) { return -1; } if ((tmpint < 0) || (tmpint > 1)) { error("invalid boolean value\n"); return -1; } if (arglist[argid].set > priority) break; *((int*)arglist[argid].var) = tmpint; break; default: error("unknown argument type\n"); return -1; } if (arglist[argid].set < priority) arglist[argid].set = priority; return 0; } int parse_args(int argc, char* argv[]) { int i; char* arg; char* value; int argid; int argcount = 1; if (argc < 1) { error("invalid argument count\n"); return -1; } /* prepare argc and argv for our main application */ /* the new argc is still unknown but the old value should be save */ app.argv = malloc((argc+1)*sizeof(argv[0])); if (!app.argv) { error_outofmemory(); return -1; } app.argc = 1; /* split arguments for applog and our main application */ for (i=1; i1) && (name[0] == '[') && (name[strlen(name)-1] == ']') ) { name = name + 1; name[strlen(name) - 1] = '\0'; if (*appconfig_section != NULL) free(*appconfig_section); *appconfig_section = strdup(name); if (!appconfig_section) { error_outofmemory(); return -1; } return 0; } /* check if the section should be ignored */ if (*appconfig_section != NULL) { if (config.configsection != NULL) { char* tmp; if (strchr(*appconfig_section, '/') == NULL) { tmp = strrchr(config.configsection, '/'); if (tmp == NULL) tmp = config.configsection; else tmp = tmp + 1; } else { tmp = config.configsection; } if (strcmp(*appconfig_section, tmp)) { return 0; } } else { return 0; } } /* get the configuration */ if ((argid = get_argid(name, 0)) >= 0) { if ((strlen(value) == 0) && (arglist[argid].type == TNONE)) { value = NULL; } get_argvalue(1, argid, NULL, 0, NULL, value); } else { warning("unknown parameter in config file (line %d)\n", lineno); return -1; } return 0; } int get_config(void) { FILE* conffile = NULL; char *filename; char *appconfig_section = NULL; char linebuf[LINEBUFSIZE]; /* open config file */ if (config.custconfigfile == NULL) { unsigned int i; for (i=0 ; i < CONFIGSEARCHPATH_SIZE ; i++) { filename = get_longpath(configsearchpath[i]); if (filename == NULL) return -1; conffile = fopen(filename, "r"); if (conffile != NULL) break; } } else { filename = get_longpath(config.custconfigfile); if (filename != NULL) conffile = fopen(filename, "r"); if (conffile == NULL) { error("unable to open provided configuration file\n"); return -1; } } if (conffile == NULL) return 0; /* Application executable name is active config section unless * something else is provided later */ if (config.configsection==NULL) config.configsection = app.exe; /* parse each line */ config.configfile = filename; while (fgets(linebuf, LINEBUFSIZE, conffile) != NULL) { linebuf[strcspn(linebuf, "#\n")] = '\0'; if (parse_configline(linebuf, &appconfig_section)) return -1; } if (appconfig_section) free(appconfig_section); fclose(conffile); return 0; } #define MAX_KEYWORDS 50 int check_for_disable_keywords(void) { int i,j; int key; char* toksrc; char* tok[MAX_KEYWORDS]; if (!config.disable_keywords) return 0; toksrc = strdup(config.disable_keywords); key = 0; tok[key] = strtok(toksrc, ", "); while (tok[key] && (key + 1 < MAX_KEYWORDS)){ tok[++key] = strtok(NULL, ", "); } for (i=1; (i * * 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 * of the License, 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 * General Public License for more details. */ #ifndef __CAPTURE_H__ #define __CAPTURE_H__ void reset_console(void); int capture_start(void); int capture_end(void); void print_summary(void); #endif /* __CAPTURE_H__ */ logapp-0.15/README0000644000175000017500000002206211506065064013374 0ustar michaelmichael------------------------------------------------------------------------------- Logapp README ------------------------------------------------------------------------------- Logapp is a wrapper utility that helps supervise the execution of applications that produce heavy console output (e.g. make, CVS, and Subversion). It does this by logging, trimming, and coloring each line of the output before displaying it. It can be called instead of the executable that should be monitored; it then starts the application and logs all of its console output to a file. The output shown in the terminal is preprocessed, e.g. to limit the length of printed lines and to show the stderr output in a different color. It is also possible to automatically highlight lines that match a certain regular expression. The output is therefore reduced to the necessary amount, and all important lines are easy to identify. For new versions go to http://logapp.sourceforge.net/ You can always get the latest development version via Subversion: svn co https://logapp.svn.sourceforge.net/svnroot/logapp/trunk logapp Installation ------------ Simply extract the provided source package and call "make" inside the created directory to compile the logapp executable. "make install" will install the executable into "/usr/local/bin" and a manpage to "/usr/local/share/man/man1". Additionally it will create symbolic links called logmake, logcvs and logsvn that point to logapp. You will need libpthread and libutil to compile this version of the application as it uses posix threads and a pseudo terminal for console redirection. If you want to use logapp without this dependencies you can edit the Makefile and disable the options SUPPORT_PTY and/or USE_THREADS. The destination path for the installation can be changed by defining the PREFIX environment variable or by changing the Makefile directly. Calling "make help" will print a short overview of the available Makefile targets and environment variables. Usage ----- logapp can be used in two ways: 1. Call the logapp executable with the application as parameter: $ logapp echo "Hello World!" Hello World! All arguments after the executable name are ignored by logapp and provided to the application unless they start with "--logapp_". 2. Create a symlink with the applications name to the logapp executable: $ ln -s /usr/local/bin/logapp echo $ ./echo "Hello World!" Hello World! You can also add a log* prefix to the symlink to get the same effect: $ ln -s /usr/local/bin/logapp logecho $ ./logecho "Hello World!" Hello World! Symbolic links from logapp to logmake, logcvs and logsvn will be created automatically by the installation routine. Additionally you can create an alias for calling logapp automatically when you normally would execute the original application: $ alias make="logapp make" $ make $ alias make="logmake" $ make Parameter passing: If calling logapp directly, it will parse all arguments that are placed before the application filename. Arguments after that are directly passed to the application. Arguments added when calling a symlink are directly passed to the application. There is one exception: When prefixing arguments with "--logapp_" they will always be parsed by logapp. You can add --logapp_ to all long argument names provided by logapp. To get the logapp help instead of running an application you have the following possibilities: $ logapp -? $ logapp --help $ logapp --help make $ logapp make --logapp_help $ logmake --logapp_help See "logapp --help" or "man logapp" for all currently supported parameters. Function overview: Please have a look into the logapp manpage (man 1 logapp) for a detailed description of all supported options. After calling logapp, it will create a file called logapp.log in the current directory where all application output is stored together with some additional information. The file will be recreated every time you restart logapp if the --appendlog option is not set to true. You can modify the filename/path with the --logfile parameter. The logfile will start with a header that shows which application is run with what commandline options, the working path and the current time. With the --logenv option set to true, additionally a list of all active environment variables will be added to the header. After the program exited, a footer will be added to the file that shows the exit status, the line count and the time. Additionally each line of the logfile can be prefixed with a custom string by using the --std_lineprefix parameter. The default is that stdout output isn't prefixed and stderr output starts with "STDERR: ". You can limit the logfile size with the --maxlogsize parameter. There are two ways how this can be achieved: By truncating the logfile if the size is reached or by rewriting the logfile from the beginning (overwriting the old content). The first way is best used with the --logrename option set to true, this will make a copy of the old logfile with the extension ".log" added. In way the maximum size logfiles will take is two times the --maxlogsize value. The second way is selected by setting the --circularlog parameter to true. There will be additional tags included in the logfile to make it possible navigating through the data. With the --logtime option set to true there will be ms timestamps added to each line of the logfile. Those can also show the relative time since the last line if --logreltime is true. The default for console output is that all stdout lines are limited to the console width. stderr output is not limited and is printed red and bold. You can change the default by providing the --std_fgcol and --std_bold parameters. Valid color values are: -1 (console) default 0 black 1 red 2 green 3 brown 4 blue 5 magenta 6 cyan 7 white The text coloring is automatically disabled if a "dumb" console is detected, for example if you are calling logapp from inside an other application like vi. With the --std_regexp parameter you can provide a regular expression where matching lines are highlighted on console output. It is also possible to get a short summary after the application exited by setting the print_summary option to true. $ logapp --stderr_fgcol=brown --stderr_bold=true --print_summary=true \ --logfile=/tmp/make.log make This line will call make and create brown error messages in a bold font, print a summary after exit and save all output into /tmp/make.log. If you set this line as alias you can simply call make to get the same result. The logapp console output processing can be disabled with the --disable option. This can be useful for example if you start curses based applications like the menuconfig tool of the Linux kernel directly with make. As those applications are not line based the screen would otherwise get messed up. You can also provide a comma separated list of disable keywords with --disable_keywords. The arguments of the application will be checked if they contain one of those keywords. If you want to use logapp with applications that can't be used with line-based data handling you can try to use char-based stream handling. With this option enabled line clipping and coloring won't work, but the data gets still logged. This option can be enabled for both streams independently by setting the --_charbased parameters to true. If you want to use this option together with the timestamp function you should have a look at the --alignlog and --jointimeout options. You can also enable the --detectescape option so logapp will automatically switch to charbased mode for a specific stream as soon as it detects an escape sequence in the data. Configuration file: You can put the long parameters and their values into a configuration file. The search path is: ~/.logapprc, /etc/logapp.conf, /etc/logapp/logapp.conf It is also possible to use a custom config file path defined with the --configfile option. Have a look at the sample configuration file called example.conf provided with the source for more information. Example ------- Put the following lines in your ~/.bashrc file (provided you are using bash): alias make='logapp -F red -B true -c 80 -l=~/make.log make' alias svn='logapp -F red -B true -c 80 -l=~/svn.log svn' alias cvs='logapp -F red -B true -c 80 -l=~/cvs.log cvs' This will let bash automatically use logapp to call make, svn and cvs from the console and write the output into a logfile in your home directory. The normal output of the applications will be clipped at 80 chars and errors/warnings will be shown in red with bold font. You can get the same result by putting the configuration options into application specific sections inside the config file. logapp-0.15/logfile.c0000644000175000017500000004034511241614017014277 0ustar michaelmichael/* * logfile.c: logapp logfile handling * * * Copyright (C) 2007-2009 Michael Brunner * * 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 * of the License, 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 * General Public License for more details. */ #include "logapp.h" #include #include #include #include #include #include #include #include #include #include #include "configuration.h" #include "logfile.h" static int logfile_check_size(logfile_t* logfile, int addmarker, unsigned addsize); static int logfile_rename(logfile_t* logfile, char* oldname); static char* logfile_get_alternate_name(logfile_t* logfile); static int logfile_add_header(logfile_t* logfile); static int logfile_add_splitmarker(logfile_t* logfile); static int logfile_add_footer(logfile_t* logfile); pthread_mutex_t logfile_mutex = PTHREAD_MUTEX_INITIALIZER; extern char **environ; #define MAX_HEADER 80 #define MAX_FOOTER MAX_HEADER #define MAX_SPLIT MAX_HEADER #define MAX_PWD 500 #define MAX_TMP 50 static int logfile_add_header(logfile_t* logfile) { int ret; int len; int headlen; int i; struct tm* loctime; char buf[MAX_HEADER + 1]; char tmp[MAX_TMP + 1]; char pwd[MAX_PWD + 1]; /* Get to the end of the logfile and check size */ if (lseek(logfile->fh, 0, SEEK_END) != 0) { if (write(logfile->fh, "\n", 1) == -1) return -1; if (logfile_check_size(logfile, 0, MAX_HEADER * 2)) { error("Problem while checking/resizing logfile\n"); return -1; } } buf[MAX_HEADER] = '\0'; headlen = strlen(logfile->head); /* Write application name */ len = snprintf(buf, MAX_HEADER, "%s %s\n", logfile->head, app.exe); if ((len < 0) || (len > MAX_HEADER)) { return -1; } if (write(logfile->fh, buf, len) == -1) return -1; /* Write argument list */ for (i=1; ifh, logfile->head, headlen) == -1) return -1; len = snprintf(buf, MAX_HEADER, " arg[%d] = \"", i); if ((len < 0) || (len > MAX_HEADER)) { return -1; } if (write(logfile->fh, buf, len) == -1) return -1; if (write(logfile->fh, app.argv[i], strlen(app.argv[i])) == -1) return -1; if (write(logfile->fh, "\"", 1) != 1) return -1; if ((i+1)fh, "\n", 1) != 1) return -1; } if (app.argc<2) { if (write(logfile->fh, logfile->head, headlen) == -1) return -1; if (write(logfile->fh, " ", 26) != 26 ) return -1; } if (write(logfile->fh, "\n", 1) != 1) return -1; /* Write current path */ len = snprintf(buf, MAX_HEADER, "%s pwd = ", logfile->head); if ((len < 0) || (len > MAX_HEADER)) { return -1; } if (write(logfile->fh, buf, len) == -1) return -1; if (getcwd(pwd, MAX_PWD) == NULL) { if (write(logfile->fh, "\n", 34) != 34) return -1; } else { pwd[strlen(pwd)+1] = '\0'; pwd[strlen(pwd)] = '\n'; if (write(logfile->fh, pwd, strlen(pwd)) == -1) return -1; } /* Write environment variables */ if (config.logenv) { for (i=0; environ[i] != NULL; i++) { if (write(logfile->fh, logfile->head, headlen) == -1) return -1; len = snprintf(buf, MAX_HEADER, " env[%d] = \"", i); if ((len < 0) || (len > MAX_HEADER)) { return -1; } if (write(logfile->fh, buf, len) == -1) return -1; if (write(logfile->fh, environ[i], strlen(environ[i])) == -1) return -1; if (write(logfile->fh, "\"\n", 2) != 2) return -1; } } /* Write starttime */ loctime = localtime(&app.starttime); ret = strftime(tmp, MAX_TMP, "%Y-%m-%d %T %Z", loctime); if ((app.starttime < 0) || (ret == 0)) { tmp[0] = '\0'; } snprintf(buf, MAX_HEADER, "%s %s\n", logfile->head, tmp); if ((len < 0) || (len > MAX_HEADER)) { return -1; } if (write(logfile->fh, buf, strlen(buf)) == -1) return -1; return 0; } static int logfile_add_splitmarker(logfile_t* logfile) { int ret; int len; time_t sec; struct tm* loctime; char buf[MAX_SPLIT + 1]; char tmp[MAX_TMP + 1]; buf[MAX_SPLIT] = '\0'; /* Write application name */ len = snprintf(buf, MAX_SPLIT, "%s %s\n", logfile->split, app.exe); if ((len < 0) || (len > MAX_SPLIT)) { return -1; } if (write(logfile->fh, buf, len) == -1) return -1; /* Write starttime */ loctime = localtime(&app.starttime); ret = strftime(tmp, MAX_TMP, "%Y-%m-%d %T %Z", loctime); if ((app.starttime < 0) || (ret == 0)) { tmp[0] = '\0'; } len = snprintf(buf, MAX_SPLIT, "%s running since %s\n", logfile->split, tmp); if ((len < 0) || (len > MAX_SPLIT)) { return -1; } if (write(logfile->fh, buf, len) == -1) return -1; /* Write current line count */ len = snprintf(buf, MAX_SPLIT, "%s %d lines output (%d stdout, %d" " stderr)\n", logfile->split, app.pstdout-> linecount + app.pstderr->linecount, app.pstdout->linecount, app.pstderr->linecount); if ((len < 0) || (len > MAX_SPLIT)) { return -1; } if (write(logfile->fh, buf, len) == -1) return -1; /* Write execution time */ if (time(&sec) > -1) { loctime = localtime(&sec); ret = strftime(tmp, MAX_TMP, "%Y-%m-%d %T %Z", loctime); } if ((sec < 0) || (ret == 0)) { tmp[0] = '\0'; } if ((app.starttime > -1)&&(app.starttime <= sec)) { len = snprintf(buf, MAX_SPLIT, "%s execution time is %us\n", logfile->split, (unsigned int)(sec - app.starttime)); if ((len < 0) || (len > MAX_SPLIT)) { return -1; } if (write(logfile->fh, buf, len) == -1) return -1; } else { error("Problem calculating execution time\n"); } return 0; } static int logfile_add_footer(logfile_t* logfile) { int len = 0; int tmplen = 0; time_t sec; struct tm* loctime; char buf[MAX_FOOTER + 1]; char tmp[MAX_TMP + 1]; /* Add a newline if needed */ if (logfile->addnewline) if (write(logfile->fh, "\n", 1) == -1) return -1; buf[MAX_FOOTER] = '\0'; /* Write exit time */ if (time(&sec) > -1) { loctime = localtime(&sec); len = strftime(tmp, MAX_TMP, "%Y-%m-%d %T %Z", loctime); } if ((sec < 0) || (len < 1)) { tmp[0] = '\0'; len = 0; } snprintf(buf, MAX_FOOTER, "%s %s\n", logfile->foot, tmp); if ((len < 0) || (len > MAX_FOOTER)) { return -1; } len += strlen(logfile->foot) + 2; if (write(logfile->fh, buf, len) == -1) return -1; /* Write exit status */ len = snprintf(buf, MAX_FOOTER, "%s application exit status: ", logfile->foot); if ((len < 0) || (len > MAX_FOOTER)) { return -1; } if (WIFEXITED(app.exit_state)) { tmplen = snprintf(buf + len, MAX_FOOTER - len, "%d\n", WEXITSTATUS(app.exit_state)); } else if (WIFSIGNALED(app.exit_state)) { tmplen = snprintf(buf + len, MAX_FOOTER - len, "abnormal termination (signal = %d)\n", WTERMSIG(app.exit_state)); } else { tmplen += snprintf(buf + len, MAX_FOOTER - len, "unknown\n"); } if ((tmplen < 0) || (tmplen > MAX_FOOTER)) { return -1; } len += tmplen; if (write(logfile->fh, buf, len) == -1) return -1; /* Write line counts */ len = snprintf(buf, MAX_FOOTER, "%s %d lines output (%d stdout, %d" " stderr)\n", logfile->foot, app.pstdout-> linecount + app.pstderr->linecount, app.pstdout->linecount, app.pstderr->linecount); if ((len < 0) || (len > MAX_FOOTER)) { return -1; } if (write(logfile->fh, buf, len) == -1) return -1; /* Write execution time */ if ((app.starttime > -1)&&(app.starttime <= sec)) { len = snprintf(buf, MAX_FOOTER, "%s execution time was %us\n", logfile->foot, (unsigned int)(sec - app.starttime)); if ((len < 0) || (len > MAX_FOOTER)) { return -1; } if (write(logfile->fh, buf, len) == -1) return -1; } else { error("problem calculating execution time\n"); } /* Write application name */ len = snprintf(buf, MAX_FOOTER, "%s %s\n", logfile->foot, app.exe); if ((len < 0) || (len > MAX_FOOTER)) { return -1; } if (write(logfile->fh, buf, len) == -1) return -1; return 0; } static int logfile_rename(logfile_t* logfile, char* oldname) { char* newname; int ret = 0; if (!config.logrename) return 0; if (oldname == NULL) oldname = logfile->name; newname = malloc(strlen(oldname) + sizeof(logfile->oldext)); if (newname == NULL) { error_outofmemory(); return -1; } strcpy(newname, oldname); strcat(newname, logfile->oldext); if (rename(oldname, newname)) { if (errno != ENOENT) { ret = -1; warning("logfile rename failed (error: %d)\n", errno); } } free(newname); return ret; } static char* logfile_get_alternate_name(logfile_t* logfile) { int len; if (!logfile->appendnr) { return strdup(logfile->name); } char number[12]; len = snprintf(number, 12, ".%d", logfile->appendnr); if (len < 0) { return NULL; } char *name = malloc(strlen(logfile->name) + strlen(number) + 1); if (name == NULL) { error_outofmemory(); return NULL; } strcpy(name, logfile->name); strcat(name, number); return name; } static int logfile_check_size(logfile_t* logfile, int addmarker, unsigned addsize) { unsigned curpos; if (!logfile->sizelimit) return 0; curpos = lseek(logfile->fh, 0, SEEK_CUR) + addsize; if (curpos > logfile->sizelimit) { int flags = O_CREAT | O_WRONLY; if (addmarker) logfile_add_splitmarker(logfile); if (!config.circularlog) { if (logfile_rename(logfile, NULL)) error("problem moving log - continuing " "anyways\n"); flags |= O_TRUNC; } else { if (ftruncate(logfile->fh, lseek(logfile->fh, 0, SEEK_CUR)) == -1) error("Problem truncating logfile (errno=%d)", errno); return -1; } close(logfile->fh); logfile->fh = open(logfile->name, flags, 0644); if (addmarker) logfile_add_splitmarker(logfile); } return 0; } int logfile_write(logfile_t* logfile, pipe_t* pipe) { unsigned stamp_sec = 0; int stamp_msec = 0; int writetimestamp = config.logtime; int prefixlen = 0; if (!logfile->name) return 0; if (CONFIG_USE_THREADS) pthread_mutex_lock(&logfile_mutex); if (logfile->sizelimit) if (logfile_check_size(logfile, 1, pipe->bfill)) { error("Problem while checking/resizing logfile\n"); return -1; } /* Get times for join and timestamp */ if (config.logtime || (config.jointimeout && pipe->charbased)) { struct timeval tvtime; gettimeofday(&tvtime, NULL); stamp_sec = (unsigned) (tvtime.tv_sec - app.toffset.tv_sec); stamp_msec = (tvtime.tv_usec - app.toffset.tv_usec)/1000; if (stamp_msec < 0) { stamp_sec--; stamp_msec += 1000; }; /* Check if this packet should be added to the last one */ if (config.jointimeout && pipe->charbased) { static int oldmsec = -1; static unsigned oldsec = (unsigned) -1; int ossec; int osmsec; int join = 0; osmsec = stamp_msec - oldmsec; ossec = stamp_sec - oldsec; if (osmsec < 0) { ossec--; osmsec += 1000; } if ((config.jointimeout/1000) == ossec) { if ((config.jointimeout % 1000) > osmsec) { join = 1; } else { join = 0; } } else if ((config.jointimeout/1000) > ossec) { join = 1; } else { join = 0; } if (join) { logfile->addnewline = 0; writetimestamp = 0; } if (config.logreltime && writetimestamp) { oldmsec = 0; oldsec = 0; } else { oldmsec = stamp_msec; oldsec = stamp_sec; } } if (config.logreltime && writetimestamp) memcpy(&app.toffset, &tvtime, sizeof(struct timeval)); } /* Align all single writes to a new line if we are in charbased mode * and alignlog is active */ if (pipe->charbased) { /* Check if we should add a newline right now */ if (logfile->addnewline && config.alignlog) if (write(logfile->fh, "\n", 1) == -1) goto failure_exit; /* Check if we should add a newline next time */ if (pipe->buf[pipe->bfill-1] != '\n') { logfile->addnewline = 1; } else { logfile->addnewline = 0; } } /* Write timestamp */ if (writetimestamp) { char timestamp[16]; int len; /* Normally use a 9 digit timestamp but extend the size to 13 * digits if needed */ if (stamp_sec < 999999) { len = snprintf(timestamp, 12, "%06u%03u: ", stamp_sec, stamp_msec); if (len < 0) goto failure_exit; prefixlen = 11; } else { len = snprintf(timestamp, 16, "%010u%03u: ", stamp_sec, stamp_msec); if (len < 0) goto failure_exit; prefixlen = 15; } if (write(logfile->fh, timestamp, prefixlen) == -1) goto failure_exit; } /* Write prefix */ if (pipe->lineprefix != NULL) { if (write(logfile->fh, pipe->lineprefix, pipe->lineprefixlen) == -1) { goto failure_exit; } prefixlen += pipe->lineprefixlen; } int i = 0; int offset = 0; if (config.alignlinebreaks && pipe->charbased) { while(pipe->linebreakpos[i]) { if (i) { if (write(logfile->fh, logfile->indent, prefixlen) == -1) { goto failure_exit; } } if (write(logfile->fh, pipe->buf+offset, pipe->linebreakpos[i]-offset) == -1) { goto failure_exit; } offset = pipe->linebreakpos[i]; i++; } for (i=offset; ibfill; i++) { if (pipe->buf[i]=='\n' || (i+1==pipe->bfill)) { if (offset) { if (write(logfile->fh, logfile->indent, prefixlen) == -1) { goto failure_exit; } } if (write(logfile->fh, pipe->buf + offset, i-offset + 1) == -1) { goto failure_exit; } offset = i+1; } } } else { /* Write data */ if (write(logfile->fh, pipe->buf, pipe->bfill) == -1) { goto failure_exit; } } if (CONFIG_USE_THREADS) pthread_mutex_unlock(&logfile_mutex); return 0; failure_exit: if (CONFIG_USE_THREADS) pthread_mutex_unlock(&logfile_mutex); return -1; } int get_lock(logfile_t* logfile) { struct flock lock; if (!config.locklogfile) return 0; lock.l_type = F_WRLCK; lock.l_start = 0; lock.l_whence = SEEK_SET; lock.l_len = 0; return fcntl(logfile->fh, F_SETLK, &lock); } int logfile_open(char* filename, logfile_t* logfile) { int flags; char* altname = NULL; if (filename == NULL) return 0; logfile->name = get_longpath(filename); if (logfile->name == NULL) return -1; flags = O_CREAT | O_WRONLY | O_APPEND; while (logfile->appendnr < config.maxlogfiles) { if (altname != NULL) free(altname); altname = logfile_get_alternate_name(logfile); if (logfile->name == NULL) return -1; if (!config.appendlog) { /* Check if we can rename the old logfile, or if it is * still locked by a running logapp session */ logfile->fh = open(altname, O_WRONLY, 0644); if (logfile->fh != -1) { if (!get_lock(logfile)) { close(logfile->fh); if (logfile_rename(logfile, altname)) { /* unable to rename logfile -> try next slot */ logfile->fh = -1; logfile->appendnr++; continue; } } else { /* it is locked, so try next slot */ close(logfile->fh); logfile->fh = -1; logfile->appendnr++; continue; } } } logfile->fh = open(altname, flags, 0644); if (get_lock(logfile)) { close(logfile->fh); logfile->fh = -1; logfile->appendnr++; continue; } break; } /* We have to replace the original filename with the new one, in * case the lock detection forced us to change the name */ free(logfile->name); logfile->name = altname; if (logfile->fh == -1) { if (logfile->appendnr == config.maxlogfiles) { error("no possible logfile is writeable\n"); error("-> check permissions or try increasing the " "maxaltlogfiles value\n"); } return -1; } if (logfile->appendnr && config.warnlogfilelock) { warning("using %s as logfile\n", logfile->name); warning("most likely another logapp session is locking " "the logfile\n"); } if (!config.appendlog) { if (ftruncate(logfile->fh, 0) == -1) { error("Problem truncating logfile (errno = %d)\n", errno); return -1; } } if (logfile_add_header(logfile)) { error("problem writing log header to file\n"); return -1; } return 0; } int logfile_close(logfile_t* logfile) { if (!logfile->name) return 0; if (logfile_add_footer(logfile)) { error("problem writing log footer to file\n"); return -1; } close(logfile->fh); return 0; }