feat hectic C: logger log in file
This commit is contained in:
132
package/c/hectic/docs/file_logging.md
Executable file
132
package/c/hectic/docs/file_logging.md
Executable file
@@ -0,0 +1,132 @@
|
||||
# File Logging in Hectic Library
|
||||
|
||||
This document covers the file logging functionality in the Hectic library, including its configuration and usage in different scenarios.
|
||||
|
||||
## Overview
|
||||
|
||||
Hectic's logging system now supports logging to files, offering three output modes:
|
||||
1. Stderr only (default)
|
||||
2. File only
|
||||
3. Both stderr and file
|
||||
|
||||
This gives you flexibility to route logs where they're most needed while maintaining the same structured logging interface.
|
||||
|
||||
## Configuration Methods
|
||||
|
||||
### 1. Environment Variables
|
||||
|
||||
Configure logging with environment variables:
|
||||
|
||||
```sh
|
||||
# Set log file path
|
||||
export LOG_FILE=/path/to/your/logfile.log
|
||||
|
||||
# Set output mode (STDERR_ONLY, FILE_ONLY, BOTH)
|
||||
export LOG_OUTPUT_MODE=BOTH
|
||||
|
||||
# Set log level as usual
|
||||
export LOG_LEVEL=DEBUG
|
||||
|
||||
# Run your application
|
||||
./your_program
|
||||
```
|
||||
|
||||
### 2. Programmatic Configuration
|
||||
|
||||
Configure logging in your code:
|
||||
|
||||
```c
|
||||
#include "hectic.h"
|
||||
|
||||
int main() {
|
||||
// Initialize logger
|
||||
logger_init();
|
||||
|
||||
// Set log file (returns 0 on success, -1 on failure)
|
||||
if (logger_set_file("/path/to/logfile.log") != 0) {
|
||||
raise_exception("Failed to open log file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Set output mode
|
||||
logger_set_output_mode(LOG_OUTPUT_BOTH);
|
||||
|
||||
// Your application code here
|
||||
raise_info("Application started");
|
||||
|
||||
// Clean up on exit
|
||||
logger_free();
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Output Modes
|
||||
|
||||
### `LOG_OUTPUT_STDERR_ONLY` (Default)
|
||||
- All log messages go to stderr only
|
||||
- No file output even if a log file is set
|
||||
|
||||
### `LOG_OUTPUT_FILE_ONLY`
|
||||
- All log messages go to the log file only
|
||||
- Nothing is printed to stderr (useful for daemon processes)
|
||||
- ANSI color codes are automatically stripped from file output
|
||||
|
||||
### `LOG_OUTPUT_BOTH`
|
||||
- All log messages go to both stderr and the log file
|
||||
- ANSI colors appear on stderr but are stripped from file output
|
||||
|
||||
## File Handling Details
|
||||
|
||||
- Log files are opened in append mode
|
||||
- The library automatically flushes after each log message to ensure logs are written immediately
|
||||
- ANSI color codes are automatically stripped from file output to avoid cluttering log files with escape sequences
|
||||
- If a file cannot be opened, an error message is printed to stderr
|
||||
|
||||
## API Reference
|
||||
|
||||
### Setting the Log File
|
||||
|
||||
```c
|
||||
int logger_set_file(const char *file_path);
|
||||
```
|
||||
|
||||
- **Parameters**: `file_path` - Path to the log file, or NULL to disable file logging
|
||||
- **Returns**: 0 on success, -1 on failure (e.g., unable to open file)
|
||||
- **Notes**:
|
||||
- Automatically closes any previously opened log file
|
||||
- Opens the new file in append mode
|
||||
- If NULL is passed, disables file logging and resets output mode to stderr only
|
||||
|
||||
### Setting the Output Mode
|
||||
|
||||
```c
|
||||
void logger_set_output_mode(LogOutputMode mode);
|
||||
```
|
||||
|
||||
- **Parameters**: `mode` - One of `LOG_OUTPUT_STDERR_ONLY`, `LOG_OUTPUT_FILE_ONLY`, or `LOG_OUTPUT_BOTH`
|
||||
- **Notes**:
|
||||
- Has no effect if file logging is not configured and mode is file-related
|
||||
- Does not check if the log file is successfully opened
|
||||
|
||||
## Example
|
||||
|
||||
See `examples/file_logging_example.c` for a complete working example of file logging.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always check the return value of `logger_set_file()`**:
|
||||
```c
|
||||
if (logger_set_file("/path/to/logfile.log") != 0) {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
2. **Use appropriate output modes**:
|
||||
- For interactive CLI applications: `LOG_OUTPUT_STDERR_ONLY` or `LOG_OUTPUT_BOTH`
|
||||
- For daemon/service applications: `LOG_OUTPUT_FILE_ONLY`
|
||||
- For debugging sessions: `LOG_OUTPUT_BOTH`
|
||||
|
||||
3. **Consider log rotation**: The library doesn't handle log rotation, so for long-running applications, consider external log rotation solutions.
|
||||
|
||||
4. **Close properly**: Always call `logger_free()` to ensure log files are properly closed.
|
||||
52
package/c/hectic/examples/file_logging_example.c
Executable file
52
package/c/hectic/examples/file_logging_example.c
Executable file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* File Logging Example for Hectic Library
|
||||
*
|
||||
* This example demonstrates how to use the file logging capabilities
|
||||
* of the Hectic library, showing both programmatic configuration
|
||||
* and environment variable-based configuration.
|
||||
*/
|
||||
|
||||
#include "../hectic.h"
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// Initialize the logger
|
||||
logger_init();
|
||||
|
||||
// Log a message to stderr
|
||||
raise_info("Starting file logging example");
|
||||
|
||||
// Enable file logging programmatically
|
||||
const char *log_file = "example_log.txt";
|
||||
if (logger_set_file(log_file) != 0) {
|
||||
raise_exception("Failed to open log file: %s", log_file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Set output mode to write to both stderr and file
|
||||
logger_set_output_mode(LOG_OUTPUT_BOTH);
|
||||
|
||||
// Log messages at different levels
|
||||
raise_debug("This is a debug message");
|
||||
raise_info("This is an info message");
|
||||
raise_notice("This is a notice message");
|
||||
raise_warn("This is a warning message");
|
||||
raise_exception("This is an exception message");
|
||||
|
||||
// Switch to file-only mode
|
||||
logger_set_output_mode(LOG_OUTPUT_FILE_ONLY);
|
||||
raise_info("This message will only appear in the log file");
|
||||
|
||||
// Switch back to stderr-only mode
|
||||
logger_set_output_mode(LOG_OUTPUT_STDERR_ONLY);
|
||||
raise_info("This message will only appear on stderr");
|
||||
|
||||
// Clean up
|
||||
logger_free();
|
||||
|
||||
printf("\nLog file demonstration complete. Check %s for logged messages.\n", log_file);
|
||||
printf("\nYou can also run this program with environment variables:\n");
|
||||
printf(" LOG_FILE=custom.log LOG_OUTPUT_MODE=BOTH LOG_LEVEL=DEBUG ./file_logging_example\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
// On systems without strsep, provide a custom implementation
|
||||
@@ -45,6 +46,62 @@ LogLevel current_log_level = LOG_LEVEL_INFO;
|
||||
LogRule *log_rules = NULL;
|
||||
Arena *log_rules_arena = NULL;
|
||||
|
||||
// File logging configuration
|
||||
static FILE *log_file = NULL;
|
||||
static LogOutputMode log_output_mode = LOG_OUTPUT_STDERR_ONLY;
|
||||
static char *log_file_path = NULL;
|
||||
|
||||
/**
|
||||
* Set log output mode
|
||||
* @param mode The output mode (stderr only, file only, or both)
|
||||
*/
|
||||
void logger_set_output_mode(LogOutputMode mode) {
|
||||
log_output_mode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set log file path
|
||||
* @param file_path Path to the log file. If NULL, file logging is disabled.
|
||||
* @return 0 on success, -1 on failure (e.g., unable to open file)
|
||||
*/
|
||||
int logger_set_file(const char *file_path) {
|
||||
// Close current log file if open
|
||||
if (log_file != NULL && log_file != stderr) {
|
||||
fclose(log_file);
|
||||
log_file = NULL;
|
||||
}
|
||||
|
||||
// Free previous path if it exists
|
||||
if (log_file_path != NULL) {
|
||||
free(log_file_path);
|
||||
log_file_path = NULL;
|
||||
}
|
||||
|
||||
// If path is NULL, disable file logging
|
||||
if (file_path == NULL) {
|
||||
log_output_mode = LOG_OUTPUT_STDERR_ONLY;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copy the file path
|
||||
log_file_path = strdup(file_path);
|
||||
if (log_file_path == NULL) {
|
||||
fprintf(stderr, "ERROR: Failed to allocate memory for log file path\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Open the log file
|
||||
log_file = fopen(file_path, "a");
|
||||
if (log_file == NULL) {
|
||||
fprintf(stderr, "ERROR: Failed to open log file %s: %s\n", file_path, strerror(errno));
|
||||
free(log_file_path);
|
||||
log_file_path = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* color_mode_to_string(ColorMode mode) {
|
||||
switch (mode) {
|
||||
case COLOR_MODE_AUTO: return "AUTO";
|
||||
@@ -207,6 +264,35 @@ void logger_init(void) {
|
||||
fprintf(stderr, "INIT: Logger initialized with default level %s\n",
|
||||
log_level_to_string(current_log_level));
|
||||
}
|
||||
|
||||
// Check for file logging environment variables
|
||||
const char* log_file_env = getenv("LOG_FILE");
|
||||
if (log_file_env) {
|
||||
if (logger_set_file(log_file_env) == 0) {
|
||||
fprintf(stderr, "INIT: Logging to file: %s\n", log_file_env);
|
||||
|
||||
// Check for output mode
|
||||
const char* log_mode_env = getenv("LOG_OUTPUT_MODE");
|
||||
if (log_mode_env) {
|
||||
if (strcmp(log_mode_env, "FILE_ONLY") == 0) {
|
||||
logger_set_output_mode(LOG_OUTPUT_FILE_ONLY);
|
||||
fprintf(stderr, "INIT: Log output mode set to FILE_ONLY\n");
|
||||
} else if (strcmp(log_mode_env, "BOTH") == 0) {
|
||||
logger_set_output_mode(LOG_OUTPUT_BOTH);
|
||||
fprintf(stderr, "INIT: Log output mode set to BOTH\n");
|
||||
} else {
|
||||
logger_set_output_mode(LOG_OUTPUT_STDERR_ONLY);
|
||||
fprintf(stderr, "INIT: Log output mode set to STDERR_ONLY\n");
|
||||
}
|
||||
} else {
|
||||
// Default to both if file is specified but mode isn't
|
||||
logger_set_output_mode(LOG_OUTPUT_BOTH);
|
||||
fprintf(stderr, "INIT: Log output mode set to BOTH (default)\n");
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "INIT: Failed to open log file: %s\n", log_file_env);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void logger_free(void) {
|
||||
@@ -216,6 +302,21 @@ void logger_free(void) {
|
||||
free(log_rules_arena);
|
||||
log_rules_arena = NULL;
|
||||
}
|
||||
|
||||
// Close log file if open
|
||||
if (log_file != NULL && log_file != stderr) {
|
||||
fclose(log_file);
|
||||
log_file = NULL;
|
||||
}
|
||||
|
||||
// Free log file path if allocated
|
||||
if (log_file_path != NULL) {
|
||||
free(log_file_path);
|
||||
log_file_path = NULL;
|
||||
}
|
||||
|
||||
// Reset output mode
|
||||
log_output_mode = LOG_OUTPUT_STDERR_ONLY;
|
||||
}
|
||||
|
||||
char* raise_message(
|
||||
@@ -237,25 +338,57 @@ char* raise_message(
|
||||
static char timeStr[20];
|
||||
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", &tm_info);
|
||||
|
||||
// Print timestamp, log level with color, location info
|
||||
fprintf(stderr, "%s %s%s%s %s:%s:%s%d%s ",
|
||||
timeStr,
|
||||
log_level_to_color(level),
|
||||
log_level_to_string(level),
|
||||
OPTIONAL_COLOR(COLOR_RESET),
|
||||
file,
|
||||
func,
|
||||
OPTIONAL_COLOR(COLOR_GREEN),
|
||||
line,
|
||||
OPTIONAL_COLOR(COLOR_RESET));
|
||||
|
||||
// Print the actual message with variable arguments
|
||||
// Format the message first
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
|
||||
// Create a buffer for the message
|
||||
char message_buffer[4096]; // Adjust size as needed
|
||||
int header_len = snprintf(message_buffer, sizeof(message_buffer),
|
||||
"%s %s%s%s %s:%s:%s%d%s ",
|
||||
timeStr,
|
||||
log_level_to_color(level),
|
||||
log_level_to_string(level),
|
||||
OPTIONAL_COLOR(COLOR_RESET),
|
||||
file,
|
||||
func,
|
||||
OPTIONAL_COLOR(COLOR_GREEN),
|
||||
line,
|
||||
OPTIONAL_COLOR(COLOR_RESET));
|
||||
|
||||
// Add the formatted message
|
||||
vsnprintf(message_buffer + header_len, sizeof(message_buffer) - header_len, format, args);
|
||||
va_end(args);
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
// Add newline
|
||||
strcat(message_buffer, "\n");
|
||||
|
||||
// Write to stderr if needed
|
||||
if (log_output_mode == LOG_OUTPUT_STDERR_ONLY || log_output_mode == LOG_OUTPUT_BOTH) {
|
||||
fprintf(stderr, "%s", message_buffer);
|
||||
}
|
||||
|
||||
// Write to file if configured
|
||||
if ((log_output_mode == LOG_OUTPUT_FILE_ONLY || log_output_mode == LOG_OUTPUT_BOTH) && log_file != NULL) {
|
||||
// Remove ANSI color codes for file output
|
||||
char file_buffer[4096];
|
||||
char *src = message_buffer;
|
||||
char *dst = file_buffer;
|
||||
|
||||
while (*src) {
|
||||
if (*src == '\033') {
|
||||
// Skip ANSI escape sequence
|
||||
while (*src && *src != 'm') src++;
|
||||
if (*src) src++; // Skip the 'm'
|
||||
} else {
|
||||
*dst++ = *src++;
|
||||
}
|
||||
}
|
||||
*dst = '\0';
|
||||
|
||||
fprintf(log_file, "%s", file_buffer);
|
||||
fflush(log_file); // Ensure log is written immediately
|
||||
}
|
||||
|
||||
return timeStr;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,29 @@
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/*
|
||||
* Hectic Library - A C utility library
|
||||
*
|
||||
* This library includes several components:
|
||||
* - Logging system with multiple severity levels
|
||||
* - Memory management with arenas
|
||||
* - JSON parsing and serialization
|
||||
* - Template engine
|
||||
*
|
||||
* Logging System Usage:
|
||||
* - Set global log level: logger_level(LOG_LEVEL_DEBUG);
|
||||
* - Log messages: raise_debug("Debug message with %s", value);
|
||||
*
|
||||
* File Logging:
|
||||
* - Set log file: logger_set_file("/path/to/logfile.log");
|
||||
* - Select output mode: logger_set_output_mode(LOG_OUTPUT_BOTH);
|
||||
*
|
||||
* Environment Variables:
|
||||
* - LOG_LEVEL: Set global log level ("TRACE", "DEBUG", etc.)
|
||||
* - LOG_FILE: Set log file path
|
||||
* - LOG_OUTPUT_MODE: Set output mode ("STDERR_ONLY", "FILE_ONLY", "BOTH")
|
||||
*/
|
||||
|
||||
// -------------
|
||||
// -- Helpers --
|
||||
// -------------
|
||||
@@ -224,6 +247,28 @@ typedef struct LogRule {
|
||||
struct LogRule *next; // Next rule in the chain
|
||||
} LogRule;
|
||||
|
||||
/*
|
||||
* Log output mode - controls how logs are written to files
|
||||
*/
|
||||
typedef enum {
|
||||
LOG_OUTPUT_STDERR_ONLY, // Write only to stderr (default)
|
||||
LOG_OUTPUT_FILE_ONLY, // Write only to file
|
||||
LOG_OUTPUT_BOTH // Write to both stderr and file
|
||||
} LogOutputMode;
|
||||
|
||||
/**
|
||||
* Set log output mode
|
||||
* @param mode The output mode (stderr only, file only, or both)
|
||||
*/
|
||||
void logger_set_output_mode(LogOutputMode mode);
|
||||
|
||||
/**
|
||||
* Set log file path
|
||||
* @param file_path Path to the log file. If NULL, file logging is disabled.
|
||||
* @return 0 on success, -1 on failure (e.g., unable to open file)
|
||||
*/
|
||||
int logger_set_file(const char *file_path);
|
||||
|
||||
void logger_level_reset();
|
||||
|
||||
void logger_init(void);
|
||||
|
||||
@@ -2,8 +2,26 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include "hectic.h"
|
||||
|
||||
#define ASSERT_STR_EQ(actual, expected) do { \
|
||||
if (strcmp(actual, expected) != 0) { \
|
||||
fprintf(stderr, "\n--- STRING COMPARISON ERROR ---\n"); \
|
||||
fprintf(stderr, "Expected (%zu bytes):\n'%s'\n", strlen(expected), expected);\
|
||||
fprintf(stderr, "Got (%zu bytes):\n'%s'\n", strlen(actual), actual); \
|
||||
fprintf(stderr, "----------------------------\n"); \
|
||||
for (size_t i = 0; i < strlen(expected) && i < strlen(actual); i++) { \
|
||||
if (expected[i] != actual[i]) { \
|
||||
fprintf(stderr, "First mismatch at position %zu: '%c' != '%c'\n", \
|
||||
i, expected[i], actual[i]); \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
assert(0 && "Strings do not match"); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define TEST_RAISE_GENERIC(LOG_MACRO, LEVEL, LEVEL_STR) do { \
|
||||
FILE *orig_stderr = stderr; \
|
||||
FILE *temp = tmpfile(); \
|
||||
@@ -22,10 +40,156 @@
|
||||
char expected_buffer[256]; \
|
||||
const char* func = __func__; \
|
||||
sprintf(expected_buffer, "%s " LEVEL_STR " " __FILE__ ":%s:%d message\n", time_str, func, __LINE__); \
|
||||
assert(strcmp(result_buffer, expected_buffer) == 0); \
|
||||
ASSERT_STR_EQ(result_buffer, expected_buffer); \
|
||||
} while(0)
|
||||
|
||||
#define TEST_FILE_LOGGING(LOG_MACRO, LEVEL_STR, MESSAGE) do { \
|
||||
char log_path[256]; \
|
||||
snprintf(log_path, sizeof(log_path), "/tmp/hectic-test-%d.log", getpid()); \
|
||||
assert(logger_set_file(log_path) == 0); \
|
||||
logger_set_output_mode(LOG_OUTPUT_FILE_ONLY); \
|
||||
const char* time_str = LOG_MACRO(MESSAGE); \
|
||||
fflush(NULL); \
|
||||
\
|
||||
FILE *log_file = fopen(log_path, "r"); \
|
||||
assert(log_file != NULL); \
|
||||
char file_buffer[256]; \
|
||||
size_t file_read = fread(file_buffer, 1, sizeof(file_buffer)-1, log_file); \
|
||||
file_buffer[file_read] = '\0'; \
|
||||
fclose(log_file); \
|
||||
unlink(log_path); \
|
||||
\
|
||||
char expected[256]; \
|
||||
const char* func = __func__; \
|
||||
snprintf(expected, sizeof(expected), "%s %s %s:%s:%d %s\n", \
|
||||
time_str, LEVEL_STR, __FILE__, func, __LINE__, MESSAGE); \
|
||||
ASSERT_STR_EQ(file_buffer, expected); \
|
||||
logger_free(); \
|
||||
} while(0)
|
||||
|
||||
#define TEST_DUAL_LOGGING(MESSAGE) do { \
|
||||
char log_path[256]; \
|
||||
snprintf(log_path, sizeof(log_path), "/tmp/hectic-test-%d.log", getpid()); \
|
||||
assert(logger_set_file(log_path) == 0); \
|
||||
logger_set_output_mode(LOG_OUTPUT_BOTH); \
|
||||
\
|
||||
FILE *orig_stderr = stderr; \
|
||||
FILE *temp_stderr = tmpfile(); \
|
||||
assert(temp_stderr != NULL); \
|
||||
stderr = temp_stderr; \
|
||||
\
|
||||
raise_info(MESSAGE); \
|
||||
fflush(stderr); \
|
||||
\
|
||||
stderr = orig_stderr; \
|
||||
\
|
||||
FILE *log_file = fopen(log_path, "r"); \
|
||||
assert(log_file != NULL); \
|
||||
char file_buffer[256]; \
|
||||
size_t file_read = fread(file_buffer, 1, sizeof(file_buffer)-1, log_file); \
|
||||
file_buffer[file_read] = '\0'; \
|
||||
fclose(log_file); \
|
||||
\
|
||||
fseek(temp_stderr, 0, SEEK_SET); \
|
||||
char stderr_buffer[256]; \
|
||||
size_t stderr_read = fread(stderr_buffer, 1, sizeof(stderr_buffer)-1, temp_stderr);\
|
||||
stderr_buffer[stderr_read] = '\0'; \
|
||||
fclose(temp_stderr); \
|
||||
\
|
||||
unlink(log_path); \
|
||||
\
|
||||
fprintf(stdout, "stderr content (%zu bytes):\n", stderr_read); \
|
||||
for (size_t i = 0; i < stderr_read; i++) { \
|
||||
unsigned char c = (unsigned char)stderr_buffer[i]; \
|
||||
if (c < 32 || c > 126) \
|
||||
fprintf(stdout, "\\x%02x", c); \
|
||||
else \
|
||||
fputc(c, stdout); \
|
||||
} \
|
||||
fprintf(stdout, "\n"); \
|
||||
\
|
||||
fprintf(stdout, "file content (%zu bytes):\n", file_read); \
|
||||
for (size_t i = 0; i < file_read; i++) { \
|
||||
unsigned char c = (unsigned char)file_buffer[i]; \
|
||||
if (c < 32 || c > 126) \
|
||||
fprintf(stdout, "\\x%02x", c); \
|
||||
else \
|
||||
fputc(c, stdout); \
|
||||
} \
|
||||
fprintf(stdout, "\n"); \
|
||||
\
|
||||
if (strstr(file_buffer, MESSAGE) == NULL) { \
|
||||
fprintf(stderr, "Error: message not found in file.\n"); \
|
||||
fprintf(stderr, "Expected message: %s\n", MESSAGE); \
|
||||
fprintf(stderr, "File content: %s\n", file_buffer); \
|
||||
assert(0); \
|
||||
} \
|
||||
if (strstr(stderr_buffer, MESSAGE) == NULL) { \
|
||||
fprintf(stderr, "Error: message not found in stderr.\n"); \
|
||||
fprintf(stderr, "Expected message: %s\n", MESSAGE); \
|
||||
fprintf(stderr, "stderr content: %s\n", stderr_buffer); \
|
||||
assert(0); \
|
||||
} \
|
||||
\
|
||||
if (strstr(stderr_buffer, "\033") == NULL) { \
|
||||
fprintf(stdout, "Note: ANSI color codes not found in stderr.\n"); \
|
||||
fprintf(stdout, "This is normal if the test is run without color support.\n");\
|
||||
} \
|
||||
\
|
||||
if (strstr(file_buffer, "\033") != NULL) { \
|
||||
fprintf(stderr, "Error: ANSI color codes found in file.\n"); \
|
||||
fprintf(stderr, "File content: %s\n", file_buffer); \
|
||||
assert(0); \
|
||||
} \
|
||||
\
|
||||
logger_free(); \
|
||||
} while(0)
|
||||
|
||||
#define TEST_MODE_SWITCHING() do { \
|
||||
char log_path[256]; \
|
||||
snprintf(log_path, sizeof(log_path), "/tmp/hectic-mode-switch-%d.log", getpid()); \
|
||||
\
|
||||
logger_init(); \
|
||||
assert(logger_set_file(log_path) == 0); \
|
||||
\
|
||||
logger_set_output_mode(LOG_OUTPUT_FILE_ONLY); \
|
||||
raise_info("File only message"); \
|
||||
\
|
||||
logger_set_output_mode(LOG_OUTPUT_BOTH); \
|
||||
raise_info("Both stderr and file message"); \
|
||||
\
|
||||
logger_set_output_mode(LOG_OUTPUT_STDERR_ONLY); \
|
||||
raise_info("Stderr only message"); \
|
||||
\
|
||||
FILE *log_file = fopen(log_path, "r"); \
|
||||
assert(log_file != NULL); \
|
||||
char buffer[1024]; \
|
||||
size_t bytes_read = fread(buffer, 1, sizeof(buffer)-1, log_file); \
|
||||
buffer[bytes_read] = '\0'; \
|
||||
fclose(log_file); \
|
||||
unlink(log_path); \
|
||||
\
|
||||
if (strstr(buffer, "File only message") == NULL) { \
|
||||
fprintf(stderr, "Error: 'File only message' not found in file.\n"); \
|
||||
fprintf(stderr, "File content:\n%s\n", buffer); \
|
||||
assert(0); \
|
||||
} \
|
||||
if (strstr(buffer, "Both stderr and file message") == NULL) { \
|
||||
fprintf(stderr, "Error: 'Both stderr and file message' not found in file.\n"); \
|
||||
fprintf(stderr, "File content:\n%s\n", buffer); \
|
||||
assert(0); \
|
||||
} \
|
||||
if (strstr(buffer, "Stderr only message") != NULL) { \
|
||||
fprintf(stderr, "Error: 'Stderr only message' found in file but should not be there.\n");\
|
||||
fprintf(stderr, "File content:\n%s\n", buffer); \
|
||||
assert(0); \
|
||||
} \
|
||||
\
|
||||
logger_free(); \
|
||||
} while(0)
|
||||
|
||||
int main(void) {
|
||||
debug_color_mode = COLOR_MODE_DISABLE;
|
||||
printf("%sRunning %s%s%s\n", OPTIONAL_COLOR(COLOR_GREEN), OPTIONAL_COLOR(COLOR_CYAN), __FILE__, OPTIONAL_COLOR(COLOR_RESET));
|
||||
|
||||
TEST_RAISE_GENERIC(raise_debug, LOG_LEVEL_DEBUG, "DEBUG");
|
||||
@@ -35,6 +199,19 @@ int main(void) {
|
||||
TEST_RAISE_GENERIC(raise_warn, LOG_LEVEL_WARN, "WARN");
|
||||
TEST_RAISE_GENERIC(raise_exception, LOG_LEVEL_EXCEPTION, "EXCEPTION");
|
||||
|
||||
printf("%sTesting file logging functionality...%s\n", OPTIONAL_COLOR(COLOR_CYAN), OPTIONAL_COLOR(COLOR_RESET));
|
||||
|
||||
logger_init();
|
||||
logger_level(LOG_LEVEL_DEBUG);
|
||||
|
||||
TEST_FILE_LOGGING(raise_info, "INFO", "File output test");
|
||||
TEST_FILE_LOGGING(raise_debug, "DEBUG", "Debug message to file");
|
||||
TEST_FILE_LOGGING(raise_warn, "WARN", "Warning message to file");
|
||||
|
||||
TEST_DUAL_LOGGING("Dual output test message");
|
||||
|
||||
TEST_MODE_SWITCHING();
|
||||
|
||||
printf("%sall tests passed.%s%s%s\n", OPTIONAL_COLOR(COLOR_GREEN), OPTIONAL_COLOR(COLOR_CYAN), __FILE__, OPTIONAL_COLOR(COLOR_RESET));
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user