Skip to content

Commit

Permalink
Move base log interface up into common (#416)
Browse files Browse the repository at this point in the history
  • Loading branch information
bretambrose authored Jun 12, 2019
1 parent f674342 commit ba1a782
Show file tree
Hide file tree
Showing 15 changed files with 953 additions and 2 deletions.
230 changes: 230 additions & 0 deletions include/aws/common/logging.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
#ifndef AWS_COMMON_LOGGING_H
#define AWS_COMMON_LOGGING_H

/*
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 <aws/common/common.h>

#define AWS_LOG_LEVEL_NONE 0
#define AWS_LOG_LEVEL_FATAL 1
#define AWS_LOG_LEVEL_ERROR 2
#define AWS_LOG_LEVEL_WARN 3
#define AWS_LOG_LEVEL_INFO 4
#define AWS_LOG_LEVEL_DEBUG 5
#define AWS_LOG_LEVEL_TRACE 6

/**
* Controls what log calls pass through the logger and what log calls get filtered out.
* If a log level has a value of X, then all log calls using a level <= X will appear, while
* those using a value > X will not occur.
*
* You can filter both dynamically (by setting the log level on the logger object) or statically
* (by defining AWS_STATIC_LOG_LEVEL to be an appropriate integer module-wide). Statically filtered
* log calls will be completely compiled out but require a rebuild if you want to get more detail
* about what's happening.
*/
enum aws_log_level {
AWS_LL_NONE = AWS_LOG_LEVEL_NONE,
AWS_LL_FATAL = AWS_LOG_LEVEL_FATAL,
AWS_LL_ERROR = AWS_LOG_LEVEL_ERROR,
AWS_LL_WARN = AWS_LOG_LEVEL_WARN,
AWS_LL_INFO = AWS_LOG_LEVEL_INFO,
AWS_LL_DEBUG = AWS_LOG_LEVEL_DEBUG,
AWS_LL_TRACE = AWS_LOG_LEVEL_TRACE,

AWS_LL_COUNT
};

/**
* Log subject is a way of designating the topic of logging.
*
* The general idea is to support a finer-grained approach to log level control. The primary use case
* is for situations that require more detailed logging within a specific domain, where enabling that detail
* globally leads to an untenable flood of information.
*
* For example, enable TRACE logging for tls-related log statements (handshake binary payloads), but
* only WARN logging everywhere else (because http payloads would blow up the log files).
*
* Log subject is an enum similar to aws error: each library has its own value-space and someone is
* responsible for registering the value <-> string connections.
*/
typedef uint32_t aws_log_subject_t;

#define AWS_LOG_SUBJECT_BIT_SPACE 10
#define AWS_LOG_SUBJECT_SPACE_SIZE (1 << AWS_LOG_SUBJECT_BIT_SPACE)
#define AWS_LOG_SUBJECT_SPACE_MASK (AWS_LOG_SUBJECT_SPACE_SIZE - 1)

struct aws_log_subject_info {
aws_log_subject_t subject_id;
const char *subject_name;
const char *subject_description;
};

#define DEFINE_LOG_SUBJECT_INFO(id, name, desc) \
{ .subject_id = (id), .subject_name = (name), .subject_description = (desc) }

struct aws_log_subject_info_list {
struct aws_log_subject_info *subject_list;
size_t count;
};

enum aws_common_log_subject {
AWS_LS_COMMON_GENERAL = 0,
AWS_LS_COMMON_TASK_SCHEDULER,

AWS_LS_COMMON_LAST = (AWS_LS_COMMON_GENERAL + AWS_LOG_SUBJECT_SPACE_SIZE - 1)
};

struct aws_logger;

/**
* We separate the log level function from the log call itself so that we can do the filter check in the macros (see
* below)
*
* By doing so, we make it so that the variadic format arguments are not even evaluated if the filter check does not
* succeed.
*/
struct aws_logger_vtable {
int (*const log)(
struct aws_logger *logger,
enum aws_log_level log_level,
aws_log_subject_t subject,
const char *format,
...)
#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
__attribute__((format(printf, 4, 5)))
#endif /* non-ms compilers: TODO - find out what versions format support was added in */
;
enum aws_log_level (*const get_log_level)(struct aws_logger *logger, aws_log_subject_t subject);
void (*const clean_up)(struct aws_logger *logger);
};

struct aws_logger {
struct aws_logger_vtable *vtable;
struct aws_allocator *allocator;
void *p_impl;
};

/**
* The base formatted logging macro that all other formatted logging macros resolve to.
* Checks for a logger and filters based on log level.
*
*/
#define AWS_LOGF(log_level, subject, ...) \
{ \
AWS_ASSERT(log_level > 0); \
struct aws_logger *logger = aws_logger_get(); \
if (logger != NULL && logger->vtable->get_log_level(logger, (subject)) >= (log_level)) { \
logger->vtable->log(logger, log_level, subject, __VA_ARGS__); \
} \
}

/**
* LOGF_<level> variants for each level. These are what should be used directly to do all logging.
*
* i.e.
*
* LOGF_FATAL("Device \"%s\" not found", device->name);
*
*
* Later we will likely expose Subject-aware variants
*/
#if !defined(AWS_STATIC_LOG_LEVEL) || (AWS_STATIC_LOG_LEVEL >= AWS_LOG_LEVEL_FATAL)
# define AWS_LOGF_FATAL(subject, ...) AWS_LOGF(AWS_LL_FATAL, subject, __VA_ARGS__)
#else
# define AWS_LOGF_FATAL(subject, ...)
#endif

#if !defined(AWS_STATIC_LOG_LEVEL) || (AWS_STATIC_LOG_LEVEL >= AWS_LOG_LEVEL_ERROR)
# define AWS_LOGF_ERROR(subject, ...) AWS_LOGF(AWS_LL_ERROR, subject, __VA_ARGS__)
#else
# define AWS_LOGF_ERROR(subject, ...)
#endif

#if !defined(AWS_STATIC_LOG_LEVEL) || (AWS_STATIC_LOG_LEVEL >= AWS_LOG_LEVEL_WARN)
# define AWS_LOGF_WARN(subject, ...) AWS_LOGF(AWS_LL_WARN, subject, __VA_ARGS__)
#else
# define AWS_LOGF_WARN(subject, ...)
#endif

#if !defined(AWS_STATIC_LOG_LEVEL) || (AWS_STATIC_LOG_LEVEL >= AWS_LOG_LEVEL_INFO)
# define AWS_LOGF_INFO(subject, ...) AWS_LOGF(AWS_LL_INFO, subject, __VA_ARGS__)
#else
# define AWS_LOGF_INFO(subject, ...)
#endif

#if !defined(AWS_STATIC_LOG_LEVEL) || (AWS_STATIC_LOG_LEVEL >= AWS_LOG_LEVEL_DEBUG)
# define AWS_LOGF_DEBUG(subject, ...) AWS_LOGF(AWS_LL_DEBUG, subject, __VA_ARGS__)
#else
# define AWS_LOGF_DEBUG(subject, ...)
#endif

#if !defined(AWS_STATIC_LOG_LEVEL) || (AWS_STATIC_LOG_LEVEL >= AWS_LOG_LEVEL_TRACE)
# define AWS_LOGF_TRACE(subject, ...) AWS_LOGF(AWS_LL_TRACE, subject, __VA_ARGS__)
#else
# define AWS_LOGF_TRACE(subject, ...)
#endif

AWS_EXTERN_C_BEGIN

/**
* Sets the aws logger used globally across the process. Not thread-safe. Must only be called once.
*/
AWS_COMMON_API
void aws_logger_set(struct aws_logger *logger);

/**
* Gets the aws logger used globally across the process.
*/
AWS_COMMON_API
struct aws_logger *aws_logger_get(void);

/**
* Cleans up all resources used by the logger; simply invokes the clean_up v-function
*/
AWS_COMMON_API
void aws_logger_clean_up(struct aws_logger *logger);

/**
* Converts a log level to a c-string constant. Intended primarily to support building log lines that
* include the level in them, i.e.
*
* [ERROR] 10:34:54.642 01-31-19 - Json parse error....
*/
AWS_COMMON_API
int aws_log_level_to_string(enum aws_log_level log_level, const char **level_string);

/**
* Get subject name from log subject.
*/
AWS_COMMON_API
const char *aws_log_subject_name(aws_log_subject_t subject);

/**
* Connects log subject strings with log subject integer values
*/
AWS_COMMON_API
void aws_register_log_subject_info_list(struct aws_log_subject_info_list *log_subject_list);

/**
* Load aws-c-commons's log subject strings.
*/
AWS_COMMON_API
void aws_common_load_log_subject_strings(void);

AWS_EXTERN_C_END

#endif /* AWS_COMMON_LOGGING_H */
167 changes: 167 additions & 0 deletions source/logging.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
* Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 <aws/common/logging.h>

static enum aws_log_level s_null_logger_get_log_level(struct aws_logger *logger, aws_log_subject_t subject) {
(void)logger;
(void)subject;

return AWS_LL_NONE;
}

static int s_null_logger_log(
struct aws_logger *logger,
enum aws_log_level log_level,
aws_log_subject_t subject,
const char *format,
...) {

(void)logger;
(void)log_level;
(void)subject;
(void)format;

return AWS_OP_SUCCESS;
}

static void s_null_logger_clean_up(struct aws_logger *logger) {
(void)logger;
}

static struct aws_logger_vtable s_null_vtable = {
.get_log_level = s_null_logger_get_log_level,
.log = s_null_logger_log,
.clean_up = s_null_logger_clean_up,
};

static struct aws_logger s_null_logger = {.vtable = &s_null_vtable, .allocator = NULL, .p_impl = NULL};

static struct aws_logger *s_root_logger_ptr = &s_null_logger;

void aws_logger_set(struct aws_logger *logger) {
if (logger != NULL) {
s_root_logger_ptr = logger;
} else {
s_root_logger_ptr = &s_null_logger;
}
}

struct aws_logger *aws_logger_get(void) {
return s_root_logger_ptr;
}

void aws_logger_clean_up(struct aws_logger *logger) {
AWS_ASSERT(logger->vtable->clean_up != NULL);

logger->vtable->clean_up(logger);
}

static const char *s_log_level_strings[AWS_LL_COUNT] = {"NONE ", "FATAL", "ERROR", "WARN ", "INFO ", "DEBUG", "TRACE"};

int aws_log_level_to_string(enum aws_log_level log_level, const char **level_string) {
if (log_level >= AWS_LL_COUNT) {
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}

if (level_string != NULL) {
*level_string = s_log_level_strings[log_level];
}

return AWS_OP_SUCCESS;
}

#ifndef AWS_MAX_LOG_SUBJECT_SLOTS
# define AWS_MAX_LOG_SUBJECT_SLOTS 16u
#endif

static const uint32_t S_MAX_LOG_SUBJECT = AWS_LOG_SUBJECT_SPACE_SIZE * AWS_MAX_LOG_SUBJECT_SLOTS - 1;

static const struct aws_log_subject_info_list *volatile s_log_subject_slots[AWS_MAX_LOG_SUBJECT_SLOTS] = {0};

static const struct aws_log_subject_info *s_get_log_subject_info_by_id(aws_log_subject_t subject) {
if (subject > S_MAX_LOG_SUBJECT) {
return NULL;
}

uint32_t slot_index = subject >> AWS_LOG_SUBJECT_BIT_SPACE;
uint32_t subject_index = subject & AWS_LOG_SUBJECT_SPACE_MASK;

const struct aws_log_subject_info_list *subject_slot = s_log_subject_slots[slot_index];

if (!subject_slot || subject_index >= subject_slot->count) {
return NULL;
}

return &subject_slot->subject_list[subject_index];
}

const char *aws_log_subject_name(aws_log_subject_t subject) {
const struct aws_log_subject_info *subject_info = s_get_log_subject_info_by_id(subject);

if (subject_info != NULL) {
return subject_info->subject_name;
}

return "Unknown";
}

void aws_register_log_subject_info_list(struct aws_log_subject_info_list *log_subject_list) {
(void)log_subject_list;

/*
* We're not so worried about these asserts being removed in an NDEBUG build
* - we'll either segfault immediately (for the first two) or for the count
* assert, the registration will be ineffective.
*/
AWS_ASSERT(log_subject_list);
AWS_ASSERT(log_subject_list->subject_list);
AWS_ASSERT(log_subject_list->count);

uint32_t min_range = log_subject_list->subject_list[0].subject_id;

uint32_t slot_index = min_range >> AWS_LOG_SUBJECT_BIT_SPACE;

AWS_ASSERT(slot_index < AWS_MAX_LOG_SUBJECT_SLOTS);

if (slot_index >= AWS_MAX_LOG_SUBJECT_SLOTS) {
/* This is an NDEBUG build apparently. Kill the process rather than
* corrupting heap. */
fprintf(stderr, "Bad log subject slot index 0x%016x\n", slot_index);
abort();
}

s_log_subject_slots[slot_index] = log_subject_list;
}

static struct aws_log_subject_info s_common_log_subject_infos[] = {
DEFINE_LOG_SUBJECT_INFO(
AWS_LS_COMMON_GENERAL,
"aws-c-common",
"Subject for aws-c-common logging that doesn't belong to any particular category"),
DEFINE_LOG_SUBJECT_INFO(
AWS_LS_COMMON_TASK_SCHEDULER,
"task-scheduler",
"Subject for task scheduler or task specific logging."),
};

static struct aws_log_subject_info_list s_common_log_subject_list = {
.subject_list = s_common_log_subject_infos,
.count = AWS_ARRAY_SIZE(s_common_log_subject_infos),
};

void aws_common_load_log_subject_strings(void) {
aws_register_log_subject_info_list(&s_common_log_subject_list);
}
Loading

0 comments on commit ba1a782

Please sign in to comment.