Skip to content

Commit bbb183e

Browse files
committed
feat(ios): show the app's forwarded stdout and stderr
Debug builds ask the runtime to forward stdout and stderr through forwardStdio in the app's package.json. They arrive under the org.nativescript.stdio subsystem and show from --log debug, like the rest of the app's native output and as Xcode's console does, labeled with their stream.
1 parent bfef406 commit bbb183e

7 files changed

Lines changed: 48 additions & 3 deletions

File tree

‎docs/man_pages/project/testing/run-ios.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Start an emulator with specified device identifier and sdk | `$ ns run ios [--de
3333
* `--emulator` - If set, runs the app in all available and configured ios simulators. It will start a simulator if none are already running.
3434
* `--sdk` - Specifies the target simulator's sdk.
3535
* `--justlaunch` - If set, does not print the application output in the console.
36-
* `--log <Level>` - Also controls how much of the application output is printed. By default only its JavaScript output is shown. `debug` adds everything else the app logs - its native code and the system frameworks it uses, like Xcode's console - and `trace` adds debug level entries as well.
36+
* `--log <Level>` - Also controls how much of the application output is printed. By default only its JavaScript output is shown. `debug` adds everything else the app logs - its native code, `print()` output and the system frameworks it uses, like Xcode's console - and `trace` adds debug level entries as well.
3737
* `--clean` - If set, forces the complete rebuild of the native application.
3838
* `--no-watch` - If set, changes in your code will not be reflected during the execution of this command.
3939
* `--release` - If set, produces a release build by running webpack in production mode and native build in release mode. Otherwise, produces a debug build.

‎lib/common/definitions/mobile.d.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ declare global {
173173
image: string;
174174
level: string;
175175
message: string;
176+
subsystem?: string;
177+
category?: string;
176178
}
177179

178180
interface IiOSLogStreamOptions {

‎lib/common/mobile/ios/ios-log-entry.ts‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ export function isIOSRuntimeLogEntry(entry: Mobile.IiOSLogEntry): boolean {
3838
return path.basename(entry.image || "") === "NativeScript";
3939
}
4040

41+
// The runtime forwards the app's stdout and stderr under this subsystem, when
42+
// the CLI asks for it through the forwardStdio key of the app's package.json.
43+
const STDIO_SUBSYSTEM = "org.nativescript.stdio";
44+
4145
// Logged by the runtime itself on every launch, not by the app.
4246
const RUNTIME_NOISE = ["NativeScript loaded bundle"];
4347

@@ -57,6 +61,10 @@ export function getIOSLogEntryText(
5761
return null;
5862
}
5963

64+
if (entry.subsystem === STDIO_SUBSYSTEM) {
65+
return options.includeNative ? `${entry.category}: ${entry.message}` : null;
66+
}
67+
6068
const imageName = path.basename(entry.image || "");
6169
if (isIOSRuntimeLogEntry(entry)) {
6270
return RUNTIME_NOISE.some((noise) => entry.message.includes(noise))
@@ -78,7 +86,7 @@ export function getIOSLogEntryText(
7886
// asks for it, through the logChunkMarkers key of the app's package.json.
7987
const CHUNK_JOIN_MARKER = "\u2060";
8088
const CHUNK_LINE_MARKER = "\u2061";
81-
const CONSOLE_PREFIX = /^CONSOLE [A-Z]+: /;
89+
const REPEATED_PREFIX = /^(?:CONSOLE [A-Z]+|stdout|stderr): /;
8290

8391
// Long enough to ride out a backlog of chunks, while the rest of a message that
8492
// never arrives, because the app crashed, still gets printed.
@@ -109,7 +117,7 @@ export class IOSLogChunkStitcher {
109117

110118
const message = this.pending.get(key) ?? {
111119
text: "",
112-
prefix: chunk.match(CONSOLE_PREFIX)?.[0] ?? "",
120+
prefix: chunk.match(REPEATED_PREFIX)?.[0] ?? "",
113121
separator: "",
114122
timer: null,
115123
};

‎lib/common/mobile/ios/simulator/ios-simulator-log-provider.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ export class IOSSimulatorLogProvider
143143
image: event.senderImagePath,
144144
level: (event.messageType || "").toLowerCase(),
145145
message: event.eventMessage,
146+
subsystem: event.subsystem,
147+
category: event.category,
146148
},
147149
this.$devicePlatformsConstants.iOS,
148150
deviceId,

‎lib/common/test/unit-tests/mobile/ios-log-entry.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,31 @@ describe("getIOSLogEntryText", () => {
9898
});
9999
});
100100

101+
describe("getIOSLogEntryText for forwarded stdio", () => {
102+
const entry = {
103+
process: "/var/containers/Bundle/Application/X/app.app/app",
104+
image: "/private/Frameworks/NativeScript.framework/NativeScript",
105+
level: "default",
106+
message: "printed",
107+
subsystem: "org.nativescript.stdio",
108+
category: "stdout",
109+
};
110+
111+
it("is hidden by default, unlike the runtime's console output", () => {
112+
assert.isNull(getIOSLogEntryText(entry, { processName: "app" }));
113+
});
114+
115+
it("is labeled with its stream along with native output", () => {
116+
assert.equal(
117+
getIOSLogEntryText(
118+
{ ...entry, category: "stderr" },
119+
{ processName: "app", includeNative: true },
120+
),
121+
"stderr: printed",
122+
);
123+
});
124+
});
125+
101126
describe("getIOSLogScope", () => {
102127
it("maps the CLI log level to the app logs that are shown", () => {
103128
assert.deepEqual(getIOSLogScope("INFO", false), {
@@ -159,6 +184,11 @@ describe("IOSLogChunkStitcher", () => {
159184
});
160185

161186
it("restores the line break a chunk was cut at", () => {
187+
add(`stdout: one${JOIN}`);
188+
add("stdout: two");
189+
assert.deepEqual(messages, ["stdout: onetwo"]);
190+
messages.length = 0;
191+
162192
add(`CONSOLE ERROR: first${LINE}`);
163193
add("CONSOLE ERROR: second");
164194
assert.deepEqual(messages, ["CONSOLE ERROR: first\nsecond"]);

‎lib/controllers/prepare-controller.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ export class PrepareController
526526
}
527527
if (prepareData && !prepareData.release) {
528528
packageData.logChunkMarkers = true;
529+
packageData.forwardStdio = true;
529530
}
530531
if (packageData.ios && packageData.ios.discardUncaughtJsExceptions) {
531532
packageData.discardUncaughtJsExceptions =

‎test/controllers/prepare-controller.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ describe("prepareController", () => {
181181
it("asks for log chunk markers in iOS debug builds only", async () => {
182182
assert.isTrue((await writePackageJson("ios", false)).logChunkMarkers);
183183
assert.isUndefined((await writePackageJson("ios", true)).logChunkMarkers);
184+
assert.isTrue((await writePackageJson("ios", false)).forwardStdio);
185+
assert.isUndefined((await writePackageJson("ios", true)).forwardStdio);
184186
assert.isUndefined(
185187
(await writePackageJson("android", false)).logChunkMarkers,
186188
);

0 commit comments

Comments
 (0)