Skip to content
Open
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
36 changes: 34 additions & 2 deletions Core/Foundation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,51 @@ set(SOURCES
"Include/Babylon/Api.h"
"Include/Babylon/DebugTrace.h"
"Include/Babylon/PerfTrace.h"
"Include/Babylon/StandardStreamLogger.h"
"Source/DebugTrace.cpp"
"Source/PerfTrace.cpp")
"Source/PerfTrace.cpp"
"Source/StandardStreamLogger.cpp"
"Source/StandardStreamLoggerPlatform.h")

# .inl bodies are #include'd by the platform TUs (not separate translation units).
# List them in SOURCES only so IDEs/source_group show them next to the .cpp files.
set(STREAM_LOGGER_INCLUDES
"Source/StandardStreamLogger_Shared.inl")

# Shared tee body (StandardStreamLogger_Shared.inl) + thin OS-ops platform TUs.
# Match AppRuntime: one JSRUNTIMEHOST_PLATFORM implementation is compiled in.
if(JSRUNTIMEHOST_PLATFORM STREQUAL "Win32" OR JSRUNTIMEHOST_PLATFORM STREQUAL "UWP")
list(APPEND SOURCES "Source/StandardStreamLogger_Windows.cpp")
elseif(JSRUNTIMEHOST_PLATFORM STREQUAL "Android")
list(APPEND SOURCES "Source/StandardStreamLogger_Android.cpp")
list(APPEND STREAM_LOGGER_INCLUDES "Source/StandardStreamLogger_PosixOps.inl")
elseif(JSRUNTIMEHOST_PLATFORM STREQUAL "iOS" OR JSRUNTIMEHOST_PLATFORM STREQUAL "macOS")
list(APPEND SOURCES "Source/StandardStreamLogger_Apple.cpp")
list(APPEND STREAM_LOGGER_INCLUDES "Source/StandardStreamLogger_PosixOps.inl")
else()
list(APPEND SOURCES "Source/StandardStreamLogger_Unix.cpp")
endif()

list(APPEND SOURCES ${STREAM_LOGGER_INCLUDES})

add_library(Foundation ${SOURCES})

# Ensure generators never try to compile the .inl include bodies on their own.
set_source_files_properties(${STREAM_LOGGER_INCLUDES} PROPERTIES HEADER_FILE_ONLY TRUE)

target_include_directories(Foundation
PRIVATE "Include/Babylon"
PRIVATE "Source"
INTERFACE "Include")

target_link_libraries(Foundation
PUBLIC napi
PRIVATE napi-extensions
PRIVATE arcana)

if(ANDROID)
target_link_libraries(Foundation PRIVATE log)
endif()

set_property(TARGET Foundation PROPERTY FOLDER Core)
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES})
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES})
37 changes: 37 additions & 0 deletions Core/Foundation/Include/Babylon/StandardStreamLogger.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#pragma once

#include "Api.h"

namespace Babylon::StandardStreamLogger
{
/**
* Starts process-wide standard-stream forwarding.
*
* Android forwards to logcat, Apple platforms forward to os_log, and Windows
* forwards to OutputDebugString while preserving the original stream destination.
* Other Unix platforms already expose standard streams and leave them unchanged.
*
* Returns false if a platform stream could not be redirected. Repeated calls are
* idempotent.
*/
bool BABYLON_API Start();

/**
* Flushes pending output, restores the original streams, and stops forwarding.
*
* Returns false if an original stream could not be restored or pending output
* could not be drained before the shutdown timeout. Repeated calls are idempotent.
*/
bool BABYLON_API Stop();

/**
* Returns whether Start() has successfully begun process-wide forwarding and
* Stop() has not yet completed.
*
* This is the logical started flag, not a live probe of the underlying file
* descriptors. On platforms that leave stdout/stderr unchanged (plain Linux
* and other non-Android Unix hosts), Start() still succeeds and IsStarted()
* reports true even though no redirection was installed.
*/
bool BABYLON_API IsStarted();
}
63 changes: 63 additions & 0 deletions Core/Foundation/Source/StandardStreamLogger.cpp
Comment thread
bkaradzic-microsoft marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#include "StandardStreamLogger.h"
#include "StandardStreamLoggerPlatform.h"

#include <cstdlib>
#include <mutex>

namespace
{
std::mutex g_mutex{};
bool g_started{};
bool g_exitHandlerRegistered{};
}

namespace Babylon::StandardStreamLogger
{
bool Start()
{
std::lock_guard<std::mutex> lock{g_mutex};
if (g_started)
{
return true;
}

if (!Platform::Start())
{
return false;
}

if (!g_exitHandlerRegistered)
{
if (std::atexit([] {
(void)Babylon::StandardStreamLogger::Stop();
}) != 0)
{
(void)Platform::Stop();
return false;
}
g_exitHandlerRegistered = true;
}

g_started = true;
return true;
}

bool Stop()
{
std::lock_guard<std::mutex> lock{g_mutex};
if (!g_started)
{
return true;
}

const bool stopped = Platform::Stop();
g_started = false;
return stopped;
}

bool IsStarted()
{
std::lock_guard<std::mutex> lock{g_mutex};
return g_started;
}
}
16 changes: 16 additions & 0 deletions Core/Foundation/Source/StandardStreamLoggerPlatform.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

// Internal platform hooks for StandardStreamLogger.
// Each JSRUNTIMEHOST_PLATFORM TU implements these; the shared API TU owns the
// process-wide mutex / started flag and is the only public entry point.

namespace Babylon::StandardStreamLogger::Platform
{
// Install stdout/stderr redirection and drain threads.
// Not synchronized — the shared API holds the process-wide mutex.
bool Start();

// Flush, restore original streams, and join (or timeout-detach) drains.
// Not synchronized — the shared API holds the process-wide mutex.
bool Stop();
}
23 changes: 23 additions & 0 deletions Core/Foundation/Source/StandardStreamLogger_Android.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include "StandardStreamLoggerPlatform.h"

#include <android/log.h>
#include <cerrno>
#include <cstdint>
#include <string>

#include <fcntl.h>
#include <unistd.h>

namespace
{
// POSIX fd helpers (dup/pipe/CLOEXEC/devnull); sink is OsWritePlatform below.
#include "StandardStreamLogger_PosixOps.inl"

void OsWritePlatform(bool isError, const std::string& line)
{
const int priority = isError ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO;
__android_log_write(priority, "JsRuntimeHost", line.c_str());
}
}

#include "StandardStreamLogger_Shared.inl"
23 changes: 23 additions & 0 deletions Core/Foundation/Source/StandardStreamLogger_Apple.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include "StandardStreamLoggerPlatform.h"

#include <os/log.h>
#include <cerrno>
#include <cstdint>
#include <string>

#include <fcntl.h>
#include <unistd.h>

namespace
{
// POSIX fd helpers (dup/pipe/CLOEXEC/devnull); sink is OsWritePlatform below.
#include "StandardStreamLogger_PosixOps.inl"

void OsWritePlatform(bool isError, const std::string& line)
{
const os_log_type_t type = isError ? OS_LOG_TYPE_ERROR : OS_LOG_TYPE_DEFAULT;
os_log_with_type(OS_LOG_DEFAULT, type, "%{public}s", line.c_str());
}
}

#include "StandardStreamLogger_Shared.inl"
83 changes: 83 additions & 0 deletions Core/Foundation/Source/StandardStreamLogger_PosixOps.inl
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// POSIX fd primitives shared by Android and Apple. Included inside an anonymous
// namespace that already provides OsWritePlatform.

struct ChannelPlatformState
{
};

int OsDuplicate(int fd)
{
return ::dup(fd);
}

int OsDuplicateTo(int source, int target)
{
return ::dup2(source, target) < 0 ? -1 : 0;
}

int OsClose(int fd)
{
return ::close(fd);
}

int64_t OsRead(int fd, void* data, size_t size)
{
return ::read(fd, data, size);
}

int64_t OsWrite(int fd, const void* data, size_t size)
{
return ::write(fd, data, size);
}

int OsCreatePipe(int fds[2])
{
if (::pipe(fds) != 0)
{
return -1;
}
// Mark both ends CLOEXEC. Leaving the write end inheritable would let a
// concurrent exec keep the pipe open and delay Drain()'s EOF on Stop().
if (::fcntl(fds[0], F_SETFD, FD_CLOEXEC) != 0 ||
::fcntl(fds[1], F_SETFD, FD_CLOEXEC) != 0)
{
const int error = errno;
(void)::close(fds[0]);
(void)::close(fds[1]);
errno = error;
return -1;
}
return 0;
}

bool OsOccupyTarget(int target)
{
const int nullFd = ::open("/dev/null", O_WRONLY);
if (nullFd < 0)
{
return false;
}
if (nullFd == target)
{
return true;
}

const bool duplicated = OsDuplicateTo(nullFd, target) == 0;
(void)OsClose(nullFd);
return duplicated;
}

bool OsOnStartChannel(ChannelPlatformState&, int, bool)
{
return true;
}

bool OsOnRedirected(ChannelPlatformState&, int)
{
return true;
}

bool OsOnRestore(ChannelPlatformState&, int)
{
return true;
}
Loading
Loading