blob: d75a5901df84c4967e01cd1c1533b2f7109e1fc5 [file] [log] [blame]
/*
* 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.
*/
#include <apr_env.h>
#include <apr_portable.h>
#include <time.h>
#include "logger.h"
#include "port_atomic.h"
#include "hyport.h"
/**
* A hook for <code>int vfprintf(va_list)</code> function.
*/
typedef int (*VfprintfHook)(FILE *fp, const char *format, va_list args);
/**
* A hook for <code>void exit(int)</code> function.
*/
typedef void (*ExitHook)(int code);
/**
* A hook for <code>void abort()</code> function.
*/
typedef void (*AbortHook)();
struct LogCategory {
const char* name;
int len;
LogState enabled;
struct LogCategory* next;
};
/**
* Keeps a logger instance.
*/
struct Logger {
/**
* Serves as a primary source for a dynamic memory allocation.
*/
apr_pool_t* pool;
/**
* A pointer to vfprintf implementation. This pointer may be received via
* JNI_CreateJavaVM arguments and is used as a base for all VM
* logging. A static pointer is used when a logger instance cannot be
* accessed.
*/
VfprintfHook p_vfprintf;
/**
* A pointer to exit implementation. This pointer may be received via
* JNI_CreateJavaVM arguments and is used for VM graceful termination.
*/
ExitHook p_exit;
/**
* A pointer to abort implementation. This pointer may be received via
* JNI_CreateJavaVM arguments and is used for abnormal VM termination.
*/
AbortHook p_abort;
/**
* A file to write log, <code>stdout</code> by default.
*/
FILE* out;
/**
* A reference to localization library.
*/
struct HyPortLibrary* portlib;
/**
* Format mask.
*/
LogFormat format;
/**
* A head of a list of enabled info domains.
*/
struct LogCategory* info;
/**
* A head of a list of enabled trace domains.
*/
struct LogCategory* trace;
/**
* A head of a list of encountered log sites.
*/
struct LogSite* log_site;
} light_logger = {
NULL, &vfprintf, &exit, &abort, stdout, NULL, LOG_EMPTY, NULL, NULL, NULL };
Logger default_logger = {
NULL, &vfprintf, &exit, &abort, stdout, NULL, LOG_EMPTY, NULL, NULL, NULL };
static Logger* get()
{
return &default_logger;
}
/**
* Clears cached sites. If more sites are added in process from
* other threads, they won't be cleared.
*/
static void clear_cached_sites()
{
LogSite *site = get()->log_site;
while(site) {
site->state = LOG_UNKNOWN;
site = site->next;
}
}
static int add_category(const char* category, LogCategory** p_log_category_head,
int copy, LogState enabled)
{
Logger* logger = get();
if (!logger->pool) {
return 0;
}
LogCategory* log_category = *p_log_category_head;
while (log_category) {
if (strcmp(log_category->name, category) == 0) {
if (log_category->enabled == enabled) {
return 0;
}
log_category->enabled = enabled;
clear_cached_sites();
return 1;
}
log_category = log_category->next;
}
log_category = (LogCategory*) apr_palloc(logger->pool, sizeof(LogCategory));
if (!log_category) {
return 0;
}
log_category->len = (int) strlen(category);
log_category->enabled = enabled;
if (copy) {
char* name = (char*) apr_palloc(logger->pool, log_category->len + 1);
if (!name) {
return 0;
}
strncpy(name, category, log_category->len + 1);
log_category->name = name;
} else {
log_category->name = category;
}
LogCategory* old_value = *p_log_category_head;
do {
log_category->next = (LogCategory*) old_value;
old_value = (LogCategory*) port_atomic_casptr(
(volatile void **) p_log_category_head, log_category,
log_category->next);
} while (old_value != log_category->next);
clear_cached_sites();
return 1;
}
int log_enable_info_category(const char* category, int copy)
{
return add_category(category, &get()->info, copy, LOG_ENABLED);
}
int log_enable_trace_category(const char* category, int copy)
{
return add_category(category, &get()->trace, copy, LOG_ENABLED);
}
int log_disable_info_category(const char* category, int copy)
{
return add_category(category, &get()->info, copy, LOG_DISABLED);
}
int log_disable_trace_category(const char* category, int copy)
{
return add_category(category, &get()->trace, copy, LOG_DISABLED);
}
void log_init(apr_pool_t* parent_pool)
{
apr_pool_t *pool;
apr_status_t status = apr_pool_create(&pool, parent_pool);
if (APR_SUCCESS != status) {
return;
}
Logger* logger = get();
*logger = light_logger;
logger->pool = pool;
log_enable_info_category(LOG_INFO, 0);
}
static void log_close()
{
Logger* logger = (Logger*) get();
if (stdout != logger->out) {
fclose(logger->out);
logger->out = stdout;
}
}
void log_shutdown()
{
Logger* logger = (Logger*) get();
apr_pool_t* pool = logger->pool;
log_close();
*logger = light_logger;
clear_cached_sites();
apr_pool_destroy(pool);
}
/**
* Parses locale.
*/
static int set_locale(const char* logger_locale)
{
HyPortLibrary* portlib = get()->portlib;
assert(portlib);
char* lang = strdup(logger_locale);
if (NULL == lang) {
return 0; // out of C heap
}
char* region = NULL;
char* variant = NULL;
char* pos = strchr(lang, '_');
if (pos == NULL) {
goto set;
}
region = pos;
region[0] = 0;
region++;
pos = strchr(region, '.');
if (pos == NULL) {
goto set;
}
variant = pos;
variant[0] = 0;
variant++;
set:
portlib->nls_set_locale(portlib, lang, region ? region : "", variant ? variant : "");
free((void*)lang);
return 1;
}
void log_set_portlib(HyPortLibrary *portlib)
{
Logger* logger = get();
logger->portlib = portlib;
if (!portlib) {
return;
}
apr_pool_t* tmp_pool;
apr_status_t status = apr_pool_create(&tmp_pool, logger->pool);
if (APR_SUCCESS != status) {
return;
}
char* value;
if (APR_SUCCESS == apr_env_get(&value, "LC_ALL", tmp_pool)) {
set_locale(value);
} else if (APR_SUCCESS == apr_env_get(&value, "LC_MESSAGES", tmp_pool)) {
set_locale(value);
} else if (APR_SUCCESS == apr_env_get(&value, "LANG", tmp_pool)) {
set_locale(value);
}
apr_pool_destroy(tmp_pool);
}
HyPortLibrary* log_get_portlib() {
return get()->portlib;
}
void log_set_vfprintf(void* p_vfprintf)
{
get()->p_vfprintf = (VfprintfHook) p_vfprintf;
}
void log_set_exit(void* p_exit)
{
get()->p_exit = (ExitHook) p_exit;
}
void log_set_abort(void* p_abort)
{
get()->p_abort = (AbortHook) p_abort;
}
void log_set_header_format(LogFormat format)
{
get()->format = format;
}
void log_set_out(FILE* file)
{
log_close();
get()->out = file;
}
APR_DECLARE(void) log_exit(int code)
{
log_shutdown();
get()->p_exit(code);
}
APR_DECLARE(void) log_abort()
{
log_shutdown();
assert(0);
get()->p_abort();
}
APR_DECLARE_NONSTD(int) log_printf(const char* format, ...)
{
va_list args;
int ret;
va_start(args, format);
ret = get()->p_vfprintf(get()->out, format, args);
va_end(args);
fflush(get()->out);
return ret;
}
APR_DECLARE(void) log_header(const char* category, const char* file_line, const char* function_name)
{
LogFormat format = get()->format;
if (format & LOG_THREAD_ID) {
log_printf("[%p] ", apr_os_thread_current());
}
if (format & LOG_TIMESTAMP) {
log_printf("[%umus] ", (unsigned) clock());
}
if (format & LOG_CATEGORY && strcmp(category, LOG_INFO)) {
log_printf("[%s] ", category);
}
if (format & LOG_FUNCTION) {
log_printf("%s:", file_line);
}
if (format & LOG_FILELINE) {
log_printf("%s:", function_name);
}
fflush(get()->out);
}
/**
* Checks if the warnings are enabled.
*/
APR_DECLARE(LogState) log_is_warn_enabled()
{
return (get()->format & LOG_WARN) ? LOG_ENABLED : LOG_DISABLED;
}
/**
* Finds the best matching category in the category list.
* @return if the category is enabled
*/
static LogState is_enabled(const char *category, LogCategory *log_category) {
int max_size = -1;
LogState enabled = LOG_DISABLED;
while (log_category) {
if (strncmp(log_category->name, category, log_category->len) == 0) {
if (log_category->len > max_size) {
max_size = log_category->len;
enabled = log_category->enabled;
}
}
log_category = log_category->next;
}
return enabled;
}
APR_DECLARE(LogState) log_is_info_enabled(const char *category)
{
return is_enabled(category, get()->info);
}
APR_DECLARE(LogState) log_is_trace_enabled(const char *category)
{
return is_enabled(category, get()->trace);
}
/**
* Adds a site to the site list.
*/
static void add_site(LogSite* log_site) {
LogSite** p_site_head = (LogSite**) &get()->log_site;
LogSite* old_value = *p_site_head;
do {
log_site->next = (LogSite*) old_value;
old_value = (LogSite*) port_atomic_casptr(
(volatile void **) p_site_head, log_site, log_site->next);
} while (old_value != log_site->next);
}
APR_DECLARE(LogState) log_cache(LogState enabled, struct LogSite* p_log_site)
{
if(!p_log_site->next) {
add_site(p_log_site);
}
p_log_site->state = enabled ? LOG_ENABLED : LOG_DISABLED;
return enabled;
}