Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Tests

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Configure CMake
run: cmake -B build -DBUILD_TESTS=ON
- name: Build
run: cmake --build build --config Release
- name: Run tests
run: ctest --test-dir build -C Release --output-on-failure
57 changes: 45 additions & 12 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,24 +1,57 @@
cmake_minimum_required(VERSION 3.20)
project(starlet_logger VERSION 1.0.0)

set(LOG_NAME starlet_logger)
SET(LOG_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")
set(LOG_INC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/inc")
set_property(GLOBAL PROPERTY USE_FOLDERS ON)

if(NOT TARGET ${LOG_NAME})
add_library(${LOG_NAME} STATIC)
SET(PROJECT_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")
set(PROJECT_INC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/inc")

file(GLOB_RECURSE LOG_SRC CONFIGURE_DEPENDS ${LOG_SRC_DIR}/*.cpp)
file(GLOB_RECURSE LOG_HEADERS CONFIGURE_DEPENDS ${LOG_INC_DIR}/starlet-logger/*.hpp)
if(NOT TARGET ${PROJECT_NAME})
add_library(${PROJECT_NAME} STATIC)

target_sources(${LOG_NAME} PRIVATE ${LOG_SRC} ${LOG_HEADERS})
file(GLOB_RECURSE PROJECT_SRC CONFIGURE_DEPENDS
${PROJECT_SRC_DIR}/*.cpp
)
file(GLOB_RECURSE PROJECT_HEADERS CONFIGURE_DEPENDS
${PROJECT_INC_DIR}/starlet-logger/*.hpp
)

target_include_directories(${LOG_NAME}
target_sources(${PROJECT_NAME}
PRIVATE ${PROJECT_SRC} ${PROJECT_HEADERS})

target_include_directories(${PROJECT_NAME}
PUBLIC
$<BUILD_INTERFACE:${LOG_INC_DIR}>
$<BUILD_INTERFACE:${PROJECT_INC_DIR}>
$<INSTALL_INTERFACE:include>
)

target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)

# IDE organization
source_group(TREE ${LOG_SRC_DIR} PREFIX "Source Files" FILES ${LOG_SRC})
source_group(TREE ${LOG_INC_DIR} PREFIX "Header Files" FILES ${LOG_HEADERS})
source_group(TREE ${PROJECT_SRC_DIR} PREFIX "Source Files" FILES ${PROJECT_SRC})
source_group(TREE ${PROJECT_INC_DIR} PREFIX "Header Files" FILES ${PROJECT_HEADERS})
endif()

option(BUILD_TESTS "Build unit tests" OFF)

if(BUILD_TESTS)
enable_testing()

include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)

# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

FetchContent_MakeAvailable(googletest)

set_target_properties(gtest gtest_main gmock gmock_main PROPERTIES
FOLDER "Tests/GoogleTest"
)

add_subdirectory(tests)
endif()
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
# Starlet Logger

![Tests](https://github.com/masonlet/starlet-logger/actions/workflows/test.yml/badge.svg)
[![C++20](https://img.shields.io/badge/C%2B%2B-17-blue.svg)](https://isocpp.org/std/the-standard)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)

A lightweight logging utility for C++ applications.

## Features
- Four log levels: Info, Debug, Warning, Error
- Automatic stream routing (stdout for info/debug, stderror for warnings/errors)
- Caller, function, and msg context in every log message
- Debug logs compiled out in release builds (NDEBUG)
- Custom warning return value for permissible warnings

<br/>

## Prerequisites
- C++17 or later
- CMake 3.20+

## Using as a Dependency

```cmake
Expand All @@ -15,3 +32,26 @@ FetchContent_MakeAvailable(starlet_logger)

target_link_libraries(app_name PRIVATE starlet_logger)
```

<br/>

## Building and Testing
```bash
# 1. Clone starlet-logger
git clone https://github.com/masonlet/starlet-logger.git
cd starlet-logger

# 2. Create a build directory and generate build files
mkdir build
cd build
cmake -DBUILD_TESTS=ON ..

# 3. Build and run tests
cmake --build .
ctest
```

<br/>

## License
MIT License - see [LICENSE](./LICENSE) for details.
8 changes: 4 additions & 4 deletions inc/starlet-logger/logger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

namespace Starlet::Logger {
enum class Level {
Debug,
Info,
Debug,
Warning,
Error
};

void log(Level level, const char* caller, const char* function, const std::string& msg);

bool log(const char* caller, const char* function, const std::string& msg);
bool debug(const char* caller, const char* function, const std::string& msg);
bool warning(const char* caller, const char* function, const std::string& msg, bool retValue = false);
bool error(const char* caller, const char* function, const std::string& msg);
bool debugLog(const char* caller, const char* function, const std::string& msg, bool returnValue = true);
}
31 changes: 21 additions & 10 deletions src/logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,32 @@ namespace Starlet::Logger {
default: return "UNKNOWN";
}
}
}

void log(Level level, const char* caller, const char* function, const std::string& msg) {
FILE* output = (level == Level::Error) ? stderr : stdout;
fprintf(output, "[%s %s %s] %s\n", caller, function, lvlStr(level), msg.c_str());
void _log(Level level, const char* caller, const char* function, const std::string& msg, bool retValue = true) {
FILE* output = (retValue == false) ? stderr : stdout;
fprintf(output, "[%s %s %s] %s\n", caller, function, lvlStr(level), msg.c_str());
}
}

bool error(const char* caller, const char* function, const std::string& msg) {
log(Level::Error, caller, function, msg);
return false;
bool log(const char* caller, const char* function, const std::string& msg) {
_log(Level::Info, caller, function, msg);
return true;
}
bool debugLog(const char* caller, const char* function, const std::string& msg, bool returnValue) {

bool debug(const char* caller, const char* function, const std::string& msg) {
#ifndef NDEBUG
log(Level::Debug, caller, function, msg);
_log(Level::Debug, caller, function, msg);
#endif
return returnValue;
return true;
}

bool warning(const char* caller, const char* function, const std::string& msg, bool retValue) {
_log(Level::Warning, caller, function, msg, retValue);
return retValue;
}

bool error(const char* caller, const char* function, const std::string& msg) {
_log(Level::Error, caller, function, msg, false);
return false;
}
}
16 changes: 16 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
add_executable(${PROJECT_NAME}_tests
logger_test.cpp
)

target_link_libraries(${PROJECT_NAME}_tests
PRIVATE
${PROJECT_NAME}
GTest::gtest_main
)

set_target_properties(${PROJECT_NAME}_tests PROPERTIES
FOLDER "Tests"
)

include(GoogleTest)
gtest_discover_tests(${PROJECT_NAME}_tests)
70 changes: 70 additions & 0 deletions tests/logger_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include <gtest/gtest.h>
#include "starlet-logger/logger.hpp"

namespace SLogger = Starlet::Logger;

TEST(LoggerTest, BasicLog) {
testing::internal::CaptureStdout();
SLogger::log("TestCaller", "TestFunction", "Test message");
const std::string output = testing::internal::GetCapturedStdout();
EXPECT_EQ(output, "[TestCaller TestFunction INFO] Test message\n");
}
TEST(LoggerTest, BasicDebugLog) {
testing::internal::CaptureStdout();
SLogger::debug("TestCaller", "TestFunction", "True message");
const std::string outputT = testing::internal::GetCapturedStdout();
EXPECT_EQ(outputT, "[TestCaller TestFunction DEBUG] True message\n");
}
TEST(LoggerTest, BasicWarningLog) {
testing::internal::CaptureStderr();
SLogger::warning("TestCaller", "TestFunction", "Test message");
const std::string output = testing::internal::GetCapturedStderr();
EXPECT_EQ(output, "[TestCaller TestFunction WARNING] Test message\n");
}
TEST(LoggerTest, BasicErrorLog) {
testing::internal::CaptureStderr();
SLogger::error("TestCaller", "TestFunction", "Test message");
const std::string output = testing::internal::GetCapturedStderr();
EXPECT_EQ(output, "[TestCaller TestFunction ERROR] Test message\n");
}

TEST(LoggerTest, AllLevels) {
testing::internal::CaptureStdout();
SLogger::log("Caller", "Func", "info");
SLogger::debug("Caller", "Func", "debug");
SLogger::warning("Caller", "Func", "warning", true);
std::string output = testing::internal::GetCapturedStdout();
EXPECT_NE(output.find("[Caller Func INFO] info\n"), std::string::npos);
EXPECT_NE(output.find("[Caller Func DEBUG] debug\n"), std::string::npos);
EXPECT_NE(output.find("[Caller Func WARNING] warning\n"), std::string::npos);

testing::internal::CaptureStderr();
SLogger::error("Caller", "Func", "error");
SLogger::warning("Caller", "Func", "warning");
output = testing::internal::GetCapturedStderr();
EXPECT_NE(output.find("[Caller Func ERROR] error\n"), std::string::npos);
EXPECT_NE(output.find("[Caller Func WARNING] warning\n"), std::string::npos);
}

TEST(LoggerTest, AllErrorLevels) {
testing::internal::CaptureStderr();
SLogger::error("Caller", "Func", "error");
const std::string output = testing::internal::GetCapturedStderr();
EXPECT_NE(output.find("[Caller Func ERROR] error\n"), std::string::npos);
}

TEST(LoggerTest, WarningLogReturnValue) {
testing::internal::CaptureStdout();
EXPECT_TRUE(SLogger::warning("C", "F", "msg", true));
testing::internal::GetCapturedStdout();

testing::internal::CaptureStderr();
EXPECT_FALSE(SLogger::warning("C", "F", "msg", false));
testing::internal::GetCapturedStderr();
}

TEST(LoggerTest, ErrorReturnsFalse) {
testing::internal::CaptureStderr();
EXPECT_FALSE(SLogger::error("C", "F", "msg"));
testing::internal::GetCapturedStderr();
}