Skip to content

Commit 8145775

Browse files
authored
feat: separate Detox logs (#13)
1 parent fec7285 commit 8145775

File tree

2 files changed

+111
-47
lines changed

2 files changed

+111
-47
lines changed

src/logs/log-buffer.ts

Lines changed: 54 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// eslint-disable-next-line import/no-internal-modules
22
import type { AllureRuntime } from 'jest-allure2-reporter/api';
33

4-
import type { Emitter, AndroidEntry, IosEntry, Entry } from 'logkitten';
4+
import type { Emitter, AndroidEntry, IosEntry } from 'logkitten';
55
import { Level, logkitten } from 'logkitten';
66
import type { DetoxAllure2AdapterDeviceLogsOptions } from '../types';
77
import type { DeviceWrapper } from '../utils';
88

9+
import { PIDEntryCollection } from './pid-entry-collection';
10+
911
type AnyEntry = AndroidEntry & IosEntry;
1012

1113
export interface LogBufferOptions {
@@ -28,9 +30,8 @@ const noop = () => {};
2830

2931
export class LogBuffer implements StepLogRecorder {
3032
private readonly _emitter: Emitter;
31-
private _entries: AnyEntry[] = [];
32-
private _purgatory: AnyEntry[] = [];
33-
private _pid = Number.NaN;
33+
private readonly _appEntries = new PIDEntryCollection();
34+
private readonly _detoxEntries = new PIDEntryCollection();
3435
private _options: DetoxAllure2AdapterDeviceLogsOptions;
3536

3637
constructor(readonly _config: LogBufferOptions) {
@@ -57,41 +58,14 @@ export class LogBuffer implements StepLogRecorder {
5758
}
5859

5960
public resetPid() {
60-
this._pid = Number.NaN;
61+
this._appEntries.pid = Number.NaN;
62+
this._detoxEntries.pid = Number.NaN;
6163
}
6264

6365
public refreshPid() {
64-
this._pid = this._config.device.getPid();
65-
66-
if (Number.isFinite(this._pid) && this._purgatory.length > 0) {
67-
this._entries = [...this._entries, ...this._purgatory.splice(0).filter(this._matchesPid)];
68-
}
69-
}
70-
71-
public flush(failed?: boolean): string {
72-
if (this._entries.length === 0) {
73-
return '';
74-
}
75-
76-
const entries = this._entries.splice(0);
77-
78-
// Check if we should save logs based on failure status
79-
const saveAll = this._options.saveAll ?? false;
80-
if (!saveAll && !failed) {
81-
return '';
82-
}
83-
84-
const result = entries
85-
.map((entry) => {
86-
const levelLetter = Level[entry.level as Level] || 'UNKNOWN';
87-
const tagOrCategory = entry.tag || `${entry.subsystem}:${entry.category}`;
88-
const msg = entry.msg;
89-
90-
return `${levelLetter}\t${tagOrCategory}\t${msg}`;
91-
})
92-
.join('\n');
93-
94-
return result + '\n';
66+
const pid = this._config.device.getPid();
67+
this._appEntries.pid = pid;
68+
this._detoxEntries.pid = pid;
9569
}
9670

9771
public async close() {
@@ -116,25 +90,39 @@ export class LogBuffer implements StepLogRecorder {
11690
}
11791

11892
private readonly _attachLogs = (allure: AllureRuntime, failed: boolean, after: boolean) => {
119-
const content = this.flush(failed);
93+
// Check if we should save logs based on failure status
94+
const saveAll = this._options.saveAll ?? false;
95+
if (!saveAll && !failed) {
96+
return;
97+
}
12098

121-
if (content) {
99+
const appContent = this._appEntries.flushAsString();
100+
if (appContent) {
122101
const name = after ? 'app.log' : 'app-before.log';
123-
allure.attachment(name, content, 'text/plain');
102+
allure.attachment(name, appContent, 'text/plain');
124103
}
125-
};
126104

127-
private readonly _onEntry = (entry: AnyEntry) => {
128-
if (Number.isFinite(this._pid)) {
129-
this._entries.push(entry);
130-
} else {
131-
this._purgatory.push(entry);
105+
const detoxContent = this._detoxEntries.flushAsString();
106+
if (detoxContent) {
107+
const name = after ? 'detox.log' : 'detox-before.log';
108+
allure.attachment(name, detoxContent, 'text/plain');
132109
}
133110
};
134111

135-
private readonly _matchesPid = (entry: Entry) => entry.pid === this._pid;
112+
private readonly _onEntry = (entry: AnyEntry) => {
113+
this._appEntries.push(entry);
114+
};
136115

137116
private _iosFilter(entry: IosEntry): boolean {
117+
if (entry.subsystem === 'com.wix.Detox') {
118+
this._detoxEntries.push(entry);
119+
120+
// Exclude Detox logs from app logs unless they are errors
121+
if (entry.level < Level.ERROR) {
122+
return false;
123+
}
124+
}
125+
138126
const userFilter = this._options.ios;
139127
const override = this._options.override;
140128
if (!override && !this._defaultIosFilter(entry)) {
@@ -145,6 +133,15 @@ export class LogBuffer implements StepLogRecorder {
145133
}
146134

147135
private _androidFilter(entry: AndroidEntry): boolean {
136+
if (entry.tag && entry.tag.startsWith('Detox')) {
137+
this._detoxEntries.push(entry);
138+
139+
// Exclude Detox logs from app logs unless they are errors
140+
if (entry.level < Level.ERROR) {
141+
return false;
142+
}
143+
}
144+
148145
const userFilter = this._options.android;
149146
const override = this._options.override;
150147
if (!override && !this._defaultAndroidFilter(entry)) {
@@ -155,10 +152,19 @@ export class LogBuffer implements StepLogRecorder {
155152
}
156153

157154
private readonly _defaultIosFilter = (entry: IosEntry) => {
155+
// Only handle React Native app logs, not Detox logs
158156
if (entry.subsystem.startsWith('com.facebook.react.')) {
157+
if (entry.msg.startsWith('Unbalanced calls start/end for tag')) {
158+
return false;
159+
}
160+
159161
return true;
160162
}
161163

164+
if (entry.processImagePath.endsWith('/proactiveeventtrackerd')) {
165+
return false;
166+
}
167+
162168
if (entry.level >= Level.ERROR) {
163169
return !entry.subsystem.startsWith('com.apple.'); // && !entry.msg.includes('(CFNetwork)');
164170
}
@@ -167,7 +173,8 @@ export class LogBuffer implements StepLogRecorder {
167173
};
168174

169175
private readonly _defaultAndroidFilter = (entry: AndroidEntry) => {
170-
if (entry.tag.startsWith('Detox') || entry.tag.startsWith('React')) {
176+
// Only handle React Native app logs, not Detox logs
177+
if (entry.tag.startsWith('React')) {
171178
return true;
172179
}
173180

src/logs/pid-entry-collection.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// eslint-disable-next-line import/no-internal-modules
2+
import type { AndroidEntry, Entry, IosEntry } from 'logkitten';
3+
import { Level } from 'logkitten';
4+
5+
export class PIDEntryCollection {
6+
private _entries: Entry[] = [];
7+
private _purgatory: Entry[] = [];
8+
private _pid: number = Number.NaN;
9+
private _current: Entry[] = this._purgatory;
10+
11+
get pid(): number {
12+
return this._pid;
13+
}
14+
15+
set pid(value: number) {
16+
this._pid = value;
17+
this._current = Number.isFinite(this._pid) ? this._entries : this._purgatory;
18+
19+
if (this._purgatory.length > 0) {
20+
const queue = this._purgatory.splice(0);
21+
if (this._current === this._entries) {
22+
this._entries.push(...queue);
23+
}
24+
}
25+
}
26+
27+
public push(entry: Entry): void {
28+
this._current.push(entry);
29+
}
30+
31+
public flushAsString(): string {
32+
const entries = this._flush();
33+
if (entries.length === 0) {
34+
return '';
35+
}
36+
37+
const result = entries
38+
.map((entry) => {
39+
const level = Level[entry.level as Level] || 'UNKNOWN';
40+
const tagOrCategory = entry.tag || join2(entry.subsystem, entry.category);
41+
const msg = entry.msg.replace(/\n/g, '\n\t\t');
42+
43+
return `${level}\t${tagOrCategory}\t${msg}`;
44+
})
45+
.join('\n');
46+
47+
return result + '\n';
48+
}
49+
50+
private _flush() {
51+
return this._entries.splice(0) as (AndroidEntry & IosEntry)[];
52+
}
53+
}
54+
55+
function join2(a: string, b: string): string {
56+
return a && b ? `${a}:${b}` : a || b || '';
57+
}

0 commit comments

Comments
 (0)