Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.

Commit c856580

Browse files
feat: add crash report (#694)
1 parent 2702751 commit c856580

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+906
-38
lines changed

cortex-js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"rxjs": "^7.8.1",
5757
"sqlite": "^5.1.1",
5858
"sqlite3": "^5.1.7",
59-
"systeminformation": "^5.22.10",
59+
"systeminformation": "^5.22.11",
6060
"typeorm": "^0.3.20",
6161
"ulid": "^2.3.0",
6262
"update-notifier": "^5.0.0",

cortex-js/src/app.module.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ import { DatabaseModule } from './infrastructure/database/database.module';
77
import { ChatModule } from './usecases/chat/chat.module';
88
import { AssistantsModule } from './usecases/assistants/assistants.module';
99
import { ExtensionModule } from './infrastructure/repositories/extensions/extension.module';
10-
import { ModelRepositoryModule } from './infrastructure/repositories/models/model.module';
1110
import { CortexModule } from './usecases/cortex/cortex.module';
1211
import { ConfigModule } from '@nestjs/config';
1312
import { env } from 'node:process';
1413
import { SeedService } from './usecases/seed/seed.service';
1514
import { FileManagerModule } from './infrastructure/services/file-manager/file-manager.module';
1615
import { AppLoggerMiddleware } from './infrastructure/middlewares/app.logger.middleware';
16+
import { TelemetryModule } from './usecases/telemetry/telemetry.module';
17+
import { APP_FILTER } from '@nestjs/core';
18+
import { GlobalExceptionFilter } from './infrastructure/exception/global.exception';
19+
import { UtilModule } from './util/util.module';
1720
import { EventEmitterModule } from '@nestjs/event-emitter';
18-
import { DownloadManagerModule } from './download-manager/download-manager.module';
1921
import { EventsController } from './infrastructure/controllers/events.controller';
2022
import { AssistantsController } from './infrastructure/controllers/assistants.controller';
2123
import { ChatController } from './infrastructure/controllers/chat.controller';
@@ -44,8 +46,8 @@ import { ProcessController } from './infrastructure/controllers/process.controll
4446
CortexModule,
4547
ExtensionModule,
4648
FileManagerModule,
47-
ModelRepositoryModule,
48-
DownloadManagerModule,
49+
TelemetryModule,
50+
UtilModule,
4951
],
5052
controllers: [
5153
AssistantsController,
@@ -57,7 +59,13 @@ import { ProcessController } from './infrastructure/controllers/process.controll
5759
ProcessController,
5860
EventsController,
5961
],
60-
providers: [SeedService],
62+
providers: [
63+
SeedService,
64+
{
65+
provide: APP_FILTER,
66+
useClass: GlobalExceptionFilter,
67+
},
68+
],
6169
})
6270
export class AppModule implements NestModule {
6371
configure(consumer: MiddlewareConsumer): void {

cortex-js/src/command.module.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ import { FileManagerModule } from './infrastructure/services/file-manager/file-m
2727
import { PSCommand } from './infrastructure/commanders/ps.command';
2828
import { KillCommand } from './infrastructure/commanders/kill.command';
2929
import { PresetCommand } from './infrastructure/commanders/presets.command';
30+
import { TelemetryModule } from './usecases/telemetry/telemetry.module';
31+
import { TelemetryCommand } from './infrastructure/commanders/telemetry.command';
32+
import { UtilModule } from './util/util.module';
3033
import { EmbeddingCommand } from './infrastructure/commanders/embeddings.command';
3134
import { BenchmarkCommand } from './infrastructure/commanders/benchmark.command';
3235
import { EventEmitterModule } from '@nestjs/event-emitter';
@@ -50,6 +53,8 @@ import { ServeStopCommand } from './infrastructure/commanders/sub-commands/serve
5053
AssistantsModule,
5154
MessagesModule,
5255
FileManagerModule,
56+
TelemetryModule,
57+
UtilModule,
5358
DownloadManagerModule,
5459
],
5560
providers: [
@@ -80,6 +85,9 @@ import { ServeStopCommand } from './infrastructure/commanders/sub-commands/serve
8085
// Shortcuts
8186
RunCommand,
8287

88+
// Telemetry
89+
TelemetryCommand,
90+
8391
// Serve
8492
ServeStopCommand,
8593
],

cortex-js/src/command.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,32 @@
22
import { CommandFactory } from 'nest-commander';
33
import { CommandModule } from './command.module';
44
import updateNotifier from 'update-notifier';
5-
import packageJson from '@/../package.json';
5+
import packageJson from './../package.json';
6+
import { TelemetryUsecases } from './usecases/telemetry/telemetry.usecases';
7+
import { TelemetrySource } from './domain/telemetry/telemetry.interface';
8+
import { AsyncLocalStorage } from 'async_hooks';
9+
import { ContextService } from './util/context.service';
10+
11+
export const asyncLocalStorage = new AsyncLocalStorage();
612

713
async function bootstrap() {
8-
await CommandFactory.run(CommandModule, ['warn', 'error']);
14+
let telemetryUseCase: TelemetryUsecases | null = null;
15+
let contextService: ContextService | null = null;
16+
const app = await CommandFactory.createWithoutRunning(CommandModule, {
17+
logger: ['warn', 'error'],
18+
errorHandler: async (error) => {
19+
await telemetryUseCase!.createCrashReport(error, TelemetrySource.CLI);
20+
process.exit(1);
21+
},
22+
serviceErrorHandler: async (error) => {
23+
await telemetryUseCase!.createCrashReport(error, TelemetrySource.CLI);
24+
process.exit(1);
25+
},
26+
});
27+
telemetryUseCase = await app.resolve(TelemetryUsecases);
28+
contextService = await app.resolve(ContextService);
29+
telemetryUseCase!.sendCrashReport();
30+
await contextService!.init(async () => CommandFactory.runApplication(app));
931
const notifier = updateNotifier({
1032
pkg: packageJson,
1133
updateCheckInterval: 1000 * 60 * 60, // 1 hour
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {
2+
CrashReportAttributes,
3+
Telemetry,
4+
TelemetrySource,
5+
} from '../telemetry/telemetry.interface';
6+
7+
export abstract class TelemetryRepository {
8+
abstract readCrashReports(
9+
callback: (Telemetry: Telemetry) => void,
10+
): Promise<void>;
11+
abstract createCrashReport(
12+
crashReport: CrashReportAttributes,
13+
source?: TelemetrySource,
14+
): Promise<void>;
15+
abstract getLastCrashReport(): Promise<Telemetry | null>;
16+
abstract markLastCrashReportAsSent(): Promise<void>;
17+
abstract sendTelemetryToOTelCollector(
18+
endpoint: string,
19+
telemetry: Telemetry,
20+
): Promise<void>;
21+
abstract sendTelemetryToServer(telemetry: Telemetry): Promise<void>;
22+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export interface TelemetryResource {
2+
timestamp: number;
3+
osName: string;
4+
osVersion: string;
5+
appVersion: string;
6+
architecture: string;
7+
}
8+
9+
export interface CrashReportAttributes {
10+
timestamp: number;
11+
modelId: string;
12+
operation: string;
13+
params: any;
14+
contextLength: number;
15+
tokenPerSecond: number;
16+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// TODO. move openTelemetry interfaces to dto
2+
export interface TelemetryResource {
3+
osName: string;
4+
osVersion: string;
5+
architecture: string;
6+
appVersion: string;
7+
'service.name'?: string;
8+
source?: TelemetrySource;
9+
cpu?: string;
10+
gpus?: string;
11+
// todo: consider about sessionId
12+
// sessionId: string;
13+
}
14+
15+
export interface CrashReportEvent {
16+
modelId?: string;
17+
sessionId: string;
18+
stack?: string;
19+
}
20+
21+
export interface Resource {
22+
attributes: Attribute[];
23+
}
24+
25+
export enum TelemetrySource {
26+
CLI = 'cli',
27+
CORTEX_SERVER = 'cortex-server',
28+
CORTEX_CPP = 'cortex-cpp',
29+
}
30+
31+
export interface TelemetryEvent {
32+
resource: Resource;
33+
scopeLogs: ScopeLog[];
34+
}
35+
36+
interface StringValue {
37+
stringValue: string;
38+
}
39+
40+
interface ObjectValue {
41+
kvlist_value: {
42+
values: {
43+
key: string;
44+
value: StringValue;
45+
}[];
46+
};
47+
}
48+
49+
export interface Attribute {
50+
key: string;
51+
value: StringValue | ObjectValue;
52+
}
53+
54+
interface ScopeLog {
55+
scope: object;
56+
logRecords: TelemetryLog[];
57+
}
58+
59+
export interface TelemetryLog {
60+
traceId: string;
61+
startTimeUnixNano?: string;
62+
endTimeUnixNano?: string;
63+
severityText: string;
64+
body: {
65+
stringValue: string;
66+
};
67+
attributes: Attribute[];
68+
}
69+
70+
export interface CrashReportPayload {
71+
modelId?: string;
72+
endpoint?: string;
73+
command?: string;
74+
}
75+
76+
export interface CrashReportAttributes {
77+
stack?: string;
78+
message: string;
79+
payload: CrashReportPayload;
80+
}
81+
82+
export interface TelemetryEventMetadata {
83+
createdAt: string;
84+
sentAt: string | null;
85+
}
86+
87+
export interface Telemetry {
88+
metadata: TelemetryEventMetadata;
89+
event: {
90+
resourceLogs: TelemetryEvent[];
91+
};
92+
}

cortex-js/src/infrastructure/commanders/chat.command.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { ChatCliUsecases } from './usecases/chat.cli.usecases';
88
import { exit } from 'node:process';
99
import { PSCliUsecases } from './usecases/ps.cli.usecases';
1010
import { ModelsUsecases } from '@/usecases/models/models.usecases';
11+
import { SetCommandContext } from './decorators/CommandContext';
12+
import { ContextService } from '@/util/context.service';
1113
import { ModelStat } from './types/model-stat.interface';
1214

1315
type ChatOptions = {
@@ -26,12 +28,14 @@ type ChatOptions = {
2628
message: 'Message to send to the model',
2729
},
2830
})
31+
@SetCommandContext()
2932
export class ChatCommand extends CommandRunner {
3033
constructor(
3134
private readonly inquirerService: InquirerService,
3235
private readonly chatCliUsecases: ChatCliUsecases,
3336
private readonly modelsUsecases: ModelsUsecases,
3437
private readonly psCliUsecases: PSCliUsecases,
38+
readonly contextService: ContextService,
3539
) {
3640
super();
3741
}

cortex-js/src/infrastructure/commanders/cortex-command.commander.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import { PSCommand } from './ps.command';
99
import { KillCommand } from './kill.command';
1010
import pkg from '@/../package.json';
1111
import { PresetCommand } from './presets.command';
12+
import { TelemetryCommand } from './telemetry.command';
13+
import { SetCommandContext } from './decorators/CommandContext';
14+
import { ContextService } from '@/util/context.service';
1215
import { EmbeddingCommand } from './embeddings.command';
1316
import { BenchmarkCommand } from './benchmark.command';
1417
import chalk from 'chalk';
@@ -25,12 +28,17 @@ import { printSlogan } from '@/utils/logo';
2528
PSCommand,
2629
KillCommand,
2730
PresetCommand,
31+
TelemetryCommand,
2832
EmbeddingCommand,
2933
BenchmarkCommand,
3034
],
3135
description: 'Cortex CLI',
3236
})
37+
@SetCommandContext()
3338
export class CortexCommand extends CommandRunner {
39+
constructor(readonly contextService: ContextService) {
40+
super();
41+
}
3442
async run(): Promise<void> {
3543
printSlogan();
3644
console.log('\n');
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
type Constructor = new (...args: any[]) => any;
2+
3+
export const SetCommandContext = () => {
4+
return (constructor: Constructor) => {
5+
const classMethods = Object.getOwnPropertyNames(constructor.prototype);
6+
7+
classMethods.forEach((methodName) => {
8+
if (methodName !== 'run') return;
9+
const originalMethod = constructor.prototype[methodName];
10+
if (typeof originalMethod === 'function') {
11+
constructor.prototype[methodName] = async function (...args: any[]) {
12+
if (this.contextService) {
13+
const parentCommandName = this.command.parent
14+
? this.command.parent.name()
15+
: '';
16+
const comamndName = this.command.name();
17+
this.contextService.set(
18+
'command',
19+
`${parentCommandName} ${comamndName}`,
20+
);
21+
if (parentCommandName === 'models') {
22+
const modelId = args[0]?.[0];
23+
if (modelId) {
24+
this.contextService.set('model', modelId);
25+
}
26+
}
27+
}
28+
const result = await originalMethod.apply(this, args);
29+
return result;
30+
};
31+
}
32+
});
33+
};
34+
};

0 commit comments

Comments
 (0)