-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathclient.ts
More file actions
228 lines (204 loc) · 6.27 KB
/
client.ts
File metadata and controls
228 lines (204 loc) · 6.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
import { InstrumentationRegistry } from './instrumentation/registry';
import { InstrumentationBase } from './instrumentation/base';
import { Config, LogLevel } from './types';
import { API, TokenResponse, BearerToken } from './api';
import { TracingCore } from './tracing';
import { getGlobalResource } from './attributes';
import { loggingService } from './instrumentation/console-logging/service';
const debug = require('debug')('agentops:client');
/**
* Main AgentOps SDK client class.
*
* @example
* ```typescript
* import { agentops } from 'agentops';
*
* // Initialize with environment variable API key
* await agentops.init();
*
* // Or initialize with explicit configuration
* await agentops.init({
* apiKey: 'your-api-key',
* ...
* });
* ```
*/
export class Client {
public config: Config;
public readonly registry: InstrumentationRegistry;
private core: TracingCore | null = null;
private api: API | null = null;
private authToken: BearerToken | null = null;
private _initialized = false;
/**
* Creates a new Client instance with default configuration.
*/
constructor() {
this.config = {
serviceName: 'agentops',
apiEndpoint: 'https://api.agentops.ai',
otlpEndpoint: 'https://otlp.agentops.ai',
apiKey: process.env.AGENTOPS_API_KEY,
logLevel: (process.env.AGENTOPS_LOG_LEVEL as LogLevel) || 'error'
};
this.registry = new InstrumentationRegistry(this);
}
/**
* Initializes the AgentOps SDK with OpenTelemetry instrumentation.
*
* Performs the following setup:
* - Merges user configuration with defaults
* - Authenticates with AgentOps API using JWT tokens
* - Configures OpenTelemetry SDK with active instrumentations
* - Sets up automatic cleanup on process exit
*
* @param config - Partial configuration to override defaults
* @throws {Error} When API key is not provided via config or environment variable
*
* @example
* ```typescript
* // Use environment variable AGENTOPS_API_KEY
* await agentops.init();
*
* // Override specific settings
* await agentops.init({
* apiKey: 'custom-key'
* });
* ```
*/
async init(config: Partial<Config> = {}): Promise<void> {
if (this.initialized) {
console.warn('AgentOps already initialized');
return;
}
this.config = { ...this.config, ...config };
this.registry.initialize();
if (!this.config.apiKey) {
throw new Error('API key is required. Set AGENTOPS_API_KEY environment variable or pass it in config.');
}
this.api = new API(this.config.apiKey, this.config.apiEndpoint!);
// Get auth token and set it on the API instance
const authToken = await this.getAuthToken();
this.api.setBearerToken(authToken);
// Initialize logging service
loggingService.initialize(this.api);
const resource = await getGlobalResource(this.config.serviceName!);
this.core = new TracingCore(
this.config,
await this.getAuthToken(),
this.registry.getActiveInstrumentors(),
resource,
this
);
this.setupExitHandlers();
this._initialized = true;
debug('initialized');
}
/**
* Checks if the SDK has been initialized.
*
* @returns True if init() has been called successfully, false otherwise
*/
get initialized(): boolean {
return this._initialized;
}
/**
* Ensures the SDK is initialized before performing operations.
*
* @throws {Error} When the SDK has not been initialized
* @private
*/
private ensureInitialized(): void {
if (!this.initialized) {
throw new Error('AgentOps not initialized. Call agentops.init() first.');
}
}
/**
* Shuts down the OpenTelemetry SDK and cleans up resources.
*
* This method is automatically called on process exit and should contain any necessary cleanup logic.
*/
async shutdown(): Promise<void> {
if (!this.initialized) {
return;
}
// Disable logging service
loggingService.disable();
if(this.core) {
await this.core.shutdown();
}
this._initialized = false;
debug('shutdown');
}
/**
* Sets up process event handlers for automatic cleanup on exit.
*
* Handles the following scenarios:
* - Normal process exit
* - SIGINT (Ctrl+C)
* - SIGTERM (process termination)
* - Uncaught exceptions
* - Unhandled promise rejections
*
* @private
*/
private setupExitHandlers(): void {
// beforeExit allows async operations, perfect for flushing traces
process.on('beforeExit', async () => {
if (this.initialized) {
await this.flush();
}
});
process.on('exit', () => this.shutdown());
process.on('SIGINT', () => this.shutdown());
process.on('SIGTERM', () => this.shutdown());
process.on('uncaughtException', (err) => {
console.error('Uncaught exception:', err);
// TODO we can handle error states on unexported spans here
this.shutdown();
process.exit(1);
});
process.on('unhandledRejection', (reason) => {
console.error('Unhandled rejection:', reason);
this.shutdown();
process.exit(1);
});
}
/**
* Gets the current bearer token for API authentication.
*
* @returns Promise resolving to a bearer token
* @throws {Error} When the SDK is not initialized
* @private
*/
private async getAuthToken(): Promise<BearerToken> {
if (!this.authToken) {
const tokenResponse = await this.api!.authenticate();
this.authToken = new BearerToken(tokenResponse.token);
}
return this.authToken;
}
/**
* Upload captured console logs to the AgentOps API.
*
* @param traceId - The trace ID to associate with the logs
* @returns Promise resolving to upload result with ID, or null if no logs to upload
* @throws {Error} When the SDK is not initialized or upload fails
*/
async uploadLogFile(traceId: string): Promise<{ id: string } | null> {
this.ensureInitialized();
return loggingService.uploadLogs(traceId);
}
/**
* Flush all pending trace actions: print URLs and upload logs.
* Call this after execution is complete to see results and upload logs.
*
* @throws {Error} When the SDK is not initialized
*/
async flush(): Promise<void> {
this.ensureInitialized();
if (this.core) {
await this.core.flush();
}
}
}