Skip to content

Commit 4940134

Browse files
committed
fix(hub): Remove circular dep from session
``` yarn run v1.22.5 $ madge --circular src/index.ts Processed 5 files (1s) (2 warnings) ✖ Found 1 circular dependency! 1) hub.ts > interfaces.ts > scope.ts > session.ts ``` To remove this circular dep, we extract the SessionFlusher from session.ts into it's own file.
1 parent 5716acd commit 4940134

File tree

4 files changed

+131
-131
lines changed

4 files changed

+131
-131
lines changed

packages/hub/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { addGlobalEventProcessor, Scope } from './scope';
2-
export { Session, SessionFlusher } from './session';
2+
export { Session } from './session';
3+
export { SessionFlusher } from './sessionFlusher';
34
export {
45
// eslint-disable-next-line deprecation/deprecation
56
getActiveDomain,

packages/hub/src/session.ts

Lines changed: 2 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,5 @@
1-
import {
2-
AggregationCounts,
3-
RequestSessionStatus,
4-
Session as SessionInterface,
5-
SessionAggregates,
6-
SessionContext,
7-
SessionFlusherLike,
8-
SessionStatus,
9-
Transport,
10-
} from '@sentry/types';
11-
import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils';
12-
13-
import { getCurrentHub } from './hub';
1+
import { Session as SessionInterface, SessionContext, SessionStatus } from '@sentry/types';
2+
import { dropUndefinedKeys, timestampInSeconds, uuid4 } from '@sentry/utils';
143

154
/**
165
* @inheritdoc
@@ -145,119 +134,3 @@ export class Session implements SessionInterface {
145134
});
146135
}
147136
}
148-
149-
type ReleaseHealthAttributes = {
150-
environment?: string;
151-
release: string;
152-
};
153-
154-
/**
155-
* @inheritdoc
156-
*/
157-
export class SessionFlusher implements SessionFlusherLike {
158-
public readonly flushTimeout: number = 60;
159-
private _pendingAggregates: Record<number, AggregationCounts> = {};
160-
private _sessionAttrs: ReleaseHealthAttributes;
161-
private _intervalId: ReturnType<typeof setInterval>;
162-
private _isEnabled: boolean = true;
163-
private _transport: Transport;
164-
165-
public constructor(transport: Transport, attrs: ReleaseHealthAttributes) {
166-
this._transport = transport;
167-
// Call to setInterval, so that flush is called every 60 seconds
168-
this._intervalId = setInterval(() => this.flush(), this.flushTimeout * 1000);
169-
this._sessionAttrs = attrs;
170-
}
171-
172-
/** Sends session aggregates to Transport */
173-
public sendSessionAggregates(sessionAggregates: SessionAggregates): void {
174-
if (!this._transport.sendSession) {
175-
logger.warn("Dropping session because custom transport doesn't implement sendSession");
176-
return;
177-
}
178-
void this._transport.sendSession(sessionAggregates).then(null, reason => {
179-
logger.error(`Error while sending session: ${reason}`);
180-
});
181-
}
182-
183-
/** Checks if `pendingAggregates` has entries, and if it does flushes them by calling `sendSessions` */
184-
public flush(): void {
185-
const sessionAggregates = this.getSessionAggregates();
186-
if (sessionAggregates.aggregates.length === 0) {
187-
return;
188-
}
189-
this._pendingAggregates = {};
190-
this.sendSessionAggregates(sessionAggregates);
191-
}
192-
193-
/** Massages the entries in `pendingAggregates` and returns aggregated sessions */
194-
public getSessionAggregates(): SessionAggregates {
195-
const aggregates: AggregationCounts[] = Object.keys(this._pendingAggregates).map((key: string) => {
196-
return this._pendingAggregates[parseInt(key)];
197-
});
198-
199-
const sessionAggregates: SessionAggregates = {
200-
attrs: this._sessionAttrs,
201-
aggregates,
202-
};
203-
return dropUndefinedKeys(sessionAggregates);
204-
}
205-
206-
/** JSDoc */
207-
public close(): void {
208-
clearInterval(this._intervalId);
209-
this._isEnabled = false;
210-
this.flush();
211-
}
212-
213-
/**
214-
* Wrapper function for _incrementSessionStatusCount that checks if the instance of SessionFlusher is enabled then
215-
* fetches the session status of the request from `Scope.getRequestSession().status` on the scope and passes them to
216-
* `_incrementSessionStatusCount` along with the start date
217-
*/
218-
public incrementSessionStatusCount(): void {
219-
if (!this._isEnabled) {
220-
return;
221-
}
222-
const scope = getCurrentHub().getScope();
223-
const requestSession = scope?.getRequestSession();
224-
225-
if (requestSession && requestSession.status) {
226-
this._incrementSessionStatusCount(requestSession.status, new Date());
227-
// This is not entirely necessarily but is added as a safe guard to indicate the bounds of a request and so in
228-
// case captureRequestSession is called more than once to prevent double count
229-
scope?.setRequestSession(undefined);
230-
231-
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
232-
}
233-
}
234-
235-
/**
236-
* Increments status bucket in pendingAggregates buffer (internal state) corresponding to status of
237-
* the session received
238-
*/
239-
private _incrementSessionStatusCount(status: RequestSessionStatus, date: Date): number {
240-
// Truncate minutes and seconds on Session Started attribute to have one minute bucket keys
241-
const sessionStartedTrunc = new Date(date).setSeconds(0, 0);
242-
this._pendingAggregates[sessionStartedTrunc] = this._pendingAggregates[sessionStartedTrunc] || {};
243-
244-
// corresponds to aggregated sessions in one specific minute bucket
245-
// for example, {"started":"2021-03-16T08:00:00.000Z","exited":4, "errored": 1}
246-
const aggregationCounts: AggregationCounts = this._pendingAggregates[sessionStartedTrunc];
247-
if (!aggregationCounts.started) {
248-
aggregationCounts.started = new Date(sessionStartedTrunc).toISOString();
249-
}
250-
251-
switch (status) {
252-
case RequestSessionStatus.Errored:
253-
aggregationCounts.errored = (aggregationCounts.errored || 0) + 1;
254-
return aggregationCounts.errored;
255-
case RequestSessionStatus.Ok:
256-
aggregationCounts.exited = (aggregationCounts.exited || 0) + 1;
257-
return aggregationCounts.exited;
258-
case RequestSessionStatus.Crashed:
259-
aggregationCounts.crashed = (aggregationCounts.crashed || 0) + 1;
260-
return aggregationCounts.crashed;
261-
}
262-
}
263-
}

packages/hub/src/sessionFlusher.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import {
2+
AggregationCounts,
3+
RequestSessionStatus,
4+
SessionAggregates,
5+
SessionFlusherLike,
6+
Transport,
7+
} from '@sentry/types';
8+
import { dropUndefinedKeys, logger } from '@sentry/utils';
9+
10+
import { getCurrentHub } from './hub';
11+
12+
type ReleaseHealthAttributes = {
13+
environment?: string;
14+
release: string;
15+
};
16+
17+
/**
18+
* @inheritdoc
19+
*/
20+
export class SessionFlusher implements SessionFlusherLike {
21+
public readonly flushTimeout: number = 60;
22+
private _pendingAggregates: Record<number, AggregationCounts> = {};
23+
private _sessionAttrs: ReleaseHealthAttributes;
24+
private _intervalId: ReturnType<typeof setInterval>;
25+
private _isEnabled: boolean = true;
26+
private _transport: Transport;
27+
28+
public constructor(transport: Transport, attrs: ReleaseHealthAttributes) {
29+
this._transport = transport;
30+
// Call to setInterval, so that flush is called every 60 seconds
31+
this._intervalId = setInterval(() => this.flush(), this.flushTimeout * 1000);
32+
this._sessionAttrs = attrs;
33+
}
34+
35+
/** Sends session aggregates to Transport */
36+
public sendSessionAggregates(sessionAggregates: SessionAggregates): void {
37+
if (!this._transport.sendSession) {
38+
logger.warn("Dropping session because custom transport doesn't implement sendSession");
39+
return;
40+
}
41+
void this._transport.sendSession(sessionAggregates).then(null, reason => {
42+
logger.error(`Error while sending session: ${reason}`);
43+
});
44+
}
45+
46+
/** Checks if `pendingAggregates` has entries, and if it does flushes them by calling `sendSessions` */
47+
public flush(): void {
48+
const sessionAggregates = this.getSessionAggregates();
49+
if (sessionAggregates.aggregates.length === 0) {
50+
return;
51+
}
52+
this._pendingAggregates = {};
53+
this.sendSessionAggregates(sessionAggregates);
54+
}
55+
56+
/** Massages the entries in `pendingAggregates` and returns aggregated sessions */
57+
public getSessionAggregates(): SessionAggregates {
58+
const aggregates: AggregationCounts[] = Object.keys(this._pendingAggregates).map((key: string) => {
59+
return this._pendingAggregates[parseInt(key)];
60+
});
61+
62+
const sessionAggregates: SessionAggregates = {
63+
attrs: this._sessionAttrs,
64+
aggregates,
65+
};
66+
return dropUndefinedKeys(sessionAggregates);
67+
}
68+
69+
/** JSDoc */
70+
public close(): void {
71+
clearInterval(this._intervalId);
72+
this._isEnabled = false;
73+
this.flush();
74+
}
75+
76+
/**
77+
* Wrapper function for _incrementSessionStatusCount that checks if the instance of SessionFlusher is enabled then
78+
* fetches the session status of the request from `Scope.getRequestSession().status` on the scope and passes them to
79+
* `_incrementSessionStatusCount` along with the start date
80+
*/
81+
public incrementSessionStatusCount(): void {
82+
if (!this._isEnabled) {
83+
return;
84+
}
85+
const scope = getCurrentHub().getScope();
86+
const requestSession = scope?.getRequestSession();
87+
88+
if (requestSession && requestSession.status) {
89+
this._incrementSessionStatusCount(requestSession.status, new Date());
90+
// This is not entirely necessarily but is added as a safe guard to indicate the bounds of a request and so in
91+
// case captureRequestSession is called more than once to prevent double count
92+
scope?.setRequestSession(undefined);
93+
94+
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
95+
}
96+
}
97+
98+
/**
99+
* Increments status bucket in pendingAggregates buffer (internal state) corresponding to status of
100+
* the session received
101+
*/
102+
private _incrementSessionStatusCount(status: RequestSessionStatus, date: Date): number {
103+
// Truncate minutes and seconds on Session Started attribute to have one minute bucket keys
104+
const sessionStartedTrunc = new Date(date).setSeconds(0, 0);
105+
this._pendingAggregates[sessionStartedTrunc] = this._pendingAggregates[sessionStartedTrunc] || {};
106+
107+
// corresponds to aggregated sessions in one specific minute bucket
108+
// for example, {"started":"2021-03-16T08:00:00.000Z","exited":4, "errored": 1}
109+
const aggregationCounts: AggregationCounts = this._pendingAggregates[sessionStartedTrunc];
110+
if (!aggregationCounts.started) {
111+
aggregationCounts.started = new Date(sessionStartedTrunc).toISOString();
112+
}
113+
114+
switch (status) {
115+
case RequestSessionStatus.Errored:
116+
aggregationCounts.errored = (aggregationCounts.errored || 0) + 1;
117+
return aggregationCounts.errored;
118+
case RequestSessionStatus.Ok:
119+
aggregationCounts.exited = (aggregationCounts.exited || 0) + 1;
120+
return aggregationCounts.exited;
121+
case RequestSessionStatus.Crashed:
122+
aggregationCounts.crashed = (aggregationCounts.crashed || 0) + 1;
123+
return aggregationCounts.crashed;
124+
}
125+
}
126+
}

packages/hub/test/sessionflusher.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { RequestSessionStatus, Status } from '@sentry/types';
22

3-
import { SessionFlusher } from '../src';
3+
import { SessionFlusher } from '../src/sessionFlusher';
44

55
describe('Session Flusher', () => {
66
let sendSession: jest.Mock;

0 commit comments

Comments
 (0)