cds-caching 1.2.1 → 1.3.0

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 (79) hide show
  1. package/README.md +195 -1114
  2. package/app/dashboard/Component-dbg.js +59 -0
  3. package/app/dashboard/Component-dbg.js.map +1 -0
  4. package/app/dashboard/Component.js +2 -0
  5. package/app/dashboard/Component.js.map +1 -0
  6. package/app/dashboard/Component.ts +59 -0
  7. package/app/dashboard/controller/App-dbg.controller.js +132 -0
  8. package/app/dashboard/controller/App-dbg.controller.js.map +1 -0
  9. package/app/dashboard/controller/App.controller.js +2 -0
  10. package/app/dashboard/controller/App.controller.js.map +1 -0
  11. package/app/dashboard/controller/App.controller.ts +147 -0
  12. package/app/dashboard/controller/BaseController-dbg.js +75 -0
  13. package/app/dashboard/controller/BaseController-dbg.js.map +1 -0
  14. package/app/dashboard/controller/BaseController.js +2 -0
  15. package/app/dashboard/controller/BaseController.js.map +1 -0
  16. package/app/dashboard/controller/BaseController.ts +88 -0
  17. package/app/dashboard/controller/Cache-dbg.controller.js +421 -0
  18. package/app/dashboard/controller/Cache-dbg.controller.js.map +1 -0
  19. package/app/dashboard/controller/Cache.controller.js +2 -0
  20. package/app/dashboard/controller/Cache.controller.js.map +1 -0
  21. package/app/dashboard/controller/Cache.controller.ts +490 -0
  22. package/app/dashboard/controller/Main-dbg.controller.js +32 -0
  23. package/app/dashboard/controller/Main-dbg.controller.js.map +1 -0
  24. package/app/dashboard/controller/Main.controller.js +2 -0
  25. package/app/dashboard/controller/Main.controller.js.map +1 -0
  26. package/app/dashboard/controller/Main.controller.ts +31 -0
  27. package/app/dashboard/controller/SingleMetric-dbg.controller.js +306 -0
  28. package/app/dashboard/controller/SingleMetric-dbg.controller.js.map +1 -0
  29. package/app/dashboard/controller/SingleMetric.controller.js +2 -0
  30. package/app/dashboard/controller/SingleMetric.controller.js.map +1 -0
  31. package/app/dashboard/controller/SingleMetric.controller.ts +335 -0
  32. package/app/dashboard/i18n/i18n.properties +61 -0
  33. package/app/dashboard/i18n/i18n_de.properties +3 -0
  34. package/app/dashboard/i18n/i18n_en.properties +3 -0
  35. package/app/dashboard/index-cdn.html +28 -0
  36. package/app/dashboard/index.html +19 -0
  37. package/app/dashboard/manifest.json +145 -0
  38. package/app/dashboard/model/formatter-dbg.js +253 -0
  39. package/app/dashboard/model/formatter-dbg.js.map +1 -0
  40. package/app/dashboard/model/formatter.js +2 -0
  41. package/app/dashboard/model/formatter.js.map +1 -0
  42. package/app/dashboard/model/formatter.ts +275 -0
  43. package/app/dashboard/model/models-dbg.js +13 -0
  44. package/app/dashboard/model/models-dbg.js.map +1 -0
  45. package/app/dashboard/model/models.js +2 -0
  46. package/app/dashboard/model/models.js.map +1 -0
  47. package/app/dashboard/model/models.ts +13 -0
  48. package/app/dashboard/resources/sap-ui-custom-dbg.js +4695 -0
  49. package/app/dashboard/resources/sap-ui-custom-dbg.js.map +1 -0
  50. package/app/dashboard/resources/sap-ui-custom.js +5794 -0
  51. package/app/dashboard/resources/sap-ui-custom.js.map +1 -0
  52. package/app/dashboard/service/CacheStatisticsService-dbg.js +267 -0
  53. package/app/dashboard/service/CacheStatisticsService-dbg.js.map +1 -0
  54. package/app/dashboard/service/CacheStatisticsService.js +2 -0
  55. package/app/dashboard/service/CacheStatisticsService.js.map +1 -0
  56. package/app/dashboard/service/CacheStatisticsService.ts +397 -0
  57. package/app/dashboard/view/App.view.xml +18 -0
  58. package/app/dashboard/view/Cache.view.xml +777 -0
  59. package/app/dashboard/view/Main.view.xml +57 -0
  60. package/app/dashboard/view/SingleMetric.view.xml +337 -0
  61. package/cds-plugin.js +52 -0
  62. package/db/cache-store.cds +12 -0
  63. package/db/statistics.cds +128 -0
  64. package/index.cds +2 -128
  65. package/lib/CachingService.js +13 -0
  66. package/lib/add.js +17 -0
  67. package/lib/operations/AsyncOperations.js +162 -111
  68. package/lib/operations/BasicOperations.js +35 -0
  69. package/lib/operations/CapOperations.js +92 -40
  70. package/lib/support/CacheStatisticsHandler.js +35 -10
  71. package/lib/support/CacheStoreManager.js +29 -0
  72. package/lib/support/KeyvCDS.js +149 -0
  73. package/lib/support/MultitenancyDetector.js +25 -0
  74. package/lib/support/RuntimeConfigurationManager.js +69 -37
  75. package/lib/support/StatisticsPersistenceManager.js +18 -2
  76. package/lib/support/Telemetry.js +137 -0
  77. package/lib/util.js +29 -11
  78. package/package.json +43 -11
  79. package/srv/caching-api-service.js +47 -2
package/index.cds CHANGED
@@ -1,132 +1,6 @@
1
- context plugin.cds_caching {
2
-
3
- entity Caches {
4
- key name : String;
5
- config : String;
6
- metricsEnabled : Boolean default false;
7
- keyMetricsEnabled : Boolean default false;
8
- metrics : Composition of many Metrics
9
- on metrics.cache = $self.name;
10
- keyMetrics : Composition of many KeyMetrics
11
- on keyMetrics.cache = $self.name;
12
- }
13
-
14
- entity Metrics {
15
- key ID : String; // e.g., 'daily:2024-03-20' or 'hourly:2024-03-20-15'
16
- key cache : String;
17
- timestamp : DateTime;
18
- period : String enum {
19
- hourly;
20
- daily;
21
- monthly;
22
- }; // Granularity
23
-
24
- // Read-through metrics (hits and misses only)
25
- hits : Integer default 0;
26
- misses : Integer default 0;
27
- errors : Integer default 0;
28
- totalRequests : Integer default 0;
29
- // Read-through latency metrics
30
- avgHitLatency : Double; // average hit latency in milliseconds
31
- minHitLatency : Double; // minimum hit latency
32
- maxHitLatency : Double; // maximum hit latency
33
- avgMissLatency : Double; // average miss latency in milliseconds
34
- minMissLatency : Double; // minimum miss latency
35
- maxMissLatency : Double; // maximum miss latency
36
- avgReadThroughLatency : Double; // average read through latency in milliseconds
37
-
38
- // Read-through performance metrics
39
- hitRatio : Double; // hit ratio as percentage
40
- throughput : Double; // requests per second
41
- errorRate : Double; // error rate as percentage
42
- cacheEfficiency : Double; // ratio of miss latency to hit latency
43
-
44
- // Native function metrics (basic counts only)
45
- nativeSets : Integer default 0;
46
- nativeGets : Integer default 0;
47
- nativeDeletes : Integer default 0;
48
- nativeClears : Integer default 0;
49
- nativeDeleteByTags : Integer default 0;
50
- nativeErrors : Integer default 0;
51
- totalNativeOperations : Integer default 0;
52
- // Native function performance metrics
53
- nativeThroughput : Double; // native operations per second
54
- nativeErrorRate : Double; // native operation error rate
55
-
56
- // Common metrics
57
- memoryUsage : Integer; // in bytes
58
- itemCount : Integer;
59
- uptimeMs : Integer; // uptime in milliseconds
60
- }
61
-
62
- entity KeyMetrics {
63
- key ID : String;
64
- key cache : String;
65
- key keyName : String;
66
- lastAccess : DateTime;
67
- period : String enum {
68
- current;
69
- hourly;
70
- daily;
71
- };
72
-
73
- // Operation type tracking
74
- operationType : String enum {
75
- read_through;
76
- native;
77
- mixed;
78
- };
79
-
80
- // Read-through metrics (hits and misses only)
81
- hits : Integer default 0;
82
- misses : Integer default 0;
83
- errors : Integer default 0;
84
- totalRequests : Integer default 0;
85
- hitRatio : Double; // hit ratio as percentage
86
- cacheEfficiency : Double; // ratio of miss latency to hit latency
87
-
88
- // Read-through latency metrics
89
- avgHitLatency : Double; // average hit latency in milliseconds
90
- minHitLatency : Double; // minimum hit latency
91
- maxHitLatency : Double; // maximum hit latency
92
- avgMissLatency : Double; // average miss latency in milliseconds
93
- minMissLatency : Double; // minimum miss latency
94
- maxMissLatency : Double; // maximum miss latency
95
- avgReadThroughLatency : Double; // average read through latency in milliseconds
96
-
97
- // Read-through performance metrics
98
- throughput : Double; // requests per second
99
- errorRate : Double; // error rate as percentage
100
-
101
- // Native function metrics (counts only)
102
- nativeHits : Integer default 0;
103
- nativeMisses : Integer default 0;
104
- nativeSets : Integer default 0;
105
- nativeDeletes : Integer default 0;
106
- nativeClears : Integer default 0;
107
- nativeDeleteByTags : Integer default 0;
108
- nativeErrors : Integer default 0;
109
- totalNativeOperations : Integer default 0;
110
- // Native function performance metrics
111
- nativeThroughput : Double; // native operations per second
112
- nativeErrorRate : Double; // native operation error rate
113
-
114
- // Metadata fields
115
- dataType : String;
116
- operation : String; // concatenated cache service operations (e.g., SET, GET, WRAP, RUN...)
117
- metadata : LargeString; // JSON string for additional metadata
118
- // Enhanced context information
119
- context : String; // JSON string with detailed context
120
- query : LargeString; // CQL query text if applicable
121
- subject : LargeString; // JSON string with subject information
122
- target : String; // Target information
123
- tenant : String; // Tenant information
124
- user : String; // User information
125
- locale : String; // Locale information
126
- timestamp : DateTime; // When this key was first accessed
127
- cacheOptions : String; // JSON string with cache options
128
- }
1
+ using from './db/statistics';
129
2
 
3
+ context plugin.cds_caching {
130
4
 
131
5
  @impl: 'cds-caching/srv/caching-api-service'
132
6
  service CachingApiService {
@@ -7,6 +7,8 @@ const CacheStatisticsHandler = require('./support/CacheStatisticsHandler');
7
7
  const BasicOperations = require('./operations/BasicOperations');
8
8
  const CapOperations = require('./operations/CapOperations');
9
9
  const AsyncOperations = require('./operations/AsyncOperations');
10
+ const telemetry = require('./support/Telemetry');
11
+ const { isMultitenantMode } = require('./support/MultitenancyDetector');
10
12
 
11
13
  class CachingService extends cds.Service {
12
14
 
@@ -36,7 +38,18 @@ class CachingService extends cds.Service {
36
38
  // Create cache store
37
39
  const { cache, cleanup } = this.storeManager.createStore(this.options, this.name);
38
40
  this.cache = cache;
41
+
42
+ // Set default OTel attributes for all cache metrics
43
+ telemetry.setDefaultAttributes({
44
+ 'cache.name': this.name,
45
+ 'cache.store': this.options.store || 'memory',
46
+ 'cache.namespace': this.options.namespace || this.name
47
+ });
48
+
39
49
  this.log.info(`Caching service ${this.name} initialized with namespace ${this.options.namespace || this.name}`);
50
+ if (isMultitenantMode()) {
51
+ this.log.info(`Multi-tenant mode detected for ${this.name} — tenant-aware caching enabled`);
52
+ }
40
53
 
41
54
  // Set up cleanup on shutdown
42
55
  cds.once("shutdown", cleanup);
package/lib/add.js ADDED
@@ -0,0 +1,17 @@
1
+ const cds = require("@sap/cds-dk");
2
+ const { copy, path, write } = cds.utils;
3
+ const { join } = path;
4
+
5
+ module.exports = class extends cds.add.Plugin {
6
+ async run() {
7
+ const src = join(__dirname, "..", "app", "dashboard");
8
+ await copy(src).to("app", "caching-dashboard", "webapp");
9
+
10
+ const cdsFile = join(cds.root, "srv", "caching-api.cds");
11
+ if (!cds.utils.fs.existsSync(cdsFile)) {
12
+ await write(
13
+ "using {plugin.cds_caching.CachingApiService} from 'cds-caching/index.cds';\n",
14
+ ).to("srv/caching-api.cds");
15
+ }
16
+ }
17
+ };
@@ -1,3 +1,4 @@
1
+ const telemetry = require('../support/Telemetry');
1
2
  /**
2
3
  * Manages async cache operations for function wrapping and execution
3
4
  */
@@ -27,9 +28,10 @@ class AsyncOperations {
27
28
  stack: error.stack,
28
29
  context: context
29
30
  });
30
- return {
31
- success: false,
32
- result: null,
31
+ telemetry.recordError({ 'cache.error_operation': operationName });
32
+ return {
33
+ success: false,
34
+ result: null,
33
35
  error: {
34
36
  message: error.message,
35
37
  operation: operationName,
@@ -72,7 +74,7 @@ class AsyncOperations {
72
74
  // Create argument placeholders
73
75
  const argPlaceholders = args.map((_, index) => `{args[${index}]}`).join(':');
74
76
  const template = `${baseKey}:${argPlaceholders}`;
75
-
77
+
76
78
  return this.keyManager.createKey(
77
79
  { baseKey, args, functionName },
78
80
  {
@@ -95,113 +97,138 @@ class AsyncOperations {
95
97
  * @returns {function} - the wrapped function
96
98
  */
97
99
  wrap(key, asyncFunction, options = {}) {
98
- return async (...args) => {
99
- // Create dynamic key based on function arguments
100
- const cacheKey = this.createDynamicKey(key, args, options, asyncFunction.name || 'anonymous');
101
-
102
- const startTime = process.hrtime();
103
- const metadata = {
104
- dataType: 'Function',
105
- operation: 'WRAP',
106
- operationType: 'READ_THROUGH',
107
- user: cds.context?.user?.id,
108
- tenant: cds.context?.tenant,
109
- locale: cds.context?.locale,
110
- metadata: JSON.stringify({
111
- functionName: asyncFunction.name || 'anonymous',
112
- args: args,
113
- cacheKey: cacheKey
114
- }),
115
- cacheOptions: JSON.stringify(options)
116
- };
100
+ const self = this;
101
+ return async function (...args) {
102
+ const tracer = telemetry.getTracer('cds-caching');
103
+ if (tracer) {
104
+ return tracer.startActiveSpan('cds-caching - wrap', async (span) => {
105
+ try {
106
+ const result = await self._wrap(key, asyncFunction, options, args);
107
+ span.setAttribute('cache.hit', result.metadata.hit);
108
+ span.setAttribute('cache.key', result.cacheKey);
109
+ span.setAttribute('cache.operation', 'wrap');
110
+ span.setAttribute('cache.operation_type', 'read_through');
111
+ if (!result.metadata.hit) span.setAttribute('cache.ttl_ms', options.ttl || 0);
112
+ return result;
113
+ } catch (e) {
114
+ span.setStatus({ code: telemetry.SpanStatusCode.ERROR, message: e.message });
115
+ throw e;
116
+ } finally {
117
+ span.end();
118
+ }
119
+ });
120
+ }
121
+ return self._wrap(key, asyncFunction, options, args);
122
+ };
123
+ }
117
124
 
118
- // Safely check if key exists in cache
119
- const hasKeyResult = await this.safeCacheOperation(
120
- () => this.cache.has(cacheKey),
121
- 'has',
122
- { key: cacheKey, functionName: asyncFunction.name || 'anonymous' }
123
- );
125
+ /** @private */
126
+ async _wrap(key, asyncFunction, options, args) {
127
+ // Create dynamic key based on function arguments
128
+ const cacheKey = this.createDynamicKey(key, args, options, asyncFunction.name || 'anonymous');
124
129
 
125
- const hasKey = hasKeyResult.success && hasKeyResult.result;
126
- const cacheErrors = [];
127
-
128
- if (hasKey) {
129
- const latency = this.getElapsedMs(startTime);
130
-
131
- // Safely record hit statistics
132
- const hitStatsResult = await this.safeCacheOperation(
133
- () => this.statistics.recordHit(latency, cacheKey, metadata),
134
- 'recordHit',
135
- { key: cacheKey, latency }
136
- );
137
- if (!hitStatsResult.success) {
138
- cacheErrors.push(hitStatsResult.error);
139
- }
130
+ const startTime = process.hrtime();
131
+ const metadata = {
132
+ dataType: 'Function',
133
+ operation: 'WRAP',
134
+ operationType: 'READ_THROUGH',
135
+ user: cds.context?.user?.id,
136
+ tenant: cds.context?.tenant,
137
+ locale: cds.context?.locale,
138
+ metadata: JSON.stringify({
139
+ functionName: asyncFunction.name || 'anonymous',
140
+ args: args,
141
+ cacheKey: cacheKey
142
+ }),
143
+ cacheOptions: JSON.stringify(options)
144
+ };
140
145
 
141
- // Safely get value from cache
142
- const getResult = await this.safeCacheOperation(
143
- () => this.cache.send("GET", { key: cacheKey }),
144
- 'get',
145
- { key: cacheKey }
146
- );
147
-
148
- if (getResult.success && getResult.result?.value !== undefined) {
149
- return {
150
- result: getResult.result.value,
151
- cacheKey,
152
- metadata: { hit: true, latency },
153
- cacheErrors: cacheErrors
154
- };
155
- }
156
- }
146
+ // Safely check if key exists in cache
147
+ const hasKeyResult = await this.safeCacheOperation(
148
+ () => this.cache.has(cacheKey),
149
+ 'has',
150
+ { key: cacheKey, functionName: asyncFunction.name || 'anonymous' }
151
+ );
157
152
 
158
- // Cache miss or cache error - delegate to underlying function
159
- try {
160
- const response = await asyncFunction(...args);
161
- const latency = this.getElapsedMs(startTime);
162
-
163
- // Safely record miss statistics
164
- const missStatsResult = await this.safeCacheOperation(
165
- () => this.statistics.recordMiss(latency, cacheKey, metadata),
166
- 'recordMiss',
167
- { key: cacheKey, latency }
168
- );
169
- if (!missStatsResult.success) {
170
- cacheErrors.push(missStatsResult.error);
171
- }
153
+ const hasKey = hasKeyResult.success && hasKeyResult.result;
154
+ const cacheErrors = [];
172
155
 
173
- // Safely store in cache
174
- const wrappedValue = {
175
- value: response,
176
- tags: options.tags || [],
177
- timestamp: Date.now()
178
- };
179
-
180
- const setResult = await this.safeCacheOperation(
181
- () => this.cache.send("SET", { key: cacheKey, value: wrappedValue, ttl: options.ttl || 0 }),
182
- 'set',
183
- { key: cacheKey, ttl: options.ttl }
184
- );
185
- if (!setResult.success) {
186
- cacheErrors.push(setResult.error);
187
- }
156
+ if (hasKey) {
157
+ const latency = this.getElapsedMs(startTime);
158
+
159
+ // Safely record hit statistics
160
+ const hitStatsResult = await this.safeCacheOperation(
161
+ () => this.statistics.recordHit(latency, cacheKey, metadata),
162
+ 'recordHit',
163
+ { key: cacheKey, latency }
164
+ );
165
+ if (!hitStatsResult.success) {
166
+ cacheErrors.push(hitStatsResult.error);
167
+ }
188
168
 
189
- return {
190
- result: response,
191
- cacheKey,
192
- metadata: { hit: false, latency },
169
+ // Safely get value from cache
170
+ const getResult = await this.safeCacheOperation(
171
+ () => this.cache.send("GET", { key: cacheKey }),
172
+ 'get',
173
+ { key: cacheKey }
174
+ );
175
+
176
+ if (getResult.success && getResult.result?.value !== undefined) {
177
+ return {
178
+ result: getResult.result.value,
179
+ cacheKey,
180
+ metadata: { hit: true, latency },
193
181
  cacheErrors: cacheErrors
194
182
  };
195
- } catch (functionError) {
196
- // If the underlying function fails, throw the error
197
- this.log.error('Function execution failed:', {
198
- error: functionError.message,
199
- functionName: asyncFunction.name || 'anonymous',
200
- key: cacheKey
201
- });
202
- throw functionError;
203
183
  }
204
184
  }
185
+
186
+ // Cache miss or cache error - delegate to underlying function
187
+ try {
188
+ const response = await asyncFunction(...args);
189
+ const latency = this.getElapsedMs(startTime);
190
+
191
+ // Safely record miss statistics
192
+ const missStatsResult = await this.safeCacheOperation(
193
+ () => this.statistics.recordMiss(latency, cacheKey, metadata),
194
+ 'recordMiss',
195
+ { key: cacheKey, latency }
196
+ );
197
+ if (!missStatsResult.success) {
198
+ cacheErrors.push(missStatsResult.error);
199
+ }
200
+
201
+ // Safely store in cache
202
+ const wrappedValue = {
203
+ value: response,
204
+ tags: options.tags || [],
205
+ timestamp: Date.now()
206
+ };
207
+
208
+ const setResult = await this.safeCacheOperation(
209
+ () => this.cache.send("SET", { key: cacheKey, value: wrappedValue, ttl: options.ttl || 0 }),
210
+ 'set',
211
+ { key: cacheKey, ttl: options.ttl }
212
+ );
213
+ if (!setResult.success) {
214
+ cacheErrors.push(setResult.error);
215
+ }
216
+
217
+ return {
218
+ result: response,
219
+ cacheKey,
220
+ metadata: { hit: false, latency },
221
+ cacheErrors: cacheErrors
222
+ };
223
+ } catch (functionError) {
224
+ // If the underlying function fails, throw the error
225
+ this.log.error('Function execution failed:', {
226
+ error: functionError.message,
227
+ functionName: asyncFunction.name || 'anonymous',
228
+ key: cacheKey
229
+ });
230
+ throw functionError;
231
+ }
205
232
  }
206
233
 
207
234
  /**
@@ -213,6 +240,30 @@ class AsyncOperations {
213
240
  * @returns {Promise<any>} - the result
214
241
  */
215
242
  async exec(key, asyncFunction, args = [], options = {}) {
243
+ const tracer = telemetry.getTracer('cds-caching');
244
+ if (tracer) {
245
+ return tracer.startActiveSpan('cds-caching - exec', async (span) => {
246
+ try {
247
+ const result = await this._exec(key, asyncFunction, args, options);
248
+ span.setAttribute('cache.hit', result.metadata.hit);
249
+ span.setAttribute('cache.key', result.cacheKey);
250
+ span.setAttribute('cache.operation', 'exec');
251
+ span.setAttribute('cache.operation_type', 'read_through');
252
+ if (!result.metadata.hit) span.setAttribute('cache.ttl_ms', options.ttl || 0);
253
+ return result;
254
+ } catch (e) {
255
+ span.setStatus({ code: telemetry.SpanStatusCode.ERROR, message: e.message });
256
+ throw e;
257
+ } finally {
258
+ span.end();
259
+ }
260
+ });
261
+ }
262
+ return this._exec(key, asyncFunction, args, options);
263
+ }
264
+
265
+ /** @private */
266
+ async _exec(key, asyncFunction, args, options) {
216
267
  // Create dynamic key based on function arguments
217
268
  const cacheKey = this.createDynamicKey(key, args, options, asyncFunction.name || 'anonymous');
218
269
 
@@ -245,7 +296,7 @@ class AsyncOperations {
245
296
 
246
297
  if (hasKey) {
247
298
  const latency = this.getElapsedMs(startTime);
248
-
299
+
249
300
  // Safely record hit statistics
250
301
  const hitStatsResult = await this.safeCacheOperation(
251
302
  () => this.statistics.recordHit(latency, cacheKey, metadata),
@@ -264,9 +315,9 @@ class AsyncOperations {
264
315
  );
265
316
 
266
317
  if (getResult.success && getResult.result?.value !== undefined) {
267
- return {
268
- result: getResult.result.value,
269
- cacheKey,
318
+ return {
319
+ result: getResult.result.value,
320
+ cacheKey,
270
321
  metadata: { hit: true, latency },
271
322
  cacheErrors: cacheErrors
272
323
  };
@@ -277,7 +328,7 @@ class AsyncOperations {
277
328
  try {
278
329
  const response = await asyncFunction(...args);
279
330
  const latency = this.getElapsedMs(startTime);
280
-
331
+
281
332
  // Safely record miss statistics
282
333
  const missStatsResult = await this.safeCacheOperation(
283
334
  () => this.statistics.recordMiss(latency, cacheKey, metadata),
@@ -294,7 +345,7 @@ class AsyncOperations {
294
345
  tags: options.tags || [],
295
346
  timestamp: Date.now()
296
347
  };
297
-
348
+
298
349
  const setResult = await this.safeCacheOperation(
299
350
  () => this.cache.send("SET", { key: cacheKey, value: wrappedValue, ttl: options.ttl || 0 }),
300
351
  'set',
@@ -304,9 +355,9 @@ class AsyncOperations {
304
355
  cacheErrors.push(setResult.error);
305
356
  }
306
357
 
307
- return {
308
- result: response,
309
- cacheKey,
358
+ return {
359
+ result: response,
360
+ cacheKey,
310
361
  metadata: { hit: false, latency },
311
362
  cacheErrors: cacheErrors
312
363
  };
@@ -332,4 +383,4 @@ class AsyncOperations {
332
383
  }
333
384
  }
334
385
 
335
- module.exports = AsyncOperations;
386
+ module.exports = AsyncOperations;
@@ -1,3 +1,4 @@
1
+ const telemetry = require('../support/Telemetry');
1
2
  /**
2
3
  * Manages basic cache operations with statistics tracking
3
4
  */
@@ -50,6 +51,14 @@ class BasicOperations {
50
51
  cacheOptions: JSON.stringify(options)
51
52
  };
52
53
  this.statistics.recordNativeSet(createdKey, metadata);
54
+
55
+ const span = telemetry.getActiveSpan();
56
+ if (span) {
57
+ span.setAttribute('cache.key', createdKey);
58
+ span.setAttribute('cache.operation', 'set');
59
+ span.setAttribute('cache.operation_type', 'basic');
60
+ span.setAttribute('cache.ttl_ms', options.ttl || 0);
61
+ }
53
62
  }
54
63
 
55
64
  /**
@@ -80,6 +89,14 @@ class BasicOperations {
80
89
  const isHit = wrappedValue !== undefined && wrappedValue !== null;
81
90
  this.statistics.recordNativeGet(createdKey, isHit, metadata);
82
91
 
92
+ const span = telemetry.getActiveSpan();
93
+ if (span) {
94
+ span.setAttribute('cache.hit', isHit);
95
+ span.setAttribute('cache.key', createdKey);
96
+ span.setAttribute('cache.operation', 'get');
97
+ span.setAttribute('cache.operation_type', 'basic');
98
+ }
99
+
83
100
  return wrappedValue?.value;
84
101
  }
85
102
 
@@ -127,6 +144,13 @@ class BasicOperations {
127
144
  };
128
145
  this.statistics.recordNativeDelete(createdKey, metadata);
129
146
 
147
+ const span = telemetry.getActiveSpan();
148
+ if (span) {
149
+ span.setAttribute('cache.key', createdKey);
150
+ span.setAttribute('cache.operation', 'delete');
151
+ span.setAttribute('cache.operation_type', 'basic');
152
+ }
153
+
130
154
  return result;
131
155
  }
132
156
 
@@ -149,6 +173,11 @@ class BasicOperations {
149
173
  };
150
174
  this.statistics.recordNativeClear(metadata);
151
175
 
176
+ const span = telemetry.getActiveSpan();
177
+ if (span) {
178
+ span.setAttribute('cache.operation', 'clear');
179
+ span.setAttribute('cache.operation_type', 'basic');
180
+ }
152
181
  }
153
182
 
154
183
  /**
@@ -173,6 +202,12 @@ class BasicOperations {
173
202
  };
174
203
  this.statistics.recordNativeDeleteByTag(tag, metadata);
175
204
 
205
+ const span = telemetry.getActiveSpan();
206
+ if (span) {
207
+ span.setAttribute('cache.tag', tag);
208
+ span.setAttribute('cache.operation', 'deleteByTag');
209
+ span.setAttribute('cache.operation_type', 'basic');
210
+ }
176
211
  }
177
212
 
178
213
  /**