2010-04-14 4 views
24

Je travaille sur Unix sur un programme C++ qui envoie des messages à syslog.Rediriger C++ std :: clog vers syslog sous Unix

Le code actuel utilise l'appel système syslog qui fonctionne comme printf.

Maintenant, je préfère utiliser un flux à cette fin, généralement le std :: clog intégré. Mais saboter simplement rediriger la sortie vers stderr, pas vers syslog et cela est inutile pour moi car j'utilise aussi stderr et stdout à d'autres fins. J'ai vu dans another answer que c'est assez facile de le rediriger vers un fichier en utilisant rdbuf() mais je ne vois aucun moyen d'appliquer cette méthode pour appeler syslog car openlog ne retourne pas un gestionnaire de fichier que je pourrais utiliser pour lier un ruisseau dessus.

Existe-t-il une autre méthode pour le faire? (semble assez basique pour la programmation d'unix)?

Editer: Je suis à la recherche d'une solution qui n'utilise pas de bibliothèque externe. Ce que propose @Chris pourrait être un bon début mais reste un peu vague pour devenir la réponse acceptée.

Édition: l'utilisation de Boost.IOStreams est OK car mon projet utilise déjà Boost de toute façon.

La liaison avec une bibliothèque externe est possible mais est également un problème car c'est un code GPL. Les dépendances sont également un fardeau car elles peuvent entrer en conflit avec d'autres composants, ne pas être disponibles sur ma distribution Linux, introduire des bogues de tiers, etc. Si c'est la seule solution que je peux envisager complètement éviter les flux ... (dommage).

+0

syslog nécessite plus qu'une simple chaîne de message; il nécessite également un 'niveau d'erreur' et ainsi de suite. Je ne suis pas sûr si cela est possible en utilisant des flux. Peut-être avec des manipulateurs (comme 'std :: hex'')? – ereOn

+2

Jetez un coup d'œil sur les bibliothèques de journalisation disponibles. Beaucoup vous permettront d'écrire vos propres backends pour écrire votre message là où vous voulez les écrire. beaucoup viennent également avec le filtrage intégré et d'autres fonctionnalités intéressantes. Il y a seulement quelques légères qui viennent avec seulement peu de bagages, mais vous pouvez les trouver si vous voulez. J'utilise celui-ci: http://www.templog.org/ C'est juste un peu de fichier source, presque tous dans les en-têtes, et bon pour filtrer au moment de la compilation (pour le code critique) ainsi que temps. Mais vous pourriez aimer un autre. Ne réinventez simplement pas la roue. – sbi

+0

Si cela ne peut pas être fait en utilisant sabot intégré, un autre flux défini par l'utilisateur serait presque aussi bon, je m'en fous si le niveau est défini en utilisant les manipulateurs ou la fonction de membre. – kriss

Répondre

27

je besoin quelque chose de simple comme ça aussi, donc je viens de mettre cela ensemble:

log.h:

enum LogPriority { 
    kLogEmerg = LOG_EMERG, // system is unusable 
    kLogAlert = LOG_ALERT, // action must be taken immediately 
    kLogCrit = LOG_CRIT, // critical conditions 
    kLogErr  = LOG_ERR,  // error conditions 
    kLogWarning = LOG_WARNING, // warning conditions 
    kLogNotice = LOG_NOTICE, // normal, but significant, condition 
    kLogInfo = LOG_INFO, // informational message 
    kLogDebug = LOG_DEBUG // debug-level message 
}; 

std::ostream& operator<< (std::ostream& os, const LogPriority& log_priority); 

class Log : public std::basic_streambuf<char, std::char_traits<char> > { 
public: 
    explicit Log(std::string ident, int facility); 

protected: 
    int sync(); 
    int overflow(int c); 

private: 
    friend std::ostream& operator<< (std::ostream& os, const LogPriority& log_priority); 
    std::string buffer_; 
    int facility_; 
    int priority_; 
    char ident_[50]; 
}; 

log.cc:

Log::Log(std::string ident, int facility) { 
    facility_ = facility; 
    priority_ = LOG_DEBUG; 
    strncpy(ident_, ident.c_str(), sizeof(ident_)); 
    ident_[sizeof(ident_)-1] = '\0'; 

    openlog(ident_, LOG_PID, facility_); 
} 

int Log::sync() { 
    if (buffer_.length()) { 
     syslog(priority_, buffer_.c_str()); 
     buffer_.erase(); 
     priority_ = LOG_DEBUG; // default to debug for each message 
    } 
    return 0; 
} 

int Log::overflow(int c) { 
    if (c != EOF) { 
     buffer_ += static_cast<char>(c); 
    } else { 
     sync(); 
    } 
    return c; 
} 

std::ostream& operator<< (std::ostream& os, const LogPriority& log_priority) { 
    static_cast<Log *>(os.rdbuf())->priority_ = (int)log_priority; 
    return os; 
} 

Dans main() J'initialize saboterie :

std::clog.rdbuf(new Log("foo", LOG_LOCAL0)); 

Ensuite, chaque fois que je veux vous connecter, il est facile:

std::clog << kLogNotice << "test log message" << std::endl; 

std::clog << "the default is debug level" << std::endl; 
+0

Celui-ci est excellent!Je vous remercie. – wollud1969

13

Vous pouvez définir un streambuf qui appelle syslog. Par exemple:

// Pseudo-code 
class syslog_streambuf : public streambuf { 
private: 
    void internal_log(string& log) { 
     syslog(..., log, ...); 
    } 
public: 
    int sputc (char c) { 
     internal_log(...); 
    } 
    streamsize sputn (const char * s, streamsize n) { 
     internal_log(...); 
    } 
} 

alors vous simplement écrire ce qui suit pour rediriger saboterie:

clog.rdbuf(new syslog_streambuf); 

Il y a quelques fonctions que vous auriez sans doute de passer outre, voici une bonne reference to the streambuf api.

+3

Exemple de travail ici: http://pastebin.org/184922 – Basilevs

+1

Je pense que c'est une bonne pratique de créer un objet ostream séparé pour cette tâche spécifique. Méfiez-vous http://www.cplusplus.com/reference/iostream/ios_base/sync_with_stdio/. – Basilevs

+0

@Basilevs: merci pour l'échantillon. C'est encore court et c'est vraiment le genre de chose que je cherchais. Quoi qu'il en soit je vais devoir trier les trucs spécifiques à la socket pour adapter le code pour syslog ... on dirait que ça va me prendre au moins quelques jours :-( – kriss

2

J'ai conçu une classe OStreamedLog très similaire à ce qui a été indiqué ci-dessus, à l'exception de mes objets OStreamedLog sont configurés pour utiliser un objet ostringstream arbitraire, comme suggéré @Basilevs. Tout d'abord, il y a la définition de classe de Log, très similaire à ce que @eater et @Chris Kaminski mentionné ci-dessus.Ensuite, ma définition de classe OStreamedLog, qui contient un objet journal:

class OStreamedLog : public ostringstream 
{ 
    public: 
    OStreamedLog (const char* ident, int facility) 
    { 
      log = new Log (ident, facility); 
      (static_cast<ostream*>(this))->rdbuf (log); 
    } 
    private: 
    Log* log; 
}; 

Maintenant, quand vous avez besoin de se connecter, il suffit d'appeler:

OStreamedLog someLog ("MyOwnProgramThatNeedsLogging", LOG_LOCAL1); 
someLog << "Log testing" << endl; 
someLog << LOG_ERR << "some other error log" << endl; 

Bien sûr, vous pouvez réduire la définition entière Connectez-vous à votre OStreamedLog, mais vous pouvez faire d'autres choses dans votre objet Log de base et utiliser des wrappers comme ci-dessus pour différencier les différents types de journaux. Par exemple, vous pouvez avoir des journaux de diagnostic lisibles par l'utilisateur (envoyés en tant que texte ASCII), des journaux binaires (pour traitement ultérieur) ou un journal de diffusion en continu TLS (vers un serveur en direction nord, par exemple).

3

Une autre version en partie inspirée de mangeur. Il ne redirige pas std :: clog en soi, mais utilise une syntaxe de flux familière.

#ifndef SYSLOG_HPP 
#define SYSLOG_HPP 

#include <ostream> 
#include <streambuf> 
#include <string> 

#include <syslog.h> 

namespace log 
{ 

enum level 
{ 
    emergency = LOG_EMERG, 
    alert  = LOG_ALERT, 
    critical = LOG_CRIT, 
    error  = LOG_ERR, 
    warning = LOG_WARNING, 
    notice = LOG_NOTICE, 
    info  = LOG_INFO, 
    debug  = LOG_DEBUG, 
}; 

enum type 
{ 
    auth = LOG_AUTH, 
    cron = LOG_CRON, 
    daemon = LOG_DAEMON, 
    local0 = LOG_LOCAL0, 
    local1 = LOG_LOCAL1, 
    local2 = LOG_LOCAL2, 
    local3 = LOG_LOCAL3, 
    local4 = LOG_LOCAL4, 
    local5 = LOG_LOCAL5, 
    local6 = LOG_LOCAL6, 
    local7 = LOG_LOCAL7, 
    print = LOG_LPR, 
    mail = LOG_MAIL, 
    news = LOG_NEWS, 
    user = LOG_USER, 
    uucp = LOG_UUCP, 
}; 

} 

class syslog_stream; 

class syslog_streambuf: public std::basic_streambuf<char> 
{ 
public: 
    explicit syslog_streambuf(const std::string& name, log::type type): 
     std::basic_streambuf<char>() 
    { 
     openlog(name.size() ? name.data() : nullptr, LOG_PID, type); 
    } 
    ~syslog_streambuf() override { closelog(); } 

protected: 
    int_type overflow(int_type c = traits_type::eof()) override 
    { 
     if(traits_type::eq_int_type(c, traits_type::eof())) 
      sync(); 
     else buffer += traits_type::to_char_type(c); 

     return c; 
    } 

    int sync() override 
    { 
     if(buffer.size()) 
     { 
      syslog(level, "%s", buffer.data()); 

      buffer.clear(); 
      level = ini_level; 
     } 
     return 0; 
    } 

    friend class syslog_stream; 
    void set_level(log::level new_level) noexcept { level = new_level; } 

private: 
    static constexpr log::level ini_level = log::info; 
    log::level level = ini_level; 

    std::string buffer; 
}; 

class syslog_stream: public std::basic_ostream<char> 
{ 
public: 
    explicit syslog_stream(const std::string& name = std::string(), log::type type = log::user): 
     std::basic_ostream<char>(&streambuf), 
     streambuf(name, type) 
    { } 

    syslog_stream& operator<<(log::level level) noexcept 
    { 
     streambuf.set_level(level); 
     return (*this); 
    } 

private: 
    syslog_streambuf streambuf; 
}; 

#endif // SYSLOG_HPP 

Pour l'utiliser, vous pouvez faire quelque chose comme:

syslog_stream clog; 

clog << "Hello, world!" << std::endl; 
clog << log::emergency << "foo" << "bar" << "baz" << 42 << std::endl; 
Questions connexes