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
20 changes: 16 additions & 4 deletions NativeScript/runtime/Console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ namespace {
struct ConsoleTimersState {
robin_hood::unordered_map<std::string, double> startedAt;
};

// Errors get the error log type so log readers can tell severity apart. The
// default handle is kept on purpose: a subsystem would show up in the text of
// syslog-style output, which older CLIs parse.
void LogConsoleMessage(bool isError, const std::string& prefix,
const std::string& message) {
#if TNS_HAVE_OS_LOG
TNS_WriteToOsLog(message.c_str(),
isError ? OS_LOG_TYPE_ERROR : OS_LOG_TYPE_DEFAULT, prefix);
#else
Log("%s%s", prefix.c_str(), message.c_str());
#endif
}
} // namespace

void Console::Init(Local<Context> context) {
Expand Down Expand Up @@ -117,9 +130,8 @@ void Console::LogCallback(const FunctionCallbackInfo<Value>& args) {

ConsoleAPIType method = VerbosityToInspectorMethod(verbosityLevel);
SendToDevToolsFrontEnd(method, args);
std::string msgWithVerbosity =
"CONSOLE " + verbosityLevelUpper + ": " + msgToLog;
Log("%s", msgWithVerbosity.c_str());
LogConsoleMessage(verbosityLevel == "error",
"CONSOLE " + verbosityLevelUpper + ": ", msgToLog);

if (RuntimeConfig.IsDebug && Runtime::showErrorDisplay() &&
verbosityLevel == "error" && hasStackTrace) {
Expand Down Expand Up @@ -158,7 +170,7 @@ void Console::AssertCallback(const FunctionCallbackInfo<Value>& args) {
std::string log = ss.str();

SendToDevToolsFrontEnd(ConsoleAPIType::kAssert, args);
Log("%s", log.c_str());
LogConsoleMessage(true, "", log);
}
}

Expand Down
29 changes: 26 additions & 3 deletions NativeScript/runtime/Helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ extern "C" void NSLog(CFStringRef format, ...);
#define TNS_HAVE_OS_LOG 0
#endif

#include "LogChunks.h"

namespace tns {

inline v8::Local<v8::String> ToV8String(v8::Isolate* isolate, const std::string& value) {
Expand Down Expand Up @@ -353,6 +355,27 @@ void LogBacktrace(int skip = 1);
// "%{public}s" argument to os_log. When os_log is not available we
// fall back to NSLog (Objective-C) or CF/NSLog bridge in pure C++ builds.

// Whether split messages should carry markers for the CLI to stitch them with.
bool ShouldMarkLogChunks();

#if TNS_HAVE_OS_LOG
// The prefix is repeated on every chunk, so that each stays a complete log line.
static inline void TNS_WriteToOsLog(const char* message, os_log_type_t type = OS_LOG_TYPE_DEFAULT,
std::string_view prefix = "") {
ForEachLogChunk(message, kMaxLogChunkBytes - prefix.size(),
[&](std::string_view chunk, LogChunkBreak chunkBreak) {
const char* marker = "";
if (chunkBreak != LogChunkBreak::kNone && ShouldMarkLogChunks()) {
marker = chunkBreak == LogChunkBreak::kLine ? kLogChunkLineMarker
: kLogChunkJoinMarker;
}
os_log_with_type(OS_LOG_DEFAULT, type, "%{public}.*s%{public}.*s%{public}s",
static_cast<int>(prefix.size()), prefix.data(),
static_cast<int>(chunk.size()), chunk.data(), marker);
});
}
#endif

#ifdef __OBJC__
// Overload for Objective-C string literals (NSString*)
static inline void TNS_FormatAndLog(NSString* fmt, ...) {
Expand All @@ -373,7 +396,7 @@ static inline void TNS_FormatAndLog(NSString* fmt, ...) {
const char* cStr = [formattedString UTF8String];
if (cStr) {
#if TNS_HAVE_OS_LOG
os_log(OS_LOG_DEFAULT, "%{public}s", cStr);
TNS_WriteToOsLog(cStr);
#else
NSLog(@"%s", cStr);
#endif
Expand Down Expand Up @@ -406,7 +429,7 @@ static inline void TNS_FormatAndLog(const char* fmt, ...) {
if (needed < STACK_BUF_SIZE) {
// Message fit into stack buffer — single formatting pass.
#if TNS_HAVE_OS_LOG
os_log(OS_LOG_DEFAULT, "%{public}s", stack_buf);
TNS_WriteToOsLog(stack_buf);
#else
// Fall back to NSLog. Use Objective-C API when compiling as ObjC++.
#ifdef __OBJC__
Expand All @@ -421,7 +444,7 @@ static inline void TNS_FormatAndLog(const char* fmt, ...) {
vsnprintf(buffer.data(), buffer.size(), fmt, ap);

#if TNS_HAVE_OS_LOG
os_log(OS_LOG_DEFAULT, "%{public}s", buffer.data());
TNS_WriteToOsLog(buffer.data());
#else
// Fall back to NSLog. Use Objective-C API when compiling as ObjC++.
#ifdef __OBJC__
Expand Down
22 changes: 22 additions & 0 deletions NativeScript/runtime/Helpers.mm
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,28 @@ void WriteDebugLine(tns::LogCategory category, const char* message) {
}
}

static bool IsDevNull(int fd) {
struct stat target;
struct stat devNull;
return fstat(fd, &target) == 0 && stat("/dev/null", &devNull) == 0 &&
S_ISCHR(target.st_mode) && target.st_rdev == devNull.st_rdev;
}

// Syslog-style output shows the markers as visible text and an attached console
// shows them as they are, so they need a CLI that asked for them and no console.
bool tns::ShouldMarkLogChunks() {
if (RuntimeConfig.ApplicationPath.empty()) {
return false;
}

static const bool enabled = [] {
id value = Runtime::GetAppConfigValue("logChunkMarkers");
return [value isKindOfClass:[NSNumber class]] && [value boolValue] &&
IsDevNull(STDOUT_FILENO);
}();
return enabled;
}

void tns::EmitDebugLog(LogCategory category, const char* format, ...) {
va_list ap;
va_start(ap, format);
Expand Down
55 changes: 55 additions & 0 deletions NativeScript/runtime/LogChunks.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#ifndef LogChunks_h
#define LogChunks_h

#include <cstddef>
#include <string_view>

namespace tns {

// os_log replaces whatever exceeds its buffer with "<…>". The buffer also holds
// a few bytes of bookkeeping per argument, which left 996 bytes of text with two
// arguments on iPadOS 26.7, so stay well below that.
constexpr size_t kMaxLogChunkBytes = 900;

// How a chunk connects to the one after it.
enum class LogChunkBreak { kNone, kJoin, kLine };

// Invisible, so that the chunks read normally wherever they are not stitched.
// Control characters would not do: os_log escapes them.
constexpr const char* kLogChunkJoinMarker = "\u2060";
constexpr const char* kLogChunkLineMarker = "\u2061";

// Calls emit with consecutive pieces of message, each at most maxBytes long and
// never ending inside a UTF-8 sequence. A piece ends at the last line break that
// fits, so that lines stay intact whenever they can; that line break is dropped,
// since every piece is logged as a line of its own.
template <typename Emit>
void ForEachLogChunk(std::string_view message, size_t maxBytes, Emit&& emit) {
while (message.size() > maxBytes) {
size_t end = message.rfind('\n', maxBytes);
size_t next = end + 1;
LogChunkBreak chunkBreak = LogChunkBreak::kLine;

if (end == std::string_view::npos || end == 0) {
end = maxBytes;
while (end > 0 && (static_cast<unsigned char>(message[end]) & 0xC0) == 0x80) {
end--;
}
// Not UTF-8 at all: any cut is as good as another.
if (end == 0) {
end = maxBytes;
}
next = end;
chunkBreak = LogChunkBreak::kJoin;
}

emit(message.substr(0, end), chunkBreak);
message.remove_prefix(next);
}

emit(message, LogChunkBreak::kNone);
}

} // namespace tns

#endif /* LogChunks_h */
2 changes: 2 additions & 0 deletions v8ios.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,7 @@
C2DDEB6A229EAC8200345BFE /* ArgConverter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ArgConverter.mm; sourceTree = "<group>"; };
C2DDEB6B229EAC8200345BFE /* Console.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Console.cpp; sourceTree = "<group>"; };
C2DDEB6D229EAC8200345BFE /* Helpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Helpers.h; sourceTree = "<group>"; };
A1C0DE5100000000000000A1 /* LogChunks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LogChunks.h; sourceTree = "<group>"; };
C2DDEB6E229EAC8200345BFE /* Metadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Metadata.h; sourceTree = "<group>"; };
C2DDEB6F229EAC8200345BFE /* ArrayAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArrayAdapter.h; sourceTree = "<group>"; };
C2DDEB70229EAC8200345BFE /* Metadata.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Metadata.mm; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1497,6 +1498,7 @@
C266569D22B282BA00EE15CC /* FunctionReference.h */,
C266569C22B282BA00EE15CC /* FunctionReference.cpp */,
C2DDEB6D229EAC8200345BFE /* Helpers.h */,
A1C0DE5100000000000000A1 /* LogChunks.h */,
C2DDEB7D229EAC8200345BFE /* Helpers.mm */,
AA5DBFD12EC191F8008D12F9 /* HttpLoader.h */,
AA5DBFD22EC191F8008D12F9 /* HttpLoader.mm */,
Expand Down
Loading