3proxy/src/sql.c
Vladimir Dubrovin 2052dc0a4f Logs: a thread and a queue for each log; build-time defaults
Once 3proxy runs (after the first configuration is in force, so after a
daemon's fork), each logger has a thread of its own writing from a
queue, and a client never waits for a log: ODBC, RADIUS, a full disk or
a pipe nobody reads hold up only that log's thread.

A queued line is fixed size: the part of struct clientparam logging
reads (everything before the filters: addresses, counters, names and
credentials, which hold no pointer it follows but srv), the time it was
logged, and its text, 256 bytes inline or a copy of its own if longer.
The thread formats it: struct tm is per call now, timechanged() and
dologname() included, and the type's format() takes the time.

minlogqueue (64) slots are allocated as the thread starts and kept,
maxlogqueue (4096) lines are queued at most. A line which finds the
queue full is dropped and counted on the last one queued; once that is
written, "Log X has its queue full, N records dropped" goes to where
errors go. A log which fails is no longer written to the error log
instead: its lines are dropped and counted while it is retried every
minute, and the count is told when it is open again, or when it was
reopened after failed writes.

A service which ended is stopped at once (srvstop(): sockets, listener,
its clients gone), but freed (srvrelease()) through its log's queue,
behind the lines which refer to it and to its configuration: the event
is part of struct srvparam, so a full queue never drops it. logfinish()
writes what is queued, 10 seconds at most, also as 3proxy exits on Unix.
Standalone services write as before, as each line comes.

LOGQUEUEMIN, LOGQUEUEMAX, DEFAULT_MAXCHILD, DEFAULT_POOLMAX,
DEFAULT_POOLTIMEOUT and POOLPERSERVICE (the automatic thread pool cap
for each service, which was 32 in the code) can be set with -D at build
time.

Tests: a log nobody reads (a FIFO) holds no client up, drops past its
queue and says so, and lines queued for a service a reload removed are
written as it formatted them; /dev/full says what it dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 19:41:48 +03:00

139 lines
4.1 KiB
C

#include "proxy.h"
#ifdef WITH_ODBC
/* ODBC drivers require noticeably more stack than the file logger */
#define LOGSTACKSIZE 32768
struct logodbc {
SQLHENV henv;
SQLHDBC hdbc;
SQLHSTMT hstmt;
};
static void odbcdisconnect(struct logodbc *d){
if(d->hstmt) {
SQLFreeHandle(SQL_HANDLE_STMT, d->hstmt);
d->hstmt = NULL;
}
if(d->hdbc){
SQLDisconnect(d->hdbc);
SQLFreeHandle(SQL_HANDLE_DBC, d->hdbc);
d->hdbc = NULL;
}
if(d->henv) {
SQLFreeHandle(SQL_HANDLE_ENV, d->henv);
d->henv = NULL;
}
}
/* The log string is &DATASOURCE,USERNAME,PASSWORD. The data source
alone names the log; user and password are its parameters, the
password being whatever follows the comma after the user. */
static int odbcconnect(struct logger *lg){
struct logodbc *d = (struct logodbc *)lg->data;
SQLRETURN retcode;
char * datasource;
char * username = NULL;
char * password = NULL;
char * string = NULL;
size_t slen = 0;
if(d->hstmt || d->hdbc || d->henv) odbcdisconnect(d);
if(!d->henv){
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &d->henv);
if (!d->henv || (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)){
d->henv = NULL;
return 0;
}
retcode = SQLSetEnvAttr(d->henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO) {
return 0;
}
}
if(!d->hdbc){
retcode = SQLAllocHandle(SQL_HANDLE_DBC, d->henv, &d->hdbc);
if (!d->hdbc || (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)) {
d->hdbc = NULL;
SQLFreeHandle(SQL_HANDLE_ENV, d->henv);
d->henv = NULL;
return 0;
}
SQLSetConnectAttr(d->hdbc, SQL_LOGIN_TIMEOUT, (void*)15, 0);
}
datasource = (char *)lg->selector + 1;
if(lg->params){
if(!(string = strdup((char *)lg->params))) return 0;
slen = strlen(string);
username = string;
if((password = strchr(string, ','))) *password++ = 0;
}
/* Connect to data source */
retcode = SQLConnect(d->hdbc, (SQLCHAR*) datasource, (SQLSMALLINT)strlen(datasource),
(SQLCHAR*) username, (SQLSMALLINT)((username)?strlen(username):0),
(SQLCHAR*) password, (SQLSMALLINT)((password)?strlen(password):0));
if(string) {
/* no copy of the password left behind */
memset(string, 0, slen);
free(string);
}
if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO){
SQLFreeHandle(SQL_HANDLE_DBC, d->hdbc);
d->hdbc = NULL;
SQLFreeHandle(SQL_HANDLE_ENV, d->henv);
d->henv = NULL;
return 0;
}
retcode = SQLAllocHandle(SQL_HANDLE_STMT, d->hdbc, &d->hstmt);
if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO){
odbcdisconnect(d);
return 0;
}
return 1;
}
static int odbcmatch(const unsigned char *selector){
return *selector == '&';
}
/* A data source down is not an error of the configuration: the log
connects again later. */
static int odbcinit(struct logger *lg){
if(!lg->data && !(lg->data = calloc(1, sizeof(struct logodbc)))) return ENOMEM;
return odbcconnect(lg)? 0 : -1;
}
static int odbcformat(struct logger *lg, struct clientparam *param, const unsigned char *s, time_t sec, unsigned msec, unsigned char *buf){
return logbuft(param, buf, s, (unsigned char *)"\'", sec, msec);
}
static int odbcwrite(struct logger *lg, struct clientparam *param, const unsigned char *buf, int len){
struct logodbc *d = (struct logodbc *)lg->data;
SQLRETURN ret;
if(!d || !d->hstmt) return 1;
ret = SQLExecDirect(d->hstmt, (SQLCHAR *)buf, (SQLINTEGER)len);
if(ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO){
/* the connection may be gone: once more on a new one */
if(!odbcconnect(lg)) return 1;
ret = SQLExecDirect(d->hstmt, (SQLCHAR *)buf, (SQLINTEGER)len);
if(ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) return 1;
}
return 0;
}
static void odbcclose(struct logger *lg){
if(!lg->data) return;
odbcdisconnect((struct logodbc *)lg->data);
free(lg->data);
lg->data = NULL;
}
const struct logtype logtype_odbc = {
"odbc", odbcmatch, odbcinit, odbcformat, odbcwrite, odbcclose, NULL, LOGSTACKSIZE, 0, 0, 1
};
#endif