Skip to content

Commit

Permalink
log lib
Browse files Browse the repository at this point in the history
  • Loading branch information
KaisenAmin committed Feb 7, 2024
1 parent d5e32c5 commit 5f984fe
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 2 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/


# Visual Studio 2015/2017 cache/options directory
.vs/
Expand Down
64 changes: 64 additions & 0 deletions log/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@

## Example 1 : initialize logger with `log_init`

```c
#include "log/log.h"

int main() {
Log* config = log_init();

if (!config) {
fmt_fprintf(stderr, "Error: Can not Create log object.\n");
exit(-1);
}

return 0;
}
```

## Example 2 : different log level

```c
#include "log/log.h"

int main() {
Log* logger = log_init();
if (!logger) {
fmt_fprintf(stderr, "Failed to initialize logging system.\n");
return -1;
}

log_message(logger, LOG_LEVEL_DEBUG, "This is a debug message - might not be displayed.");
log_message(logger, LOG_LEVEL_INFO, "This is an info message.");
log_message(logger, LOG_LEVEL_WARN, "This is a warning message.");
log_message(logger, LOG_LEVEL_ERROR, "This is an error message.");

// Clean up and deallocate the logging system
log_deallocate(logger);
return 0;
}
```

## Example 3 : Logging with Timestamps Enabled

```c
#include "log/log.h"

int main() {
Log* logger = log_init();
if (!logger) {
fmt_fprintf(stderr, "Error: Cannot create log object.\n");
return -1;
}

// Enable timestamps in log messages
log_enable_timestamp(logger, true);

log_message(logger, LOG_LEVEL_INFO, "Application started with timestamp.");
log_message(logger, LOG_LEVEL_WARN, "Low disk space warning logged with timestamp.");
log_message(logger, LOG_LEVEL_ERROR, "File not found error logged with timestamp.");

log_deallocate(logger);
return 0;
}
```
172 changes: 172 additions & 0 deletions log/log.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#include "log.h"
#include "../file_io/file_writer.h"
#include "../file_io/file_reader.h"
#include "../string/string.h"
#include <string.h>
#include <time.h>
#include <stdlib.h>


static const char* log_level_to_string(LogLevel level) {
switch (level) {
case LOG_LEVEL_DEBUG:
return "DEBUG";
case LOG_LEVEL_INFO:
return "INFO";
case LOG_LEVEL_WARN:
return "WARN";
case LOG_LEVEL_ERROR:
return "ERROR";
case LOG_LEVEL_FATAL:
return "FATAL";
default: return "UNKNOWN";
}
}

Log* log_init() {
Log *config = (Log*) malloc(sizeof(Log));

if (!config) {
#ifdef LOG_ENABLE_LOGGING
fmt_fprintf(stderr, "Error: Memory allocation failed for config in log_init.\n");
#endif
return NULL;
}

config->level = LOG_LEVEL_DEBUG;
config->output = LOG_OUTPUT_BOTH;
config->file_writer = file_writer_open("log.txt", WRITE_TEXT);

if (!config->file_writer) {
#ifdef LOG_ENABLE_LOGGING
fmt_fprintf(stderr, "Error: Failed to open log file. Defaulting to console output in log_init.\n");
#endif
config->output = LOG_OUTPUT_CONSOLE;
}

config->file_reader = NULL;
config->enable_timestamp = true;
config->enable_log_level = true;

#ifdef LOG_ENABLE_LOGGING
fmt_fprintf(stdout, "Info: Logging system initialized in log_init.\n");
#endif

return config;
}

bool log_set_output(Log* config, LogOutput output) {
if (!config) {
#ifdef LOG_ENABLE_LOGGING
fmt_fprintf(stderr, "Error: log object is null and invalid.\n");
#endif
return false;
}

config->output = output;
if (output == LOG_OUTPUT_FILE && !config->file_writer) {
// Attempt to open log file if not already open
config->file_writer = file_writer_open("log.txt", WRITE_TEXT);
if (!config->file_writer) {
config->output = LOG_OUTPUT_CONSOLE;
}

#ifdef LOG_ENABLE_LOGGING
fmt_fprintf(stderr, "Success: LogOutput set successfully.\n");
#endif
return true;
}
#ifdef LOG_ENABLE_LOGGING
fmt_fprintf(stderr, "Error: LogOutput not set.\n");
#endif
return false;
}

bool log_enable_timestamp(Log* config, bool enable) {
if (!config) {
#ifdef LOG_ENABLE_LOGGING
fmt_fprintf(stderr, "Error: log configuration object is null in log_enable_timestamp.\n");
#endif
return false;
}
config->enable_timestamp = enable;
return true;
}

void log_message(Log* config, LogLevel level, const char* message, ...) {
if (!config) {
#ifdef LOG_ENABLE_LOGGING
fmt_fprintf(stderr, "Error: Log configuration is null.\n");
#endif
return;
}

if (level < config->level) {
#ifdef LOG_ENABLE_LOGGING
fmt_fprintf(stderr, "Error: Current log level (%d) is higher than the message log level (%d); message not logged.\n", config->level, level);
#endif
return;
}

va_list args;
char formatted_message[1024]; // Use a fixed size buffer for simplicity
char log_buffer[2048]; // Larger buffer to accommodate timestamps, log level, and message

// Generate timestamp if enabled
char timestamp[64] = ""; // Buffer to hold timestamp
if (config->enable_timestamp) {
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info); // Customize this format as needed
}

// Prepare log message with vsnprintf to prevent buffer overflows
va_start(args, message);
vsnprintf(formatted_message, sizeof(formatted_message), message, args);
va_end(args);

// Construct the final log message with optional log level and timestamp
const char* level_str = log_level_to_string(level);
snprintf(log_buffer, sizeof(log_buffer), "%s [%s] %s", timestamp, level_str, formatted_message);

// Actual logging
if (config->output == LOG_OUTPUT_CONSOLE || config->output == LOG_OUTPUT_BOTH) {
fmt_fprintf(stdout, "%s\n", log_buffer);
}
if ((config->output == LOG_OUTPUT_FILE || config->output == LOG_OUTPUT_BOTH) && config->file_writer) {
fmt_fprintf(config->file_writer->file_writer, "%s\n", log_buffer);
}
}

void log_deallocate(Log* config) {
if (!config) {
#ifdef LOG_ENABLE_LOGGING
fmt_fprintf(stderr, "Error: Log configuration is null in log_deallocate.\n");
#endif
return;
}

// Close the file writer if it's open
if (config->file_writer) {
file_writer_close(config->file_writer);
config->file_writer = NULL; // Avoid dangling pointer
#ifdef LOG_ENABLE_LOGGING
fmt_fprintf(stderr, "Success: file_writer of Log now is free in log_deallocate.\n");
#endif
}
// if you had allocated memory for `file_reader`
if (config->file_reader) {
file_reader_close(config->file_reader);
config->file_reader = NULL;
#ifdef LOG_ENABLE_LOGGING
fmt_fprintf(stderr, "Success: file_reader of Log now is free in log_deallocate.\n");
#endif
}

// Finally, free the log config itself
free(config);

#ifdef LOG_ENABLE_LOGGING
fmt_fprintf(stderr, "Success: Log Object now is free in log_deallocate.\n");
#endif
}
51 changes: 51 additions & 0 deletions log/log.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#ifndef LOG_H_
#define LOG_H_

#include <stddef.h>
#include <stdbool.h>
#include <stdarg.h>
#include "../fmt/fmt.h"

#define LOG_ENABLE_LOGGING
// Log levels
typedef enum {
LOG_LEVEL_DEBUG,
LOG_LEVEL_INFO,
LOG_LEVEL_WARN,
LOG_LEVEL_ERROR,
LOG_LEVEL_FATAL
} LogLevel;

// Log output options
typedef enum {
LOG_OUTPUT_CONSOLE,
LOG_OUTPUT_FILE,
LOG_OUTPUT_BOTH
} LogOutput;

// Log configuration structure
typedef struct {
LogLevel level;
LogOutput output;
FileWriter* file_writer;
FileReader* file_reader;
bool enable_timestamp;
bool enable_log_level;
} Log;

// Initialize the logging system
Log* log_init();

// Log a message at a specified level
void log_message(Log* config, LogLevel level, const char* message, ...);

// Set the log output
bool log_set_output(Log* config, LogOutput output);

// Enable or disable timestamps in log messages
bool log_enable_timestamp(Log* config, bool enable);

// Clean up the logging system
void log_deallocate(Log* config);

#endif // LOG_H_

0 comments on commit 5f984fe

Please sign in to comment.