@vtvlive/interactive-apm 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +201 -123
  2. package/dist/factories/tracing-provider.factory.d.ts +8 -3
  3. package/dist/factories/tracing-provider.factory.d.ts.map +1 -1
  4. package/dist/factories/tracing-provider.factory.js +17 -13
  5. package/dist/index.d.ts +12 -10
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -1
  8. package/dist/init/elastic-apm-init.d.ts +25 -3
  9. package/dist/init/elastic-apm-init.d.ts.map +1 -1
  10. package/dist/init/elastic-apm-init.js +29 -12
  11. package/dist/init/opentelemetry-init.d.ts +8 -1
  12. package/dist/init/opentelemetry-init.d.ts.map +1 -1
  13. package/dist/init/opentelemetry-init.js +145 -44
  14. package/dist/interfaces/tracing-provider.interface.d.ts +13 -3
  15. package/dist/interfaces/tracing-provider.interface.d.ts.map +1 -1
  16. package/dist/modules/tracing.module.d.ts +5 -5
  17. package/dist/modules/tracing.module.d.ts.map +1 -1
  18. package/dist/modules/tracing.module.js +2 -1
  19. package/dist/providers/elastic-apm.tracing-provider.d.ts +23 -5
  20. package/dist/providers/elastic-apm.tracing-provider.d.ts.map +1 -1
  21. package/dist/providers/elastic-apm.tracing-provider.js +127 -28
  22. package/dist/providers/opentelemetry.tracing-provider.d.ts +12 -4
  23. package/dist/providers/opentelemetry.tracing-provider.d.ts.map +1 -1
  24. package/dist/providers/opentelemetry.tracing-provider.js +328 -67
  25. package/dist/services/tracing.service.d.ts +6 -5
  26. package/dist/services/tracing.service.d.ts.map +1 -1
  27. package/dist/services/tracing.service.js +2 -2
  28. package/dist/types/apm.types.d.ts +162 -0
  29. package/dist/types/apm.types.d.ts.map +1 -0
  30. package/dist/types/apm.types.js +37 -0
  31. package/dist/types/otlp-transport.type.d.ts +14 -0
  32. package/dist/types/otlp-transport.type.d.ts.map +1 -0
  33. package/dist/types/otlp-transport.type.js +17 -0
  34. package/dist/utils/debug-exporter-wrapper.d.ts +36 -0
  35. package/dist/utils/debug-exporter-wrapper.d.ts.map +1 -0
  36. package/dist/utils/debug-exporter-wrapper.js +247 -0
  37. package/dist/utils/debug-logger.d.ts +81 -0
  38. package/dist/utils/debug-logger.d.ts.map +1 -0
  39. package/dist/utils/debug-logger.js +236 -0
  40. package/dist/utils/tracing.helper.d.ts +2 -2
  41. package/dist/utils/tracing.helper.d.ts.map +1 -1
  42. package/dist/utils/tracing.helper.js +8 -4
  43. package/package.json +24 -3
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ElasticApmTracingProvider = void 0;
4
+ const debug_logger_1 = require("../utils/debug-logger");
4
5
  /**
5
6
  * Elastic APM Tracing Provider Implementation
6
7
  * Sử dụng elastic-apm-node agent để gửi traces về Elastic APM server
@@ -8,45 +9,65 @@ exports.ElasticApmTracingProvider = void 0;
8
9
  class ElasticApmTracingProvider {
9
10
  constructor(config = {}) {
10
11
  this.apm = null;
11
- this.serviceName = config.serviceName || process.env.ELASTIC_APM_SERVICE_NAME || 'interactive-backend';
12
- this.environment = config.environment || process.env.ELASTIC_APM_ENVIRONMENT || 'development';
12
+ this.serviceName =
13
+ config.serviceName || process.env.ELASTIC_APM_SERVICE_NAME || "interactive-backend";
14
+ this.environment = config.environment || process.env.ELASTIC_APM_ENVIRONMENT || "development";
13
15
  }
14
16
  /**
15
17
  * Initialize Elastic APM agent
16
18
  * Should be called before using the provider
17
19
  */
18
- initialize(config) {
20
+ async initialize(config) {
19
21
  try {
20
22
  // @ts-ignore - Optional peer dependency
21
- const apm = require('elastic-apm-node');
23
+ const apm = require("elastic-apm-node");
22
24
  // If agent is already started, just get reference
23
25
  if (apm.isStarted()) {
24
26
  this.apm = apm;
27
+ if ((0, debug_logger_1.isDebugEnabled)()) {
28
+ console.log(`[APM-DEBUG] [ElasticAPM] Agent already started, using existing instance`);
29
+ }
25
30
  return;
26
31
  }
32
+ const serverUrl = config?.serverUrl || process.env.ELASTIC_APM_SERVER_URL || "http://localhost:8200";
33
+ const secretToken = config?.secretToken || process.env.ELASTIC_APM_SECRET_TOKEN;
34
+ const serviceVersion = config?.serviceVersion || process.env.npm_package_version || "1.0.0";
35
+ const environment = config?.environment || this.environment;
36
+ // Log initialization details when debug mode is on
37
+ (0, debug_logger_1.logInitialization)("ElasticAPM", {
38
+ serviceName: config?.serviceName || this.serviceName,
39
+ serverUrl,
40
+ hasSecretToken: !!secretToken,
41
+ serviceVersion,
42
+ environment,
43
+ });
27
44
  // Start the agent with provided config
28
45
  this.apm = apm.start({
29
46
  serviceName: config?.serviceName || this.serviceName,
30
- serverUrl: config?.serverUrl || process.env.ELASTIC_APM_SERVER_URL || 'http://localhost:8200',
31
- secretToken: config?.secretToken || process.env.ELASTIC_APM_SECRET_TOKEN,
32
- serviceVersion: config?.serviceVersion || process.env.npm_package_version || '1.0.0',
33
- environment: config?.environment || this.environment,
34
- logLevel: 'error',
47
+ serverUrl,
48
+ secretToken,
49
+ serviceVersion,
50
+ environment,
51
+ logLevel: (0, debug_logger_1.isDebugEnabled)() ? "info" : "error",
35
52
  });
36
- console.log(`[ElasticAPM] Service: ${this.serviceName}, Environment: ${this.environment}`);
37
- console.log('[ElasticAPM] Initialized');
53
+ if ((0, debug_logger_1.isDebugEnabled)()) {
54
+ (0, debug_logger_1.infoLog)(`[ElasticAPM] Service: ${this.serviceName}, Environment: ${this.environment}`);
55
+ (0, debug_logger_1.infoLog)("[ElasticAPM] Initialized");
56
+ }
38
57
  }
39
58
  catch (error) {
40
- console.error('[ElasticAPM] Failed to initialize:', error);
59
+ (0, debug_logger_1.errorLog)("[ElasticAPM] Failed to initialize:", error);
41
60
  throw error;
42
61
  }
43
62
  }
44
63
  /**
45
64
  * Bắt đầu một span mới
46
65
  */
47
- startSpan(name, attributes, spanKind = 'INTERNAL') {
66
+ startSpan(name, attributes, spanKind = "INTERNAL") {
48
67
  if (!this.apm) {
49
- console.warn('[ElasticAPM] Not initialized');
68
+ if ((0, debug_logger_1.isDebugEnabled)()) {
69
+ console.warn("[ElasticAPM] Not initialized");
70
+ }
50
71
  return null;
51
72
  }
52
73
  // Elastic APM cần transaction để tạo span
@@ -60,7 +81,7 @@ class ElasticApmTracingProvider {
60
81
  }
61
82
  createdTransaction = true;
62
83
  }
63
- const span = this.apm.startSpan(name, 'custom');
84
+ const span = this.apm.startSpan(name, "custom");
64
85
  if (!span) {
65
86
  if (createdTransaction && transaction) {
66
87
  transaction.end();
@@ -75,10 +96,56 @@ class ElasticApmTracingProvider {
75
96
  }
76
97
  // Set span type dựa trên kind
77
98
  span.setType(this.mapSpanKindToType(spanKind));
99
+ // Capture startTime for logging (will log when span ends)
100
+ const startTime = Date.now();
101
+ // Add end protection to prevent operations on ended span
102
+ let isEnded = false;
103
+ // Override setLabel to check if span has ended
104
+ const originalSetLabel = span.setLabel.bind(span);
105
+ span.setLabel = (key, value) => {
106
+ if (isEnded) {
107
+ if ((0, debug_logger_1.isDebugEnabled)()) {
108
+ console.warn(`[ElasticAPM] Cannot set label "${key}" on ended span "${name}". ` +
109
+ `This attribute will not be sent to APM server.`);
110
+ }
111
+ return;
112
+ }
113
+ originalSetLabel(key, value);
114
+ };
115
+ // Override setAttribute (if it exists, maps to setLabel)
116
+ if (typeof span.setAttribute === "function") {
117
+ const originalSetAttribute = span.setAttribute.bind(span);
118
+ span.setAttribute = (key, value) => {
119
+ if (isEnded) {
120
+ if ((0, debug_logger_1.isDebugEnabled)()) {
121
+ console.warn(`[ElasticAPM] Cannot set attribute "${key}" on ended span "${name}". ` +
122
+ `This attribute will not be sent to APM server.`);
123
+ }
124
+ return;
125
+ }
126
+ originalSetAttribute(key, value);
127
+ };
128
+ }
78
129
  // Override end() để end transaction sau
79
130
  const originalEnd = span.end.bind(span);
80
131
  span.end = () => {
132
+ if (isEnded) {
133
+ if ((0, debug_logger_1.isDebugEnabled)()) {
134
+ console.warn(`[ElasticAPM] Span "${name}" has already been ended. Ignoring duplicate end().`);
135
+ }
136
+ return;
137
+ }
138
+ isEnded = true;
139
+ // Log span details with actual duration
140
+ const endTime = Date.now();
141
+ (0, debug_logger_1.logSpan)("ElasticAPM", {
142
+ name: span.name,
143
+ kind: this.mapSpanKindToNumber(spanKind),
144
+ startTime,
145
+ endTime,
146
+ });
81
147
  originalEnd();
148
+ // End transaction if we created it
82
149
  if (createdTransaction && transaction) {
83
150
  transaction.end();
84
151
  }
@@ -97,7 +164,10 @@ class ElasticApmTracingProvider {
97
164
  catch (error) {
98
165
  this.apm?.captureError(error);
99
166
  if (span) {
100
- span.outcome = 'failure';
167
+ span.outcome = "failure";
168
+ }
169
+ if ((0, debug_logger_1.isDebugEnabled)()) {
170
+ (0, debug_logger_1.infoLog)(`[ElasticAPM] Span failed: ${name} ${error.message || error}`);
101
171
  }
102
172
  throw error;
103
173
  }
@@ -128,7 +198,7 @@ class ElasticApmTracingProvider {
128
198
  * End span manually
129
199
  */
130
200
  endSpan(span) {
131
- const activeSpan = span || this.apm?.currentSpan;
201
+ const activeSpan = (span || this.apm?.currentSpan);
132
202
  if (activeSpan) {
133
203
  activeSpan.end();
134
204
  }
@@ -138,7 +208,18 @@ class ElasticApmTracingProvider {
138
208
  */
139
209
  async shutdown() {
140
210
  if (this.apm) {
141
- await this.apm.flush();
211
+ try {
212
+ if (this.apm.flush) {
213
+ await this.apm.flush();
214
+ }
215
+ if ((0, debug_logger_1.isDebugEnabled)()) {
216
+ (0, debug_logger_1.infoLog)("[ElasticAPM] Shutdown completed successfully");
217
+ }
218
+ }
219
+ catch (error) {
220
+ (0, debug_logger_1.errorLog)("[ElasticAPM] Shutdown failed:", error);
221
+ throw error;
222
+ }
142
223
  }
143
224
  }
144
225
  /**
@@ -146,17 +227,35 @@ class ElasticApmTracingProvider {
146
227
  */
147
228
  mapSpanKindToType(kind) {
148
229
  switch (kind.toUpperCase()) {
149
- case 'SERVER':
150
- return 'request';
151
- case 'CLIENT':
152
- return 'db';
153
- case 'PRODUCER':
154
- return 'messaging';
155
- case 'CONSUMER':
156
- return 'messaging';
157
- case 'INTERNAL':
230
+ case "SERVER":
231
+ return "request";
232
+ case "CLIENT":
233
+ return "db";
234
+ case "PRODUCER":
235
+ return "messaging";
236
+ case "CONSUMER":
237
+ return "messaging";
238
+ case "INTERNAL":
239
+ default:
240
+ return "code";
241
+ }
242
+ }
243
+ /**
244
+ * Map string span kind to numeric value (for logging)
245
+ */
246
+ mapSpanKindToNumber(kind) {
247
+ switch (kind.toUpperCase()) {
248
+ case "SERVER":
249
+ return 1;
250
+ case "CLIENT":
251
+ return 2;
252
+ case "PRODUCER":
253
+ return 3;
254
+ case "CONSUMER":
255
+ return 4;
256
+ case "INTERNAL":
158
257
  default:
159
- return 'code';
258
+ return 0;
160
259
  }
161
260
  }
162
261
  }
@@ -1,4 +1,6 @@
1
- import { ITracingProvider } from '../interfaces/tracing-provider.interface';
1
+ import { ITracingProvider } from "../interfaces/tracing-provider.interface";
2
+ import { OtlpTransport } from "../types/otlp-transport.type";
3
+ import { ISpan } from "../types/apm.types";
2
4
  /**
3
5
  * OpenTelemetry Tracing Provider Implementation
4
6
  * Sử dụng OpenTelemetry SDK với OTLP exporter để gửi traces về Elastic APM
@@ -9,6 +11,7 @@ export declare class OpenTelemetryTracingProvider implements ITracingProvider {
9
11
  private readonly serviceName;
10
12
  private readonly environment;
11
13
  private readonly otlpEndpoint;
14
+ private readonly otlpTransport;
12
15
  private readonly secretToken;
13
16
  private readonly otlpAuthToken;
14
17
  private readonly otlpHeaders;
@@ -18,6 +21,7 @@ export declare class OpenTelemetryTracingProvider implements ITracingProvider {
18
21
  serviceName?: string;
19
22
  environment?: string;
20
23
  otlpEndpoint?: string;
24
+ otlpTransport?: OtlpTransport | string;
21
25
  secretToken?: string;
22
26
  otlpAuthToken?: string;
23
27
  otlpHeaders?: Record<string, string>;
@@ -39,11 +43,11 @@ export declare class OpenTelemetryTracingProvider implements ITracingProvider {
39
43
  /**
40
44
  * Bắt đầu một span mới
41
45
  */
42
- startSpan(name: string, attributes?: Record<string, string | number>, spanKind?: string): any;
46
+ startSpan(name: string, attributes?: Record<string, string | number>, spanKind?: string): ISpan | null;
43
47
  /**
44
48
  * Thực thi function với tracing context
45
49
  */
46
- startSpanWithParent<T>(name: string, fn: (span: any) => Promise<T>, attributes?: Record<string, string | number>): Promise<T>;
50
+ startSpanWithParent<T>(name: string, fn: (span: ISpan | null) => Promise<T>, attributes?: Record<string, string | number>): Promise<T>;
47
51
  /**
48
52
  * Capture error vào active span
49
53
  */
@@ -52,10 +56,14 @@ export declare class OpenTelemetryTracingProvider implements ITracingProvider {
52
56
  * Set attribute cho active span
53
57
  */
54
58
  setAttribute(key: string, value: string | number): void;
59
+ /**
60
+ * Force flush all pending spans
61
+ */
62
+ forceFlush(): Promise<void>;
55
63
  /**
56
64
  * End span manually
57
65
  */
58
- endSpan(span?: any): void;
66
+ endSpan(span?: ISpan | null): void;
59
67
  /**
60
68
  * Shutdown gracefully
61
69
  */
@@ -1 +1 @@
1
- {"version":3,"file":"opentelemetry.tracing-provider.d.ts","sourceRoot":"","sources":["../../src/providers/opentelemetry.tracing-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAE5E;;;GAGG;AACH,qBAAa,4BAA6B,YAAW,gBAAgB;IACnE,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IACrD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAU;IAChD,OAAO,CAAC,WAAW,CAAS;gBAEhB,MAAM,GAAE;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC;KAC5B;IAUN;;;OAGG;IACG,UAAU,CAAC,MAAM,CAAC,EAAE;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC;QAChC,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwIjB;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,QAAQ,GAAE,MAAmB,GAAG,GAAG;IAqBzG;;OAEG;IACG,mBAAmB,CAAC,CAAC,EACzB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,EAC7B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAC3C,OAAO,CAAC,CAAC,CAAC;IAoBb;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAchC;;OAEG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAavD;;OAEG;IACH,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAazB;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAM/B;;OAEG;IACH,OAAO,CAAC,WAAW;CAiBpB"}
1
+ {"version":3,"file":"opentelemetry.tracing-provider.d.ts","sourceRoot":"","sources":["../../src/providers/opentelemetry.tracing-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAyG3C;;;GAGG;AACH,qBAAa,4BAA6B,YAAW,gBAAgB;IACnE,OAAO,CAAC,GAAG,CAAiB;IAC5B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IACrD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAU;IAChD,OAAO,CAAC,WAAW,CAAS;gBAG1B,MAAM,GAAE;QACN,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,aAAa,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;QACvC,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC;KAC5B;IA6BR;;;OAGG;IACG,UAAU,CAAC,MAAM,CAAC,EAAE;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC;QAChC,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC;IA+NjB;;OAEG;IACH,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAC5C,QAAQ,GAAE,MAAmB,GAC5B,KAAK,GAAG,IAAI;IA+Gf;;OAEG;IACG,mBAAmB,CAAC,CAAC,EACzB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EACtC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAC3C,OAAO,CAAC,CAAC,CAAC;IAmCb;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAiBhC;;OAEG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAavD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAajC;;OAEG;IACH,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,GAAG,IAAI,GAAG,IAAI;IAalC;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAM/B;;OAEG;IACH,OAAO,CAAC,WAAW;CAiBpB"}