Skip to content

Commit 93e7f14

Browse files
authored
Merge pull request #266 from upcFrost/master
Lua redis.log implementation
2 parents fc07cf4 + c95beb0 commit 93e7f14

File tree

2 files changed

+98
-3
lines changed

2 files changed

+98
-3
lines changed

fakeredis/_server.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
import os
23
import time
34
import threading
@@ -19,6 +20,20 @@
1920
from ._zset import ZSet
2021

2122

23+
LOGGER = logging.getLogger('fakeredis')
24+
REDIS_LOG_LEVELS = {
25+
b'LOG_DEBUG': 0,
26+
b'LOG_VERBOSE': 1,
27+
b'LOG_NOTICE': 2,
28+
b'LOG_WARNING': 3
29+
}
30+
REDIS_LOG_LEVELS_TO_LOGGING = {
31+
0: logging.DEBUG,
32+
1: logging.INFO,
33+
2: logging.INFO,
34+
3: logging.WARNING
35+
}
36+
2237
MAX_STRING_SIZE = 512 * 1024 * 1024
2338

2439
INVALID_EXPIRE_MSG = "invalid expire time in {}"
@@ -59,6 +74,9 @@
5974
BAD_COMMAND_IN_PUBSUB_MSG = \
6075
"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context"
6176
CONNECTION_ERROR_MSG = "FakeRedis is emulating a connection error."
77+
REQUIRES_MORE_ARGS = "{} requires {} arguments or more."
78+
LOG_WRONG_FIRST_ARG = "First argument must be a number (log level)."
79+
LOG_INVALID_DEBUG_LEVEL = "Invalid debug level."
6280

6381
FLAG_NO_SCRIPT = 's' # Command not allowed in scripts
6482

@@ -2312,9 +2330,20 @@ def _lua_redis_pcall(self, lua_runtime, expected_globals, op, *args):
23122330
except Exception as ex:
23132331
return lua_runtime.table_from({b"err": str(ex)})
23142332

2333+
def _lua_redis_log(self, lua_runtime, expected_globals, lvl, *args):
2334+
self._check_for_lua_globals(lua_runtime, expected_globals)
2335+
if len(args) < 1:
2336+
raise redis.ResponseError(REQUIRES_MORE_ARGS.format("redis.log()", "two"))
2337+
if lvl not in REDIS_LOG_LEVELS.values():
2338+
raise redis.ResponseError(LOG_INVALID_DEBUG_LEVEL)
2339+
msg = ' '.join([x.decode('utf-8')
2340+
if isinstance(x, bytes) else str(x)
2341+
for x in args if not isinstance(x, bool)])
2342+
LOGGER.log(REDIS_LOG_LEVELS_TO_LOGGING[lvl], msg)
2343+
23152344
@command((bytes, Int), (bytes,), flags='s')
23162345
def eval(self, script, numkeys, *keys_and_args):
2317-
from lupa import LuaRuntime, LuaError
2346+
from lupa import LuaRuntime, LuaError, as_attrgetter
23182347

23192348
if numkeys > len(keys_and_args):
23202349
raise redis.ResponseError(TOO_MANY_KEYS_MSG)
@@ -2326,10 +2355,14 @@ def eval(self, script, numkeys, *keys_and_args):
23262355

23272356
set_globals = lua_runtime.eval(
23282357
"""
2329-
function(keys, argv, redis_call, redis_pcall)
2358+
function(keys, argv, redis_call, redis_pcall, redis_log, redis_log_levels)
23302359
redis = {}
23312360
redis.call = redis_call
23322361
redis.pcall = redis_pcall
2362+
redis.log = redis_log
2363+
for level, pylevel in python.iterex(redis_log_levels.items()) do
2364+
redis[level] = pylevel
2365+
end
23332366
redis.error_reply = function(msg) return {err=msg} end
23342367
redis.status_reply = function(msg) return {ok=msg} end
23352368
KEYS = keys
@@ -2342,7 +2375,9 @@ def eval(self, script, numkeys, *keys_and_args):
23422375
lua_runtime.table_from(keys_and_args[:numkeys]),
23432376
lua_runtime.table_from(keys_and_args[numkeys:]),
23442377
functools.partial(self._lua_redis_call, lua_runtime, expected_globals),
2345-
functools.partial(self._lua_redis_pcall, lua_runtime, expected_globals)
2378+
functools.partial(self._lua_redis_pcall, lua_runtime, expected_globals),
2379+
functools.partial(self._lua_redis_log, lua_runtime, expected_globals),
2380+
as_attrgetter(REDIS_LOG_LEVELS)
23462381
)
23472382
expected_globals.update(lua_runtime.globals().keys())
23482383

test_fakeredis.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ def skip_test(*args, **kwargs):
5656
redis3_only = pytest.mark.skipif(not REDIS3, reason="Test is only applicable to redis-py 3.x")
5757

5858

59+
def fake_only(reason):
60+
def wrap(func):
61+
def wrapper(self):
62+
if not isinstance(self.redis, (fakeredis.FakeRedis, fakeredis.FakeStrictRedis)):
63+
pytest.skip("Works only on fakeredis: %s" % reason)
64+
func(self)
65+
return wrapper
66+
return wrap
67+
68+
5969
def key_val_dict(size=100):
6070
return {b'key:' + bytes([i]): b'val:' + bytes([i])
6171
for i in range(size)}
@@ -4160,6 +4170,56 @@ def test_script(self):
41604170
result = script(args=[42])
41614171
self.assertEqual(result, b'42')
41624172

4173+
@fake_only("requires access to redis log file")
4174+
def test_lua_log(self):
4175+
logger = fakeredis._server.LOGGER
4176+
script = """
4177+
redis.log(redis.LOG_DEBUG, "debug")
4178+
redis.log(redis.LOG_VERBOSE, "verbose")
4179+
redis.log(redis.LOG_NOTICE, "notice")
4180+
redis.log(redis.LOG_WARNING, "warning")
4181+
"""
4182+
script = self.redis.register_script(script)
4183+
with self.assertLogs(logger, 'DEBUG') as cm:
4184+
script()
4185+
self.assertEqual(cm.output, ['DEBUG:%s:debug' % logger.name,
4186+
'INFO:%s:verbose' % logger.name,
4187+
'INFO:%s:notice' % logger.name,
4188+
'WARNING:%s:warning' % logger.name])
4189+
4190+
def test_lua_log_no_message(self):
4191+
script = "redis.log(redis.LOG_DEBUG)"
4192+
script = self.redis.register_script(script)
4193+
with self.assertRaises(redis.ResponseError):
4194+
script()
4195+
4196+
@fake_only("requires access to redis log file")
4197+
def test_lua_log_different_types(self):
4198+
logger = fakeredis._server.LOGGER
4199+
script = "redis.log(redis.LOG_DEBUG, 'string', 1, true, 3.14, 'string')"
4200+
script = self.redis.register_script(script)
4201+
with self.assertLogs(logger, 'DEBUG') as cm:
4202+
script()
4203+
self.assertEqual(cm.output, ['DEBUG:%s:string 1 3.14 string' % logger.name])
4204+
4205+
def test_lua_log_wrong_level(self):
4206+
script = "redis.log(10, 'string')"
4207+
script = self.redis.register_script(script)
4208+
with self.assertRaises(redis.ResponseError):
4209+
script()
4210+
4211+
@fake_only("requires access to redis log file")
4212+
def test_lua_log_defined_vars(self):
4213+
logger = fakeredis._server.LOGGER
4214+
script = """
4215+
local var='string'
4216+
redis.log(redis.LOG_DEBUG, var)
4217+
"""
4218+
script = self.redis.register_script(script)
4219+
with self.assertLogs(logger, 'DEBUG') as cm:
4220+
script()
4221+
self.assertEqual(cm.output, ['DEBUG:%s:string' % logger.name])
4222+
41634223
@redis3_only
41644224
def test_unlink(self):
41654225
self.redis.set('foo', 'bar')

0 commit comments

Comments
 (0)