Skip to content

Commit 6ec83e2

Browse files
committed
Fix the log levels mapping
The libssh provides the most verbose logging with SSH_LOG_TRACE, which was not mapped to any of the standard values so the users are unable to get full debug logs. These are critical for libssh developers to be able to investigate issues. Signed-off-by: Jakub Jelen <[email protected]>
1 parent e4ea18f commit 6ec83e2

File tree

4 files changed

+136
-16
lines changed

4 files changed

+136
-16
lines changed

src/pylibsshext/includes/callbacks.pxd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,10 @@ cdef extern from "libssh/callbacks.h":
127127
ctypedef ssh_channel_callbacks_struct * ssh_channel_callbacks
128128

129129
int ssh_set_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb)
130+
131+
ctypedef void(*ssh_logging_callback)(
132+
int priority,
133+
const char *function,
134+
const char *buffer,
135+
void *userdata)
136+
int ssh_set_log_callback(ssh_logging_callback cb)

src/pylibsshext/logging.pxd

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# distutils: libraries = ssh
2+
#
3+
# This file is part of the pylibssh library
4+
#
5+
# This library is free software; you can redistribute it and/or
6+
# modify it under the terms of the GNU Lesser General Public
7+
# License as published by the Free Software Foundation; either
8+
# version 2.1 of the License, or (at your option) any later version.
9+
#
10+
# This library is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
# Lesser General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU Lesser General Public
16+
# License along with this library; if not, see file LICENSE.rst in this
17+
# repository.
18+
#
19+
from pylibsshext.includes cimport callbacks, libssh
20+
21+
22+
cdef class Logging:
23+
pass

src/pylibsshext/logging.pyx

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#
2+
# This file is part of the pylibssh library
3+
#
4+
# This library is free software; you can redistribute it and/or
5+
# modify it under the terms of the GNU Lesser General Public
6+
# License as published by the Free Software Foundation; either
7+
# version 2.1 of the License, or (at your option) any later version.
8+
#
9+
# This library is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
# Lesser General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU Lesser General Public
15+
# License along with this library; if not, see file LICENSE.rst in this
16+
# repository.
17+
18+
import logging
19+
20+
from pylibsshext.errors cimport LibsshSessionException
21+
22+
23+
ANSIBLE_PYLIBSSH_TRACE = 5
24+
25+
LOG_MAP = {
26+
logging.NOTSET: libssh.SSH_LOG_NONE,
27+
ANSIBLE_PYLIBSSH_TRACE: libssh.SSH_LOG_TRACE,
28+
logging.DEBUG: libssh.SSH_LOG_DEBUG,
29+
logging.INFO: libssh.SSH_LOG_INFO,
30+
logging.WARNING: libssh.SSH_LOG_WARN,
31+
logging.ERROR: libssh.SSH_LOG_WARN,
32+
logging.CRITICAL: libssh.SSH_LOG_WARN
33+
}
34+
35+
LOG_MAP_REV = {
36+
libssh.SSH_LOG_NONE: logging.NOTSET,
37+
libssh.SSH_LOG_TRACE: ANSIBLE_PYLIBSSH_TRACE,
38+
libssh.SSH_LOG_DEBUG: logging.DEBUG,
39+
libssh.SSH_LOG_INFO: logging.INFO,
40+
libssh.SSH_LOG_WARN: logging.WARNING,
41+
}
42+
43+
logger = logging.getLogger("libssh")
44+
45+
46+
def add_trace_log_level():
47+
level_num = ANSIBLE_PYLIBSSH_TRACE
48+
level_name = "TRACE"
49+
method_name = level_name.lower()
50+
logger_class = logging.getLoggerClass()
51+
52+
if hasattr(logging, level_name):
53+
raise AttributeError('{} already defined in logging module'.format(level_name))
54+
if hasattr(logging, method_name):
55+
raise AttributeError('{} already defined in logging module'.format(method_name))
56+
if hasattr(logger_class, method_name):
57+
raise AttributeError('{} already defined in logger class'.format(method_name))
58+
59+
def logForLevel(self, message, *args, **kwargs):
60+
if self.isEnabledFor(level_num):
61+
self._log(level_num, message, args, **kwargs)
62+
63+
def logToRoot(message, *args, **kwargs):
64+
logging.log(level_num, message, *args, **kwargs)
65+
66+
logging.addLevelName(level_num, level_name)
67+
setattr(logging, level_name, level_num)
68+
setattr(logging, method_name, logToRoot)
69+
setattr(logger_class, method_name, logForLevel)
70+
71+
72+
cdef void _pylibssh_log_wrapper(int priority,
73+
const char *function,
74+
const char *buffer,
75+
void *userdata) noexcept nogil:
76+
with gil:
77+
log_level = LOG_MAP_REV[priority]
78+
logger.log(log_level, f"{buffer}")
79+
80+
81+
def set_log_callback():
82+
callbacks.ssh_set_log_callback(_pylibssh_log_wrapper)
83+
84+
85+
def logging_init():
86+
try:
87+
add_trace_log_level()
88+
except AttributeError:
89+
pass
90+
set_log_callback()
91+
92+
93+
def set_level(level):
94+
logging_init()
95+
if level in LOG_MAP.keys():
96+
rc = libssh.ssh_set_log_level(LOG_MAP[level])
97+
if rc != libssh.SSH_OK:
98+
raise LibsshSessionException("Failed to set log level [%d] with error [%d]" % (level, rc))
99+
logger.setLevel(level)
100+
else:
101+
raise LibsshSessionException("Invalid log level [%d]" % level)

src/pylibsshext/session.pyx

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
# License along with this library; if not, see file LICENSE.rst in this
1616
# repository.
1717
import inspect
18-
import logging
1918

2019
from cpython.bytes cimport PyBytes_AS_STRING
2120

2221
from pylibsshext.channel import Channel
22+
2323
from pylibsshext.errors cimport LibsshSessionException
24+
25+
from pylibsshext.logging import set_level
2426
from pylibsshext.scp import SCP
2527
from pylibsshext.sftp import SFTP
2628

@@ -44,15 +46,6 @@ OPTS_DIR_MAP = {
4446
"add_identity": libssh.SSH_OPTIONS_ADD_IDENTITY,
4547
}
4648

47-
LOG_MAP = {
48-
logging.NOTSET: libssh.SSH_LOG_NONE,
49-
logging.DEBUG: libssh.SSH_LOG_DEBUG,
50-
logging.INFO: libssh.SSH_LOG_INFO,
51-
logging.WARNING: libssh.SSH_LOG_WARN,
52-
logging.ERROR: libssh.SSH_LOG_WARN,
53-
logging.CRITICAL: libssh.SSH_LOG_TRACE
54-
}
55-
5649
KNOW_HOST_MSG_MAP = {
5750
libssh.SSH_KNOWN_HOSTS_CHANGED: "Host key for server has changed: ",
5851
libssh.SSH_KNOWN_HOSTS_OTHER: "Host key type for server has changed: ",
@@ -334,6 +327,7 @@ cdef class Session(object):
334327
if rc != libssh.SSH_OK:
335328
return
336329

330+
# FIXME The SHA1 hashes are non-standard -- the most common these days are SHA256 hashes for fingerprints
337331
rc = libssh.ssh_get_publickey_hash(srv_pubkey, libssh.SSH_PUBLICKEY_HASH_SHA1, &hash, &hash_len)
338332

339333
cdef libssh.ssh_keytypes_e key_type = libssh.ssh_key_type(srv_pubkey)
@@ -520,12 +514,7 @@ cdef class Session(object):
520514
return SFTP(self)
521515

522516
def set_log_level(self, level):
523-
if level in LOG_MAP.keys():
524-
rc = libssh.ssh_set_log_level(LOG_MAP[level])
525-
if rc != libssh.SSH_OK:
526-
raise LibsshSessionException("Failed to set log level [%d] with error [%d]" % (level, rc))
527-
else:
528-
raise LibsshSessionException("Invalid log level [%d]" % level)
517+
set_level(level)
529518

530519
def close(self):
531520
if self._libssh_session is not NULL:

0 commit comments

Comments
 (0)