Skip to content

Commit 0b89ee5

Browse files
feat(analytics): write analytics data to passed file
Write analytics data to a file passed as `--analyticsLogFile`. The file will contain timestamp when the action is executed and what is the data passed to analytics. Also add some additional logging to the file, so when something fails, it will be easier for diagnostics. In case `--analyticsLogFile` is not passed, information will not be tracked.
1 parent 9e0bb64 commit 0b89ee5

File tree

7 files changed

+115
-9
lines changed

7 files changed

+115
-9
lines changed

lib/declarations.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ interface IOptions extends IRelease, IDeviceIdentifier, IJustLaunch, IAvd, IAvai
557557
background: string;
558558
hmr: boolean;
559559
link: boolean;
560+
analyticsLogFile: string;
560561
}
561562

562563
interface IEnvOptions {

lib/options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export class Options {
114114
var: { type: OptionType.Object },
115115
default: { type: OptionType.Boolean },
116116
count: { type: OptionType.Number },
117+
analyticsLogFile: { type: OptionType.String },
117118
hooks: { type: OptionType.Boolean, default: true },
118119
link: { type: OptionType.Boolean, default: false },
119120
aab: { type: OptionType.Boolean }

lib/services/analytics/analytics-broker-process.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,22 @@
22
// The instances here are not shared with the ones in main CLI process.
33
import * as fs from "fs";
44
import { AnalyticsBroker } from "./analytics-broker";
5+
import { AnalyticsLoggingService } from "./analytics-logging-service";
56

67
const pathToBootstrap = process.argv[2];
78
if (!pathToBootstrap || !fs.existsSync(pathToBootstrap)) {
89
throw new Error("Invalid path to bootstrap.");
910
}
1011

12+
const logFile = process.argv[3];
1113
// After requiring the bootstrap we can use $injector
1214
require(pathToBootstrap);
1315

14-
const analyticsBroker = $injector.resolve<IAnalyticsBroker>(AnalyticsBroker, { pathToBootstrap });
16+
const analyticsLoggingService = $injector.resolve<IAnalyticsLoggingService>(AnalyticsLoggingService, { logFile });
17+
analyticsLoggingService.logData({ message: "Initializing AnalyticsBroker." });
18+
19+
const analyticsBroker = $injector.resolve<IAnalyticsBroker>(AnalyticsBroker, { pathToBootstrap, analyticsLoggingService });
20+
1521
let trackingQueue: Promise<void> = Promise.resolve();
1622

1723
let sentFinishMsg = false;
@@ -23,12 +29,15 @@ const sendDataForTracking = async (data: ITrackingInformation) => {
2329
};
2430

2531
const finishTracking = async (data?: ITrackingInformation) => {
32+
analyticsLoggingService.logData({ message: `analytics-broker-process finish tracking started, sentFinishMsg: ${sentFinishMsg}, receivedFinishMsg: ${receivedFinishMsg}` });
33+
2634
if (!sentFinishMsg) {
2735
sentFinishMsg = true;
2836

2937
data = data || { type: TrackingTypes.Finish };
3038
const action = async () => {
31-
await sendDataForTracking(data);
39+
await trackingQueue;
40+
analyticsLoggingService.logData({ message: `analytics-broker-process tracking finished` });
3241
process.disconnect();
3342
};
3443

@@ -46,6 +55,8 @@ const finishTracking = async (data?: ITrackingInformation) => {
4655
};
4756

4857
process.on("message", async (data: ITrackingInformation) => {
58+
analyticsLoggingService.logData({ message: `analytics-broker-process received message of type: ${data.type}` });
59+
4960
if (data.type === TrackingTypes.Finish) {
5061
receivedFinishMsg = true;
5162
await finishTracking(data);
@@ -56,7 +67,10 @@ process.on("message", async (data: ITrackingInformation) => {
5667
});
5768

5869
process.on("disconnect", async () => {
70+
analyticsLoggingService.logData({ message: "analytics-broker-process received process.disconnect event" });
5971
await finishTracking();
6072
});
6173

74+
analyticsLoggingService.logData({ message: `analytics-broker-process will send ${AnalyticsMessages.BrokerReadyToReceive} message` });
75+
6276
process.send(AnalyticsMessages.BrokerReadyToReceive);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { EOL } from "os";
2+
3+
export class AnalyticsLoggingService implements IAnalyticsLoggingService {
4+
constructor(private $fs: IFileSystem,
5+
private logFile: string) { }
6+
7+
public logData(analyticsLoggingMessage: IAnalyticsLoggingMessage): void {
8+
if (this.logFile && analyticsLoggingMessage && analyticsLoggingMessage.message) {
9+
analyticsLoggingMessage.type = analyticsLoggingMessage.type || AnalyticsLoggingMessageType.Info;
10+
const formattedDate = this.getFormattedDate();
11+
this.$fs.appendFile(this.logFile, `[${formattedDate}] [${analyticsLoggingMessage.type}] ${analyticsLoggingMessage.message}${EOL}`);
12+
}
13+
}
14+
15+
private getFormattedDate(): string {
16+
const currentDate = new Date();
17+
const year = currentDate.getFullYear();
18+
const month = this.getFormattedDateComponent((currentDate.getMonth() + 1));
19+
const day = this.getFormattedDateComponent(currentDate.getDate());
20+
const hour = this.getFormattedDateComponent(currentDate.getHours());
21+
const minutes = this.getFormattedDateComponent(currentDate.getMinutes());
22+
const seconds = this.getFormattedDateComponent(currentDate.getSeconds());
23+
const milliseconds = this.getFormattedMilliseconds(currentDate);
24+
25+
return `${[year, month, day].join('-')} ${[hour, minutes, seconds].join(":")}.${milliseconds}`;
26+
}
27+
28+
private getFormattedDateComponent(component: number): string {
29+
const stringComponent = component.toString();
30+
return stringComponent.length === 1 ? `0${stringComponent}` : stringComponent;
31+
}
32+
33+
private getFormattedMilliseconds(date: Date): string {
34+
let milliseconds = date.getMilliseconds().toString();
35+
while (milliseconds.length < 3) {
36+
milliseconds = `0${milliseconds}`;
37+
}
38+
39+
return milliseconds;
40+
}
41+
}

lib/services/analytics/analytics-service.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,11 @@ export class AnalyticsService implements IAnalyticsService, IDisposable {
147147
@cache()
148148
private getAnalyticsBroker(): Promise<ChildProcess> {
149149
return new Promise<ChildProcess>((resolve, reject) => {
150-
const broker = this.$childProcess.spawn("node",
150+
const broker = this.$childProcess.spawn(process.execPath,
151151
[
152152
path.join(__dirname, "analytics-broker-process.js"),
153-
this.$staticConfig.PATH_TO_BOOTSTRAP
153+
this.$staticConfig.PATH_TO_BOOTSTRAP,
154+
this.$options.analyticsLogFile // TODO: Check if passing path with space or quotes will work
154155
],
155156
{
156157
stdio: ["ignore", "ignore", "ignore", "ipc"],

lib/services/analytics/analytics.d.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,23 @@ interface IGoogleAnalyticsProvider {
5858
*/
5959
trackHit(data: IGoogleAnalyticsData): Promise<void>;
6060
}
61+
62+
/**
63+
* Describes message that needs to be logged in the analytics logging file.
64+
*/
65+
interface IAnalyticsLoggingMessage {
66+
message: string;
67+
type?: AnalyticsLoggingMessageType
68+
}
69+
70+
/**
71+
* Describes methods to get local logs from analytics tracking.
72+
*/
73+
interface IAnalyticsLoggingService {
74+
/**
75+
* Logs specified message to the file specified with `--analyticsLogFile`.
76+
* @param {IAnalyticsLoggingMessage} analyticsLoggingMessage The message that has to be written to the logs file.
77+
* @returns {void}
78+
*/
79+
logData(analyticsLoggingMessage: IAnalyticsLoggingMessage): void;
80+
}

lib/services/analytics/google-analytics-provider.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as uuid from "uuid";
22
import * as ua from "universal-analytics";
33
import { AnalyticsClients } from "../../common/constants";
4+
import { cache } from "../../common/decorators";
45

56
export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider {
67
private currentPage: string;
@@ -10,7 +11,8 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider {
1011
private $analyticsSettingsService: IAnalyticsSettingsService,
1112
private $logger: ILogger,
1213
private $proxyService: IProxyService,
13-
private $config: IConfiguration) {
14+
private $config: IConfiguration,
15+
private analyticsLoggingService: IAnalyticsLoggingService) {
1416
}
1517

1618
public async trackHit(trackInfo: IGoogleAnalyticsData): Promise<void> {
@@ -19,13 +21,14 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider {
1921
try {
2022
await this.track(this.$config.GA_TRACKING_ID, trackInfo, sessionId);
2123
} catch (e) {
24+
this.analyticsLoggingService.logData({ type: AnalyticsLoggingMessageType.Error, message: `Unable to track information ${JSON.stringify(trackInfo)}. Error is: ${e}` });
2225
this.$logger.trace("Analytics exception: ", e);
2326
}
2427
}
2528

26-
private async track(gaTrackingId: string, trackInfo: IGoogleAnalyticsData, sessionId: string): Promise<void> {
27-
const proxySettings = await this.$proxyService.getCache();
28-
const proxy = proxySettings && proxySettings.proxy;
29+
@cache()
30+
private getVisitor(gaTrackingId: string, proxy: string): ua.Visitor {
31+
this.analyticsLoggingService.logData({ message: `Initializing Google Analytics visitor for id: ${gaTrackingId} with clientId: ${this.clientId}.` });
2932
const visitor = ua({
3033
tid: gaTrackingId,
3134
cid: this.clientId,
@@ -34,9 +37,20 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider {
3437
},
3538
requestOptions: {
3639
proxy
37-
}
40+
},
41+
https: true
3842
});
3943

44+
this.analyticsLoggingService.logData({ message: `Successfully initialized Google Analytics visitor for id: ${gaTrackingId} with clientId: ${this.clientId}.` });
45+
return visitor;
46+
}
47+
48+
private async track(gaTrackingId: string, trackInfo: IGoogleAnalyticsData, sessionId: string): Promise<void> {
49+
const proxySettings = await this.$proxyService.getCache();
50+
const proxy = proxySettings && proxySettings.proxy;
51+
52+
const visitor = this.getVisitor(gaTrackingId, proxy);
53+
4054
await this.setCustomDimensions(visitor, trackInfo.customDimensions, sessionId);
4155

4256
switch (trackInfo.googleAnalyticsDataType) {
@@ -68,6 +82,7 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider {
6882
customDimensions = _.merge(defaultValues, customDimensions);
6983

7084
_.each(customDimensions, (value, key) => {
85+
this.analyticsLoggingService.logData({ message: `Setting custom dimension ${key} to value ${value}` });
7186
visitor.set(key, value);
7287
});
7388
}
@@ -76,10 +91,17 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider {
7691
return new Promise<void>((resolve, reject) => {
7792
visitor.event(trackInfo.category, trackInfo.action, trackInfo.label, trackInfo.value, { p: this.currentPage }, (err: Error) => {
7893
if (err) {
94+
this.analyticsLoggingService.logData({
95+
message: `Unable to track event with category: '${trackInfo.category}', action: '${trackInfo.action}', label: '${trackInfo.label}', ` +
96+
`value: '${trackInfo.value}' attached page: ${this.currentPage}. Error is: ${err}.`,
97+
type: AnalyticsLoggingMessageType.Error
98+
});
99+
79100
reject(err);
80101
return;
81102
}
82103

104+
this.analyticsLoggingService.logData({ message: `Tracked event with category: '${trackInfo.category}', action: '${trackInfo.action}', label: '${trackInfo.label}', value: '${trackInfo.value}' attached page: ${this.currentPage}.` });
83105
resolve();
84106
});
85107
});
@@ -96,10 +118,16 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider {
96118

97119
visitor.pageview(pageViewData, (err) => {
98120
if (err) {
121+
this.analyticsLoggingService.logData({
122+
message: `Unable to track pageview with path '${trackInfo.path}' and title: '${trackInfo.title}' Error is: ${err}.`,
123+
type: AnalyticsLoggingMessageType.Error
124+
});
125+
99126
reject(err);
100127
return;
101128
}
102129

130+
this.analyticsLoggingService.logData({ message: `Tracked pageview with path '${trackInfo.path}' and title: '${trackInfo.title}'.` });
103131
resolve();
104132
});
105133
});

0 commit comments

Comments
 (0)