Skip to content

Commit 3f3480a

Browse files
committed
ripped out logging, switched to monitoring-based system
1 parent 5404bd0 commit 3f3480a

32 files changed

+763
-477
lines changed

package-lock.json

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

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@
9898
"axios": "^1.6.7",
9999
"bson": "^6.3.0",
100100
"object-hash": "^3.0.0",
101-
"uuidv7": "^0.6.3",
102-
"winston": "^3.7.2"
101+
"typed-emitter": "^2.1.0",
102+
"uuidv7": "^0.6.3"
103103
},
104104
"engines": {
105105
"node": ">=14.0.0"

src/api/data-api-http-client.ts

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
14+
// noinspection ExceptionCaughtLocallyJS
1415

15-
import { DEFAULT_NAMESPACE, DEFAULT_TIMEOUT, HttpClient, HttpMethods, RawDataApiResponse } from '@/src/api';
16+
import { DEFAULT_NAMESPACE, DEFAULT_TIMEOUT, hrTimeMs, HttpClient, HttpMethods, RawDataApiResponse } from '@/src/api';
1617
import { DataAPIResponseError, DataAPITimeout, mkRespErrorFromResponse, ObjectId, UUID } from '@/src/data-api';
17-
import { logger } from '@/src/logger';
18-
import {
19-
MkTimeoutError,
20-
TimeoutManager,
21-
TimeoutOptions,
22-
} from '@/src/api/timeout-managers';
23-
24-
interface DataApiRequestInfo {
18+
import { MkTimeoutError, TimeoutManager, TimeoutOptions } from '@/src/api/timeout-managers';
19+
import { CommandFailedEvent, CommandStartedEvent, CommandSucceededEvent } from '@/src/data-api/events';
20+
21+
/**
22+
* @internal
23+
*/
24+
export interface DataApiRequestInfo {
2525
url: string;
2626
collection?: string;
2727
namespace?: string;
@@ -48,58 +48,68 @@ export class DataApiHttpClient extends HttpClient {
4848
public async executeCommand(command: Record<string, any>, options: ExecuteCommandOptions | undefined) {
4949
const timeoutManager = options?.timeoutManager ?? mkTimeoutManager(options?.maxTimeMS);
5050

51-
const response = await this._requestDataApi({
51+
return await this._requestDataApi({
5252
url: this.baseUrl,
5353
timeoutManager: timeoutManager,
5454
collection: options?.collection,
5555
namespace: options?.namespace,
5656
command: command,
5757
});
58-
59-
handleIfErrorResponse(response, command);
60-
return response;
6158
}
6259

63-
protected async _requestDataApi(info: DataApiRequestInfo): Promise<RawDataApiResponse> {
60+
private async _requestDataApi(info: DataApiRequestInfo): Promise<RawDataApiResponse> {
61+
let started = 0;
62+
6463
try {
6564
info.collection ||= this.collection;
6665
info.namespace ||= this.namespace || DEFAULT_NAMESPACE;
6766

6867
const keyspacePath = `/${info.namespace}`;
6968
const collectionPath = info.collection ? `/${info.collection}` : '';
70-
const url = info.url + keyspacePath + collectionPath;
69+
info.url += keyspacePath + collectionPath;
70+
71+
if (this.monitorCommands) {
72+
started = hrTimeMs();
73+
this.emitter.emit('commandStarted', new CommandStartedEvent(info));
74+
}
7175

7276
const response = await this._request({
73-
url: url,
77+
url: info.url,
7478
data: JSON.stringify(info.command, replacer),
7579
timeoutManager: info.timeoutManager,
7680
method: HttpMethods.Post,
7781
reviver: reviver,
7882
});
7983

8084
if (response.status === 401 || (response.data?.errors?.length > 0 && response.data?.errors[0]?.message === 'UNAUTHENTICATED: Invalid token')) {
81-
return mkFauxErroredResponse('Authentication failed; is your token valid?');
85+
const fauxResponse = mkFauxErroredResponse('Authentication failed; is your token valid?');
86+
throw mkRespErrorFromResponse(DataAPIResponseError, info.command, fauxResponse);
8287
}
8388

8489
if (response.status === 200) {
85-
return {
90+
if (response.data?.errors && response.data?.errors.length > 0) {
91+
throw mkRespErrorFromResponse(DataAPIResponseError, info.command, response.data);
92+
}
93+
94+
const respData = {
8695
status: response.data?.status,
8796
data: response.data?.data,
8897
errors: response.data?.errors,
89-
};
98+
}
99+
100+
if (this.monitorCommands) {
101+
this.emitter.emit('commandSucceeded', new CommandSucceededEvent(info, respData, started));
102+
}
103+
104+
return respData;
90105
} else {
91-
logger.error(info.url + ": " + response.status);
92-
logger.error("Data: " + JSON.stringify(info.command));
93-
return mkFauxErroredResponse(`Some non-200 status code was returned. Check the logs for more information. ${response.status}, ${JSON.stringify(response.data)}`);
106+
const fauxResponse = mkFauxErroredResponse(`Some non-200 status code was returned. Check the logs for more information. ${response.status}, ${JSON.stringify(response.data)}`);
107+
throw mkRespErrorFromResponse(DataAPIResponseError, info.command, fauxResponse);
94108
}
95109
} catch (e: any) {
96-
logger.error(info.url + ": " + e.message);
97-
logger.error("Data: " + JSON.stringify(info.command));
98-
99-
if (e?.response?.data) {
100-
logger.error("Response Data: " + JSON.stringify(e.response.data));
110+
if (this.monitorCommands) {
111+
this.emitter.emit('commandFailed', new CommandFailedEvent(info, e, started));
101112
}
102-
103113
throw e;
104114
}
105115
}
@@ -111,7 +121,7 @@ const mkTimeoutManager = (maxMs: number | undefined) => {
111121
}
112122

113123
const mkTimeoutErrorMaker = (timeout: number): MkTimeoutError => {
114-
return (info) => new DataAPITimeout(info.data!, timeout);
124+
return () => new DataAPITimeout(timeout);
115125
}
116126

117127
const mkFauxErroredResponse = (message: string): RawDataApiResponse => {
@@ -157,12 +167,3 @@ export function reviver(_: string, value: any): any {
157167
}
158168
return value;
159169
}
160-
161-
/**
162-
* @internal
163-
*/
164-
export function handleIfErrorResponse(response: any, command: Record<string, any>) {
165-
if (response.errors && response.errors.length > 0) {
166-
throw mkRespErrorFromResponse(DataAPIResponseError, command, response);
167-
}
168-
}

src/api/devops-api-http-client.ts

Lines changed: 89 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,35 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import { HttpClient } from '@/src/api/http-client';
15+
import { hrTimeMs, HttpClient } from '@/src/api/http-client';
1616
import { AxiosError, AxiosResponse } from 'axios';
1717
import { HTTPClientOptions, HttpMethodStrings } from '@/src/api/types';
1818
import { HTTP1AuthHeaderFactories, HTTP1Strategy } from '@/src/api/http1';
1919
import { DevopsApiResponseError, DevopsApiTimeout, DevopsUnexpectedStateError } from '@/src/devops/errors';
20-
import { AdminBlockingOptions, PollBlockingOptions } from '@/src/devops/types';
21-
import {
22-
MkTimeoutError, TimeoutManager,
23-
TimeoutOptions,
24-
} from '@/src/api/timeout-managers';
20+
import { AdminBlockingOptions } from '@/src/devops/types';
21+
import { MkTimeoutError, TimeoutManager, TimeoutOptions } from '@/src/api/timeout-managers';
2522
import { HttpMethods } from '@/src/api/constants';
23+
import {
24+
AdminCommandFailedEvent,
25+
AdminCommandPollingEvent,
26+
AdminCommandStartedEvent,
27+
AdminCommandSucceededEvent,
28+
} from '@/src/devops';
2629

27-
interface DevopsApiRequestInfo {
30+
/**
31+
* @internal
32+
*/
33+
export interface DevopsApiRequestInfo {
2834
path: string,
2935
method: HttpMethodStrings,
3036
data?: Record<string, any>,
3137
params?: Record<string, any>,
3238
}
3339

34-
interface LongRunningRequestInfo {
40+
/**
41+
* @internal
42+
*/
43+
export interface LongRunningRequestInfo {
3544
id: string | ((resp: AxiosResponse) => string),
3645
target: string,
3746
legalStates: string[],
@@ -48,19 +57,41 @@ export class DevopsApiHttpClient extends HttpClient {
4857
this.requestStrategy = new HTTP1Strategy(HTTP1AuthHeaderFactories.DevopsApi);
4958
}
5059

51-
public async request(info: DevopsApiRequestInfo, options: TimeoutOptions | undefined): Promise<AxiosResponse> {
60+
public async request(req: DevopsApiRequestInfo, options: TimeoutOptions | undefined, started: number = 0): Promise<AxiosResponse> {
61+
const isLongRunning = started !== 0;
62+
5263
try {
5364
const timeoutManager = options?.timeoutManager ?? mkTimeoutManager(options?.maxTimeMS);
54-
const url = this.baseUrl + info.path;
65+
const url = this.baseUrl + req.path;
5566

56-
return await this._request({
67+
if (this.monitorCommands && !isLongRunning) {
68+
this.emitter.emit('adminCommandStarted', new AdminCommandStartedEvent(req, isLongRunning, timeoutManager.ms));
69+
}
70+
71+
started ||= hrTimeMs();
72+
73+
const resp = await this._request({
5774
url: url,
58-
method: info.method,
59-
params: info.params,
60-
data: info.data,
75+
method: req.method,
76+
params: req.params,
77+
data: req.data,
6178
timeoutManager,
62-
}) as any;
79+
}) as AxiosResponse;
80+
81+
if (this.monitorCommands && !isLongRunning) {
82+
this.emitter.emit('adminCommandSucceeded', new AdminCommandSucceededEvent(req, false, resp, started));
83+
}
84+
85+
return resp;
6386
} catch (e) {
87+
if (!(e instanceof Error)) {
88+
throw e;
89+
}
90+
91+
if (this.monitorCommands) {
92+
this.emitter.emit('adminCommandFailed', new AdminCommandFailedEvent(req, isLongRunning, e, started));
93+
}
94+
6495
if (!(e instanceof AxiosError)) {
6596
throw e;
6697
}
@@ -70,38 +101,72 @@ export class DevopsApiHttpClient extends HttpClient {
70101

71102
public async requestLongRunning(req: DevopsApiRequestInfo, info: LongRunningRequestInfo): Promise<AxiosResponse> {
72103
const timeoutManager = mkTimeoutManager(info.options?.maxTimeMS);
73-
const resp = await this.request(req, { timeoutManager });
104+
const isLongRunning = info?.options?.blocking !== false;
105+
106+
if (this.monitorCommands) {
107+
this.emitter.emit('adminCommandStarted', new AdminCommandStartedEvent(req, isLongRunning, timeoutManager.ms));
108+
}
109+
110+
const started = hrTimeMs();
111+
const resp = await this.request(req, { timeoutManager }, started);
74112

75113
const id = (typeof info.id === 'function')
76114
? info.id(resp)
77115
: info.id;
78116

79-
if (info?.options?.blocking !== false) {
80-
await this._awaitStatus(id, info.target, info.legalStates, info.options, info.defaultPollInterval, timeoutManager);
117+
await this._awaitStatus(id, req, info, timeoutManager, started);
118+
119+
if (this.monitorCommands && isLongRunning) {
120+
this.emitter.emit('adminCommandSucceeded', new AdminCommandSucceededEvent(req, true, resp, started));
81121
}
82122

83123
return resp;
84124
}
85125

86-
private async _awaitStatus(id: string, target: string, legalStates: string[], options: PollBlockingOptions | undefined, defaultPollInterval: number, timeoutManager: TimeoutManager): Promise<void> {
126+
private async _awaitStatus(id: string, req: DevopsApiRequestInfo, info: LongRunningRequestInfo, timeoutManager: TimeoutManager, started: number): Promise<void> {
127+
if (info.options?.blocking === false) {
128+
return;
129+
}
130+
131+
const pollInterval = info.options?.pollInterval || info.defaultPollInterval;
132+
let waiting = false;
133+
87134
for (;;) {
135+
if (waiting) {
136+
continue;
137+
}
138+
waiting = true;
139+
140+
if (this.monitorCommands) {
141+
this.emitter.emit('adminCommandPolling', new AdminCommandPollingEvent(req, started, pollInterval));
142+
}
143+
88144
const resp = await this.request({
89145
method: HttpMethods.Get,
90146
path: `/databases/${id}`,
91147
}, {
92148
timeoutManager: timeoutManager,
93-
});
149+
}, started);
94150

95-
if (resp.data?.status === target) {
151+
if (resp.data?.status === info.target) {
96152
break;
97153
}
98154

99-
if (!legalStates.includes(resp.data?.status)) {
100-
throw new DevopsUnexpectedStateError(`Created database is not in any legal state [${[target, ...legalStates].join(',')}]`, resp)
155+
if (!info.legalStates.includes(resp.data?.status)) {
156+
const error = new DevopsUnexpectedStateError(`Created database is not in any legal state [${[info.target, ...info.legalStates].join(',')}]`, resp);
157+
158+
if (this.monitorCommands) {
159+
this.emitter.emit('adminCommandFailed', new AdminCommandFailedEvent(req, true, error, started));
160+
}
161+
162+
throw error;
101163
}
102164

103165
await new Promise<void>((resolve) => {
104-
setTimeout(resolve, options?.pollInterval || defaultPollInterval);
166+
setTimeout(() => {
167+
waiting = false;
168+
resolve();
169+
}, pollInterval);
105170
});
106171
}
107172
}

src/api/http-client.ts

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import { GuaranteedAPIResponse, HTTPClientOptions, HTTPRequestInfo, HTTPRequestS
1717
import { HTTP1AuthHeaderFactories, HTTP1Strategy } from '@/src/api/http1';
1818
import { HTTP2Strategy } from '@/src/api/http2';
1919
import { Mutable } from '@/src/data-api/types/utils';
20-
import { Caller } from '@/src/client';
20+
import { Caller, DataApiClientEvents } from '@/src/client';
21+
import TypedEmitter from 'typed-emitter';
2122

2223
/**
2324
* @internal
@@ -26,24 +27,15 @@ export class HttpClient {
2627
public readonly baseUrl: string;
2728
public readonly userAgent: string;
2829
public requestStrategy: HTTPRequestStrategy;
30+
public emitter: TypedEmitter<DataApiClientEvents>;
31+
public monitorCommands: boolean;
2932
#applicationToken: string;
3033

3134
constructor(options: HTTPClientOptions) {
32-
if (typeof window !== "undefined") {
33-
throw new Error("not for use in a web browser");
34-
}
35-
36-
if (!options.baseUrl) {
37-
throw new Error("baseUrl required for initialization");
38-
}
39-
40-
if (!options.applicationToken) {
41-
throw new Error("applicationToken required for initialization");
42-
}
43-
4435
this.#applicationToken = options.applicationToken;
45-
4636
this.baseUrl = options.baseUrl;
37+
this.emitter = options.emitter;
38+
this.monitorCommands = options.monitorCommands;
4739

4840
this.requestStrategy =
4941
(options.requestStrategy)
@@ -77,6 +69,8 @@ export class HttpClient {
7769
applicationToken: this.#applicationToken,
7870
requestStrategy: this.requestStrategy,
7971
userAgent: this.userAgent,
72+
emitter: this.emitter,
73+
monitorCommands: this.monitorCommands,
8074
});
8175
initialize(clone);
8276
return clone;
@@ -109,6 +103,11 @@ export class HttpClient {
109103
}
110104
}
111105

106+
export function hrTimeMs(): number {
107+
const hrtime = process.hrtime();
108+
return Math.floor(hrtime[0] * 1000 + hrtime[1] / 1000000);
109+
}
110+
112111
function buildUserAgent(client: string, caller?: Caller | Caller[]): string {
113112
const callers = (
114113
(!caller)

0 commit comments

Comments
 (0)