Add logging functions
The mkdir dance is a bit awkward...
This commit is contained in:
		
							parent
							
								
									4f40ace9d4
								
							
						
					
					
						commit
						d99f20c0ff
					
				
							
								
								
									
										1
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Makefile
									
									
									
									
									
								
							| @ -13,6 +13,7 @@ OBJS += config.o | |||||||
| OBJS += edit.o | OBJS += edit.o | ||||||
| OBJS += handle.o | OBJS += handle.o | ||||||
| OBJS += irc.o | OBJS += irc.o | ||||||
|  | OBJS += log.o | ||||||
| OBJS += ui.o | OBJS += ui.o | ||||||
| OBJS += url.o | OBJS += url.o | ||||||
| OBJS += xdg.o | OBJS += xdg.o | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								README.7
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								README.7
									
									
									
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| .Dd February 12, 2020 | .Dd March 25, 2020 | ||||||
| .Dt README 7 | .Dt README 7 | ||||||
| .Os "Causal Agency" | .Os "Causal Agency" | ||||||
| . | . | ||||||
| @ -132,6 +132,8 @@ line editing | |||||||
| tab complete | tab complete | ||||||
| .It Pa url.c | .It Pa url.c | ||||||
| URL detection | URL detection | ||||||
|  | .It Pa log.c | ||||||
|  | chat logging | ||||||
| .It Pa config.c | .It Pa config.c | ||||||
| configuration parsing | configuration parsing | ||||||
| .It Pa xdg.c | .It Pa xdg.c | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| .Dd March 23, 2020 | .Dd March 25, 2020 | ||||||
| .Dt CATGIRL 1 | .Dt CATGIRL 1 | ||||||
| .Os | .Os | ||||||
| . | . | ||||||
| @ -8,7 +8,7 @@ | |||||||
| . | . | ||||||
| .Sh SYNOPSIS | .Sh SYNOPSIS | ||||||
| .Nm | .Nm | ||||||
| .Op Fl Rev | .Op Fl Relv | ||||||
| .Op Fl C Ar copy | .Op Fl C Ar copy | ||||||
| .Op Fl H Ar hash | .Op Fl H Ar hash | ||||||
| .Op Fl N Ar send | .Op Fl N Ar send | ||||||
| @ -155,6 +155,10 @@ Join the comma-separated list of channels | |||||||
| Load the TLS client private key from | Load the TLS client private key from | ||||||
| .Ar path . | .Ar path . | ||||||
| . | . | ||||||
|  | .It Fl l , Cm log | ||||||
|  | Log chat events to files in paths | ||||||
|  | .Pa $XDG_DATA_HOME/catgirl/log/network/channel/YYYY-MM-DD.log . | ||||||
|  | . | ||||||
| .It Fl n Ar nick , Cm nick = Ar nick | .It Fl n Ar nick , Cm nick = Ar nick | ||||||
| Set nickname to | Set nickname to | ||||||
| .Ar nick . | .Ar nick . | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								chat.c
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								chat.c
									
									
									
									
									
								
							| @ -129,7 +129,7 @@ int main(int argc, char *argv[]) { | |||||||
| 	const char *user = NULL; | 	const char *user = NULL; | ||||||
| 	const char *real = NULL; | 	const char *real = NULL; | ||||||
| 
 | 
 | ||||||
| 	const char *Opts = "!C:H:N:O:RS:a:c:eg:h:j:k:n:p:r:s:u:vw:"; | 	const char *Opts = "!C:H:N:O:RS:a:c:eg:h:j:k:ln:p:r:s:u:vw:"; | ||||||
| 	const struct option LongOpts[] = { | 	const struct option LongOpts[] = { | ||||||
| 		{ "insecure", no_argument, NULL, '!' }, | 		{ "insecure", no_argument, NULL, '!' }, | ||||||
| 		{ "copy", required_argument, NULL, 'C' }, | 		{ "copy", required_argument, NULL, 'C' }, | ||||||
| @ -144,6 +144,7 @@ int main(int argc, char *argv[]) { | |||||||
| 		{ "host", required_argument, NULL, 'h' }, | 		{ "host", required_argument, NULL, 'h' }, | ||||||
| 		{ "join", required_argument, NULL, 'j' }, | 		{ "join", required_argument, NULL, 'j' }, | ||||||
| 		{ "priv", required_argument, NULL, 'k' }, | 		{ "priv", required_argument, NULL, 'k' }, | ||||||
|  | 		{ "log", no_argument, NULL, 'l' }, | ||||||
| 		{ "nick", required_argument, NULL, 'n' }, | 		{ "nick", required_argument, NULL, 'n' }, | ||||||
| 		{ "port", required_argument, NULL, 'p' }, | 		{ "port", required_argument, NULL, 'p' }, | ||||||
| 		{ "real", required_argument, NULL, 'r' }, | 		{ "real", required_argument, NULL, 'r' }, | ||||||
| @ -171,6 +172,7 @@ int main(int argc, char *argv[]) { | |||||||
| 			break; case 'h': host = optarg; | 			break; case 'h': host = optarg; | ||||||
| 			break; case 'j': self.join = optarg; | 			break; case 'j': self.join = optarg; | ||||||
| 			break; case 'k': priv = optarg; | 			break; case 'k': priv = optarg; | ||||||
|  | 			break; case 'l': logEnable = true; | ||||||
| 			break; case 'n': nick = optarg; | 			break; case 'n': nick = optarg; | ||||||
| 			break; case 'p': port = optarg; | 			break; case 'p': port = optarg; | ||||||
| 			break; case 'r': real = optarg; | 			break; case 'r': real = optarg; | ||||||
| @ -327,5 +329,6 @@ int main(int argc, char *argv[]) { | |||||||
| 	handle(msg); | 	handle(msg); | ||||||
| 
 | 
 | ||||||
| 	ircClose(); | 	ircClose(); | ||||||
|  | 	logClose(); | ||||||
| 	uiHide(); | 	uiHide(); | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								chat.h
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								chat.h
									
									
									
									
									
								
							| @ -259,8 +259,14 @@ void urlOpenCount(uint id, uint count); | |||||||
| void urlOpenMatch(uint id, const char *str); | void urlOpenMatch(uint id, const char *str); | ||||||
| void urlCopyMatch(uint id, const char *str); | void urlCopyMatch(uint id, const char *str); | ||||||
| 
 | 
 | ||||||
|  | extern bool logEnable; | ||||||
|  | void logFormat(uint id, const time_t *time, const char *format, ...) | ||||||
|  | 	__attribute__((format(printf, 3, 4))); | ||||||
|  | void logClose(void); | ||||||
|  | 
 | ||||||
| FILE *configOpen(const char *path, const char *mode); | FILE *configOpen(const char *path, const char *mode); | ||||||
| FILE *dataOpen(const char *path, const char *mode); | FILE *dataOpen(const char *path, const char *mode); | ||||||
|  | void dataMkdir(const char *path); | ||||||
| 
 | 
 | ||||||
| int getopt_config( | int getopt_config( | ||||||
| 	int argc, char *const *argv, | 	int argc, char *const *argv, | ||||||
|  | |||||||
							
								
								
									
										112
									
								
								log.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								log.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,112 @@ | |||||||
|  | /* Copyright (C) 2020  C. McEnroe <june@causal.agency>
 | ||||||
|  |  * | ||||||
|  |  * 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 3 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, see <https://www.gnu.org/licenses/>.
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <assert.h> | ||||||
|  | #include <err.h> | ||||||
|  | #include <limits.h> | ||||||
|  | #include <stdarg.h> | ||||||
|  | #include <stdio.h> | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <sysexits.h> | ||||||
|  | #include <time.h> | ||||||
|  | 
 | ||||||
|  | #include "chat.h" | ||||||
|  | 
 | ||||||
|  | bool logEnable; | ||||||
|  | 
 | ||||||
|  | static struct { | ||||||
|  | 	int year; | ||||||
|  | 	int month; | ||||||
|  | 	int day; | ||||||
|  | 	FILE *file; | ||||||
|  | } logs[IDCap]; | ||||||
|  | 
 | ||||||
|  | static FILE *logFile(uint id, const struct tm *tm) { | ||||||
|  | 	if ( | ||||||
|  | 		logs[id].file && | ||||||
|  | 		logs[id].year == tm->tm_year && | ||||||
|  | 		logs[id].month == tm->tm_mon && | ||||||
|  | 		logs[id].day == tm->tm_mday | ||||||
|  | 	) return logs[id].file; | ||||||
|  | 
 | ||||||
|  | 	if (logs[id].file) { | ||||||
|  | 		int error = fclose(logs[id].file); | ||||||
|  | 		if (error) err(EX_IOERR, "%s", idNames[id]); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	logs[id].year = tm->tm_year; | ||||||
|  | 	logs[id].month = tm->tm_mon; | ||||||
|  | 	logs[id].day = tm->tm_mday; | ||||||
|  | 
 | ||||||
|  | 	char path[PATH_MAX] = "log"; | ||||||
|  | 	size_t len = strlen(path); | ||||||
|  | 	dataMkdir(""); | ||||||
|  | 	dataMkdir(path); | ||||||
|  | 
 | ||||||
|  | 	path[len++] = '/'; | ||||||
|  | 	for (const char *ch = network.name; *ch; ++ch) { | ||||||
|  | 		path[len++] = (*ch == '/' ? '_' : *ch); | ||||||
|  | 	} | ||||||
|  | 	path[len] = '\0'; | ||||||
|  | 	dataMkdir(path); | ||||||
|  | 
 | ||||||
|  | 	path[len++] = '/'; | ||||||
|  | 	for (const char *ch = idNames[id]; *ch; ++ch) { | ||||||
|  | 		path[len++] = (*ch == '/' ? '_' : *ch); | ||||||
|  | 	} | ||||||
|  | 	path[len] = '\0'; | ||||||
|  | 	dataMkdir(path); | ||||||
|  | 
 | ||||||
|  | 	strftime(&path[len], sizeof(path) - len, "/%F.log", tm); | ||||||
|  | 	logs[id].file = dataOpen(path, "a"); | ||||||
|  | 	if (!logs[id].file) exit(EX_CANTCREAT); | ||||||
|  | 
 | ||||||
|  | 	setlinebuf(logs[id].file); | ||||||
|  | 	return logs[id].file; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void logClose(void) { | ||||||
|  | 	if (!logEnable) return; | ||||||
|  | 	for (uint id = 0; id < IDCap; ++id) { | ||||||
|  | 		if (!logs[id].file) continue; | ||||||
|  | 		int error = fclose(logs[id].file); | ||||||
|  | 		if (error) err(EX_IOERR, "%s", idNames[id]); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void logFormat(uint id, const time_t *src, const char *format, ...) { | ||||||
|  | 	if (!logEnable) return; | ||||||
|  | 
 | ||||||
|  | 	time_t ts = (src ? *src : time(NULL)); | ||||||
|  | 	struct tm *tm = localtime(&ts); | ||||||
|  | 	if (!tm) err(EX_OSERR, "localtime"); | ||||||
|  | 
 | ||||||
|  | 	FILE *file = logFile(id, tm); | ||||||
|  | 
 | ||||||
|  | 	char buf[sizeof("0000-00-00T00:00:00+0000")]; | ||||||
|  | 	strftime(buf, sizeof(buf), "%FT%T%z", tm); | ||||||
|  | 	fprintf(file, "[%s] ", buf); | ||||||
|  | 	if (ferror(file)) err(EX_IOERR, "%s", idNames[id]); | ||||||
|  | 
 | ||||||
|  | 	va_list ap; | ||||||
|  | 	va_start(ap, format); | ||||||
|  | 	vfprintf(file, format, ap); | ||||||
|  | 	va_end(ap); | ||||||
|  | 	if (ferror(file)) err(EX_IOERR, "%s", idNames[id]); | ||||||
|  | 
 | ||||||
|  | 	fprintf(file, "\n"); | ||||||
|  | 	if (ferror(file)) err(EX_IOERR, "%s", idNames[id]); | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								xdg.c
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								xdg.c
									
									
									
									
									
								
							| @ -134,3 +134,25 @@ local: | |||||||
| 	if (!file) warn("%s", path); | 	if (!file) warn("%s", path); | ||||||
| 	return file; | 	return file; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void dataMkdir(const char *path) { | ||||||
|  | 	const char *home = getenv("HOME"); | ||||||
|  | 	const char *dataHome = getenv("XDG_DATA_HOME"); | ||||||
|  | 
 | ||||||
|  | 	char homePath[PATH_MAX]; | ||||||
|  | 	if (dataHome) { | ||||||
|  | 		snprintf( | ||||||
|  | 			homePath, sizeof(homePath), | ||||||
|  | 			"%s/" SUBDIR "/%s", dataHome, path | ||||||
|  | 		); | ||||||
|  | 	} else { | ||||||
|  | 		if (!home) return; | ||||||
|  | 		snprintf( | ||||||
|  | 			homePath, sizeof(homePath), | ||||||
|  | 			"%s/.local/share/" SUBDIR "/%s", home, path | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	int error = mkdir(homePath, S_IRWXU); | ||||||
|  | 	if (error && errno != EEXIST) warn("%s", homePath); | ||||||
|  | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user