Files
util.nix/package/c/hectic/docs/logging.md

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:

  1. Environment Variable: Set LOG_LEVEL environment variable

    export LOG_LEVEL=DEBUG
    
  2. Programmatically: Use the logger_level() function

    logger_level(LOG_LEVEL_DEBUG);
    
  3. Compile-Time: Define PRECOMPILED_LOG_LEVEL before 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;
}