-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathinstrumentation.ts
More file actions
98 lines (83 loc) · 3.2 KB
/
instrumentation.ts
File metadata and controls
98 lines (83 loc) · 3.2 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
import {
InstrumentationBase,
type InstrumentationConfig,
type InstrumentationModuleDefinition,
InstrumentationNodeModuleDefinition,
} from '@opentelemetry/instrumentation';
import type { Integration, OpenAiClient, OpenAiOptions } from '@sentry/core';
import {
_INTERNAL_shouldSkipAiProviderWrapping,
instrumentOpenAiClient,
OPENAI_INTEGRATION_NAME,
replaceExports,
SDK_VERSION,
} from '@sentry/core';
const supportedVersions = ['>=4.0.0 <7'];
export interface OpenAiIntegration extends Integration {
options: OpenAiOptions;
}
type OpenAiInstrumentationOptions = InstrumentationConfig & OpenAiOptions;
/**
* Represents the patched shape of the OpenAI module export.
*/
interface PatchedModuleExports {
[key: string]: unknown;
OpenAI: abstract new (...args: unknown[]) => OpenAiClient;
AzureOpenAI?: abstract new (...args: unknown[]) => OpenAiClient;
}
/**
* Sentry OpenAI instrumentation using OpenTelemetry.
*/
export class SentryOpenAiInstrumentation extends InstrumentationBase<OpenAiInstrumentationOptions> {
public constructor(config: OpenAiInstrumentationOptions = {}) {
super('@sentry/instrumentation-openai', SDK_VERSION, config);
}
/**
* Initializes the instrumentation by defining the modules to be patched.
*/
public init(): InstrumentationModuleDefinition {
const module = new InstrumentationNodeModuleDefinition('openai', supportedVersions, this._patch.bind(this));
return module;
}
/**
* Core patch logic applying instrumentation to the OpenAI and AzureOpenAI client constructors.
*/
private _patch(exports: PatchedModuleExports): PatchedModuleExports | void {
let result = exports;
result = this._patchClient(result, 'OpenAI');
result = this._patchClient(result, 'AzureOpenAI');
return result;
}
/**
* Patch logic applying instrumentation to the specified client constructor.
*/
private _patchClient(exports: PatchedModuleExports, exportKey: 'OpenAI' | 'AzureOpenAI'): PatchedModuleExports {
const Original = exports[exportKey];
if (!Original) {
return exports;
}
const config = this.getConfig();
const WrappedOpenAI = function (this: unknown, ...args: unknown[]) {
// Check if wrapping should be skipped (e.g., when LangChain is handling instrumentation)
if (_INTERNAL_shouldSkipAiProviderWrapping(OPENAI_INTEGRATION_NAME)) {
return Reflect.construct(Original, args) as OpenAiClient;
}
const instance = Reflect.construct(Original, args);
return instrumentOpenAiClient(instance as OpenAiClient, config);
} as unknown as abstract new (...args: unknown[]) => OpenAiClient;
// Preserve static and prototype chains
Object.setPrototypeOf(WrappedOpenAI, Original);
Object.setPrototypeOf(WrappedOpenAI.prototype, Original.prototype);
for (const key of Object.getOwnPropertyNames(Original)) {
if (!['length', 'name', 'prototype'].includes(key)) {
const descriptor = Object.getOwnPropertyDescriptor(Original, key);
if (descriptor) {
Object.defineProperty(WrappedOpenAI, key, descriptor);
}
}
}
// Replace exports with the wrapped constructor
replaceExports(exports, exportKey, WrappedOpenAI);
return exports;
}
}