├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .gitmodules ├── .project ├── .pydevproject ├── LICENSE ├── MANIFEST.in ├── README.md ├── create_dist.sh ├── setup.cfg ├── setup.py ├── src └── pyspdlog.cpp ├── tests ├── sink_test.py ├── spdlog_vs_logging.py └── test_spdlog.py └── upload_to_pypi.sh /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | name: "🐍 Ubuntu 20.04 - Python ${{ matrix.python-version }}" 10 | runs-on: ubuntu-20.04 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | with: 20 | submodules: true 21 | 22 | - name: Setup Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v3 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | 27 | - name: Dependencies 28 | run: | 29 | python -m pip install pytest 30 | 31 | - name: Install 32 | run: | 33 | python -m pip install -v . 34 | 35 | - name: Test 36 | run: | 37 | python -m pytest -vs tests 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.swp 2 | *.so 3 | Makefile 4 | *.log 5 | build/ 6 | dist/ 7 | .eggs/ 8 | *.egg-info/ 9 | .project 10 | .pydevproject 11 | tests/__pycache__/ 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "spdlog"] 2 | path = spdlog 3 | url = https://github.com/gabime/spdlog.git 4 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | spdlog-python.git 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | Default 4 | python interpreter 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 bodgergely 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE setup.py setup.cfg 2 | graft src 3 | graft spdlog/include/spdlog 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://img.shields.io/github/actions/workflow/status/bodgergely/spdlog-python/ci.yaml?branch=master)](https://github.com/bodgergely/spdlog-python/actions) 2 | 3 | spdlog-python 4 | ============= 5 | 6 | python wrapper around the fast C++ logger called [spdlog](https://github.com/gabime/spdlog) 7 | 8 | 9 | Introduction 10 | ============ 11 | 12 | Python wrapper (pybind11) around the C++ spdlog logging library. 13 | 14 | Why choose [spdlog](https://github.com/gabime/spdlog)? 15 | 16 | https://kjellkod.wordpress.com/2015/06/30/the-worlds-fastest-logger-vs-g3log/ 17 | 18 | Try running [tests/spdlog_vs_logging.py](https://github.com/bodgergely/spdlog-python/blob/master/tests/test_spdlog.py) and see what results you get on your system. 19 | 20 | spdlog-python vs logging (standard lib) 21 | --------------------------------------- 22 | 23 | How many microseconds it takes on average to complete a log function (info(), debug() etc) using a FileLogger. 24 | On reasonable sized log messages spdlog takes **4% (async mode enabled)** and **6% (sync mode)** of the time it would take to complete using the standard logging module. 25 | 26 | Async mode with 8MB queue with blocking mode. 27 | 28 | | msg len (bytes) | spdlog **sync** (microsec)| spdlog **async** (microsec)| logging (microsec) | 29 | | ------- | :--------: | :--------: | :--------: | 30 | | 10 | 1.2 | 0.87 | 24.6 | 31 | | 100 | 1.2 | 1.03 | 24.6 | 32 | | 300 | 1.5 | 1.07 | 24.9 | 33 | | 1000 | 2.4 | 1.16 | 26.8 | 34 | | 5000 | 6.2 | 2.31 | 31.7 | 35 | | 20000 | 15.3 | 7.51 | 48.0 | 36 | 37 | Installation 38 | ============ 39 | 40 | 1) `pip install spdlog` will get a distribution from pypi.org 41 | 42 | or 43 | 44 | 2) from github: 45 | 46 | `pip install pybind11` - if missing 47 | 48 | ```bash 49 | git clone https://github.com/bodgergely/spdlog-python.git 50 | cd spdlog-python 51 | git submodule update --init --recursive 52 | python setup.py install 53 | ``` 54 | 55 | Usage 56 | ===== 57 | 58 | ```python 59 | ./python 60 | import spdlog as spd 61 | logger = spd.FileLogger('fast_logger', '/tmp/spdlog_example.log') 62 | logger.set_level(spd.LogLevel.INFO) 63 | logger.info('Hello World!') 64 | logger.debug('I am not so important.') 65 | ``` 66 | 67 | To run the speed test: 68 | 69 | ```bash 70 | python ./tests/spdlog_vs_logging.py 71 | ``` 72 | -------------------------------------------------------------------------------- /create_dist.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | python setup.py sdist 3 | #python3 setup.py sdist bdist_wheel 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import sys 4 | 5 | import sysconfig 6 | from setuptools import setup 7 | from setuptools.extension import Extension 8 | from distutils.command.install_headers import install_headers 9 | 10 | def is_posix(): 11 | return platform.os.name == "posix" 12 | 13 | def link_libs(): 14 | libs = [] 15 | if is_posix(): 16 | libs.append("stdc++") 17 | return libs 18 | 19 | class get_pybind_include(object): 20 | def __init__(self, user=False): 21 | self.user = user 22 | 23 | def __str__(self): 24 | import pybind11 25 | return pybind11.get_include(self.user) 26 | 27 | def get_include_dirs(): 28 | include_dirs=[ 29 | 'spdlog/include/', 30 | get_pybind_include(), 31 | get_pybind_include(user=True), 32 | ] 33 | 34 | conda_prefix = os.environ.get('CONDA_PREFIX') 35 | if conda_prefix is not None: 36 | include_dirs.append(os.path.join(conda_prefix, "include")) 37 | 38 | return include_dirs 39 | 40 | 41 | def include_dir_files(folder): 42 | """Find all C++ header files in folder""" 43 | from os import walk 44 | files = [] 45 | for (dirpath, _, filenames) in walk(folder): 46 | for fn in filenames: 47 | if os.path.splitext(fn)[1] in {'.h', '.hpp'}: 48 | files.append(os.path.join(dirpath, fn)) 49 | return files 50 | 51 | class install_headers_subdir(install_headers): 52 | """Install headers and keep subfolder structure""" 53 | def run(self): 54 | headers = self.distribution.headers or [] 55 | for header in headers: 56 | submod_dir = os.path.dirname(os.path.relpath(header, 'spdlog/include/spdlog')) 57 | install_dir = os.path.join(self.install_dir, submod_dir) 58 | self.mkpath(install_dir) 59 | (out, _) = self.copy_file(header, install_dir) 60 | self.outfiles.append(out) 61 | 62 | setup( 63 | name='spdlog', 64 | version='2.0.6', 65 | author='Gergely Bod', 66 | author_email='bodgergely@hotmail.com', 67 | description='python wrapper around C++ spdlog logging library (https://github.com/bodgergely/spdlog-python)', 68 | license='MIT', 69 | long_description='python wrapper (https://github.com/bodgergely/spdlog-python) around C++ spdlog (http://github.com/gabime/spdlog.git) logging library.', 70 | setup_requires=['pybind11>=2.2', 'wheel', 'pytest-runner'], 71 | install_requires=['pybind11>=2.2'], 72 | tests_require=['pytest'], 73 | ext_modules=[ 74 | Extension( 75 | 'spdlog', 76 | ['src/pyspdlog.cpp'], 77 | include_dirs=get_include_dirs(), 78 | libraries=link_libs(), 79 | extra_compile_args=["-std=c++11", "-v"], 80 | language='c++11' 81 | ) 82 | ], 83 | headers=include_dir_files('spdlog/include/spdlog'), 84 | cmdclass={'install_headers': install_headers_subdir}, 85 | zip_safe=False, 86 | ) 87 | -------------------------------------------------------------------------------- /src/pyspdlog.cpp: -------------------------------------------------------------------------------- 1 | #ifndef _WIN32 2 | #define SPDLOG_ENABLE_SYSLOG 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace pybind11::literals; 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #ifndef _WIN32 27 | #include 28 | #endif 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | namespace spd = spdlog; 39 | namespace py = pybind11; 40 | 41 | namespace { // Avoid cluttering the global namespace. 42 | 43 | class Logger; 44 | 45 | bool g_async_mode_on = false; 46 | auto g_async_overflow_policy = spdlog::async_overflow_policy::block; 47 | 48 | std::unordered_map g_loggers; 49 | std::mutex mutex_loggers; 50 | 51 | void register_logger(const std::string& name, Logger* logger) 52 | { 53 | std::lock_guard lck(mutex_loggers); 54 | g_loggers[name] = logger; 55 | } 56 | 57 | Logger* access_logger(const std::string& name) 58 | { 59 | std::lock_guard lck(mutex_loggers); 60 | return g_loggers[name]; 61 | } 62 | 63 | void remove_logger(const std::string& name) 64 | { 65 | std::lock_guard lck(mutex_loggers); 66 | g_loggers[name] = nullptr; 67 | g_loggers.erase(name); 68 | } 69 | 70 | void remove_logger_all() 71 | { 72 | std::lock_guard lck(mutex_loggers); 73 | g_loggers.clear(); 74 | } 75 | 76 | class LogLevel { 77 | public: 78 | const static int trace{ (int)spd::level::trace }; 79 | const static int debug{ (int)spd::level::debug }; 80 | const static int info{ (int)spd::level::info }; 81 | const static int warn{ (int)spd::level::warn }; 82 | const static int err{ (int)spd::level::err }; 83 | const static int critical{ (int)spd::level::critical }; 84 | const static int off{ (int)spd::level::off }; 85 | }; 86 | 87 | class Sink { 88 | public: 89 | Sink() {} 90 | Sink(const spd::sink_ptr& sink) 91 | : _sink(sink) 92 | { 93 | } 94 | virtual ~Sink() {} 95 | virtual void log(const spd::details::log_msg& msg) 96 | { 97 | _sink->log(msg); 98 | } 99 | bool should_log(int msg_level) const 100 | { 101 | return _sink->should_log((spd::level::level_enum)msg_level); 102 | } 103 | void set_level(int log_level) 104 | { 105 | _sink->set_level((spd::level::level_enum)log_level); 106 | } 107 | int level() const 108 | { 109 | return (int)_sink->level(); 110 | } 111 | 112 | spd::sink_ptr get_sink() const { return _sink; } 113 | 114 | protected: 115 | spd::sink_ptr _sink{ nullptr }; 116 | }; 117 | 118 | // template 119 | // class generic_sink : public Sink { 120 | // public: 121 | // generic_sink() { 122 | // _sink = std::make_shared(); 123 | // } 124 | // }; 125 | 126 | // class stdout_sink_st : public generic_sink { }; 127 | // class stdout_sink_mt : public generic_sink { }; 128 | 129 | class stdout_sink_st : public Sink { 130 | public: 131 | stdout_sink_st() 132 | { 133 | _sink = std::make_shared(); 134 | } 135 | }; 136 | 137 | class stdout_sink_mt : public Sink { 138 | public: 139 | stdout_sink_mt() 140 | { 141 | _sink = std::make_shared(); 142 | } 143 | }; 144 | 145 | class stdout_color_sink_st : public Sink { 146 | public: 147 | stdout_color_sink_st() 148 | { 149 | _sink = std::make_shared(); 150 | } 151 | }; 152 | 153 | class stdout_color_sink_mt : public Sink { 154 | public: 155 | stdout_color_sink_mt() 156 | { 157 | _sink = std::make_shared(); 158 | } 159 | }; 160 | 161 | class stderr_sink_st : public Sink { 162 | public: 163 | stderr_sink_st() 164 | { 165 | _sink = std::make_shared(); 166 | } 167 | }; 168 | 169 | class stderr_sink_mt : public Sink { 170 | public: 171 | stderr_sink_mt() 172 | { 173 | _sink = std::make_shared(); 174 | } 175 | }; 176 | 177 | class stderr_color_sink_st : public Sink { 178 | public: 179 | stderr_color_sink_st() 180 | { 181 | _sink = std::make_shared(); 182 | } 183 | }; 184 | 185 | class stderr_color_sink_mt : public Sink { 186 | public: 187 | stderr_color_sink_mt() 188 | { 189 | _sink = std::make_shared(); 190 | } 191 | }; 192 | 193 | class basic_file_sink_st : public Sink { 194 | public: 195 | basic_file_sink_st(const std::string& base_filename, bool truncate) 196 | { 197 | _sink = std::make_shared(base_filename, truncate); 198 | } 199 | }; 200 | 201 | class basic_file_sink_mt : public Sink { 202 | public: 203 | basic_file_sink_mt(const std::string& base_filename, bool truncate) 204 | { 205 | _sink = std::make_shared(base_filename, truncate); 206 | } 207 | }; 208 | 209 | class daily_file_sink_mt : public Sink { 210 | public: 211 | daily_file_sink_mt(const std::string& base_filename, int rotation_hour, int rotation_minute) 212 | { 213 | _sink = std::make_shared(base_filename, rotation_hour, rotation_minute); 214 | } 215 | }; 216 | 217 | class daily_file_sink_st : public Sink { 218 | public: 219 | daily_file_sink_st(const std::string& base_filename, int rotation_hour, int rotation_minute) 220 | { 221 | _sink = std::make_shared(base_filename, rotation_hour, rotation_minute); 222 | } 223 | }; 224 | 225 | class rotating_file_sink_mt : public Sink { 226 | public: 227 | rotating_file_sink_mt(const std::string& filename, size_t max_file_size, size_t max_files) 228 | { 229 | _sink = std::make_shared(filename, max_file_size, max_files); 230 | } 231 | }; 232 | 233 | class rotating_file_sink_st : public Sink { 234 | public: 235 | rotating_file_sink_st(const std::string& filename, size_t max_file_size, size_t max_files) 236 | { 237 | _sink = std::make_shared(filename, max_file_size, max_files); 238 | } 239 | }; 240 | 241 | template 242 | class dist_sink: public Sink { 243 | public: 244 | dist_sink() { _sink = std::make_shared>();} 245 | dist_sink(std::vector sinks) 246 | { 247 | std::vector sink_vec; 248 | for (uint i =0; i>(sink_vec); 253 | } 254 | void add_sink(const Sink& sink) 255 | { 256 | std::dynamic_pointer_cast>(_sink)->add_sink(sink.get_sink()); 257 | } 258 | 259 | void remove_sink(const Sink& sink) 260 | { 261 | std::dynamic_pointer_cast>(_sink)->remove_sink(sink.get_sink()); 262 | } 263 | 264 | void set_sinks(std::vector sinks) 265 | { 266 | std::vector sink_vec; 267 | for (uint i =0; i>(_sink)->set_sinks(sink_vec); 272 | } 273 | 274 | std::vector &sinks() 275 | { 276 | return std::dynamic_pointer_cast>(_sink)->sinks(); 277 | } 278 | }; 279 | 280 | using dist_sink_mt = dist_sink; 281 | using dist_sink_st = dist_sink; 282 | 283 | class dup_filter_sink_mt: public dist_sink_mt { 284 | public: 285 | dup_filter_sink_mt(float max_skip_duration_sec) 286 | { 287 | _sink = std::make_shared(std::chrono::milliseconds((int)(max_skip_duration_sec*1000.0))); 288 | } 289 | }; 290 | 291 | class dup_filter_sink_st: public dist_sink_st { 292 | public: 293 | dup_filter_sink_st(float max_skip_duration_sec) 294 | { 295 | _sink = std::make_shared(std::chrono::milliseconds((int)(max_skip_duration_sec*1000.0))); 296 | } 297 | }; 298 | 299 | class null_sink_st : public Sink { 300 | public: 301 | null_sink_st() 302 | { 303 | _sink = std::make_shared(); 304 | } 305 | }; 306 | 307 | class null_sink_mt : public Sink { 308 | public: 309 | null_sink_mt() 310 | { 311 | _sink = std::make_shared(); 312 | } 313 | }; 314 | 315 | class tcp_sink_st : public Sink { 316 | public: 317 | tcp_sink_st(std::string server_host, int server_port, bool lazy_connect) 318 | { 319 | struct spdlog::sinks::tcp_sink_config tcp_config(server_host, server_port); 320 | tcp_config.lazy_connect = lazy_connect; 321 | 322 | _sink = std::make_shared(tcp_config); 323 | } 324 | }; 325 | 326 | class tcp_sink_mt : public Sink { 327 | public: 328 | tcp_sink_mt(std::string server_host, int server_port, bool lazy_connect) 329 | { 330 | struct spdlog::sinks::tcp_sink_config tcp_config(server_host, server_port); 331 | tcp_config.lazy_connect = lazy_connect; 332 | 333 | _sink = std::make_shared(tcp_config); 334 | } 335 | }; 336 | 337 | #ifdef SPDLOG_ENABLE_SYSLOG 338 | class syslog_sink_st : public Sink { 339 | public: 340 | syslog_sink_st(const std::string& ident = "", int syslog_option = 0, int syslog_facility = (1 << 3), bool enable_formatting = true) 341 | { 342 | _sink = std::make_shared(ident, syslog_option, syslog_facility, enable_formatting); 343 | } 344 | }; 345 | 346 | class syslog_sink_mt : public Sink { 347 | public: 348 | syslog_sink_mt(const std::string& ident = "", int syslog_option = 0, int syslog_facility = (1 << 3), bool enable_formatting = true) 349 | { 350 | _sink = std::make_shared(ident, syslog_option, syslog_facility, enable_formatting); 351 | } 352 | }; 353 | #endif 354 | 355 | class Logger { 356 | public: 357 | using async_factory_nb = spdlog::async_factory_impl; 358 | 359 | Logger(const std::string& name, bool async_mode) 360 | : _name(name) 361 | , _async(async_mode) 362 | { 363 | register_logger(name, this); 364 | } 365 | 366 | virtual ~Logger() {} 367 | std::string name() const 368 | { 369 | if (_logger) 370 | return _logger->name(); 371 | else 372 | return "NULL"; 373 | } 374 | void log(int level, const std::string& msg) const { this->_logger->log((spd::level::level_enum)level, msg); } 375 | void trace(const std::string& msg) const { this->_logger->trace(msg); } 376 | void debug(const std::string& msg) const { this->_logger->debug(msg); } 377 | void info(const std::string& msg) const { this->_logger->info(msg); } 378 | void warn(const std::string& msg) const { this->_logger->warn(msg); } 379 | void error(const std::string& msg) const { this->_logger->error(msg); } 380 | void critical(const std::string& msg) const { this->_logger->critical(msg); } 381 | 382 | bool should_log(int level) const 383 | { 384 | return _logger->should_log((spd::level::level_enum)level); 385 | } 386 | 387 | void set_level(int level) 388 | { 389 | _logger->set_level((spd::level::level_enum)level); 390 | } 391 | 392 | int level() const 393 | { 394 | return (int)_logger->level(); 395 | } 396 | 397 | void set_pattern(const std::string& pattern, spd::pattern_time_type type = spd::pattern_time_type::local) 398 | { 399 | _logger->set_pattern(pattern, type); 400 | } 401 | 402 | // automatically call flush() if message level >= log_level 403 | void flush_on(int log_level) 404 | { 405 | _logger->flush_on((spd::level::level_enum)log_level); 406 | } 407 | 408 | void flush() 409 | { 410 | _logger->flush(); 411 | } 412 | 413 | bool async() 414 | { 415 | return _async; 416 | } 417 | 418 | void close() 419 | { 420 | remove_logger(_name); 421 | _logger = nullptr; 422 | spdlog::drop(_name); 423 | } 424 | 425 | std::vector sinks() const 426 | { 427 | std::vector snks; 428 | for (const spd::sink_ptr& sink : _logger->sinks()) { 429 | snks.push_back(Sink(sink)); 430 | } 431 | return snks; 432 | } 433 | 434 | void set_error_handler(spd::err_handler handler) 435 | { 436 | _logger->set_error_handler(handler); 437 | } 438 | 439 | std::shared_ptr get_underlying_logger() { 440 | return _logger; 441 | } 442 | 443 | protected: 444 | const std::string _name; 445 | bool _async; 446 | std::shared_ptr _logger{ nullptr }; 447 | }; 448 | 449 | class ConsoleLogger : public Logger { 450 | public: 451 | ConsoleLogger(const std::string& logger_name, bool multithreaded, bool standard_out, bool colored, bool async_mode = g_async_mode_on) 452 | : Logger(logger_name, async_mode) 453 | { 454 | if (standard_out) { 455 | if (multithreaded) { 456 | if (colored) { 457 | if (async_mode) { 458 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 459 | _logger = spd::stdout_color_mt(logger_name); 460 | } else { 461 | _logger = spd::stdout_color_mt(logger_name); 462 | } 463 | } else { 464 | _logger = spd::stdout_color_mt(logger_name); 465 | } 466 | } else { 467 | if (async_mode) { 468 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 469 | _logger = spd::stdout_logger_mt(logger_name); 470 | } else { 471 | _logger = spd::stdout_logger_mt(logger_name); 472 | } 473 | } else { 474 | _logger = spd::stdout_logger_mt(logger_name); 475 | } 476 | } 477 | } else { 478 | if (colored) { 479 | if (async_mode) { 480 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 481 | _logger = spd::stdout_color_st(logger_name); 482 | } else { 483 | _logger = spd::stdout_color_st(logger_name); 484 | } 485 | } else { 486 | _logger = spd::stdout_color_st(logger_name); 487 | } 488 | } else { 489 | if (async_mode) { 490 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 491 | _logger = spd::stdout_logger_st(logger_name); 492 | } else { 493 | _logger = spd::stdout_logger_st(logger_name); 494 | } 495 | } else { 496 | _logger = spd::stdout_logger_st(logger_name); 497 | } 498 | } 499 | } 500 | 501 | } else { 502 | if (multithreaded) { 503 | if (colored) { 504 | if (async_mode) { 505 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 506 | _logger = spd::stderr_color_mt(logger_name); 507 | } else { 508 | _logger = spd::stderr_color_mt(logger_name); 509 | } 510 | } else { 511 | _logger = spd::stderr_color_mt(logger_name); 512 | } 513 | } else { 514 | if (async_mode) { 515 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 516 | _logger = spd::stderr_logger_mt(logger_name); 517 | } else { 518 | _logger = spd::stderr_logger_mt(logger_name); 519 | } 520 | } else { 521 | _logger = spd::stderr_logger_mt(logger_name); 522 | } 523 | } 524 | } else { 525 | if (colored) { 526 | if (async_mode) { 527 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 528 | _logger = spd::stderr_color_st(logger_name); 529 | } else { 530 | _logger = spd::stderr_color_st(logger_name); 531 | } 532 | } else { 533 | _logger = spd::stderr_color_st(logger_name); 534 | } 535 | } else { 536 | if (async_mode) { 537 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 538 | _logger = spd::stderr_logger_st(logger_name); 539 | } else { 540 | _logger = spd::stderr_logger_st(logger_name); 541 | } 542 | } else { 543 | _logger = spd::stderr_logger_st(logger_name); 544 | } 545 | } 546 | } 547 | } 548 | } 549 | }; 550 | 551 | class FileLogger : public Logger { 552 | public: 553 | FileLogger(const std::string& logger_name, const std::string& filename, bool multithreaded, bool truncate = false, bool async_mode = g_async_mode_on) 554 | : Logger(logger_name, async_mode) 555 | { 556 | if (multithreaded) { 557 | if (async_mode) { 558 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 559 | _logger = spd::basic_logger_mt(logger_name, filename, truncate); 560 | } else { 561 | _logger = spd::basic_logger_mt(logger_name, filename, truncate); 562 | } 563 | } else { 564 | _logger = spd::basic_logger_mt(logger_name, filename, truncate); 565 | } 566 | } else { 567 | if (async_mode) { 568 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 569 | _logger = spd::basic_logger_st(logger_name, filename, truncate); 570 | } else { 571 | _logger = spd::basic_logger_st(logger_name, filename, truncate); 572 | } 573 | } else { 574 | _logger = spd::basic_logger_st(logger_name, filename, truncate); 575 | } 576 | } 577 | } 578 | }; 579 | 580 | class RotatingLogger : public Logger { 581 | public: 582 | RotatingLogger(const std::string& logger_name, const std::string& filename, bool multithreaded, size_t max_file_size, size_t max_files, bool async_mode = g_async_mode_on) 583 | : Logger(logger_name, async_mode) 584 | { 585 | if (multithreaded) { 586 | if (async_mode) { 587 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 588 | _logger = spd::rotating_logger_mt(logger_name, filename, max_file_size, max_files); 589 | } else { 590 | _logger = spd::rotating_logger_mt(logger_name, filename, max_file_size, max_files); 591 | } 592 | } else { 593 | _logger = spd::rotating_logger_mt(logger_name, filename, max_file_size, max_files); 594 | } 595 | } else { 596 | if (async_mode) { 597 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 598 | _logger = spd::rotating_logger_st(logger_name, filename, max_file_size, max_files); 599 | } else { 600 | _logger = spd::rotating_logger_st(logger_name, filename, max_file_size, max_files); 601 | } 602 | } else { 603 | _logger = spd::rotating_logger_st(logger_name, filename, max_file_size, max_files); 604 | } 605 | } 606 | } 607 | }; 608 | 609 | class DailyLogger : public Logger { 610 | public: 611 | DailyLogger(const std::string& logger_name, const std::string& filename, bool multithreaded = false, int hour = 0, int minute = 0, bool async_mode = g_async_mode_on) 612 | : Logger(logger_name, async_mode) 613 | { 614 | if (multithreaded) { 615 | if (async_mode) { 616 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 617 | _logger = spd::daily_logger_mt(logger_name, filename, hour, minute); 618 | } else { 619 | _logger = spd::daily_logger_mt(logger_name, filename, hour, minute); 620 | } 621 | } else { 622 | _logger = spd::daily_logger_mt(logger_name, filename, hour, minute); 623 | } 624 | } else { 625 | if (async_mode) { 626 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 627 | _logger = spd::daily_logger_st(logger_name, filename, hour, minute); 628 | } else { 629 | _logger = spd::daily_logger_st(logger_name, filename, hour, minute); 630 | } 631 | } else { 632 | _logger = spd::daily_logger_st(logger_name, filename, hour, minute); 633 | } 634 | } 635 | } 636 | }; 637 | 638 | #ifdef SPDLOG_ENABLE_SYSLOG 639 | class SyslogLogger : public Logger { 640 | public: 641 | SyslogLogger(const std::string& logger_name, bool multithreaded = false, const std::string& ident = "", int syslog_option = 0, int syslog_facilty = (1 << 3), bool async_mode = g_async_mode_on) 642 | : Logger(logger_name, async_mode) 643 | { 644 | if (multithreaded) { 645 | if (async_mode) { 646 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 647 | _logger = spd::syslog_logger_mt(logger_name, ident, syslog_option, syslog_facilty); 648 | } else { 649 | _logger = spd::syslog_logger_mt(logger_name, ident, syslog_option, syslog_facilty); 650 | } 651 | } else { 652 | _logger = spd::syslog_logger_mt(logger_name, ident, syslog_option, syslog_facilty); 653 | } 654 | } else { 655 | if (async_mode) { 656 | if (g_async_overflow_policy == spdlog::async_overflow_policy::overrun_oldest) { 657 | _logger = spd::syslog_logger_st(logger_name, ident, syslog_option, syslog_facilty); 658 | } else { 659 | _logger = spd::syslog_logger_st(logger_name, ident, syslog_option, syslog_facilty); 660 | } 661 | } else { 662 | _logger = spd::syslog_logger_st(logger_name, ident, syslog_option, syslog_facilty); 663 | } 664 | } 665 | } 666 | }; 667 | #endif 668 | 669 | class AsyncOverflowPolicy { 670 | public: 671 | const static int block{ (int)spd::async_overflow_policy::block }; 672 | const static int overrun_oldest{ (int)spd::async_overflow_policy::overrun_oldest }; 673 | }; 674 | 675 | void set_async_mode(size_t queue_size = spdlog::details::default_async_q_size, size_t thread_count = 1, int async_overflow_policy = AsyncOverflowPolicy::block) { 676 | // Initialize/replace the global spdlog thread pool. 677 | auto& registry = spdlog::details::registry::instance(); 678 | std::lock_guard tp_lck(registry.tp_mutex()); 679 | auto tp = std::make_shared(queue_size, thread_count); 680 | registry.set_tp(tp); 681 | 682 | g_async_overflow_policy = static_cast(async_overflow_policy); 683 | g_async_mode_on = true; 684 | } 685 | 686 | std::shared_ptr thread_pool() { 687 | auto& registry = spdlog::details::registry::instance(); 688 | std::lock_guard tp_lck(registry.tp_mutex()); 689 | auto tp = registry.get_tp(); 690 | if(tp == nullptr) { 691 | set_async_mode(); 692 | auto tp = registry.get_tp(); 693 | } 694 | 695 | return tp; 696 | } 697 | 698 | class SinkLogger : public Logger { 699 | public: 700 | SinkLogger(const std::string& logger_name, const Sink& sink, bool async_mode = g_async_mode_on) 701 | : Logger(logger_name, async_mode) 702 | { 703 | if (async_mode) { 704 | _logger = std::shared_ptr(new spd::async_logger(logger_name, sink.get_sink(), thread_pool(), g_async_overflow_policy)); 705 | } else { 706 | _logger = std::shared_ptr(new spd::logger(logger_name, sink.get_sink())); 707 | } 708 | } 709 | SinkLogger(const std::string& logger_name, const std::vector& sink_list, bool async_mode = g_async_mode_on) 710 | : Logger(logger_name, async_mode) 711 | { 712 | std::vector sinks; 713 | for (auto sink : sink_list) 714 | sinks.push_back(sink.get_sink()); 715 | 716 | if (async_mode) { 717 | _logger = std::shared_ptr(new spd::async_logger(logger_name, sinks.begin(), sinks.end(), thread_pool(), g_async_overflow_policy)); 718 | } else { 719 | _logger = std::shared_ptr(new spd::logger(logger_name, sinks.begin(), sinks.end())); 720 | } 721 | } 722 | }; 723 | 724 | Logger get(const std::string& name) 725 | { 726 | Logger* logger = access_logger(name); 727 | if (logger) 728 | return *logger; 729 | else 730 | throw std::runtime_error(std::string("Logger name: " + name + " could not be found")); 731 | } 732 | 733 | void drop(const std::string& name) 734 | { 735 | remove_logger(name); 736 | spdlog::drop(name); 737 | } 738 | 739 | void drop_all() 740 | { 741 | remove_logger_all(); 742 | spdlog::drop_all(); 743 | } 744 | 745 | } 746 | 747 | PYBIND11_MODULE(spdlog, m) 748 | { 749 | m.doc() = R"pbdoc( 750 | spdlog module 751 | ----------------------- 752 | 753 | .. currentmodule:: spdlog 754 | 755 | .. autosummary:: 756 | :toctree: _generate 757 | 758 | LogLevel 759 | Logger 760 | )pbdoc"; 761 | 762 | py::class_>(m, "_spd_logger"); 763 | 764 | m.def("set_async_mode", set_async_mode, 765 | py::arg("queue_size") = 1 << 16, 766 | py::arg("thread_count") = 1, 767 | py::arg("overflow_policy") = 0); 768 | 769 | py::class_(m, "Sink") 770 | .def(py::init<>()) 771 | .def("set_level", &Sink::set_level); 772 | 773 | py::class_(m, "stdout_sink_st") 774 | .def(py::init<>()); 775 | 776 | py::class_(m, "stdout_sink_mt") 777 | .def(py::init<>()); 778 | 779 | py::class_(m, "stdout_color_sink_st") 780 | .def(py::init<>()); 781 | 782 | py::class_(m, "stdout_color_sink_mt") 783 | .def(py::init<>()); 784 | 785 | py::class_(m, "stderr_sink_st") 786 | .def(py::init<>()); 787 | 788 | py::class_(m, "stderr_sink_mt") 789 | .def(py::init<>()); 790 | 791 | py::class_(m, "stderr_color_sink_st") 792 | .def(py::init<>()); 793 | 794 | py::class_(m, "stderr_color_sink_mt") 795 | .def(py::init<>()); 796 | 797 | py::class_(m, "basic_file_sink_st") 798 | .def(py::init(), py::arg("filename"), py::arg("truncate") = false); 799 | 800 | py::class_(m, "basic_file_sink_mt") 801 | .def(py::init(), py::arg("filename"), py::arg("truncate") = false); 802 | 803 | py::class_(m, "daily_file_sink_st") 804 | .def(py::init(), py::arg("filename"), 805 | py::arg("rotation_hour"), 806 | py::arg("rotation_minute")); 807 | 808 | py::class_(m, "daily_file_sink_mt") 809 | .def(py::init(), py::arg("filename"), 810 | py::arg("rotation_hour"), 811 | py::arg("rotation_minute")); 812 | 813 | py::class_(m, "rotating_file_sink_st") 814 | .def(py::init(), py::arg("filename"), 815 | py::arg("max_size"), 816 | py::arg("max_files")); 817 | 818 | py::class_(m, "rotating_file_sink_mt") 819 | .def(py::init(), py::arg("filename"), 820 | py::arg("max_size"), 821 | py::arg("max_files")); 822 | 823 | py::class_(m, "dist_sink_mt") 824 | .def(py::init<>()) 825 | .def(py::init>(), py::arg("sinks")) 826 | .def("add_sink", &dist_sink_mt::add_sink, py::arg("sink")) 827 | .def("remove_sink", &dist_sink_mt::remove_sink, py::arg("sink")) 828 | .def("set_sinks", &dist_sink_mt::set_sinks, py::arg("sinks")) 829 | .def("sinks", &dist_sink_mt::sinks); 830 | 831 | py::class_(m, "dist_sink_st") 832 | .def(py::init<>()) 833 | .def(py::init>(), py::arg("sinks")) 834 | .def("add_sink", &dist_sink_st::add_sink, py::arg("sink")) 835 | .def("remove_sink", &dist_sink_st::remove_sink, py::arg("sink")) 836 | .def("set_sinks", &dist_sink_st::set_sinks, py::arg("sinks")) 837 | .def("sinks", &dist_sink_st::sinks); 838 | 839 | py::class_(m, "dup_filter_sink_st") 840 | .def(py::init(), py::arg("max_skip_duration_seconds")); 841 | 842 | py::class_(m, "dup_filter_sink_mt") 843 | .def(py::init(), py::arg("max_skip_duration_seconds")); 844 | 845 | py::class_(m, "null_sink_st") 846 | .def(py::init<>()); 847 | 848 | py::class_(m, "null_sink_mt") 849 | .def(py::init<>()); 850 | 851 | py::class_(m, "tcp_sink_st") 852 | .def(py::init(), 853 | py::arg("server_host"), 854 | py::arg("server_port"), 855 | py::arg("lazy_connect")); 856 | 857 | py::class_(m, "tcp_sink_mt") 858 | .def(py::init(), 859 | py::arg("server_host"), 860 | py::arg("server_port"), 861 | py::arg("lazy_connect")); 862 | 863 | py::class_(m, "LogLevel") 864 | .def_property_readonly_static("TRACE", [](py::object) { return LogLevel::trace; }) 865 | .def_property_readonly_static("DEBUG", [](py::object) { return LogLevel::debug; }) 866 | .def_property_readonly_static("INFO", [](py::object) { return LogLevel::info; }) 867 | .def_property_readonly_static("WARN", [](py::object) { return LogLevel::warn; }) 868 | .def_property_readonly_static("ERR", [](py::object) { return LogLevel::err; }) 869 | .def_property_readonly_static("CRITICAL", [](py::object) { return LogLevel::critical; }) 870 | .def_property_readonly_static("OFF", [](py::object) { return LogLevel::off; }); 871 | 872 | py::class_(m, "AsyncOverflowPolicy") 873 | .def_property_readonly_static("BLOCK", [](py::object) { return AsyncOverflowPolicy::block; }) 874 | .def_property_readonly_static("OVERRUN_OLDEST", [](py::object) { return AsyncOverflowPolicy::overrun_oldest; }); 875 | 876 | py::enum_(m, "PatternTimeType") 877 | .value("local", spdlog::pattern_time_type::local) 878 | .value("utc", spdlog::pattern_time_type::utc) 879 | .export_values(); 880 | 881 | py::class_(m, "Logger") 882 | .def("log", &Logger::log) 883 | .def("trace", &Logger::trace) 884 | .def("debug", &Logger::debug) 885 | .def("info", &Logger::info) 886 | .def("warn", &Logger::warn) 887 | .def("error", &Logger::error) 888 | .def("critical", &Logger::critical) 889 | .def("name", &Logger::name) 890 | .def("should_log", &Logger::should_log) 891 | .def("set_level", &Logger::set_level) 892 | .def("level", &Logger::level) 893 | .def("set_pattern", &Logger::set_pattern, 894 | py::arg("pattern"), py::arg("type") = spd::pattern_time_type::local, "type refers to time format and takes 'local' or 'utc'") 895 | .def("flush_on", &Logger::flush_on) 896 | .def("flush", &Logger::flush) 897 | .def("close", &Logger::close) 898 | .def("async_mode", &Logger::async) 899 | .def("sinks", &Logger::sinks) 900 | .def("set_error_handler", &Logger::set_error_handler) 901 | .def("get_underlying_logger", &Logger::get_underlying_logger); 902 | 903 | py::class_(m, "SinkLogger") 904 | .def(py::init&>(), 905 | py::arg("name"), 906 | py::arg("sinks")) 907 | .def(py::init&, bool>(), 908 | py::arg("name"), 909 | py::arg("sinks"), 910 | py::arg("async_mode")); 911 | 912 | py::class_(m, "ConsoleLogger") 913 | .def(py::init(), 914 | py::arg("name"), 915 | py::arg("multithreaded") = false, 916 | py::arg("stdout") = true, 917 | py::arg("colored") = true) 918 | .def(py::init(), 919 | py::arg("name"), 920 | py::arg("multithreaded") = false, 921 | py::arg("stdout") = true, 922 | py::arg("colored") = true, 923 | py::arg("async_mode")); 924 | 925 | py::class_(m, "FileLogger") 926 | .def(py::init(), 927 | py::arg("name"), 928 | py::arg("filename"), 929 | py::arg("multithreaded") = false, 930 | py::arg("truncate") = false) 931 | .def(py::init(), 932 | py::arg("name"), 933 | py::arg("filename"), 934 | py::arg("multithreaded") = false, 935 | py::arg("truncate") = false, 936 | py::arg("async_mode")); 937 | py::class_(m, "RotatingLogger") 938 | .def(py::init(), 939 | py::arg("name"), 940 | py::arg("filename"), 941 | py::arg("multithreaded"), 942 | py::arg("max_file_size"), 943 | py::arg("max_files")) 944 | .def(py::init(), 945 | py::arg("name"), 946 | py::arg("filename"), 947 | py::arg("multithreaded"), 948 | py::arg("max_file_size"), 949 | py::arg("max_files"), 950 | py::arg("async_mode")); 951 | py::class_(m, "DailyLogger") 952 | .def(py::init(), 953 | py::arg("name"), 954 | py::arg("filename"), 955 | py::arg("multithreaded") = false, 956 | py::arg("hour") = 0, 957 | py::arg("minute") = 0) 958 | .def(py::init(), 959 | py::arg("name"), 960 | py::arg("filename"), 961 | py::arg("multithreaded") = false, 962 | py::arg("hour") = 0, 963 | py::arg("minute") = 0, 964 | py::arg("async_mode")); 965 | 966 | //SyslogLogger(const std::string& logger_name, const std::string& ident = "", int syslog_option = 0, int syslog_facilty = (1<<3)) 967 | #ifdef SPDLOG_ENABLE_SYSLOG 968 | py::class_(m, "syslog_sink_st") 969 | .def(py::init(), 970 | py::arg("ident") = "", 971 | py::arg("syslog_option") = 0, 972 | py::arg("syslog_facility") = (1 << 3), 973 | py::arg("enable_formatting") = true); 974 | py::class_(m, "syslog_sink_mt") 975 | .def(py::init(), 976 | py::arg("ident") = "", 977 | py::arg("syslog_option") = 0, 978 | py::arg("syslog_facility") = (1 << 3), 979 | py::arg("enable_formatting") = true); 980 | py::class_(m, "SyslogLogger") 981 | .def(py::init(), 982 | py::arg("name"), 983 | py::arg("multithreaded") = false, 984 | py::arg("ident") = "", 985 | py::arg("syslog_option") = 0, 986 | py::arg("syslog_facility") = (1 << 3)) 987 | .def(py::init(), 988 | py::arg("name"), 989 | py::arg("multithreaded") = false, 990 | py::arg("ident") = "", 991 | py::arg("syslog_option") = 0, 992 | py::arg("syslog_facility") = (1 << 3), 993 | py::arg("async_mode")); 994 | #endif 995 | m.def("get", get, py::arg("name"), py::return_value_policy::copy); 996 | m.def("drop", drop, py::arg("name")); 997 | m.def("drop_all", drop_all); 998 | 999 | #ifdef VERSION_INFO 1000 | m.attr("__version__") = VERSION_INFO; 1001 | #else 1002 | m.attr("__version__") = "dev"; 1003 | #endif 1004 | } 1005 | -------------------------------------------------------------------------------- /tests/sink_test.py: -------------------------------------------------------------------------------- 1 | import spdlog 2 | import time 3 | import os 4 | 5 | sinks = [ 6 | spdlog.stdout_sink_st(), 7 | spdlog.stdout_sink_mt(), 8 | spdlog.stderr_sink_st(), 9 | spdlog.stderr_sink_mt(), 10 | spdlog.daily_file_sink_st("DailySinkSt.log", 0, 0), 11 | spdlog.daily_file_sink_mt("DailySinkMt.log", 0, 0), 12 | spdlog.rotating_file_sink_st("RotSt.log", 1024, 1024), 13 | spdlog.rotating_file_sink_mt("RotMt.log", 1024, 1024), 14 | ] 15 | 16 | 17 | logger = spdlog.SinkLogger("Hello", sinks) 18 | logger.info("Kukucs") 19 | logger.info("Alma") -------------------------------------------------------------------------------- /tests/spdlog_vs_logging.py: -------------------------------------------------------------------------------- 1 | import spdlog 2 | import logging 3 | import time 4 | import statistics 5 | import random 6 | import numpy as np 7 | from functools import partial 8 | 9 | MICROSEC_IN_SEC = 1e6 10 | 11 | def timed(func): 12 | def wrapper(*args): 13 | start = time.perf_counter() 14 | func(*args) 15 | return (time.perf_counter() - start) * MICROSEC_IN_SEC 16 | return wrapper 17 | 18 | 19 | def generate_message(msg_len): 20 | msg = "" 21 | for i in range(msg_len): 22 | c = random.randint(ord('a'), ord('z')) 23 | msg += chr(c) 24 | return msg 25 | 26 | def generate_numpy_array(array_len): 27 | return np.random.rand(array_len) 28 | 29 | def generate_numpy_array_str(array_len): 30 | return f'{np.random.rand(array_len)}' 31 | 32 | 33 | 34 | @timed 35 | def do_logging(logger, message, count): 36 | for i in range(count): 37 | logger.info(message) 38 | 39 | 40 | 41 | def build_timings_per_len(message_lengths): 42 | timings = {"spdlog" : {}, "logging" : {}} 43 | for msg_len in message_lengths: 44 | timings["spdlog"][msg_len] = [] 45 | timings["logging"][msg_len] = [] 46 | return timings 47 | 48 | 49 | def candidate_logger(logger, name, epochs, sub_epochs,repeat_cnt, message_lengths, message_generator, worker, timings): 50 | for epoch in range(epochs): 51 | for msg_len in message_lengths: 52 | msg = message_generator(msg_len) 53 | for _ in range(sub_epochs): 54 | took = logger(msg, repeat_cnt) 55 | timings[name][msg_len].append(took / repeat_cnt) 56 | worker() 57 | 58 | def lets_do_some_work(): 59 | x = [i for i in range(1 << 5)] 60 | y = [i for i in range(1 << 5)] 61 | result = [] 62 | for i, j in zip(x,y): 63 | z = x + y 64 | result.append(z) 65 | 66 | 67 | def mode(data): 68 | data = sorted(data) 69 | return data[len(data)//2] 70 | 71 | def generate_stats(timings): 72 | d = {"spdlog": {}, "logging" : {}} 73 | for logger, time_per_msg_len in timings.items(): 74 | for msg_len, times in time_per_msg_len.items(): 75 | mean = statistics.mean(times) 76 | mo = mode(times) 77 | stddev = statistics.stdev(times) 78 | m = max(times) 79 | d[logger][msg_len] = {"mean": mean, "mode" : mo, 80 | "stddev" : stddev, "max" : m} 81 | return d 82 | 83 | def calculate_ratio(timings, logger1, logger2): 84 | t1,t2 = timings[logger1], timings[logger2] 85 | msg_lens = t1.keys() 86 | return { ml : t1[ml]["mean"]/t2[ml]["mean"] for ml in msg_lens } 87 | 88 | 89 | def print_stats(timings): 90 | for logger in timings.keys(): 91 | print("Logger: ", logger) 92 | for msg_len in timings[logger].keys(): 93 | t = timings[logger][msg_len]["mean"] 94 | print(msg_len, " - ", t) 95 | 96 | 97 | def run_test(async_mode): 98 | 99 | message_lengths = [10, 20, 40, 100, 300, 1000, 5000, 20000] 100 | repeat_cnt = 5 101 | epochs = 20 102 | sub_epochs = 10 103 | if async_mode: 104 | spdlog.set_async_mode(queue_size=1 << 24) 105 | 106 | spd_logger = spdlog.FileLogger(name='speedlogger', filename='speedlog.log', multithreaded=False, truncate=False) 107 | if spd_logger.async_mode() != async_mode: 108 | print(f"spdlog should be in {async_mode} mode but is in {spd_logger.async_mode()}") 109 | 110 | standard_logger = logging.getLogger('logging') 111 | fh = logging.FileHandler('logging.log') 112 | fh.setLevel(logging.DEBUG) 113 | standard_logger.addHandler(fh) 114 | standard_logger.setLevel(logging.DEBUG) 115 | 116 | timings = build_timings_per_len(message_lengths) 117 | 118 | candidate_logger(partial(do_logging, spd_logger), 'spdlog', epochs, sub_epochs, repeat_cnt, message_lengths, generate_message, lets_do_some_work, timings) 119 | candidate_logger(partial(do_logging, standard_logger), 'logging', epochs,sub_epochs, repeat_cnt, message_lengths, generate_message, lets_do_some_work, timings) 120 | 121 | 122 | final = generate_stats(timings) 123 | print("Message len -> time microsec") 124 | #print(final) 125 | 126 | print_stats(final) 127 | ratios = calculate_ratio(final, 'spdlog', 'logging') 128 | 129 | for msg_len, ratio in ratios.items(): 130 | print(f"spdlog takes {ratio * 100}% of logging at message len: {msg_len}") 131 | 132 | if async_mode: 133 | sleeptime = 4 134 | print(f"Sleeping for secs: {sleeptime}") 135 | time.sleep(sleeptime) 136 | spd_logger.close() 137 | 138 | 139 | if __name__ == "__main__": 140 | print("Running in spdlog in sync mode") 141 | run_test(False) 142 | print("Running in spdlog in async mode") 143 | run_test(True) 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /tests/test_spdlog.py: -------------------------------------------------------------------------------- 1 | import spdlog 2 | import unittest 3 | 4 | from spdlog import ConsoleLogger, FileLogger, RotatingLogger, DailyLogger, LogLevel 5 | 6 | def set_log_level(logger, level): 7 | print("Setting Log level to %d" % level) 8 | logger.set_level(level) 9 | 10 | 11 | def log_msg(logger): 12 | logger.trace('I am Trace') 13 | logger.debug('I am Debug') 14 | logger.info('I am Info') 15 | logger.warn('I am Warning') 16 | logger.error('I am Error') 17 | logger.critical('I am Critical') 18 | 19 | 20 | class SpdLogTest(unittest.TestCase): 21 | def test_console_logger(self): 22 | name = 'Console Logger' 23 | tf = (True, False) 24 | for multithreaded in tf: 25 | for stdout in tf: 26 | for colored in tf: 27 | logger = ConsoleLogger(name, multithreaded, stdout, colored) 28 | logger.info('I am a console log test.') 29 | spdlog.drop(name) 30 | 31 | def test_drop(self): 32 | name = 'Console Logger' 33 | for i in range(10): 34 | tf = (True, False) 35 | for multithreaded in tf: 36 | for stdout in tf: 37 | for colored in tf: 38 | logger = ConsoleLogger(name, multithreaded, stdout, colored) 39 | spdlog.drop(logger.name()) 40 | def test_log_level(self): 41 | logger = ConsoleLogger('Logger', False, True, True) 42 | for level in (LogLevel.TRACE, LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, 43 | LogLevel.ERR, LogLevel.CRITICAL): 44 | set_log_level(logger, level) 45 | log_msg(logger) 46 | 47 | 48 | 49 | if __name__ == "__main__": 50 | unittest.main() 51 | 52 | -------------------------------------------------------------------------------- /upload_to_pypi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | twine upload dist/* 3 | --------------------------------------------------------------------------------