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
2 changes: 2 additions & 0 deletions NativeScript/NativeScript.mm
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "runtime/NativeScriptException.h"
#include "runtime/Runtime.h"
#include "runtime/RuntimeConfig.h"
#include "runtime/StdioForwarder.h"
#include "runtime/Tasks.h"

using namespace v8;
Expand Down Expand Up @@ -242,6 +243,7 @@ - (instancetype)initializeWithConfig:(Config*)config {
RuntimeConfig.LogToSystemConsole = [config LogToSystemConsole];

Runtime::Initialize();
StartStdioForwarding();
DestroyMainRuntime();
runtime_ = new Runtime();

Expand Down
6 changes: 4 additions & 2 deletions NativeScript/runtime/Helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -358,18 +358,20 @@ void LogBacktrace(int skip = 1);
// Whether split messages should carry markers for the CLI to stitch them with.
bool ShouldMarkLogChunks();

bool IsDevNull(int fd);

#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 = "") {
std::string_view prefix = "", os_log_t log = OS_LOG_DEFAULT) {
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",
os_log_with_type(log, type, "%{public}.*s%{public}.*s%{public}s",
static_cast<int>(prefix.size()), prefix.data(),
static_cast<int>(chunk.size()), chunk.data(), marker);
});
Expand Down
2 changes: 1 addition & 1 deletion NativeScript/runtime/Helpers.mm
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ void WriteDebugLine(tns::LogCategory category, const char* message) {
}
}

static bool IsDevNull(int fd) {
bool tns::IsDevNull(int fd) {
struct stat target;
struct stat devNull;
return fstat(fd, &target) == 0 && stat("/dev/null", &devNull) == 0 &&
Expand Down
14 changes: 14 additions & 0 deletions NativeScript/runtime/StdioForwarder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef StdioForwarder_h
#define StdioForwarder_h

namespace tns {

// Sends what the app writes to stdout and stderr (print(), NSLog on devices) to
// os_log. Without a console those go to /dev/null, and a process can't be
// attached to later: its stdio is fixed at launch. Only debug builds whose CLI
// asks for it through the forwardStdio key of the app's package.json do this.
void StartStdioForwarding();

} // namespace tns

#endif /* StdioForwarder_h */
99 changes: 99 additions & 0 deletions NativeScript/runtime/StdioForwarder.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include "StdioForwarder.h"
#include <Foundation/Foundation.h>
#include <TargetConditionals.h>
#include <dispatch/dispatch.h>
#include <os/log.h>
#include <stdio.h>
#include <unistd.h>
#include <memory>
#include <mutex>
#include <string>
#include "Helpers.h"
#include "Runtime.h"
#include "RuntimeConfig.h"

namespace tns {

namespace {

// The CLI tells these entries apart from JS console output by this subsystem.
constexpr const char* kStdioSubsystem = "org.nativescript.stdio";

// A longer line goes out in parts instead of growing the buffer without limit.
constexpr size_t kMaxBufferedLineBytes = 64 * 1024;

void Forward(int fd, const char* category, dispatch_queue_t queue) {
int fds[2];
if (pipe(fds) != 0) {
return;
}
if (dup2(fds[1], fd) < 0) {
close(fds[0]);
close(fds[1]);
return;
}
close(fds[1]);

int readEnd = fds[0];
os_log_t log = os_log_create(kStdioSubsystem, category);
auto pending = std::make_shared<std::string>();
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, readEnd, 0, queue);
dispatch_source_set_event_handler(source, ^{
char buffer[4096];
ssize_t count = read(readEnd, buffer, sizeof(buffer));
if (count <= 0) {
return;
}
pending->append(buffer, count);

size_t start = 0;
size_t end;
while ((end = pending->find('\n', start)) != std::string::npos) {
TNS_WriteToOsLog(pending->substr(start, end - start).c_str(), OS_LOG_TYPE_DEFAULT, "", log);
start = end + 1;
}
pending->erase(0, start);

if (pending->size() >= kMaxBufferedLineBytes) {
TNS_WriteToOsLog(pending->c_str(), OS_LOG_TYPE_DEFAULT, "", log);
pending->clear();
}
});
dispatch_resume(source);
}

} // namespace

void StartStdioForwarding() {
static std::once_flag once;
std::call_once(once, [] {
if (!RuntimeConfig.IsDebug || RuntimeConfig.ApplicationPath.empty()) {
return;
}

id value = Runtime::GetAppConfigValue("forwardStdio");
if (![value isKindOfClass:[NSNumber class]] || ![value boolValue] ||
!IsDevNull(STDOUT_FILENO)) {
return;
}

// Decided once and from stdout, which is about to stop being /dev/null.
ShouldMarkLogChunks();

// Otherwise stdout is fully buffered into a pipe, and print() only shows up
// when the buffer fills.
setvbuf(stdout, nullptr, _IOLBF, 0);

// Serial, so that the chunks of a split line stay together.
dispatch_queue_t queue =
dispatch_queue_create("org.nativescript.stdio", DISPATCH_QUEUE_SERIAL);
Forward(STDOUT_FILENO, "stdout", queue);
#if !TARGET_OS_SIMULATOR
// On the simulator NSLog reaches the unified log as well, so forwarding
// stderr there would print it twice.
Forward(STDERR_FILENO, "stderr", queue);
#endif
});
}

} // namespace tns
8 changes: 8 additions & 0 deletions v8ios.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@
A1B2C3D40000000000000004 /* WorkerInspectorClient.mm in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D40000000000000002 /* WorkerInspectorClient.mm */; };
F6191AB129C0FCE8003F588F /* InspectorServer.h in Headers */ = {isa = PBXBuildFile; fileRef = F6191AA929C0FCE7003F588F /* InspectorServer.h */; };
F6191AB229C0FCE8003F588F /* InspectorServer.mm in Sources */ = {isa = PBXBuildFile; fileRef = F6191AAA29C0FCE7003F588F /* InspectorServer.mm */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; };
A1C0DE5400000000000000A4 /* StdioForwarder.h in Headers */ = {isa = PBXBuildFile; fileRef = A1C0DE5200000000000000A2 /* StdioForwarder.h */; };
A1C0DE5500000000000000A5 /* StdioForwarder.mm in Sources */ = {isa = PBXBuildFile; fileRef = A1C0DE5300000000000000A3 /* StdioForwarder.mm */; };
F6191AB629C0FF87003F588F /* utils.h in Headers */ = {isa = PBXBuildFile; fileRef = F6191AB429C0FF86003F588F /* utils.h */; };
F6191AB729C0FF87003F588F /* utils.mm in Sources */ = {isa = PBXBuildFile; fileRef = F6191AB529C0FF86003F588F /* utils.mm */; };
D3FA1E7A0000000000000004 /* NativeScriptDefaultMetadata.c in Sources */ = {isa = PBXBuildFile; fileRef = D3FA1E7A0000000000000002 /* NativeScriptDefaultMetadata.c */; };
Expand Down Expand Up @@ -819,6 +821,8 @@
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>"; };
A1C0DE5200000000000000A2 /* StdioForwarder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StdioForwarder.h; sourceTree = "<group>"; };
A1C0DE5300000000000000A3 /* StdioForwarder.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = StdioForwarder.mm; 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 @@ -1499,6 +1503,8 @@
C266569C22B282BA00EE15CC /* FunctionReference.cpp */,
C2DDEB6D229EAC8200345BFE /* Helpers.h */,
A1C0DE5100000000000000A1 /* LogChunks.h */,
A1C0DE5200000000000000A2 /* StdioForwarder.h */,
A1C0DE5300000000000000A3 /* StdioForwarder.mm */,
C2DDEB7D229EAC8200345BFE /* Helpers.mm */,
AA5DBFD12EC191F8008D12F9 /* HttpLoader.h */,
AA5DBFD22EC191F8008D12F9 /* HttpLoader.mm */,
Expand Down Expand Up @@ -1669,6 +1675,7 @@
C2DDEB97229EAC8300345BFE /* Metadata.h in Headers */,
C247C16522F82842001D2CA2 /* v8-platform.h in Headers */,
F6191AB129C0FCE8003F588F /* InspectorServer.h in Headers */,
A1C0DE5400000000000000A4 /* StdioForwarder.h in Headers */,
C247C16722F82842001D2CA2 /* libplatform.h in Headers */,
C2DDEBA4229EAC8300345BFE /* Runtime.h in Headers */,
F1CB51842D5C37100042555E /* URLPatternImpl.h in Headers */,
Expand Down Expand Up @@ -2352,6 +2359,7 @@
C266569722AFFFB000EE15CC /* Reference.cpp in Sources */,
6573B9EE291FE5B700B0ED7C /* JSIRuntime.m in Sources */,
F6191AB229C0FCE8003F588F /* InspectorServer.mm in Sources */,
A1C0DE5500000000000000A5 /* StdioForwarder.mm in Sources */,
6573B9D3291FE29F00B0ED7C /* HostProxy.cpp in Sources */,
6573B9E5291FE2A700B0ED7C /* jsi.cpp in Sources */,
C2DDEBB3229EAC8300345BFE /* ClassBuilder.mm in Sources */,
Expand Down
Loading