blob: 776872f2dfb02003519c477896a5cb75960945bf [file]
/* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version
* 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* etchlog.c
* etch c binding logger
*/
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <time.h>
#include <direct.h>
#include <sys/stat.h>
#if IS_WINDOWS_ETCH
#pragma warning(disable:4996) /* potentially unsafe function warning */
#endif
#include "etchthread.h" /* for mutex */
#include "etchlog.h"
#include "etchutl.h"
void etchlog_timestamp (char* buf, const char* component, etchlog_level lvl);
void etchlogw_timestamp(wchar_t* buf, const wchar_t* component, etchlog_level lvl);
int etchlog_copy_logfile(const int);
#define ETCHLOG_STACKBUFSIZE 256
#define ETCHLOG_PREAMBLESIZE 19
#define ETCHLOG_STAMPLEN 12
#define ETCHLOG_CBUFLEN 4
char* ETCHLOGX = "LOGX";
char* etchlog_level_str[] =
{
"X","D","I","W","E"
};
wchar_t* etchlogw_level_str[] =
{
L"X",L"D",L"I",L"W",L"E"
};
char etchlogbuf[ETCHLOG_STACKBUFSIZE];
int etch_loglines, etch_logfiles;
int is_etch_logfile_open, is_etchlog_shutdown, is_etchlog_suspended;
FILE* flog;
char* logpath;
#define ETCHCLIENTLOGDIR "../logcli"
#define ETCHCLIENTLOGPATH "../logcli/etchclient.log"
#define ETCHSERVERLOGDIR "../logsrv"
#define ETCHSERVERLOGPATH "../logsrv/etchserver.log"
#define IS_ETCH_LOG_FILE TRUE
#define IS_ETCH_LOG_CONSOLE TRUE
/**
* etchlog_open()
* log file open.
*/
int etchlog_open (const int is_client)
{
return is_client? etchlog_open_client(): etchlog_open_server();
}
/**
* etchlog_open_server()
* log file open.
*/
int etchlog_open_server()
{
config.calculated_is_client = FALSE;
return etchlog_openx(ETCHSERVERLOGPATH);
}
/**
* etchlog_open_client()
* client log file lazy open.
*/
int etchlog_open_client()
{
config.calculated_is_client = TRUE;
return etchlog_openx(ETCHCLIENTLOGPATH);
}
/**
* etchlog_openx()
* log file lazy open.
* note that the etch .exe directory will be different when the etch is started
* in the debugger, than when it is started from the command line, and so the log
* directories, being relative to the .exe directory, will change accordingly.
*/
int etchlog_openx (char* filepath)
{
int result = 0;
if (is_etch_logfile_open);
else
if (is_etchlog_shutdown)
result = -1;
else
{ const char* dirpath = config.calculated_is_client? ETCHCLIENTLOGDIR: ETCHSERVERLOGDIR;
mkdir (dirpath); /* create the log directory if it does not already exist */
logpath = filepath;
#if(0) /* code to identify effective directory */
{ const char* t = "it works!", *fn = "etchtest.txt";
FILE* f = fopen(fn,"w"); size_t n = fwrite(t,1,strlen(t),f); fclose(f);
printf("*** etchlog_openx wrote %d to %s\n", n, fn);
}
#endif
etchlog_copy_logfile (FALSE); /* back up etchlog.log */
if (flog = fopen(filepath, "w"))
{ is_etch_logfile_open = TRUE;
etch_loglines = 0;
}
else
{ printf("could not open log file %s\n", filepath);
is_etchlog_shutdown = TRUE;
result = -1;
}
}
return result;
}
/**
* etchlog_close()
* currently can't be reopened.
*/
int etchlog_close()
{
if (is_etch_logfile_open);
fclose(flog);
is_etch_logfile_open = FALSE;
is_etchlog_shutdown = TRUE;
return 0;
}
/**
* etchlog_write()
* write specified ansi string to log.
* @return count of bytes written.
*/
int etchlog_write(char* buf)
{
int bytecount = 0, bytesout = 0, result = 0;
if (is_etchlog_suspended) return 0;
if (buf && (0 > (bytecount = (int) strlen(buf))))
if (config.is_log_to_file && is_etch_logfile_open && !is_etchlog_suspended)
if (0 < (bytesout = (int) fwrite(buf, 1, bytecount, flog)))
{ fflush(flog); /* linecount assumes each line has a line feed */
if (++etch_loglines >= config.max_log_lines)
{ result = etchlog_copy_logfile(TRUE); /* copy and reopen */
assert(0 == result);
}
}
return bytesout;
}
/**
* etchlog()
* write ansi string to log.
*/
void etchlog (char *comp, etchlog_level level, const char *fmt, ...)
{
char *buf;
va_list args;
int result = 0, arglen, totallen, charcount;
if (level < config.loglevel) return;
loglock->acquire(loglock); /* todo use a queue instead of serializing? */
do {
va_start(args, fmt);
if (0 > (arglen = _vscprintf (fmt, args) + 1)) break;
if ((totallen = arglen + ETCHLOG_PREAMBLESIZE) > ETCHLOG_STACKBUFSIZE)
buf = malloc(totallen); /* eschew etch_malloc since immediately freed */
else buf = etchlogbuf;
charcount = totallen - 1;
memset(buf,' ', charcount); buf[charcount] = '\0';
if (strlen(comp) > 4) comp[4] = '\0'; /* module id exactly 4 characters */
etchlog_timestamp(buf, comp, level);
vsprintf(buf + ETCHLOG_PREAMBLESIZE, fmt, args);
va_end(fmt);
#if IS_ETCH_LOG_CONSOLE /* log to console, unless configured otherwise */
if (config.is_log_to_console)
printf(buf);
#endif /* IS_ETCH_LOG_CONSOLE */
#if IS_ETCH_LOG_FILE /* log to file, unless configured otherwise */
if (config.is_log_to_file && is_etch_logfile_open && !is_etchlog_suspended)
if (fwrite(buf, 1, charcount, flog) > 0)
{ fflush(flog); /* linecount assumes each line has a line feed */
if (++etch_loglines >= config.max_log_lines)
{ result = etchlog_copy_logfile(TRUE); /* copy and reopen */
if (-1 == result) printf("*** LOGFILE COPY FAILED\n");
assert(0 == result);
}
}
#endif /* IS_ETCH_LOG_FILE */
if (totallen > ETCHLOG_STACKBUFSIZE)
free(buf); /* see etch_malloc comment above */
} while(0);
loglock->release(loglock); /* todo use a queue instead of serializing */
}
/**
* etchlogw()
* write unicode string to log.
*/
void etchlogw (wchar_t *comp, etchlog_level level, const wchar_t *fmt, ...)
{
wchar_t *buf;
va_list args;
int argcharlen, totalcharlen, bytelen;
if (level < config.loglevel) return;
loglock->acquire(loglock); /* todo use a queue instead of serializing? */
do {
va_start(args, fmt);
if (0 > (argcharlen = _vscwprintf(fmt, args) + 1)) break;
totalcharlen = argcharlen + ETCHLOG_PREAMBLESIZE;
bytelen = totalcharlen * sizeof(wchar_t);
if (bytelen > ETCHLOG_STACKBUFSIZE)
buf = malloc(bytelen); /* eschew etch_malloc since immediately freed */
else buf = (wchar_t*) etchlogbuf;
memset (buf, 0, bytelen);
wmemset(buf, L' ', totalcharlen);
if (wcslen(comp) > 4) comp[4] = L'\0'; /* module id exactly 4 characters */
etchlogw_timestamp(buf, comp, level);
/* note that offsets are specified in characters not bytes */
vswprintf(buf + ETCHLOG_PREAMBLESIZE, totalcharlen, fmt, args);
va_end((void*) fmt);
#if IS_ETCH_LOG_CONSOLE /* log to console, unless configured otherwise */
if (config.is_log_to_console)
wprintf(buf);
#endif /* IS_ETCH_LOG_CONSOLE */
#if IS_ETCH_LOG_FILE /* log to file, unless configured otherwise */
if (config.is_log_to_file)
if (0 == etchlog_open_server())
if (fwrite(buf, 1, bytelen, flog) > 0)
{ fflush(flog);
if (++etch_loglines >= config.max_log_lines)
{ const int result = etchlog_copy_logfile(TRUE);
assert(0 == result);
}
}
#endif /* IS_ETCH_LOG_FILE */
if (bytelen > ETCHLOG_STACKBUFSIZE)
free(buf); /* see etch_malloc comment above */
} while(0);
loglock->release(loglock); /* todo use a queue instead of serializing */
}
/**
* etchlog_timestamp()
* sets preamble to supplied buffer which must be at least 20 bytes.
* string length of preamble is always 19.
*/
void etchlog_timestamp(char* buf, const char* component, etchlog_level lvl)
{ /* 012345678901234567 */
/* MMDD HHMMSS CCCC L */
static const char* TIMESTAMPMASK = "%02d%02d %02d%02d%02d ";
char cbuf[ETCHLOG_CBUFLEN+1];
struct tm* t;
time_t l;
time(&l);
t = localtime(&l);
memset (cbuf,' ',ETCHLOG_CBUFLEN+1);
memcpy (cbuf, component, strlen(component));
sprintf(buf, TIMESTAMPMASK, /* t->tm_year+1900, */
t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
memcpy(buf+12, cbuf, ETCHLOG_CBUFLEN);
*(buf+17) = *etchlog_level_str[lvl];
*(buf+19) = '\0'; /* caller will overwrite term */
}
/**
* etchlogw_timestamp()
*/
void etchlogw_timestamp(wchar_t* buf, const wchar_t* component, etchlog_level lvl)
{ /* 012345678901234567 */
/* MMDD HHMMSS CCCC L */
static const wchar_t* TIMESTAMPMASKW = L"%02d%02d %02d%02d%02d ";
wchar_t cbuf[ETCHLOG_CBUFLEN+1];
struct tm* t;
time_t l;
time(&l);
t = localtime(&l);
wmemset (cbuf, L' ', ETCHLOG_CBUFLEN+1);
wmemcpy (cbuf, component, wcslen(component));
swprintf(buf, ETCHLOG_STAMPLEN+1, TIMESTAMPMASKW, /* t->tm_year+1900, */
t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
wmemcpy(buf+12, cbuf, ETCHLOG_CBUFLEN); /* note that numeric constants */
*(buf+17) = *etchlog_level_str[lvl]; /* specify characters not bytes */
*(buf+19) = '\0'; /* caller will overwrite term */
}
/**
* etchlog_get_dirpath()
* get the relative path to the log file directory.
* @return a path string or null.
*/
char* etchlog_get_dirpath()
{
return config.calculated_is_client? ETCHCLIENTLOGDIR: ETCHSERVERLOGDIR;
}
/**
* etchlog_get_logfile_count()
* @return the current running count of log files.
*/
int etchlog_get_logfile_count()
{
return etch_logfiles;
}
/**
* etchlog_set_logfile_count()
* set the initial count of log files.
*/
void etchlog_set_logfile_count(const int count)
{
etch_logfiles = count;
}
/**
* etchlog_get_bkuppath()
* get a backup log file name/relative path.
* @param outbuf buffer to receive the file name/path
* @param outbufsize size of the out buffer, must be a/l 23 + characters plus
* the size of the relative path. if relative directory strings are "../logcli/"
* and "../logsrv/", the return string will be 32 characters including null term.
* @param seqno the start value of the timestamp sequencer.
* @return current sequencer on success, -1 failure. file name and relative path in outbuf.
*/
int etchlog_get_bkuppath (char* outbuf, const int outbufsize, int seqno)
{
/* 0 1 2 3
/* 012345678901234567890123456789012 */
/* ../logcli/YYYYMMDDHHMMSSNNNN.log */
struct tm* t;
char buf[48], *p = 0;
const char *BKMASK = "%04d%02d%02d%02d%02d%02d%04d%s", *DOTLOG = ".log";
time_t l; time(&l);
t = localtime(&l);
strcpy (buf, config.calculated_is_client? ETCHCLIENTLOGDIR: ETCHSERVERLOGDIR);
strcat (buf, "/");
p = buf + (int) strlen(buf);
while(1)
{ sprintf (p, BKMASK, t->tm_year+1900, t->tm_mon+1,
t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, seqno, DOTLOG);
if (!etch_existsfile(buf)) break;
if (++seqno > 9999) return -1;
}
if (outbufsize < ((int) strlen(buf) + 1)) return -1;
strcpy(outbuf, buf);
return seqno + 1;
}
/**
* etchlog_purge_logfiles()
* purge files from current log directory.
* @return count of files purged.
*/
int etchlog_purge_logfiles ()
{
int purgedcount = 0;
if (etch_logfiles >= config.max_log_files + ETCHCONFIGDEF_LOGCOUNT_GRACE)
{
char* dirpath = etchlog_get_dirpath();
const int purgecount = etch_logfiles - config.max_log_files;
etchlog (ETCHLOGX, ETCHLOG_XDEBUG, "purging %d log files from %s ...\n",
purgecount, dirpath);
purgedcount = etchlog_purgefiles (dirpath, purgecount, 0);
etchlog (ETCHLOGX, ETCHLOG_DEBUG, "purged %d log files\n", purgedcount);
etch_logfiles -= purgedcount;
}
return purgedcount;
}
/**
* etchlog_copy_logfile()
* copy primary log file to a backup file.
* @param is_purge_logfiles if true, and count of log files exceeds configured
* maximum plus a grace overage, a number of log files is deleted such that the
* log file count is brought back down to the configured maximum.
* @remarks we may at some point want to move the log file purge to a work thread.
* @return 0 success, -1 failure.
*/
int etchlog_copy_logfile (const int is_purge_logfiles)
{
char newpath[48];
int result = -1, attempts = 0, seqno = 0;
if (NULL == logpath) return -1;
is_etchlog_suspended = TRUE;
if (is_etch_logfile_open)
fclose(flog);
while (attempts++ < 10)
{
if (-1 == (seqno = etchlog_get_bkuppath (newpath, sizeof(newpath), seqno)))
break;
if (0 != rename (logpath, newpath))
continue;
etch_logfiles++;
etch_loglines = 0;
if (is_etch_logfile_open)
if (NULL == (flog = fopen(logpath, "w")))
break;
is_etchlog_suspended = FALSE;
if (is_purge_logfiles)
etchlog_purge_logfiles();
result = 0;
break;
}
return result;
}