cyrusbehr
cyrusbehr

Reputation: 1301

How to use custom logger with websocketpp?

I am creating a telemetry server using websocketpp, and have followed the example here. My application will be running as a linux daemon which starts on boot, and therefore I won't be able to write logs to standard out. I would therefore like to add a customer logger using spdlog, and understand that it can be done based on what's on this page. Looks like I need to use the websocketpp::log::stub interface to create my own customer logger. The issue is, the documentation on this is quite limited regarding logging, and I am not sure where to begin and how to incorporate it in the context of the telemetry server example linked above. I am not sure how to specify the logger when I define my server: typedef websocketpp::server<websocketpp::config::asio> server;.

How do I go about extending the stub class, and how do I initialize my server with this customer logger?

The only sample code I could find is in this thread here, but based on the linked comment this code is no longer relevant after V 0.3.x+.

Upvotes: 0

Views: 1368

Answers (2)

cyrusbehr
cyrusbehr

Reputation: 1301

For anyone wanting sample code using spdlog as a customer logger, here it is:

Create a new file customerLogger.hpp with the contents:

#pragma once

#include <websocketpp/logger/basic.hpp>

#include <websocketpp/common/cpp11.hpp>
#include <websocketpp/logger/levels.hpp>
#include "spdlog/logger.h"
#include "spdlog/sinks/rotating_file_sink.h"

namespace websocketpp {
    namespace log {

/// Basic logger that outputs to syslog
        template <typename concurrency, typename names>
        class myLogger : public basic<concurrency, names> {
        public:
            typedef basic<concurrency, names> base;

            /// Construct the logger
            /**
             * @param hint A channel type specific hint for how to construct the logger
             */
            myLogger<concurrency,names>(channel_type_hint::value hint =
            channel_type_hint::access)
                    : basic<concurrency,names>(hint), m_channel_type_hint(hint) {
                auto max_size = 1048576 * 5;
                auto max_files = 3;
                auto rotating_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt> ("/var/logs/my_logger.log", max_size, max_files);
                m_logger = std::make_shared<spdlog::logger>("my_logger", rotating_sink);
                m_logger->flush_on(spdlog::level::info);
                m_logger->set_level(spdlog::level::level_enum::info);
            }

            /// Construct the logger
            /**
             * @param channels A set of channels to statically enable
             * @param hint A channel type specific hint for how to construct the logger
             */
            myLogger<concurrency,names>(level channels, channel_type_hint::value hint =
            channel_type_hint::access)
                    : basic<concurrency,names>(channels, hint), m_channel_type_hint(hint) {
                auto max_size = 1048576 * 5;
                auto max_files = 3;
                auto rotating_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt> ("/var/logs/my_logger.log", max_size, max_files);
                m_logger = std::make_shared<spdlog::logger>("my_logger", rotating_sink);
                m_logger->flush_on(spdlog::level::info);
                m_logger->set_level(spdlog::level::level_enum::info);
            }

            /// Write a string message to the given channel
            /**
             * @param channel The channel to write to
             * @param msg The message to write
             */
            void write(level channel, std::string const & msg) {
                write(channel, msg.c_str());
            }

            /// Write a cstring message to the given channel
            /**
             * @param channel The channel to write to
             * @param msg The message to write
             */
            void write(level channel, char const * msg) {
                scoped_lock_type lock(base::m_lock);
                if (!this->dynamic_test(channel)) { return; }

                if (m_channel_type_hint == channel_type_hint::access) {
                    m_logger->info(msg);
                } else {
                    if (channel == elevel::devel) {
                        m_logger->debug(msg);
                    } else if (channel == elevel::library) {
                        m_logger->debug(msg);
                    } else if (channel == elevel::info) {
                        m_logger->info(msg);
                    } else if (channel == elevel::warn) {
                        m_logger->warn(msg);
                    } else if (channel == elevel::rerror) {
                        m_logger->error(msg);
                    } else if (channel == elevel::fatal) {
                        m_logger->critical(msg);
                    }
                }

            }

        private:
            std::shared_ptr<spdlog::logger> m_logger;
            typedef typename base::scoped_lock_type scoped_lock_type;
            channel_type_hint::value m_channel_type_hint;
        };

    } // log
} // websocketpp

Next, create another file, customConfig.hpp which has the following content:

#pragma once

#include "./customLogger.hpp"
#include <websocketpp/logger/syslog.hpp>
#include <websocketpp/extensions/permessage_deflate/enabled.hpp>

// Custom server config based on bundled asio config
struct my_config : public websocketpp::config::asio {
    // Replace default stream logger with the custom logger
    typedef websocketpp::log::myLogger<concurrency_type, websocketpp::log::elevel> elog_type;
    typedef websocketpp::log::myLogger<concurrency_type, websocketpp::log::alevel> alog_type;
};

typedef websocketpp::server<my_config> my_server;

Finally, when you want to create the server, you simple do my_server endpoint;

Upvotes: 4

zaphoyd
zaphoyd

Reputation: 2750

Building a custom logger has two steps. First, write a policy class with the appropriate interface then create a custom config that uses that policy.

To write the policy class websocketpp::log::stub is a minimal implementation that doesn't actually do anything (it is primarily used for stubbing out logging in the unit tests) but it demonstrates and documents the interface that a logging class needs to implement. The logging class does not need to be a subclass of websocketpp::log::stub. You can look at other examples in the websocketpp/logger/* folder. The syslog logger in particular might be interesting as an example of a logging policy that outputs to something other than standard out.

To set up the custom config you will create a config class. It can be standalone or a subclass of one of the standard ones, like websocketpp::config::asio, that just overrides a small number of things. Your config might only override the loggers, for example. Once created, you will pass your config class into the endpoint template parameter instead of websocketpp::config::asio.

More details about what you can override at compile time via this config system can be found at https://docs.websocketpp.org/reference_8config.html. There is an example on this page that shows a custom config that replaces the default logger (among other changes).

Upvotes: 3

Related Questions