Herzlich Willkommen, lieber Gast!
  Sie befinden sich hier:

  Forum » C / C++ (ANSI-Standard) » Klasse für logging

Forum | Hilfe | Team | Links | Impressum | > Suche < | Mitglieder | Registrieren | Einloggen
  Quicklinks: MSDN-Online || STL || clib Reference Grundlagen || Literatur || E-Books || Zubehör || > F.A.Q. < || Downloads   

Autor Thread - Seiten: > 1 <
000
28.08.2008, 19:31 Uhr
~fasmat
Gast


Hey Leute!

Ich hab eine Frage zu einem kleinem Projekt. Das Ziel dieses Projekts ist eine Klasse für logging Aktivitäten zu schreiben. Diese will ich in eine DLL exportieren um sie dann in anderen Projekten nutzen zu können. Außerdem will ich sie ANSI-konform halten um sie sowohl unter Windows (mit .NET) als auch unter Linux (mit Qt) einbinden zu können.

Wie weit ich momentan gekommen bin:

Die Klasse wird erfolgreich erstellt und in eine DLL exportiert, die ich als LoadingTime DLL in einem Konsolen-Programm einbinde (nur für Testzwecke). Die Klasse sieht wie folgt aus:

log.h

C++:
#ifdef DLLDIR_EX
   #define DLLDIR  __declspec(dllexport)   // export DLL information
#else
   #define DLLDIR  __declspec(dllimport)   // import DLL information
#endif

class DLLDIR log
{
public:
    log(void);
    log(char *);
    ~log(void);

public:
    log & operator << (int);
    log & operator << (char*);

protected:
    char *getCurrentTime();

private:
    std::ofstream logFile;
    char *CurrentTime;
};

Die Header Datei binde ich sowohl für die DLL als auch für die Exe ein.

log.cpp

C++:
#define DLLDIR_EX

#include "log.h"

log::log(void)
{
}

log::log(char * filename)
{
    this->CurrentTime = new char();
    this->logFile.open(filename);

    logFile << this->getCurrentTime() << "Start of logging" << std::endl;
}

char *log::getCurrentTime()
{
    time_t now = time(NULL);
    tm *now_f = new tm();
    localtime_s(now_f, &now);

    sprintf_s(CurrentTime, 25, "%02d.%02d.%d %02d:%02d:%02d     ",
        now_f->tm_mday,
        now_f->tm_mon+1,
        now_f->tm_year+1900,
        now_f->tm_hour,
        now_f->tm_min,
        now_f->tm_sec);

    return CurrentTime;    
}

log::~log(void)
{
}

log &log::operator << (int value)
{
    logFile << this->getCurrentTime() << value << std::endl;

    return *this;
}

log &log::operator << (char *value)
{
    logFile << this->getCurrentTime() << value << std::endl;

    return *this;
}


Ich weiß dass meine getCurrentTime()-Funktion so wie sie jetzt ist nicht unter Linux funktionieren wird, da dort andere Funktionen verwendet werden. Dieses Problem will ich dann mit #ifdef's lösen sobald die Klasse fertig ist und unter Windows das macht was ich will. Außerdem will ich noch logging-Stufen hinzufügen (WARNING, INFO, DEBUG), so dass nur das gespeichert/angezeigt wird, was auch für den jeweiligen Benutzer interessant ist (nur Warnmeldungen, Informationen, alles)

Das Programm das ich für Testzwecke verwende:

C++:
#include "../log/log.h"

#pragma comment(lib,"log.lib")

using namespace std;

int main(int argc, char* argv[])
{
    cout << "This is a test" << endl;

    log *cLog = new log("test.log");

    *cLog << (char *)"test";
    *cLog << (int)5;

    delete cLog;

    return 0;
}


liefert mir folgende Ausgabe in eine Datei:

Code:
28.08.2008 19:16:19     Start of logging
28.08.2008 19:16:19     test
28.08.2008 19:16:19     5


Was ich nun erreichen will:

1. Ist der Code soweit "sauber" oder würde er sich vereinfachen/verbessern lassen?
2. Kann ich im Konstruktor log(void) logFile auf std::cerr umleiten? So dass die log-Ausgaben auf stderr ausgegeben werden, wenn keine Datei angegeben wird?
3. Ist es möglich die Klasse so zu verändern, dass sie nicht nur die Nachrichten in eine Text-Datei schreibt, sondern sie auch gleichzeitig am Bildschirm anzeigen kann (in einem eigenen log-Fenster)?

Dieser Post wurde am 28.08.2008 um 19:34 Uhr von fasmat editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
001
28.08.2008, 19:59 Uhr
xXx
Devil


Du kannst dir nen dualstream basteln ... d.h. nen Stream der den Inhalt auf 2 Streams aufteilt ... sollteste was im inet zu finden ... notfalls kann ich dir acuh miene implementierung geben
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
002
28.08.2008, 22:14 Uhr
~fasmat
Gast


Ich würds gern selbst machen, aber ein bisschen Hilfe wär nicht schlecht ;-)

Wie mache ich einen dual-stream? Könnte ich eventuell die funktionen

C++:
public:
    log & operator << (int);
    log & operator << (char*);

Wie folgt schreiben:

C++:
log &log::operator << (int value)
{
    logFile << this->getCurrentTime() << value << std::endl;
    logWindow << this->getCurrentTime() << value << std::endl;

    return *this;
}

log &log::operator << (char *value)
{
    logFile << this->getCurrentTime() << value << std::endl;
    logWindow << this->getCurrentTime() << value << std::endl;

    return *this;
}

und logWindow auf z.B. cout oder ein (readonly) Textfeld eines Fensters pipen, dessen handle ich der Klasse über den Konstruktor übergebe? Wie kann ich die Funktion umschreiben um beim Aufruf folgender Zeile:

C++:
char * name_der_funktion = "test";
int counter = 5;
*cLog << "Funktion " << name_der_funktion << " zum " << counter << ". mal aufgerufen";

dazu zu bewegen in die datei

Code:
01.01.2009 22:03:45    Funktion test zum 5. mal aufgerufen

und nicht

Code:
01.01.2009 22:03:45    Funktion
01.01.2009 22:03:45    test
01.01.2009 22:03:45     zum
01.01.2009 22:03:45    5
01.01.2009 22:03:45    . mal aufgerufen

schreiben zu lassen?

Dieser Post wurde am 28.08.2008 um 22:30 Uhr von fasmat editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
003
28.08.2008, 23:17 Uhr
xXx
Devil


Nja das Problem ist, das du direkt in die Datei schreibst Mit std::endl rufst du ja flush auf und fügst vorher nen Newline deinem Stream an ...

Ja du kannst das auch so schreiben Aber es geht auch etwas eleganter ... nja aber das is denk ich mal dann was zu kompliziert ... aber was du auf jedenfall machen könntest, wäre:

C++:
template<typename value_type>
log &log::operator << (value_type const& value)
{
    logFile << this->getCurrentTime() << value << "\n";
    logWindow << this->getCurrentTime() << value << "\n";
    return *this;
}
...
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
004
28.08.2008, 23:44 Uhr
0xdeadbeef
Gott
(Operator)


Das erste, was mir dazu einfällt, ist, eine Funktion draus zu machen, in etwa so:

C++:
std::ostream &log() {
  return std::clog << getCurrentTime();
}


und dann halt

C++:
log() << "Funktion " << name_der_funktion << " zum " << counter << ". Mal aufgerufen" << std::endl;


--
Einfachheit ist Voraussetzung für Zuverlässigkeit.
-- Edsger Wybe Dijkstra
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
005
29.08.2008, 01:52 Uhr
~fasmat
Gast


Danke für eure Hilfe Aber ich bekomm mit xXx Lösungsweg nur Linkerfehler:

Code:
Verknüpfen...
test_log.obj : error LNK2019: Verweis auf nicht aufgelöstes externes Symbol ""public: class log & __thiscall log::operator<<<char const [14]>(char const (&)[14])" (??$?6$$BY0O@$$CBD@log@@QAEAAV0@AAY0O@$$CBD@Z)" in Funktion "_main".
test_log.obj : error LNK2019: Verweis auf nicht aufgelöstes externes Symbol ""public: class log & __thiscall log::operator<<<char *>(char * const &)" (??$?6PAD@log@@QAEAAV0@ABQAD@Z)" in Funktion "_main".
test_log.obj : error LNK2019: Verweis auf nicht aufgelöstes externes Symbol ""public: class log & __thiscall log::operator<<<int>(int const &)" (??$?6H@log@@QAEAAV0@ABH@Z)" in Funktion "_main".
test_log.obj : error LNK2019: Verweis auf nicht aufgelöstes externes Symbol ""public: class log & __thiscall log::operator<<<char const [5]>(char const (&)[5])" (??$?6$$BY04$$CBD@log@@QAEAAV0@AAY04$$CBD@Z)" in Funktion "_main".
test_log.exe : fatal error LNK1120: 4 nicht aufgelöste externe Verweise.

Ich nehme mal an, dass es daran liegt, dass ich die Klasse in eine DLL exportiere und aus welchem Grund auch immer das Template beim Compilieren der exe Datei anders interpretiert wird als beim Compilieren der DLL Datei... (klär mich auf, wenn ich falsch liege)

@0xdeadbeef:
Dein Lösungsweg ist simpel und funktioniert, aber wohin führt clog? Mit deiner Funktion gibt mir mein Testprogramm die logging-Zeilen am Bildschirm aus. Ich denke mir ich könnte einfach clog durch einen Filestream ersetzen, um statt auf den Bildschirm in eine Datei zu schreiben, aber wie kriege ich den log dann in ein Eingabefeld eines Fensters während der log gleichzeitig gespeichert wird?

Außerdem hätte ich gerne alles in einer Klasse gehabt, da ich denke, dass es übersichtlicher wird diese zu warten/erweitern. Ich hab die Klasse jetzt einmal ein bisschen verändert und nutze statt dem << operator eine Funktion namens write(int level, char *value):

C++:
log *cLog = new log("test.log");

cLog->write(INFO, "Programm wurde gestartet");

...

cLog->write(DEBUG, "Führe Befehl asdf aus");

...

cLog->write(WARNING, "In Funktion xyz wurde abgebrochen: Fehler 1003");

...

cLog->write(INFO, "Programm wird beendet");

WARNING < INFO < DEBUG
Gespeichert werden alle Mitteilungen in der Datei "test.log". Wenn das level auf DEBUG gesetzt wird, sollen alle log-Mitteilungen angezeigt werden. Wenn es auf INFO gesetzt wird nur die erste und die letzten beiden, auf WARNING nur die 3.

Für das Anzeigen der log-Mitteilungen in einem Fenster werde ich noch eine weitere Funktion hinzufügen: char * getLog(loggingLevel level).
Ich hab mir gedacht, dass diese die .log Datei einliest, verarbeitet (also nur die mit dem loggingLevel level oder höher zurückliefert) und ich diese dann in einem Text-Feld ausgeben kann, z.B. sowas wie

C++:
Textfeld.inhalt = cLog->getLog(DEBUG)

Nur wie schaffe ich es dann, dass sich dieses Text-Feld updated, sobald eine neue log-Nachricht hinzukommt?
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
006
29.08.2008, 02:12 Uhr
0xdeadbeef
Gott
(Operator)


std::clog kannste ein entsprechendes streambuf-Objekt verpassen. Wie dem auch sei, du kannst das ganze ja zu einer Objektmethode machen, also

C++:
log_class &log_class::start_log() {
  return (*this) << get_time();
}


...und dann in der log_class eine Reihe von ostreams verwalten, in die im operator<< der Reihe nach reingeschrieben wird - vielleicht in der Größenordnung:

C++:
class log_class {
public:
  template<typename T> operator<<(T const &data) {
    for(std::list<ostream*>::iterator i = streams_.begin(); i != streams_.end(); ++i) {
      **i << data;
    }
  }

  log_class &start_log();

private:
  std::list<ostream*> streams_;
};


...und dann

C++:
log_class log;

// Streams zuweisen etc.

log.start_log() << "foo" << "bar" << "baz" << std::endl;


--
Einfachheit ist Voraussetzung für Zuverlässigkeit.
-- Edsger Wybe Dijkstra
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
007
29.08.2008, 02:22 Uhr
~fasmat
Gast


OK... jetzt steig ich aus ^^

C++:
public:
  template<typename T> operator<<(T const &data) {
    for(std::list<ostream*>::iterator i = streams_.begin(); i != streams_.end(); ++i) {
      **i << data;
    }
  }

ist etwas, das ich gar nicht mehr verstehe. Ich glaub ich bleib bei meiner write Funktion und formatiere mir den string im vorhinein mit sprintf_s, aber Danke für deine Mühen

OK fürs Verständnis:
template<typename T> bewirkt dass für jeden Typ den T annehmen kann (char *, int, bool, etc.) eine Funktion erzeugt wird oder? So dass ich nicht von Hand operator << (char * const &data) {} dann operator << (int const &data), usw. schreiben muss?
Den Sinn der for-Schleife selbst versteh ich: alle Werte die per << übergeben werden, werden schrittweise durchgegangen, nur die Umsetzung blick ich nicht durch. Was ist das für ein seltsamer Variablentyp std::list<ostream*>::iterator? Was genau liefern streams_.begin() und streams_.end() zurück? Adressen der übergebenen Variablen? Warum ++i und nicht i++ (ich weiß dass bei ++i zuerst erhöht und dann zurückgeliefert wird und bei i++ zuerst zurückgeliefert und dann erhöht wird, aber bei ner Forschleife sollte das doch keinen unterschied machen)? Was genau bewirkt die Zeile **i << data und wieso 2 *?

Naja bin hald noch Anfänger

Kannst du mir aber vieleicht mit meinem Problem bei dem Update des Eingabefelds des log-Fensters helfen? Wenn das Fenster geöffnet wird, kann ich mit meiner getLog(loggingLevel) Funktion das Text-Feld füllen, nur wie update ich es, wenn ein neuer Eintrag per write hinzugefügt wird?

Dieser Post wurde am 29.08.2008 um 02:31 Uhr von fasmat editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
008
29.08.2008, 02:51 Uhr
0xdeadbeef
Gott
(Operator)


Es handelt sich dabei um eine Funktionsvorlage, d.h., für jeden Typ, mit dem du sie benutzt, wird eine entsprechende Funktion erstellt, ja. Es wird ja bei jedem Typ das gleiche gemacht, deswegen macht es wenig Sinn, sich da mit zwanzigtausend Einzelfunktionen rumzuschlagen.

Übrigens hab ich da nicht richtig aufgepasst, eigentlich müsste das

C++:
  template<typename T> log_class &operator<<(T const &data) {
    for(std::list<ostream*>::iterator i = streams_.begin(); i != streams_.end(); ++i) {
      **i << data;
    }

    return *this
  }


heißen. Die for-Schleife geht durch alle registrierten ostreams durch, und schreibt was auch immer übergeben wurde da rein. streams_ ist eine Liste, streams_.begin() und streams_.end() sind Iteratoren, die auf den Anfang der Liste bzw. hinter das Ende zeigen; i läuft von einem zum anderen. Du kannst dir Iteratoren ein bisschen wie Zeiger vorstellen, sie implementieren jedenfalls eine vergleichbare Schnittstelle. *i gibt dir das, worauf i zeigt, und da das ein Zeiger ist (ostreams sind nicht kopierbar), muss der noch dereferenziert werden, bevor man da reinschreiben kann - deshalb **i.

Du könntest auch einen std::vector<ostream*> streams_ benutzen (oder ein Array, und die Speicherverwaltung von Hand machen; aber das wäre unnötig kompliziert) und

C++:
for(int i = 0; i < streams_.size(); ++i) {
  *streams_[i] << data;
}


schreiben, aber ich finde eine Liste hier sinnvoller.

++i ist im Zusammenhang mit Iteratoren schneller als i++, weil bei komplexen Datentypen für das Postinkrement eine Kopie angefertigt werden muss, was beim Präinkrement nicht der Fall ist. Es ist deshalb eine gute Angewohnheit, wo sowohl ++i als auch i++ funktionieren, ++i zu schreiben.

Was die mit << übergebenen Elemente angeht, jedes << bedeutet einen weiteren Funktionsaufruf. <<-Operatoren geben, wenn man es richtig macht, das ostream-Objekt wieder zurück, um diese Notation zu ermöglichen - im Grunde ist

C++:
std::cout << "foo" << "bar";


das selbe wie

C++:
std::cout << "foo";
std::cout << "bar";


...nur halt kürzer.
--
Einfachheit ist Voraussetzung für Zuverlässigkeit.
-- Edsger Wybe Dijkstra
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
Seiten: > 1 <     [ C / C++ (ANSI-Standard) ]  


ThWBoard 2.73 FloSoft-Edition
© by Paul Baecher & Felix Gonschorek (www.thwboard.de)

Anpassungen des Forums
© by Flo-Soft (www.flo-soft.de)

Sie sind Besucher: