Skip to content

Commit 41af09e

Browse files
[CLIENT-2740] Fix bug where LOG_LEVEL_OFF causes more logs to be produced (as if LOG_LEVEL_TRACE was set) (#572)
Extra changes: - aerospike.set_log_handler(): add support for accepting None parameter to remove current log handler - aerospike.set_log_level(): fix bug where passing in an invalid log level does not raise an exception. This was caused by a bad comparison between long and uint32_t where the latter gets converted to a long in 64-bit systems. This causes the exception to never get handled properly - Docs: aerospike.set_log_handler(): add expected callback signature for parameter - Docs: add code example to show how to enable global-level logging
1 parent 428047e commit 41af09e

File tree

7 files changed

+273
-123
lines changed

7 files changed

+273
-123
lines changed

doc/aerospike.rst

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -282,30 +282,45 @@ If we read the data for each record using ``aql``, it outputs the following data
282282
Logging
283283
-------
284284

285-
.. py:function:: set_log_handler(callback)
285+
.. _logging_default_behavior:
286286

287-
Enables aerospike log handler
287+
Default behavior
288+
^^^^^^^^^^^^^^^^
288289

289-
:param optional callable callback: the function used as the logging handler.
290+
By default:
290291

291-
.. note:: The callback function must have the five parameters (level, func, path, line, msg)
292+
- The client has a default log level of :py:obj:`aerospike.LOG_LEVEL_ERROR`.
293+
- The client's default log handler is set and prints logs in this format: ``<process id>:<counter> <error message>``.
294+
For each log, the counter starts at 1 and increments by 1.
292295

293-
.. code-block:: python
296+
The following example shows several different methods to configuring logging for the Aerospike Python Client:
294297

295-
import aerospike
298+
.. include:: examples/log.py
299+
:code: python
296300

297-
from __future__ import print_function
298-
import aerospike
301+
.. py:function:: set_log_handler(log_handler: Optional[Callable[[int, str, str, int, str], None]])
302+
303+
Set logging callback globally across all clients.
304+
305+
When no argument is passed, the default log handler is used. See :ref:`logging_default_behavior` for more details.
306+
307+
When callback is :py:obj:`None`, the saved log handler is cleared.
308+
309+
When a callable is passed, it must have these five parameters in this order:
310+
311+
.. code-block:: python
299312
300-
aerospike.set_log_level(aerospike.LOG_LEVEL_DEBUG)
301-
aerospike.set_log_handler(callback)
313+
def callback(level: int, function: str, path: str, line: int, message: str):
314+
pass
302315
316+
:param optional callable log_handler: the function used as the logging handler.
303317

304-
.. py:function:: set_log_level(log_level)
318+
.. py:function:: set_log_level(loglevel)
305319
306-
Declare the logging level threshold for the log handler.
320+
Declare the logging level threshold for the log handler. If setting log level to :py:obj:`aerospike.LOG_LEVEL_OFF`,
321+
the current log handler does not get reset.
307322

308-
:param int log_level: one of the :ref:`aerospike_log_levels` constant values.
323+
:param int loglevel: one of the :ref:`aerospike_log_levels` constant values.
309324

310325
Other
311326
-----

doc/examples/log.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Enable the logging at application start, before connecting to the server.
2+
import aerospike
3+
4+
## SETTING THE LOG HANDLER ##
5+
6+
# Clears saved log handler and disable logging
7+
aerospike.set_log_handler(None)
8+
9+
# Set default log handler to print to the console
10+
aerospike.set_log_handler()
11+
12+
def log_callback(level, func, path, line, msg):
13+
print("[{}] {}".format(func, msg))
14+
15+
# Set log handler to custom callback function (defined above)
16+
aerospike.set_log_handler(log_callback)
17+
18+
19+
## SETTING THE LOG LEVEL ##
20+
21+
# disables log handling
22+
aerospike.set_log_level(aerospike.LOG_LEVEL_OFF)
23+
24+
# Enables log handling and sets level to LOG_LEVEL_TRACE
25+
aerospike.set_log_level(aerospike.LOG_LEVEL_TRACE)
26+
27+
# Create a client and connect it to the cluster
28+
# This line will print use log_callback to print logs with a log level of TRACE
29+
config = {
30+
"hosts": [
31+
("127.0.0.1", 3000)
32+
]
33+
}
34+
client = aerospike.client(config)

src/include/log.h

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,7 @@
1818

1919
#include <Python.h>
2020
#include <aerospike/as_status.h>
21-
22-
/*
23-
* Structure to hold user's log_callback object
24-
*/
25-
typedef struct Aerospike_log_callback {
26-
PyObject *callback;
27-
} AerospikeLogCallback;
21+
#include <aerospike/as_log.h>
2822

2923
/**
3024
* Set log level for C-SDK
@@ -40,3 +34,5 @@ PyObject *Aerospike_Set_Log_Handler(PyObject *parent, PyObject *args,
4034
PyObject *kwds);
4135

4236
void Aerospike_Enable_Default_Logging();
37+
38+
#define LOG_LEVEL_OFF -1

src/main/aerospike.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ static struct module_constant_name_to_value module_constants[] = {
525525
.value.integer = AS_QUERY_DURATION_LONG_RELAX_AP},
526526
{"QUERY_DURATION_SHORT", .value.integer = AS_QUERY_DURATION_SHORT},
527527

528-
{"LOG_LEVEL_OFF", .value.integer = -1},
528+
{"LOG_LEVEL_OFF", .value.integer = LOG_LEVEL_OFF},
529529
{"LOG_LEVEL_ERROR", .value.integer = AS_LOG_LEVEL_ERROR},
530530
{"LOG_LEVEL_WARN", .value.integer = AS_LOG_LEVEL_WARN},
531531
{"LOG_LEVEL_INFO", .value.integer = AS_LOG_LEVEL_INFO},

src/main/client/type.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <aerospike/as_error.h>
2424
#include <aerospike/as_policy.h>
2525
#include <aerospike/as_vector.h>
26+
#include <aerospike/as_log_macros.h>
2627

2728
#include "pythoncapi_compat.h"
2829
#include "admin.h"
@@ -570,6 +571,8 @@ int does_py_dict_contain_valid_keys(as_error *err, PyObject *py_dict,
570571
static int AerospikeClient_Type_Init(AerospikeClient *self, PyObject *args,
571572
PyObject *kwds)
572573
{
574+
as_log_trace("Starting to create a new client...");
575+
573576
PyObject *py_config = NULL;
574577
int error_code = 0;
575578
as_error constructor_err;

src/main/log.c

Lines changed: 93 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -25,73 +25,23 @@
2525
#include "exceptions.h"
2626
#include "log.h"
2727

28+
bool is_current_log_level_off = true;
29+
PyObject *py_current_custom_callback = NULL;
30+
2831
#ifdef _WIN32
2932
#define __sync_fetch_and_add InterlockedExchangeAdd64
3033
#endif
3134

32-
static AerospikeLogCallback user_callback;
33-
34-
PyObject *Aerospike_Set_Log_Level(PyObject *parent, PyObject *args,
35-
PyObject *kwds)
36-
{
37-
// Aerospike vaiables
38-
as_error err;
39-
as_status status = AEROSPIKE_OK;
40-
41-
// Python Function Arguments
42-
PyObject *py_log_level = NULL;
43-
44-
// Initialise error object.
45-
as_error_init(&err);
46-
47-
// Python Function Keyword Arguments
48-
static char *kwlist[] = {"loglevel", NULL};
49-
50-
// Python Function Argument Parsing
51-
if (PyArg_ParseTupleAndKeywords(args, kwds, "O|:setLogLevel", kwlist,
52-
&py_log_level) == false) {
53-
return NULL;
54-
}
55-
56-
// Type check for incoming parameters
57-
if (!PyLong_Check(py_log_level)) {
58-
as_error_update(&err, AEROSPIKE_ERR_PARAM, "Invalid log level");
59-
goto CLEANUP;
60-
}
61-
62-
long lLogLevel = PyLong_AsLong(py_log_level);
63-
if (lLogLevel == (uint32_t)-1 && PyErr_Occurred()) {
64-
if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
65-
as_error_update(&err, AEROSPIKE_ERR_PARAM,
66-
"integer value exceeds sys.maxsize");
67-
goto CLEANUP;
68-
}
69-
}
70-
71-
// Invoke C API to set log level
72-
as_log_set_level((as_log_level)lLogLevel);
73-
74-
CLEANUP:
75-
76-
// Check error object and act accordingly
77-
if (err.code != AEROSPIKE_OK) {
78-
raise_exception(&err);
79-
return NULL;
80-
}
81-
82-
return PyLong_FromLong(status);
83-
}
84-
8535
volatile int log_counter = 0;
8636

87-
bool console_log_cb(as_log_level level, const char *func, const char *file,
88-
uint32_t line, const char *fmt, ...)
37+
bool default_log_handler(as_log_level level, const char *func, const char *file,
38+
uint32_t line, const char *fmt, ...)
8939
{
9040

9141
char msg[1024];
9242
va_list ap;
9343

94-
int counter = __sync_fetch_and_add((&log_counter), 1);
44+
int counter = __sync_fetch_and_add(&log_counter, 1);
9545

9646
va_start(ap, fmt);
9747
vsnprintf(msg, 1024, fmt, ap);
@@ -102,8 +52,9 @@ bool console_log_cb(as_log_level level, const char *func, const char *file,
10252
return true;
10353
}
10454

105-
static bool log_cb(as_log_level level, const char *func, const char *file,
106-
uint32_t line, const char *fmt, ...)
55+
static bool call_custom_py_log_handler(as_log_level level, const char *func,
56+
const char *file, uint32_t line,
57+
const char *fmt, ...)
10758
{
10859

10960
char msg[1024];
@@ -112,8 +63,6 @@ static bool log_cb(as_log_level level, const char *func, const char *file,
11263
vsnprintf(msg, 1024, fmt, ap);
11364
va_end(ap);
11465

115-
// Extract pyhton user callback
116-
PyObject *py_callback = user_callback.callback;
11766
// User callback's argument list
11867
PyObject *py_arglist = NULL;
11968

@@ -139,7 +88,7 @@ static bool log_cb(as_log_level level, const char *func, const char *file,
13988
PyTuple_SetItem(py_arglist, 4, message);
14089

14190
// Invoke user callback, passing in argument's list
142-
PyObject_Call(py_callback, py_arglist, NULL);
91+
PyObject_Call(py_current_custom_callback, py_arglist, NULL);
14392

14493
Py_DECREF(py_arglist);
14594

@@ -149,31 +98,100 @@ static bool log_cb(as_log_level level, const char *func, const char *file,
14998
return true;
15099
}
151100

101+
PyObject *Aerospike_Set_Log_Level(PyObject *parent, PyObject *args,
102+
PyObject *kwds)
103+
{
104+
as_status status = AEROSPIKE_OK;
105+
106+
// Python Function Argument Parsing
107+
static char *kwlist[] = {"loglevel", NULL};
108+
PyObject *py_log_level = NULL;
109+
110+
if (PyArg_ParseTupleAndKeywords(args, kwds, "O|:setLogLevel", kwlist,
111+
&py_log_level) == false) {
112+
return NULL;
113+
}
114+
115+
as_error err;
116+
as_error_init(&err);
117+
118+
// Type check for incoming parameters
119+
if (!PyLong_Check(py_log_level)) {
120+
as_error_update(&err, AEROSPIKE_ERR_PARAM, "Invalid log level");
121+
goto CLEANUP;
122+
}
123+
124+
long log_level = PyLong_AsLong(py_log_level);
125+
if (log_level == -1 && PyErr_Occurred()) {
126+
if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
127+
as_error_update(&err, AEROSPIKE_ERR_PARAM,
128+
"integer value exceeds sys.maxsize");
129+
goto CLEANUP;
130+
}
131+
}
132+
133+
is_current_log_level_off = log_level == LOG_LEVEL_OFF;
134+
135+
if (log_level == LOG_LEVEL_OFF) {
136+
as_log_set_callback(NULL);
137+
}
138+
else {
139+
as_log_set_level((as_log_level)log_level);
140+
141+
// Re-enable log handler
142+
if (py_current_custom_callback != NULL) {
143+
as_log_set_callback((as_log_callback)call_custom_py_log_handler);
144+
}
145+
else {
146+
as_log_set_callback((as_log_callback)default_log_handler);
147+
}
148+
}
149+
150+
CLEANUP:
151+
152+
if (err.code != AEROSPIKE_OK) {
153+
raise_exception(&err);
154+
return NULL;
155+
}
156+
157+
return PyLong_FromLong(status);
158+
}
159+
152160
PyObject *Aerospike_Set_Log_Handler(PyObject *parent, PyObject *args,
153161
PyObject *kwds)
154162
{
155163
// Python variables
156164
PyObject *py_callback = NULL;
157-
as_error err;
158-
as_error_init(&err);
159165
// Python function keyword arguments
160166
static char *kwlist[] = {"log_handler", NULL};
161167

162168
// Python function arguments parsing
163-
PyArg_ParseTupleAndKeywords(args, kwds, "|O:setLogHandler", kwlist,
164-
&py_callback);
169+
if (PyArg_ParseTupleAndKeywords(args, kwds, "|O:setLogHandler", kwlist,
170+
&py_callback) == false) {
171+
return NULL;
172+
}
165173

166-
if (py_callback && PyCallable_Check(py_callback)) {
167-
// Store user callback
168-
Py_INCREF(py_callback);
169-
user_callback.callback = py_callback;
174+
// Clean up existing log handler
175+
Py_CLEAR(py_current_custom_callback);
170176

171-
// Register callback to C-SDK
172-
as_log_set_callback((as_log_callback)log_cb);
177+
// 3 cases (when args are passed):
178+
if (py_callback == NULL) {
179+
// 1. No args -> enable Python client's default log handler IF log level is not OFF
180+
// IF log level is OFF, don't enable the default log handler.
181+
if (!is_current_log_level_off) {
182+
as_log_set_callback((as_log_callback)default_log_handler);
183+
}
173184
}
174-
else {
175-
// Register callback to C-SDK
176-
as_log_set_callback((as_log_callback)console_log_cb);
185+
else if (Py_IsNone(py_callback)) {
186+
// Disable log handler altogether
187+
as_log_set_callback(NULL);
188+
}
189+
else if (PyCallable_Check(py_callback)) {
190+
// Register custom log handler
191+
py_current_custom_callback = Py_NewRef(py_callback);
192+
if (!is_current_log_level_off) {
193+
as_log_set_callback((as_log_callback)call_custom_py_log_handler);
194+
}
177195
}
178196

179197
return PyLong_FromLong(0);
@@ -184,7 +202,7 @@ void Aerospike_Enable_Default_Logging()
184202
// Invoke C API to set log level
185203
as_log_set_level((as_log_level)AS_LOG_LEVEL_ERROR);
186204
// Register callback to C-SDK
187-
as_log_set_callback((as_log_callback)console_log_cb);
205+
as_log_set_callback((as_log_callback)default_log_handler);
188206

189207
return;
190208
}

0 commit comments

Comments
 (0)