Skip to content

Commit 8d12580

Browse files
committed
fix: initial work on respecting source map for logs
1 parent 24dd109 commit 8d12580

File tree

11 files changed

+186
-55
lines changed

11 files changed

+186
-55
lines changed

lib/bootstrap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ $injector.requireCommand("post-install-cli", "./commands/post-install");
175175
$injector.requireCommand("update", "./commands/update");
176176

177177
$injector.require("iOSLogFilter", "./services/ios-log-filter");
178+
$injector.require("logSourceMapService", "./services/log-source-map-service");
178179
$injector.require("projectChangesService", "./services/project-changes-service");
179180

180181
$injector.require("pbxprojDomXcode", "./node/pbxproj-dom-xcode");

lib/common/definitions/mobile.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,13 @@ declare module Mobile {
247247
filterData(platform: string, data: string, deviceLogOptions: Mobile.IDeviceLogOptions): string;
248248
}
249249

250+
/**
251+
* Replaces file paths in device log with their original location
252+
*/
253+
interface ILogSourceMapService {
254+
replaceWithOriginalFileLocations(platform: string, messageData: string): string
255+
}
256+
250257
/**
251258
* Describes filtering logic for specific platform (Android, iOS).
252259
*/

lib/common/mobile/android/android-log-filter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export class AndroidLogFilter implements Mobile.IPlatformLogFilter {
4444

4545
if (match && acceptedTags.indexOf(match[1].trim()) !== -1) {
4646
consoleLogMessage = { tag: match[1].trim(), message: match[2] };
47+
consoleLogMessage.message = lineText;
4748
}
4849

4950
return consoleLogMessage;

lib/common/mobile/device-log-emitter.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ import { DEVICE_LOG_EVENT_NAME } from "../constants";
44
export class DeviceLogEmitter extends DeviceLogProviderBase {
55
constructor(protected $logFilter: Mobile.ILogFilter,
66
$logger: ILogger,
7-
private $loggingLevels: Mobile.ILoggingLevels) {
7+
private $loggingLevels: Mobile.ILoggingLevels,
8+
private $logSourceMapService: Mobile.ILogSourceMapService) {
89
super($logFilter, $logger);
910
}
1011

1112
public logData(line: string, platform: string, deviceIdentifier: string): void {
1213
this.setDefaultLogLevelForDevice(deviceIdentifier);
1314

1415
const loggingOptions = this.getDeviceLogOptionsForDevice(deviceIdentifier) || { logLevel: this.$loggingLevels.info };
15-
const data = this.$logFilter.filterData(platform, line, loggingOptions);
16+
let data = this.$logFilter.filterData(platform, line, loggingOptions);
17+
data = this.$logSourceMapService.replaceWithOriginalFileLocations(platform, data);
1618

1719
if (data) {
1820
this.emit('data', deviceIdentifier, data);

lib/common/mobile/device-log-provider.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import { LoggerConfigData } from "../../constants";
44

55
export class DeviceLogProvider extends DeviceLogProviderBase {
66
constructor(protected $logFilter: Mobile.ILogFilter,
7-
protected $logger: ILogger) {
7+
protected $logger: ILogger,
8+
private $logSourceMapService: Mobile.ILogSourceMapService) {
89
super($logFilter, $logger);
910
}
1011

1112
public logData(lineText: string, platform: string, deviceIdentifier: string): void {
1213
const loggingOptions = this.getDeviceLogOptionsForDevice(deviceIdentifier);
13-
const data = this.$logFilter.filterData(platform, lineText, loggingOptions);
14+
let data = this.$logFilter.filterData(platform, lineText, loggingOptions);
15+
data = this.$logSourceMapService.replaceWithOriginalFileLocations(platform, data);
1416
if (data) {
1517
this.logDataCore(data);
1618
this.emit(DEVICE_LOG_EVENT_NAME, lineText, deviceIdentifier, platform);

lib/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const TNS_CORE_MODULES_WIDGETS_NAME = "tns-core-modules-widgets";
1414
export const TNS_ANDROID_RUNTIME_NAME = "tns-android";
1515
export const TNS_IOS_RUNTIME_NAME = "tns-ios";
1616
export const PACKAGE_JSON_FILE_NAME = "package.json";
17+
export const ANDROID_DEVICE_APP_ROOT_TEMPLATE = `data/data/%s/files`;
1718
export const NODE_MODULE_CACHE_PATH_KEY_NAME = "node-modules-cache-path";
1819
export const DEFAULT_APP_IDENTIFIER_PREFIX = "org.nativescript";
1920
export const LIVESYNC_EXCLUDED_DIRECTORIES = ["app_resources"];

lib/services/ios-log-filter.ts

Lines changed: 2 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
const sourcemap = require("source-map");
2-
import * as path from "path";
3-
import { cache } from "../common/decorators";
4-
51
export class IOSLogFilter implements Mobile.IPlatformLogFilter {
62
// Used to recognize output related to the current project
73
// This looks for artifacts like: AppName[22432] or AppName(SomeTextHere)[23123]
@@ -18,9 +14,7 @@ export class IOSLogFilter implements Mobile.IPlatformLogFilter {
1814
private partialLine: string = null;
1915

2016
constructor(private $logger: ILogger,
21-
private $loggingLevels: Mobile.ILoggingLevels,
22-
private $fs: IFileSystem,
23-
private $projectData: IProjectData) {
17+
private $loggingLevels: Mobile.ILoggingLevels) {
2418
}
2519

2620
public filterData(data: string, loggingOptions: Mobile.IDeviceLogOptions = <any>{}): string {
@@ -72,7 +66,7 @@ export class IOSLogFilter implements Mobile.IPlatformLogFilter {
7266
}
7367

7468
currentLine = currentLine.trim();
75-
output += this.getOriginalFileLocation(currentLine) + '\n';
69+
output += currentLine + '\n';
7670
}
7771

7872
return output.length === 0 ? null : output;
@@ -84,48 +78,6 @@ export class IOSLogFilter implements Mobile.IPlatformLogFilter {
8478
currentLine.indexOf("NativeScript loaded bundle") !== -1 ||
8579
(currentLine.indexOf("assertion failed:") !== -1 && data.indexOf("libxpc.dylib") !== -1);
8680
}
87-
88-
private getOriginalFileLocation(data: string): string {
89-
const fileString = "file:///";
90-
const fileIndex = data.indexOf(fileString);
91-
const projectDir = this.getProjectDir();
92-
93-
if (fileIndex >= 0 && projectDir) {
94-
const parts = data.substring(fileIndex + fileString.length).split(":");
95-
if (parts.length >= 4) {
96-
const file = parts[0];
97-
const sourceMapFile = path.join(projectDir, file + ".map");
98-
const row = parseInt(parts[1]);
99-
const column = parseInt(parts[2]);
100-
if (this.$fs.exists(sourceMapFile)) {
101-
const sourceMap = this.$fs.readText(sourceMapFile);
102-
const smc = new sourcemap.SourceMapConsumer(sourceMap);
103-
const originalPosition = smc.originalPositionFor({ line: row, column: column });
104-
const sourceFile = smc.sources.length > 0 ? file.replace(smc.file, smc.sources[0]) : file;
105-
data = data.substring(0, fileIndex + fileString.length)
106-
+ sourceFile + ":"
107-
+ originalPosition.line + ":"
108-
+ originalPosition.column;
109-
110-
for (let i = 3; i < parts.length; i++) {
111-
data += ":" + parts[i];
112-
}
113-
}
114-
}
115-
}
116-
117-
return data;
118-
}
119-
120-
@cache()
121-
private getProjectDir(): string {
122-
try {
123-
this.$projectData.initializeProjectData();
124-
return this.$projectData.projectDir;
125-
} catch (err) {
126-
return null;
127-
}
128-
}
12981
}
13082

13183
$injector.register("iOSLogFilter", IOSLogFilter);

lib/services/livesync/android-device-livesync-service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { AndroidDeviceLiveSyncServiceBase } from "./android-device-livesync-serv
22
import { performanceLog } from "../../common/decorators";
33
import * as helpers from "../../common/helpers";
44
import { LiveSyncPaths } from "../../common/constants";
5+
import { ANDROID_DEVICE_APP_ROOT_TEMPLATE } from "../../constants";
6+
import * as util from "util"
57
import * as path from "path";
68
import * as net from "net";
79

@@ -28,7 +30,7 @@ export class AndroidDeviceLiveSyncService extends AndroidDeviceLiveSyncServiceBa
2830
}
2931

3032
public async restartApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<void> {
31-
const devicePathRoot = `/data/data/${liveSyncInfo.deviceAppData.appIdentifier}/files`;
33+
const devicePathRoot = util.format(ANDROID_DEVICE_APP_ROOT_TEMPLATE, liveSyncInfo.deviceAppData.appIdentifier);
3234
const devicePath = this.$mobileHelper.buildDevicePath(devicePathRoot, "code_cache", "secondary_dexes", "proxyThumb");
3335
await this.device.adb.executeShellCommand(["rm", "-rf", devicePath]);
3436
await this.device.applicationManager.restartApplication({
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
const sourcemap = require("source-map");
2+
import * as path from "path";
3+
const sourceMapConverter = require("convert-source-map");
4+
import { ANDROID_DEVICE_APP_ROOT_TEMPLATE, APP_FOLDER_NAME, NODE_MODULES_FOLDER_NAME } from "../constants";
5+
import * as util from "util"
6+
7+
interface IParsedMessage {
8+
filePath?: string,
9+
line?: number,
10+
column?: number,
11+
message: string
12+
}
13+
14+
interface IFileLocation {
15+
line: number,
16+
column: number,
17+
sourceFile: string
18+
}
19+
20+
export class LogSourceMapService {
21+
private static FILE_PREFIX = "file:///";
22+
constructor(
23+
private $fs: IFileSystem,
24+
private $projectDataService: IProjectDataService,
25+
private $injector: IInjector,
26+
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants) {
27+
}
28+
29+
public replaceWithOriginalFileLocations(platform: string, messageData: string): string {
30+
if (!messageData) {
31+
return messageData;
32+
}
33+
34+
const lines = messageData.split("\n");
35+
const isAndroid = platform.toLowerCase() === this.$devicePlatformsConstants.Android.toLowerCase();
36+
const parserFunction = isAndroid ? this.parseAndroidLog.bind(this) : this.parseIosLog.bind(this);
37+
let outputData = "";
38+
39+
lines.forEach(rawLine => {
40+
const parsedLine = parserFunction(rawLine);
41+
const originalLocation = this.getOriginalFileLocation(platform, parsedLine);
42+
43+
if (originalLocation && originalLocation.sourceFile) {
44+
const {sourceFile, line, column} = originalLocation;
45+
outputData = `${outputData}${parsedLine.message} ${LogSourceMapService.FILE_PREFIX}${sourceFile}:${line}:${column}\n`
46+
} else if (rawLine !== "") {
47+
outputData = `${outputData}${rawLine}\n`
48+
}
49+
});
50+
51+
return outputData;
52+
}
53+
54+
private getOriginalFileLocation(platform: string, parsedLine: IParsedMessage): IFileLocation {
55+
const fileLoaction = path.join(this.getFilesLocation(platform), APP_FOLDER_NAME);
56+
57+
if (parsedLine && parsedLine.filePath) {
58+
const sourceMapFile = path.join(fileLoaction, parsedLine.filePath);
59+
if (this.$fs.exists(sourceMapFile)) {
60+
const source = this.$fs.readText(sourceMapFile);
61+
const sourceMapRaw = sourceMapConverter.fromSource(source);
62+
if (sourceMapRaw && sourceMapRaw.sourcemap) {
63+
const sourceMap = sourceMapRaw.sourcemap;
64+
const smc = new sourcemap.SourceMapConsumer(sourceMap);
65+
const originalPosition = smc.originalPositionFor({ line: parsedLine.line, column: parsedLine.column });
66+
let sourceFile = originalPosition.source && originalPosition.source.replace("webpack:///", "");
67+
if (sourceFile) {
68+
if (!_.startsWith(sourceFile, NODE_MODULES_FOLDER_NAME)) {
69+
sourceFile = path.join(APP_FOLDER_NAME, sourceFile);
70+
}
71+
72+
return { sourceFile, line: originalPosition.line, column: originalPosition.column};
73+
}
74+
}
75+
}
76+
}
77+
}
78+
79+
private parseAndroidLog(rawMessage: string): IParsedMessage {
80+
const fileIndex = rawMessage.lastIndexOf(LogSourceMapService.FILE_PREFIX);
81+
const projectData = this.$projectDataService.getProjectData();
82+
const deviceProjectPath = util.format(ANDROID_DEVICE_APP_ROOT_TEMPLATE, projectData.projectIdentifiers.android);
83+
let message = rawMessage;
84+
let parts, filePath, line, column;
85+
86+
87+
if (fileIndex >= 0) {
88+
const fileSubstring = rawMessage.substring(fileIndex + LogSourceMapService.FILE_PREFIX.length);
89+
parts = fileSubstring.split(",");
90+
if (parts.length >= 3) {
91+
parts[0] = parts[0].replace("'", "");
92+
parts[1] = parts[1].replace(" line: ", "");
93+
parts[2] = parts[2].replace(" column: ", "");
94+
} else {
95+
parts = fileSubstring.split(":");
96+
}
97+
98+
if (parts.length >= 3) {
99+
const devicePath = `${deviceProjectPath}/${APP_FOLDER_NAME}/`;
100+
filePath = path.relative(devicePath, parts[0]);
101+
line = parseInt(parts[1]);
102+
column = parseInt(parts[2]);
103+
message = rawMessage.substring(0, fileIndex);
104+
for (let i = 3; i < parts.length; i++) {
105+
message += parts[i];
106+
}
107+
message = _.trimEnd(message, "(");
108+
}
109+
}
110+
111+
return { filePath, line, column, message }
112+
}
113+
114+
private parseIosLog(rawMessage: string): IParsedMessage {
115+
const fileIndex = rawMessage.lastIndexOf(LogSourceMapService.FILE_PREFIX);
116+
let message = rawMessage;
117+
let parts, filePath, line, column;
118+
119+
120+
121+
if (fileIndex >= 0) {
122+
const fileSubstring = rawMessage.substring(fileIndex + LogSourceMapService.FILE_PREFIX.length);
123+
parts = fileSubstring.split(":");
124+
125+
if (parts && parts.length >= 3) {
126+
filePath = parts[0];
127+
if (_.startsWith(filePath, APP_FOLDER_NAME)) {
128+
filePath = path.relative(APP_FOLDER_NAME ,parts[0]);
129+
}
130+
line = parseInt(parts[1]);
131+
column = parseInt(parts[2]);
132+
133+
message = rawMessage.substring(0, fileIndex).trim();
134+
for (let i = 3; i < parts.length; i++) {
135+
message += parts[i];
136+
}
137+
}
138+
}
139+
140+
return { filePath, line, column, message }
141+
}
142+
143+
private getFilesLocation(platform: string): string {
144+
try {
145+
const projectData = this.$projectDataService.getProjectData();
146+
const platformsData = this.$injector.resolve("platformsData").getPlatformData(platform.toLowerCase(), projectData);
147+
return platformsData.appDestinationDirectoryPath;
148+
} catch (err) {
149+
return null;
150+
}
151+
}
152+
}
153+
154+
$injector.register("logSourceMapService", LogSourceMapService);

npm-shrinkwrap.json

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)