Skip to content

Commit 42e158f

Browse files
committed
feat: rework logging system
1 parent 5db2f79 commit 42e158f

15 files changed

+211
-137
lines changed

benchmarks/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"keywords": [],
1010
"main": "index.js",
1111
"scripts": {
12-
"test": "echo \"Error: no test specified\" && exit 1"
12+
"bench:all": "pnpm tsx all.ts"
1313
},
1414
"devDependencies": {
1515
"@keyv/redis": "^4.2.0",

packages/bentocache/src/bento_cache.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export class BentoCache<KnownCaches extends Record<string, BentoStore>> implemen
7070
busOptions: entry.bus?.options,
7171
})
7272

73-
return new Cache(cacheName, cacheStack, this.#options)
73+
return new Cache(cacheName, cacheStack)
7474
}
7575

7676
get defaultStoreName() {

packages/bentocache/src/cache/cache.ts

+15-15
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ export class Cache implements CacheProvider {
2828
#stack: CacheStack
2929
#options: BentoCacheOptions
3030

31-
constructor(name: string, stack: CacheStack, options: BentoCacheOptions) {
31+
constructor(name: string, stack: CacheStack) {
3232
this.name = name
3333

3434
this.#stack = stack
35-
this.#options = options
35+
this.#options = stack.options
3636
this.#getSetHandler = new GetSetHandler(this.#stack)
3737
}
3838

@@ -44,7 +44,7 @@ export class Cache implements CacheProvider {
4444
* Returns a new instance of the driver namespaced
4545
*/
4646
namespace(namespace: string) {
47-
return new Cache(this.name, this.#stack.namespace(namespace), this.#options)
47+
return new Cache(this.name, this.#stack.namespace(namespace))
4848
}
4949

5050
get<T = any>(options: GetOptions<T>): Promise<T>
@@ -56,32 +56,32 @@ export class Cache implements CacheProvider {
5656
this.#options.logger.logMethod({ method: 'get', key, options, cacheName: this.name })
5757

5858
const localItem = this.#stack.l1?.get(key, options)
59-
if (localItem !== undefined && !localItem.isLogicallyExpired()) {
60-
this.#stack.emit(cacheEvents.hit(key, localItem.getValue(), this.name))
59+
if (localItem?.isGraced === false) {
60+
this.#stack.emit(cacheEvents.hit(key, localItem.entry.getValue(), this.name))
6161
this.#options.logger.logL1Hit({ cacheName: this.name, key, options })
62-
return localItem.getValue()
62+
return localItem.entry.getValue()
6363
}
6464

6565
const remoteItem = await this.#stack.l2?.get(key, options)
6666

67-
if (remoteItem !== undefined && !remoteItem.isLogicallyExpired()) {
68-
this.#stack.l1?.set(key, remoteItem.serialize(), options)
69-
this.#stack.emit(cacheEvents.hit(key, remoteItem.getValue(), this.name))
67+
if (remoteItem?.isGraced === false) {
68+
this.#stack.l1?.set(key, remoteItem.entry.serialize(), options)
69+
this.#stack.emit(cacheEvents.hit(key, remoteItem.entry.getValue(), this.name))
7070
this.#options.logger.logL2Hit({ cacheName: this.name, key, options })
71-
return remoteItem.getValue()
71+
return remoteItem.entry.getValue()
7272
}
7373

7474
if (remoteItem && options.isGraceEnabled()) {
75-
this.#stack.l1?.set(key, remoteItem.serialize(), options)
76-
this.#stack.emit(cacheEvents.hit(key, remoteItem.serialize(), this.name, true))
75+
this.#stack.l1?.set(key, remoteItem.entry.serialize(), options)
76+
this.#stack.emit(cacheEvents.hit(key, remoteItem.entry.serialize(), this.name, true))
7777
this.#options.logger.logL2Hit({ cacheName: this.name, key, options, graced: true })
78-
return remoteItem.getValue()
78+
return remoteItem.entry.getValue()
7979
}
8080

8181
if (localItem && options.isGraceEnabled()) {
82-
this.#stack.emit(cacheEvents.hit(key, localItem.serialize(), this.name, true))
82+
this.#stack.emit(cacheEvents.hit(key, localItem.entry.serialize(), this.name, true))
8383
this.#options.logger.logL1Hit({ cacheName: this.name, key, options, graced: true })
84-
return localItem.getValue()
84+
return localItem.entry.getValue()
8585
}
8686

8787
this.#stack.emit(cacheEvents.miss(key, this.name))

packages/bentocache/src/cache/facades/local_cache.ts

+16-8
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export class LocalCache {
1515
constructor(driver: L1CacheDriver, logger: Logger, serializer: CacheSerializer | undefined) {
1616
this.#driver = driver
1717
this.#serializer = serializer
18-
this.#logger = logger.child({ context: 'bentocache.localCache' })
18+
this.#logger = logger.child({ layer: 'l1' })
1919
}
2020

2121
/**
@@ -25,18 +25,26 @@ export class LocalCache {
2525
/**
2626
* Try to get the item from the local cache
2727
*/
28-
this.#logger.trace({ key, opId: options.id }, 'try getting local cache item')
28+
this.#logger.trace({ key, opId: options.id }, 'try getting from l1 cache')
2929
const value = this.#driver.get(key)
3030

3131
/**
3232
* If the item is not found, return undefined
3333
*/
3434
if (value === undefined) {
35-
this.#logger.trace({ key, opId: options.id }, 'local cache item not found')
35+
this.#logger.debug({ key, opId: options.id }, 'cache miss')
3636
return
3737
}
3838

39-
return CacheEntry.fromDriver(key, value, this.#serializer)
39+
const entry = CacheEntry.fromDriver(key, value, this.#serializer)
40+
const isGraced = entry.isLogicallyExpired()
41+
if (isGraced) {
42+
this.#logger.debug({ key, opId: options.id }, 'cache hit (graced)')
43+
} else {
44+
this.#logger.debug({ key, opId: options.id }, 'cache hit')
45+
}
46+
47+
return { entry, isGraced }
4048
}
4149

4250
/**
@@ -54,15 +62,15 @@ export class LocalCache {
5462
/**
5563
* Save the item to the local cache
5664
*/
57-
this.#logger.trace({ key, value, opId: options.id }, 'saving local cache item')
65+
this.#logger.debug({ key, opId: options.id }, 'saving item')
5866
this.#driver.set(key, value, physicalTtl)
5967
}
6068

6169
/**
6270
* Delete an item from the local cache
6371
*/
6472
delete(key: string, options?: CacheEntryOptions) {
65-
this.#logger.trace({ key, opId: options?.id }, 'deleting local cache item')
73+
this.#logger.debug({ key, opId: options?.id }, 'deleting item')
6674
return this.#driver.delete(key)
6775
}
6876

@@ -74,7 +82,7 @@ export class LocalCache {
7482
* is down and the grace period is enabled
7583
*/
7684
logicallyExpire(key: string) {
77-
this.#logger.trace({ key }, 'logically expiring local cache item')
85+
this.#logger.debug({ key }, 'logically expiring item')
7886

7987
const value = this.#driver.get(key)
8088
if (value === undefined) return
@@ -87,7 +95,7 @@ export class LocalCache {
8795
* Delete many item from the local cache
8896
*/
8997
deleteMany(keys: string[], options: CacheEntryOptions) {
90-
this.#logger.trace({ keys, options, opId: options.id }, 'deleting local cache items')
98+
this.#logger.debug({ keys, options, opId: options.id }, 'deleting items')
9199
this.#driver.deleteMany(keys)
92100
}
93101

packages/bentocache/src/cache/facades/remote_cache.ts

+18-7
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,22 @@ export class RemoteCache {
3131
? new CircuitBreaker({ breakDuration: options.l2CircuitBreakerDuration })
3232
: undefined
3333

34-
this.#logger = logger.child({ context: 'bentocache.remoteCache' })
34+
this.#logger = logger.child({ layer: 'l2' })
3535
}
3636

3737
/**
3838
* Try to execute a cache operation and fallback to a default value
3939
* if the operation fails
4040
*/
41-
async #tryCacheOperation(
41+
async #tryCacheOperation<T>(
4242
operation: string,
4343
options: CacheEntryOptions,
4444
fallbackValue: unknown,
45-
fn: () => any,
46-
) {
45+
fn: () => T,
46+
): Promise<T> {
4747
if (this.#circuitBreaker?.isOpen()) {
4848
this.#logger.error({ opId: options.id }, `circuit breaker is open. ignoring operation`)
49-
return fallbackValue
49+
return fallbackValue as any
5050
}
5151

5252
try {
@@ -64,7 +64,7 @@ export class RemoteCache {
6464
(is.undefined(options.suppressL2Errors) && this.#hasL1Backup) ||
6565
options.suppressL2Errors
6666
) {
67-
return fallbackValue
67+
return fallbackValue as any
6868
}
6969

7070
throw error
@@ -79,7 +79,15 @@ export class RemoteCache {
7979
const value = await this.#driver.get(key)
8080
if (value === undefined) return
8181

82-
return CacheEntry.fromDriver(key, value, this.#options.serializer)
82+
const entry = CacheEntry.fromDriver(key, value, this.#options.serializer)
83+
const isGraced = entry.isLogicallyExpired()
84+
if (isGraced) {
85+
this.#logger.debug({ key, opId: options.id }, 'cache hit (graced)')
86+
} else {
87+
this.#logger.debug({ key, opId: options.id }, 'cache hit')
88+
}
89+
90+
return { entry, isGraced }
8391
})
8492
}
8593

@@ -88,6 +96,7 @@ export class RemoteCache {
8896
*/
8997
async set(key: string, value: string, options: CacheEntryOptions) {
9098
return await this.#tryCacheOperation('set', options, false, async () => {
99+
this.#logger.debug({ key, opId: options.id }, 'saving item')
91100
await this.#driver.set(key, value, options.getPhysicalTtl())
92101
return true
93102
})
@@ -98,6 +107,7 @@ export class RemoteCache {
98107
*/
99108
async delete(key: string, options: CacheEntryOptions) {
100109
return await this.#tryCacheOperation('delete', options, false, async () => {
110+
this.#logger.debug({ key, opId: options.id }, 'deleting item')
101111
return await this.#driver.delete(key)
102112
})
103113
}
@@ -107,6 +117,7 @@ export class RemoteCache {
107117
*/
108118
async deleteMany(keys: string[], options: CacheEntryOptions) {
109119
return await this.#tryCacheOperation('deleteMany', options, false, async () => {
120+
this.#logger.debug({ keys, opId: options.id }, 'deleting items')
110121
return await this.#driver.deleteMany(keys)
111122
})
112123
}

packages/bentocache/src/cache/factory_runner.ts

+17
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,14 @@ export class FactoryRunner {
3838

3939
if (result === this.#skipSymbol) return
4040

41+
this.#stack.logger.info({ cache: this.#stack.name, opId: options.id, key }, 'factory success')
4142
await this.#stack.set(key, result, options)
4243
return result
4344
} catch (error) {
45+
this.#stack.logger.warn(
46+
{ cache: this.#stack.name, opId: options.id, key, error },
47+
'factory failed',
48+
)
4449
options.onFactoryError?.(new errors.E_FACTORY_ERROR(key, error, isBackground))
4550

4651
if (!isBackground) throw new errors.E_FACTORY_ERROR(key, error)
@@ -57,6 +62,14 @@ export class FactoryRunner {
5762
lockReleaser: MutexInterface.Releaser,
5863
) {
5964
const timeout = options.factoryTimeout(hasFallback)
65+
if (timeout) {
66+
this.#stack.logger.info(
67+
{ cache: this.#stack.name, opId: options.id, key },
68+
`running factory with ${timeout.type} timeout of ${timeout.duration}ms`,
69+
)
70+
} else {
71+
this.#stack.logger.info({ cache: this.#stack.name, opId: options.id, key }, 'running factory')
72+
}
6073

6174
/**
6275
* If the timeout is 0, we will not wait for the factory to resolve
@@ -71,6 +84,10 @@ export class FactoryRunner {
7184
const result = await pTimeout(runFactory, {
7285
milliseconds: timeout?.duration ?? Number.POSITIVE_INFINITY,
7386
fallback: async () => {
87+
this.#stack.logger.warn(
88+
{ cache: this.#stack.name, opId: options.id, key },
89+
`factory timed out after ${timeout?.duration}ms`,
90+
)
7491
throw new timeout!.exception(key)
7592
},
7693
})

packages/bentocache/src/cache/get_set/single_tier_handler.ts

+23-28
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { FactoryRunner } from '../factory_runner.js'
77
import type { Factory } from '../../types/helpers.js'
88
import type { CacheEvent } from '../../types/events.js'
99
import { cacheEvents } from '../../events/cache_events.js'
10-
import type { CacheEntry } from '../cache_entry/cache_entry.js'
10+
import type { GetCacheValueReturn } from '../../types/internals/index.js'
1111
import type { CacheEntryOptions } from '../cache_entry/cache_entry_options.js'
1212

1313
export class SingleTierHandler {
@@ -35,11 +35,15 @@ export class SingleTierHandler {
3535
/**
3636
* Returns a value from the remote cache and emit a CacheHit event
3737
*/
38-
async #returnRemoteCacheValue(key: string, item: CacheEntry, options: CacheEntryOptions) {
39-
this.logger.trace({ key, cache: this.stack.name, opId: options.id }, 'remote cache hit')
38+
async #returnRemoteCacheValue(
39+
key: string,
40+
item: GetCacheValueReturn,
41+
options: CacheEntryOptions,
42+
) {
43+
this.logger.logL2Hit({ cacheName: this.stack.name, key, options })
4044

41-
this.#emit(cacheEvents.hit(key, item.getValue(), this.stack.name))
42-
return item.getValue()
45+
this.#emit(cacheEvents.hit(key, item.entry.getValue(), this.stack.name))
46+
return item.entry.getValue()
4347
}
4448

4549
/**
@@ -55,27 +59,21 @@ export class SingleTierHandler {
5559

5660
#returnGracedValueOrThrow(
5761
key: string,
58-
item: CacheEntry | undefined,
62+
item: GetCacheValueReturn | undefined,
5963
options: CacheEntryOptions,
6064
err: Error,
6165
) {
6266
if (options.isGraceEnabled() && item) {
63-
const isLogicallyExpired = item.isLogicallyExpired()
64-
this.#emit(cacheEvents.hit(key, item.getValue(), this.stack.name, isLogicallyExpired))
65-
this.logger.trace(
66-
{ key, cache: this.stack.name, opId: options.id },
67-
'remote cache hit (graced)',
68-
)
69-
70-
return item.getValue()
67+
this.#emit(cacheEvents.hit(key, item.entry.getValue(), this.stack.name, item.isGraced))
68+
return item.entry.getValue()
7169
}
7270

7371
throw err
7472
}
7573

7674
async #applyFallbackAndReturnGracedValue(
7775
key: string,
78-
item: CacheEntry,
76+
item: GetCacheValueReturn,
7977
options: CacheEntryOptions,
8078
) {
8179
if (options.grace && options.graceBackoff) {
@@ -84,29 +82,26 @@ export class SingleTierHandler {
8482
'apply fallback duration',
8583
)
8684

87-
this.stack.l2?.set(key, item.applyBackoff(options.graceBackoff).serialize() as any, options)
85+
this.stack.l2?.set(
86+
key,
87+
item.entry.applyBackoff(options.graceBackoff).serialize() as any,
88+
options,
89+
)
8890
}
8991

9092
this.logger.trace({ key, cache: this.stack.name, opId: options.id }, 'returns stale value')
91-
this.#emit(cacheEvents.hit(key, item.getValue(), this.stack.name, true))
92-
return item.getValue()
93-
}
94-
95-
/**
96-
* Check if a cache item is not undefined and not logically expired
97-
*/
98-
#isItemValid(item: CacheEntry | undefined): item is CacheEntry {
99-
return !!item && !item.isLogicallyExpired()
93+
this.#emit(cacheEvents.hit(key, item.entry.getValue(), this.stack.name, true))
94+
return item.entry.getValue()
10095
}
10196

10297
async handle(key: string, factory: Factory, options: CacheEntryOptions) {
103-
let remoteItem: CacheEntry | undefined
98+
let remoteItem: GetCacheValueReturn | undefined
10499

105100
/**
106101
* Check in the remote cache first if we have something
107102
*/
108103
remoteItem = await this.stack.l2?.get(key, options)
109-
if (this.#isItemValid(remoteItem)) {
104+
if (remoteItem?.isGraced === false) {
110105
return this.#returnRemoteCacheValue(key, remoteItem, options)
111106
}
112107

@@ -126,7 +121,7 @@ export class SingleTierHandler {
126121
* already set the value
127122
*/
128123
remoteItem = await this.stack.l2?.get(key, options)
129-
if (this.#isItemValid(remoteItem)) {
124+
if (remoteItem?.isGraced === false) {
130125
this.#locks.release(key, releaser)
131126
return this.#returnRemoteCacheValue(key, remoteItem, options)
132127
}

0 commit comments

Comments
 (0)