Add logging functions

The mkdir dance is a bit awkward...
master
C. McEnroe 2020-03-25 18:56:09 -04:00
parent 4f40ace9d4
commit d99f20c0ff
7 changed files with 154 additions and 4 deletions

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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
View File

@ -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 100644
View 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
View File

@ -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);
}