logger klasse



  • Hallo,

    ich habe mir eine logger-klasse gebaut...
    ziel ist es, die logging-fkt einfach ein- und ausschalten zu können (compilezeit) und wenn ausgeschaltet sollte sie kostenlos sein... so weit so gut. nun noch das problem, dass ich gern vor jeder log-zeile datum+uhrzeit stehen hätte.

    erst ma quellcode:

    //main.cpp
    struct print_date_time
    { //spielt keine rolle, nur der vollständigkeit halber
    	void operator()( std::ostream& s )
    	{
    		s << "date and time: ";
    	}
    };
    
    #include "my/logger.hpp"
    #include <fstream>
    
    #ifdef BUILD_DEBUG_MSG
    my::logger_t<std::ofstream, print_date_time> LOGGING_DEBUG( "debug.txt", std::ios_base::app );
    #else
    my::logger_dummy_t<char> LOGGING_DEBUG;
    #endif
    
    int main()
    {
      LOGGING_DEBUG << "asd" << std::endl;
    }
    

    funktioniert so weit alles, wie es soll, außer das die sache mit write ein wenig hässlich ist:
    (LOGGING_DEBUG << 'a').write("sda", 1) << 'd' << std::endl;
    FRAGE #1: geht das irgendwie schöner?

    jetzt habe ich aber auch noch ein LOGGING_LOG und hätte gerne in der debug-log auch die log-log stehen.
    also wollte ich ein append_to_stream() bauen.
    dann wiederrum habe ich aber das problem, dass es sein kann, dass das log-stream objekt zu erst zerstört wird und danach erst der debug-stream. also kann ich nicht einfach eine referenz übergeben...
    FRAGE #2: wie könnte ich einen anderen stream anhängen?

    //my/logger.hpp
    #include <ostream>
    #include <string>
    
    namespace my
    {
    	namespace detail
    	{
    		template< class logger_type >
    		struct proxy_logger
    		{
    			explicit proxy_logger( logger_type* stream_ptr ) : stream( stream_ptr ) {}
    
    			template< class T >
    			proxy_logger< logger_type >& operator<<(const T& x)
    			{
    				stream->base_stream << x;
    				return *this;
    			}
    
    			typedef std::basic_ostream<typename logger_type::char_type, typename logger_type::traits_type> basic_ostream;
    			logger_type& operator<<( basic_ostream&(*manip)(basic_ostream&) )
    			{
    				manip( stream->base_stream );
    
    				return *stream;
    			}
    
    			proxy_logger< logger_type >& write( const typename logger_type::char_type* str, typename logger_type::pos_type length )
    			{
    				stream->base_stream.write( str, length );
    				return *this;
    			}
    
    		private:
    			logger_type* stream;
    		};
    	}
    
    	template< class base_stream_t, class preamble_op >
    	struct logger_t
        {
        public:
    		typedef base_stream_t base;
    		typedef typename base::char_type char_type;
    		typedef typename base::traits_type traits_type;
    		typedef typename base::int_type int_type;
    		typedef typename base::pos_type pos_type;
    		typedef typename base::off_type off_type;
    
    		explicit logger_t( const char* filename, std::ios_base::openmode mode = std::ios_base::out )
    		:	base_stream( filename, mode )
    		,	proxy( this )
    		{
    			*this << "\tCREATED\r\n";
    		}
    		explicit logger_t( const std::string& filename, std::ios_base::openmode mode = std::ios_base::out )
    		:	base_stream( filename, mode )
    		,	proxy( this )
    		{
    			*this << "\tCREATED\r\n";
    		}
    
    		~logger_t()
    		{
    			*this << "\tCLOSED\r\n";
    		}
    
    		logger_t( const logger_t& rhs ) = delete;
    
    		template< class T >
    		detail::proxy_logger< logger_t >& operator<<(const T& x)
    		{
    			preamble_printer( base_stream );
    			return proxy << x;
    		}
    		detail::proxy_logger< logger_t >& write( const char_type* str, pos_type length )
    		{
    			preamble_printer( base_stream );
    			return proxy.write( str, length );
    		}
    	private:
    		preamble_op preamble_printer;
    		detail::proxy_logger< logger_t > proxy;
    		friend detail::proxy_logger < logger_t > ;
    
    		base_stream_t base_stream;
        };
    
    	template< class char_type >
    	struct logger_dummy_t
    	{
    		typedef std::basic_ostream<char_type> basic_ostream;
    		friend logger_dummy_t& operator<<(logger_dummy_t& s, basic_ostream&(*manip)(basic_ostream&))
    		{
    			return s;
    		}
    		template< typename T >
    		friend logger_dummy_t& operator<<(logger_dummy_t& s, const T&)
    		{
    			return s;
    		}
    		logger_dummy_t& write( const typename basic_ostream::char_type*, typename basic_ostream::pos_type )
    		{
    			return *this;
    		}
    	};
    }
    

    FRAGE #3: bessere idee bzgl. des dummy-loggers?

    DENKANSÄTZE
    1.: fällt mir nichts ein.
    2.: unappend oder aber fkt in append_stream_once umbennen und DEBUG die LOG referenz geben und LOG einen ptr auf DEBUG halten lassen. dann kann LOG im DTor unappend auf DEBUG aufrufen. (das once weil ich nicht sicher bin, ob man zu problemen kommen kann, wenn mehrere streams sich doof gegenseitig appenden(das keine kreise entstehen dürfen ist klar))
    3.: ich bin mir recht sicher, dass er rausoptimiert wird. im grunde genommen auch egal, aber da gehts ums prinzip 😉 mein problem ist nur, dass ich zusätzliche fkt bzw überladungen ja trotzdem noch mal dahinschreiben muss... ist zwar jetzt nicht so viel arbeit, aber evtl gibts ja ne elegantere möglichkeit?

    bb

    PS: Bei der Foren-Suche bin ich auch auf die Idee gestoßen, statt std::endl den dtor vom proxy zu nehmen, da bin ich noch sehr gespalten, was ich besser finde 😃



  • ich versteh zwar nicht alles, was du möchtest und warum du bestimmte sachen so gelöst hast, aber für #1 du könntest dir ein beispiel an iomanip nehmen o.ä.

    LOGGING_DEBUG << 'a' << my::limit("sda", 1) << 'd' << std::endl; 
    //oder generell
    LOGGING_DEBUG << my::limit_next(255) << "sda..." << std::endl; //limit beeinflusst nur die *nächste* ausgabe (nicht std::endl)
    

    add #2 - du könntest von streambuf erben ( http://www.cplusplus.com/reference/streambuf/streambuf/ ) und einfach jede funktion von streambuf in zwei substreams kopieren und dann den ios::rdbuf deiner streams wechseln. allerdings finde ich die iostream/fstream dinge äußerst komplex (auch mit berücksichtigung von locales etwa), hier hat das aber schon jemand gemacht: http://stackoverflow.com/questions/1760726/how-can-i-compose-output-streams-so-output-goes-multiple-places-at-once
    auch: http://wordaligned.org/articles/cpp-streambufs



  • #2 So was hat werner salomon hier auch schon mal gepostet glaube ich. Jedenfalls habe ich so was auch hier rum liegen und drüber nachgedacht - allerdings ist da (mMn) das problem, dass ich (da globale objekte) keine reihenfolge der dtor's kenne...

    #1 ne, das fällt raus. Hab vergessen zu erwähnen, dass es ostream kompatibel sein soll^^

    bb + ty:)



  • #1 kann man ja ostream kompatibel machen. (ohne "echte iomanip")

    struct truncate_helper {
        explicit truncate_helper (std::ostream& out, std::size_t cutoff) : out{out}, cutoff{cutoff} {}
    
        template <typename T>
        friend std::ostream& operator<<(truncate_helper& trunc, T const& t) {
             return trunc.out << t;
        }
    
        friend std::ostream& operator<<(truncate_helper trunc, std::string const& s) {
             trunc.out.write(s.c_str(), std::min(s.length(), trunc.cutoff));
             return trunc.out;
        } 
    
        friend std::ostream& operator<<(truncate_helper trunc, char const* cs) {
            trunc.out.write(cs, std::min(std::strlen(cs), trunc.cutoff));
            return trunc.out;
        } 
    
    private:
        std::ostream &out;
        std::size_t cutoff;
    };
    
    struct truncate_creator {    
        friend truncate_helper operator<< (std::ostream& out, truncate_creator tc) {
            return truncate_helper { out, tc.cutoff };
        }
    
        truncate_creator (std::size_t cutoff) : cutoff{cutoff} {}    
    
    private:
        std::size_t cutoff = std::string::npos;
    } ;
    
    truncate_creator truncate (std::size_t cutoff) {
        return truncate_creator{cutoff};
    }
    
    int main()
    {
        std::cout << truncate(5) << "Hello, world!\n";
    }
    

    was damit nicht geht ist sowas:

    std::cout << truncate(5); cout << "Hello, world!\n";
    

    ad #2: hört sich so an, als wäre was du machst, sowieso gefährlich für einen logger, der "irgendwann" zerstört werden könnte.



  • Logging_... ist halt global weil ich ihn sonst überall rumschleppen müsste... Das man keine globalen objekte mit ausgabe im dtor haben darf ist schon klar. Deshalb auch nicht gefährlich (imo). Außer im logger weil sinnvoll...
    So kann ich in meinen header nen include<logger.hpp> machen und ganz normal wie nach bspw cout loggen.



  • du könntest jedenfalls für #2 auch die ios_base::Init variante übernehmen: mach dir eine Init-klasse, die die konstruktion deiner objekte in der richtigen reihenfolge sicherstellt und erstelle ein objekt vom typ Init mit interner linkage in den headern, die deine logger bereitstellen.

    //logger.h
    #include "init.h"
    
    class Init {
       Init () {
          /* initialiisiere globale logger in der richtigen reihenfolge */  
       }
    };
    
    static Init safe_logger_initialization;
    

    das garantiert dir, dass die logger in der richtigen reihenfolge initialisiert und destruiert werden, und das vor allen anderen objekten.



  • Hallo,

    ich habe so etwas mal geschriebe. Siehe http://www.tntnet.org/cxxtools.html. Ich habe mich dafür entschieden, die komplette Logging-Ausgabe in ein Makro zu packen. Nämlich so:

    log_debug("Das Ergebnis ist " << a);
    

    Wenn Du so eine Dummy-Logging-Klasse hast, wird für jede Ausgabe zumindest mal der Operator << aufgerufen. Auch wenn der Ausgabestream die Ausgabe verwerfen würde, wird die Ausgabe dennoch formatiert, was kostet. Packst Du das ganze in ein Makro, dann kannst Du so was definieren (mal vereinfacht):

    #ifdef BUILD_DEBUG_MSG
    #define log_debug(expr)  do { std::cerr << expr; } while (false)
    #else
    #define log_debug(expr)
    #endif
    

    Ist das BUILD_DEBUG_MSG nicht definiert, dann wird gar kein Code erzeugt. In meinen cxxtools ist das ein wenig ausgefeilter aber das Prinzip sollte klar sein.

    Das do..while ist übrigens dabei, damit das log_debug genau einem Ausdruck entspricht und nicht irgendwelche Operatorprioritäten oder etwas ähnliches das Makro kaputt machen.



  • TO logged off schrieb:

    So was hat werner salomon hier auch schon mal gepostet glaube ich. ...

    Ja z.B.:
    https://www.c-plusplus.net/forum/p1952714#1952714 und noch was zum Logging
    https://www.c-plusplus.net/forum/302134-full

    Der Beitrag ist schon ziemlich alt. Gibt bestimmt noch Verbesserungspotential.

    Hier noch ein Beitrag auf Stackoveflow http://stackoverflow.com/questions/22118713/add-time-stamp-with-stdcout

    Gruß
    Werner



  • Dann werfe ich meine Lösung von vor 100 Jahren (vor Github) auch noch mal auf den Tisch:
    http://robitzki.de/log.h
    http://robitzki.de/log.cpp
    http://robitzki.de/log_test.cpp

    zu nutzen wäre dass dann in etwa so:

    LOG_DEBUG( "Foo hat den Wert: " << foo );
    

    Das ganze kennt mehre Aspekte, ist thread safe und kann mit mehreren Senken umgehen.



  • viele möglichkeiten jedenfalls. ohne genaueres über die anforderungen zu wissen, kann man wohl nur die simpelste variante empfehlen.



  • Der Vorteil mit dem proxy war halt, dass ich den user zwingen konnte, den eintrag abzuschließen (nicht im gezeigten code enthalten, aber sind ja im prinzip nur ein bool finished(false) ; finished=true im op<<(manip) bei std::endl und assert(finished) im dtor).

    abgesehen davon halt compile time polymorphie und deshalb entfiel der sonst fällige test auf neuer eintrag oder nicht.

    aber habs mittlerweile doch ohne den proxy gemacht weil write sonst eben nur so hässlich zu benutzen geht und zweitens ich keine lust hatte, jede fkt. drei (bzw 2x und einmal nichts machend) mal zu implementieren...

    falls es interessiert:

    template< class base_stream_t, class preamble_op >
    struct logger_t
    {
    public:
    	typedef base_stream_t base;
    	typedef typename base::char_type char_type;
    	typedef typename base::traits_type traits_type;
    	typedef typename base::int_type int_type;
    	typedef typename base::pos_type pos_type;
    	typedef typename base::off_type off_type;
    	typedef std::basic_ostream< char_type, traits_type > basic_ostream;
    
    	logger_t( const logger_t& rhs ) = delete;
    	logger_t& operator=( const logger_t& rhs ) = delete;
    
    	explicit logger_t( const char* filename, std::ios_base::openmode mode = std::ios_base::out )
    	:	base_stream( filename, mode )
    	{
    		*this << "\tCREATED" << std::endl;
    	}
    	explicit logger_t( const std::string& filename, std::ios_base::openmode mode = std::ios_base::out )
    	:	base_stream( filename, mode )
    	{
    		*this << "\tCREATED" << std::endl;
    	}
    
    	~logger_t()
    	{
    		added_streams.clear();
    		*this << "\tCLOSED\r\n";
    	}
    
    	template< class T >
    	logger_t& operator<<( const T& x )
    	{
    		for( auto i( added_streams.begin() ), e( added_streams.end() ); i != e; ++i )
    			**i << x;
    
    		if( entry_manager.is_new_entry() )
    			entry_manager.make_new_entry( base_stream );
    
    		base_stream << x;
    		return *this;
    	}
    	logger_t& operator<<( basic_ostream&(*manip)(basic_ostream&) )
    	{
    		for( auto i( added_streams.begin() ), e( added_streams.end() ); i != e; ++i )
    			**i << manip;
    
    		//correct order: ?begin_entry; manip; ?finish_entry
    		//otherwise manip affect begin_entry
    
    		if( entry_manager.is_new_entry() )
    			entry_manager.make_new_entry( base_stream );
    
    		manip( base_stream );
    
    		if( manip == std::endl )
    			entry_manager.finish_entry();
    
    		return *this;
    	}
    	logger_t& write( const char_type* str, pos_type length )
    	{
    		for( auto i( added_streams.begin() ), e( added_streams.end() ); i != e; ++i )
    			(*i)->write( str, length );
    
    		if( entry_manager.is_new_entry() )
    			entry_manager.make_new_entry( base_stream );
    
    		base_stream.write( str, length );
    		return *this;
    	}
    
    	void duplicate_to_add( logger_t& other_stream )
    	{
    		added_streams.push_back( &other_stream );
    	}
    	void duplicate_to_remove( logger_t& other_stream )
    	{
    		using std::find;
    		auto iter_to_del = find( added_streams.begin(), added_streams.end(), &other_stream );
    		my_assert( iter_to_del != added_streams.end() && "cannot delete non-existing item" );
    
    		//dont care for remove not at the end: (very small) vector<pointer>
    		added_streams.erase( iter_to_del );
    	}
    private:
    
    	base_stream_t base_stream;
    	std::vector< logger_t* > added_streams;
    
    	struct entry_manager_t : private preamble_op
    	{
    		entry_manager_t() : next_in_is_new_entry( true ) {}
    
    		void finish_entry() { next_in_is_new_entry = true; }
    		bool is_new_entry() const { return next_in_is_new_entry; }
    		void make_new_entry( base_stream_t& the_stream )
    		{
    			this->operator()( the_stream );
    			next_in_is_new_entry = false;
    		}
    
    	private:
    		bool next_in_is_new_entry;
    	};
    	entry_manager_t entry_manager;
    };
    
    //template< class char_type >
    //struct logger_dummy_t
    //...
    

    bb


Log in to reply