Skip to content
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f0bdf8f
Fixed LOG_LEVEL_OFF to disable logging
DomPeliniAerospike Jan 23, 2024
24aed52
Merge branch 'stage' into CLIENT-2740
DomPeliniAerospike Nov 14, 2025
986bd14
refactor code to preserve original ordering
DomPeliniAerospike Nov 14, 2025
bba6577
Update log.h
DomPeliniAerospike Nov 14, 2025
18d31a2
Refactored log.c
DomPeliniAerospike Nov 14, 2025
acc5e94
Fixed test bug
DomPeliniAerospike Nov 14, 2025
d77f33d
Corrected test for coverage
DomPeliniAerospike Nov 17, 2025
ce63d4a
Added coverage for overflow
DomPeliniAerospike Nov 17, 2025
ec4dcc6
FF CLIENT-2740 to dev (#866)
DomPeliniAerospike Nov 18, 2025
3ca724b
running PR tests
DomPeliniAerospike Nov 18, 2025
a13cabe
Merge remote-tracking branch 'origin/dev' into CLIENT-2740
juliannguyen4 Jan 26, 2026
30e5447
fix
juliannguyen4 Jan 26, 2026
3512ce3
Clean up example code
juliannguyen4 Jan 26, 2026
0081c9b
Add missing documentation for aerospike.set_log_handler and aerospike…
juliannguyen4 Jan 26, 2026
5f2999f
Since this is main behavior, move outside of param
juliannguyen4 Jan 26, 2026
adf31ae
Fix formatting. Document default behavior of global logging...
juliannguyen4 Jan 26, 2026
49aa78e
Improve docs for callback signature
juliannguyen4 Jan 26, 2026
64ebad7
Callback can be None
juliannguyen4 Jan 26, 2026
25796a9
fix
juliannguyen4 Jan 26, 2026
2b536bf
fix
juliannguyen4 Jan 26, 2026
635d58f
aerospike.set_log_level() parameter name: Just update docs to match i…
juliannguyen4 Jan 26, 2026
0db421c
Rename vars in implementation to be consistent. Match docs with imple…
juliannguyen4 Jan 26, 2026
3106b1f
Refactor
juliannguyen4 Jan 26, 2026
3a13d25
Refactor LOG_LEVEL_OFF callback logic. Just use a boolean flag to kee…
juliannguyen4 Jan 26, 2026
3333fb8
Have debug log printed when client is created
juliannguyen4 Jan 26, 2026
6d58eae
Refactor and add more test cases
juliannguyen4 Jan 26, 2026
d9a1548
Add kwd tests
juliannguyen4 Jan 26, 2026
f8dc1fe
Fix test
juliannguyen4 Jan 27, 2026
0baccf3
fix test
juliannguyen4 Jan 28, 2026
c21fc8a
capsys doesn't capture printf in c code
juliannguyen4 Jan 28, 2026
9aaae03
Don't check for live logs in stdout since test case will fail if pyth…
juliannguyen4 Jan 28, 2026
a486518
Rm since capsys doesn't work properly
juliannguyen4 Jan 28, 2026
97e0a21
Merge remote-tracking branch 'origin/dev' into CLIENT-2740
juliannguyen4 Jan 28, 2026
9407955
Rm this part because it implies log level is set to LOG_LEVEL_OFF whi…
juliannguyen4 Jan 28, 2026
95c96c2
Add section for default behavior and reference it in docstring
juliannguyen4 Jan 28, 2026
29c166b
Move macro to header for reuse
juliannguyen4 Jan 28, 2026
37f24f0
Add more tests for code coverage.
juliannguyen4 Jan 28, 2026
81434b9
Add more thorough test case that checks overriding behavior for log l…
juliannguyen4 Jan 28, 2026
9933712
Clear up test cases
juliannguyen4 Jan 28, 2026
0c6ab6c
just fix syntax
juliannguyen4 Jan 28, 2026
e857a3f
Clear up
juliannguyen4 Jan 28, 2026
708951c
fix test. clarify why undefined
juliannguyen4 Jan 29, 2026
7d1e4d3
just rename to make simpler to read
juliannguyen4 Jan 29, 2026
74cc6af
Clear up default behavior
juliannguyen4 Jan 29, 2026
853f8b0
Update doc/examples/log.py
juliannguyen4 Jan 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 28 additions & 13 deletions doc/aerospike.rst
Original file line number Diff line number Diff line change
Expand Up @@ -282,30 +282,45 @@ If we read the data for each record using ``aql``, it outputs the following data
Logging
-------

.. py:function:: set_log_handler(callback)
.. _logging_default_behavior:

Enables aerospike log handler
Default behavior
^^^^^^^^^^^^^^^^

:param optional callable callback: the function used as the logging handler.
By default:

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

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

import aerospike
.. include:: examples/log.py
:code: python

from __future__ import print_function
import aerospike
.. py:function:: set_log_handler(log_handler: Optional[Callable[[int, str, str, int, str], None]])

Set logging callback globally across all clients.

When no argument is passed, the default log handler is used. See :ref:`logging_default_behavior` for more details.

When callback is :py:obj:`None`, the saved log handler is cleared.

When a callable is passed, it must have these five parameters in this order:

.. code-block:: python

aerospike.set_log_level(aerospike.LOG_LEVEL_DEBUG)
aerospike.set_log_handler(callback)
def callback(level: int, function: str, path: str, line: int, message: str):
pass

:param optional callable log_handler: the function used as the logging handler.

.. py:function:: set_log_level(log_level)
.. py:function:: set_log_level(loglevel)

Declare the logging level threshold for the log handler.
Declare the logging level threshold for the log handler. If setting log level to :py:obj:`aerospike.LOG_LEVEL_OFF`,
the current log handler does not get reset.

:param int log_level: one of the :ref:`aerospike_log_levels` constant values.
:param int loglevel: one of the :ref:`aerospike_log_levels` constant values.

Other
-----
Expand Down
34 changes: 34 additions & 0 deletions doc/examples/log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Enable the logging at application start, before connecting to the server.
import aerospike

## SETTING THE LOG HANDLER ##

# Set log handler to print to the data to the console
aerospike.set_log_handler()

# Clears saved log handler and disable logging
aerospike.set_log_handler(None)

def log_callback(level, func, path, line, msg):
print("[{}] {}".format(func, msg))

# Set log handler to custom callback function (defined above)
aerospike.set_log_handler(log_callback)


## SETTING THE LOG LEVEL ##

# disables log handling
aerospike.set_log_level(aerospike.LOG_LEVEL_OFF)

# Enables log handling and sets level to LOG_LEVEL_TRACE
aerospike.set_log_level(aerospike.LOG_LEVEL_TRACE)

# Create a client and connect it to the cluster
# This line will print use log_callback to print logs with a log level of TRACE
config = {
"hosts": [
("127.0.0.1", 3000)
]
}
client = aerospike.client(config)
10 changes: 3 additions & 7 deletions src/include/log.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@

#include <Python.h>
#include <aerospike/as_status.h>

/*
* Structure to hold user's log_callback object
*/
typedef struct Aerospike_log_callback {
PyObject *callback;
} AerospikeLogCallback;
#include <aerospike/as_log.h>

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

void Aerospike_Enable_Default_Logging();

#define LOG_LEVEL_OFF -1
2 changes: 1 addition & 1 deletion src/main/aerospike.c
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ static struct module_constant_name_to_value module_constants[] = {
.value.integer = AS_QUERY_DURATION_LONG_RELAX_AP},
{"QUERY_DURATION_SHORT", .value.integer = AS_QUERY_DURATION_SHORT},

{"LOG_LEVEL_OFF", .value.integer = -1},
{"LOG_LEVEL_OFF", .value.integer = LOG_LEVEL_OFF},
{"LOG_LEVEL_ERROR", .value.integer = AS_LOG_LEVEL_ERROR},
{"LOG_LEVEL_WARN", .value.integer = AS_LOG_LEVEL_WARN},
{"LOG_LEVEL_INFO", .value.integer = AS_LOG_LEVEL_INFO},
Expand Down
3 changes: 3 additions & 0 deletions src/main/client/type.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <aerospike/as_error.h>
#include <aerospike/as_policy.h>
#include <aerospike/as_vector.h>
#include <aerospike/as_log_macros.h>

#include "pythoncapi_compat.h"
#include "admin.h"
Expand Down Expand Up @@ -570,6 +571,8 @@ int does_py_dict_contain_valid_keys(as_error *err, PyObject *py_dict,
static int AerospikeClient_Type_Init(AerospikeClient *self, PyObject *args,
PyObject *kwds)
{
as_log_trace("Starting to create a new client...");

PyObject *py_config = NULL;
int error_code = 0;
as_error constructor_err;
Expand Down
168 changes: 93 additions & 75 deletions src/main/log.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,73 +25,23 @@
#include "exceptions.h"
#include "log.h"

bool is_current_log_level_off = true;
PyObject *py_current_custom_callback = NULL;

#ifdef _WIN32
#define __sync_fetch_and_add InterlockedExchangeAdd64
#endif

static AerospikeLogCallback user_callback;

PyObject *Aerospike_Set_Log_Level(PyObject *parent, PyObject *args,
PyObject *kwds)
{
// Aerospike vaiables
as_error err;
as_status status = AEROSPIKE_OK;

// Python Function Arguments
PyObject *py_log_level = NULL;

// Initialise error object.
as_error_init(&err);

// Python Function Keyword Arguments
static char *kwlist[] = {"loglevel", NULL};

// Python Function Argument Parsing
if (PyArg_ParseTupleAndKeywords(args, kwds, "O|:setLogLevel", kwlist,
&py_log_level) == false) {
return NULL;
}

// Type check for incoming parameters
if (!PyLong_Check(py_log_level)) {
as_error_update(&err, AEROSPIKE_ERR_PARAM, "Invalid log level");
goto CLEANUP;
}

long lLogLevel = PyLong_AsLong(py_log_level);
if (lLogLevel == (uint32_t)-1 && PyErr_Occurred()) {
if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
as_error_update(&err, AEROSPIKE_ERR_PARAM,
"integer value exceeds sys.maxsize");
goto CLEANUP;
}
}

// Invoke C API to set log level
as_log_set_level((as_log_level)lLogLevel);

CLEANUP:

// Check error object and act accordingly
if (err.code != AEROSPIKE_OK) {
raise_exception(&err);
return NULL;
}

return PyLong_FromLong(status);
}

volatile int log_counter = 0;

bool console_log_cb(as_log_level level, const char *func, const char *file,
uint32_t line, const char *fmt, ...)
bool default_log_handler(as_log_level level, const char *func, const char *file,
uint32_t line, const char *fmt, ...)
{

char msg[1024];
va_list ap;

int counter = __sync_fetch_and_add((&log_counter), 1);
int counter = __sync_fetch_and_add(&log_counter, 1);

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

static bool log_cb(as_log_level level, const char *func, const char *file,
uint32_t line, const char *fmt, ...)
static bool call_custom_py_log_handler(as_log_level level, const char *func,
const char *file, uint32_t line,
const char *fmt, ...)
{

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

// Extract pyhton user callback
PyObject *py_callback = user_callback.callback;
// User callback's argument list
PyObject *py_arglist = NULL;

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

// Invoke user callback, passing in argument's list
PyObject_Call(py_callback, py_arglist, NULL);
PyObject_Call(py_current_custom_callback, py_arglist, NULL);

Py_DECREF(py_arglist);

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

PyObject *Aerospike_Set_Log_Level(PyObject *parent, PyObject *args,
PyObject *kwds)
{
as_status status = AEROSPIKE_OK;

// Python Function Argument Parsing
static char *kwlist[] = {"loglevel", NULL};
PyObject *py_log_level = NULL;

if (PyArg_ParseTupleAndKeywords(args, kwds, "O|:setLogLevel", kwlist,
&py_log_level) == false) {
return NULL;
}

as_error err;
as_error_init(&err);

// Type check for incoming parameters
if (!PyLong_Check(py_log_level)) {
as_error_update(&err, AEROSPIKE_ERR_PARAM, "Invalid log level");
goto CLEANUP;
}

long log_level = PyLong_AsLong(py_log_level);
if (log_level == -1 && PyErr_Occurred()) {
if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
as_error_update(&err, AEROSPIKE_ERR_PARAM,
"integer value exceeds sys.maxsize");
goto CLEANUP;
}
}

is_current_log_level_off = log_level == LOG_LEVEL_OFF;

if (log_level == LOG_LEVEL_OFF) {
as_log_set_callback(NULL);
}
else {
as_log_set_level((as_log_level)log_level);

// Re-enable log handler
if (py_current_custom_callback != NULL) {
as_log_set_callback((as_log_callback)call_custom_py_log_handler);
}
else {
as_log_set_callback((as_log_callback)default_log_handler);
}
}

CLEANUP:

if (err.code != AEROSPIKE_OK) {
raise_exception(&err);
return NULL;
}

return PyLong_FromLong(status);
}

PyObject *Aerospike_Set_Log_Handler(PyObject *parent, PyObject *args,
PyObject *kwds)
{
// Python variables
PyObject *py_callback = NULL;
as_error err;
as_error_init(&err);
// Python function keyword arguments
static char *kwlist[] = {"log_handler", NULL};

// Python function arguments parsing
PyArg_ParseTupleAndKeywords(args, kwds, "|O:setLogHandler", kwlist,
&py_callback);
if (PyArg_ParseTupleAndKeywords(args, kwds, "|O:setLogHandler", kwlist,
&py_callback) == false) {
return NULL;
}

if (py_callback && PyCallable_Check(py_callback)) {
// Store user callback
Py_INCREF(py_callback);
user_callback.callback = py_callback;
// Clean up existing log handler
Py_CLEAR(py_current_custom_callback);

// Register callback to C-SDK
as_log_set_callback((as_log_callback)log_cb);
// 3 cases (when args are passed):
if (py_callback == NULL) {
// 1. No args -> enable Python client's default log handler IF log level is not OFF
// IF log level is OFF, don't enable the default log handler.
if (!is_current_log_level_off) {
as_log_set_callback((as_log_callback)default_log_handler);
}
}
else {
// Register callback to C-SDK
as_log_set_callback((as_log_callback)console_log_cb);
else if (Py_IsNone(py_callback)) {
// Disable log handler altogether
as_log_set_callback(NULL);
}
else if (PyCallable_Check(py_callback)) {
// Register custom log handler
py_current_custom_callback = Py_NewRef(py_callback);
if (!is_current_log_level_off) {
as_log_set_callback((as_log_callback)call_custom_py_log_handler);
}
}

return PyLong_FromLong(0);
Expand All @@ -184,7 +202,7 @@ void Aerospike_Enable_Default_Logging()
// Invoke C API to set log level
as_log_set_level((as_log_level)AS_LOG_LEVEL_ERROR);
// Register callback to C-SDK
as_log_set_callback((as_log_callback)console_log_cb);
as_log_set_callback((as_log_callback)default_log_handler);

return;
}
Loading
Loading