5.4 KiB
Executable File
Hectic Library Logging System
This document provides guidelines on how to use the logging system in the Hectic library effectively.
Log Levels
The Hectic library implements a graduated severity-based logging system with the following levels (from lowest to highest severity):
TRACE
Purpose: Most detailed information for in-depth debugging
- Use for: Deep diagnostic details, function entry/exit, variable dumps
- Visibility: Development environments only
- Performance Impact: High
- Example:
raise_trace("Entering function with value=%d, ptr=%p", value, ptr);
DEBUG
Purpose: Detailed information useful during development
- Use for: Development-time debugging, showing variable states, internal flows
- Visibility: Development and debugging environments
- Performance Impact: Medium-High
- Example:
raise_debug("Buffer allocated with size %zu bytes at %p", size, buffer);
LOG
Purpose: General operational events
- Use for: Runtime events worth logging but not requiring attention
- Visibility: Always written to logs, useful for auditing/diagnostics
- Performance Impact: Medium
- Example:
raise_log("Processing file %s, size: %zu bytes", filename, file_size);
INFO
Purpose: Informational messages highlighting progress
- Use for: Normal but noteworthy events, state changes, startup/shutdown events
- Visibility: Visible to client applications if configured
- Performance Impact: Low-Medium
- Example:
raise_info("Connection established to %s, session ID: %s", host, session_id);
NOTICE
Purpose: More important events than INFO, but not warnings
- Use for: Important state changes, significant operations, configuration changes
- Visibility: Displayed to client by default
- Performance Impact: Low
- Example:
raise_notice("Switching to backup server due to high load");
WARN
Purpose: Potential problems that don't prevent normal operation
- Use for: Unexpected behaviors, deprecated feature usage, recoverable errors
- Visibility: Alerts both client and server logs
- Performance Impact: Low
- Example:
raise_warn("API call retry limit (%d) reached for endpoint %s", retries, endpoint);
EXCEPTION
Purpose: Serious errors requiring immediate attention
- Use for: Critical failures, data loss risks, business rule violations
- Visibility: Highest priority, often leads to operation termination
- Performance Impact: Low
- Example:
raise_exception("Failed to open database: %s", error_msg);
Setting the Log Level
You can control the verbosity of logs in three ways:
-
Environment Variable: Set
LOG_LEVELenvironment variableexport LOG_LEVEL=DEBUG -
Programmatically: Use the
logger_level()functionlogger_level(LOG_LEVEL_DEBUG); -
Compile-Time: Define
PRECOMPILED_LOG_LEVELbefore including hectic.h#define PRECOMPILED_LOG_LEVEL LOG_LEVEL_INFO #include <hectic.h>
Logging Best Practices
DO
- Be specific and concise: Include relevant details but avoid verbose descriptions
- Include context: Add identifiers (IDs, filenames, pointers) to help with troubleshooting
- Use the appropriate level: Understand the purpose of each level and use it accordingly
- Log state transitions: Important changes in application state should be logged
- Use structured data: When possible, include structured information rather than unformatted text
DON'T
- Log sensitive information: Never log passwords, tokens, or personal information
- Overuse high-severity levels: Reserve WARN and EXCEPTION for real issues
- Log in tight loops: Avoid excessive logging in performance-critical paths
- Use inconsistent formats: Follow a consistent message format throughout your code
- Ignore log levels: Don't use DEBUG for important operational events or EXCEPTION for minor issues
Examples of Good Logging
Error Handling Pattern
void *allocate_resource(size_t size) {
raise_debug("Allocating %zu bytes", size);
void *ptr = malloc(size);
if (!ptr) {
raise_exception("Memory allocation failed for %zu bytes", size);
return NULL;
}
raise_debug("Successfully allocated %zu bytes at %p", size, ptr);
return ptr;
}
State Transition Pattern
void change_connection_state(Connection *conn, ConnState new_state) {
raise_debug("Connection %p state change requested: %s -> %s",
conn, conn_state_to_string(conn->state), conn_state_to_string(new_state));
if (!is_valid_transition(conn->state, new_state)) {
raise_warn("Invalid state transition from %s to %s",
conn_state_to_string(conn->state), conn_state_to_string(new_state));
return;
}
ConnState old_state = conn->state;
conn->state = new_state;
raise_info("Connection %s state changed: %s -> %s",
conn->id, conn_state_to_string(old_state), conn_state_to_string(new_state));
}
Operation Pattern
int process_file(const char *filename) {
raise_log("Processing file: %s", filename);
FILE *f = fopen(filename, "r");
if (!f) {
raise_warn("Could not open file %s: %s", filename, strerror(errno));
return -1;
}
// Process file...
raise_info("Successfully processed file %s: %d records", filename, record_count);
return 0;
}