Como adicionar o registro a uma biblioteca para que ele possa se integrar facilmente ao sistema de registro do programa usando a biblioteca?

9

Estou escrevendo uma biblioteca que possui muitas informações que podem ser úteis em um log do programa que a utiliza, mas não sei a melhor maneira de expô-la de tal maneira que o programa que usa minha biblioteca possa integrar os logs da minha biblioteca com seus próprios logs de maneira aparentemente inexistente (se desejado).

Escolher uma biblioteca de log específica para minha biblioteca adiciona à lista de dependências para usar minha biblioteca e vincula o programa principal a essa biblioteca - e se várias bibliotecas usadas pelo programa principal fizessem isso, cada uma poderia ter selecionado uma biblioteca diferente .

Já pensei em ter o programa registrar um objeto de fluxo C ++ na biblioteca para ele usar. Parece que seria um objetivo relativamente geral, mas também pensei em fazer com que o programa principal registrasse uma função de retorno de chamada que seria solicitada com o conteúdo e os metadados quando os dados forem registrados. Outra opção seria apenas armazenar os dados de log na biblioteca em algum tipo de lista para o programa principal pegar sempre que quiser lidar com esses dados, deixando o programa principal decidir quando tem tempo para lidar com os dados.

Estou procurando sugestões e prós / contras de diferentes abordagens para que eu possa decidir o que é melhor na minha situação.

xaxxon
fonte
1
Não é realmente uma resposta, mas sugiro que você veja como o kit de ferramentas Qt faz isso. Google QtMessageHandler. E QMessageLogger. Parece muito com o que as outras respostas sugerem.
Teimpz

Respostas:

8

Você pode expor vários métodos para receber o log da sua biblioteca e agrupar todos, exceto um nos adaptadores, ao "real" usado na biblioteca.

Por exemplo, você decide internamente ter uma std::function<void(std::string)>coleção que é cada retorno de chamada do criador de logs. Tu forneces:

void registerLogCallback(std::function<void(std::string)> callback); 
// Main logging implemention

e também

registerLogStream(std::ostream stream) { 
    registerLogCallback([stream](std::string message){ stream << message; }); 
}

e também

template<typename OutputIterator>
registerLogOutputIterator(OutputIterator iter) { 
    registerLogCallback([iter](std::string message){ *iter++ = message; }); 
}

e mais variações dos tipos "receber seqüências de caracteres de algum lugar" para os quais você deseja implementar adaptadores.

Caleth
fonte
Eu acho que eventualmente também precisará de algum tipo de "nível de log", como depuração, aviso, fatal, ... Para que o usuário possa filtrar o que precisa. Bom começo, no entanto.
Teimpz
4

A maneira mais simples de permitir que um aplicativo possa acessar a funcionalidade de log é permitir que ele registre uma classe / função para receber a mensagem de log. O que eles fazem com essa mensagem depende inteiramente do aplicativo.

Usando C / C ++, você pode usar o seguinte em sua biblioteca:

typedef void (*LogMessageReceiver)(char const* message,
                                   void* user_data);

void registerLogMessageReceiver(LogMessageReceiver receiver,
                                void* user_data);

Um aplicativo pode registrar uma função chamando registerLogMessageReceiver. com o apropriado user_data. Na seção de log da sua base de código, você deve chamar essa função com a mensagem apropriada e a registrada user_data.

Se você não precisa se preocupar com C, pode usar uma classe como destinatário de mensagens.

struct LogMessageReceiver
{
   virtual ~LogMessageReceiver() {}
   virtual void receive(std::string const& message) = 0;
};

e adicione uma função na biblioteca para permitir que um aplicativo registre um receptor de mensagem de log.

void registerLogMessageReceiver(LogMessageReceiver* receiver);

Um aplicativo pode registrar um LogMessageReceiverchamando a função acima. Você terá que tomar algumas decisões políticas em relação à propriedade dos registrados LogMessageReceiver. Se a biblioteca se apropriar do receptor, ele deve ser deleteo ponteiro. Se a biblioteca não se apropria do receptor, o aplicativo deve cuidar do deletereceptor.

O uso de uma classe como receptor de mensagem de log permite que o user_databit seja omitido, registerLogMessageReceiverpois o subtipo de LogMessageReceiveré livre para armazenar quaisquer dados úteis para seu funcionamento. Não precisa receber dados adicionais do usuário na receivefunção.

A partir daí, pode ficar mais complexo, dependendo de quão sofisticado é o seu mecanismo de registro.

Por exemplo, você pode ter vários níveis de log: Conciso, Normal, Detalhado ou LoggingLevel1, LoggingLevel2, ..., LoggingLevelN.

Nesse caso, você deverá permitir que o aplicativo controle o nível de log que deseja usar.

Há um conjunto interminável de opções quando você decide ir além do mecanismo simplista de registro. Não faz sentido se aprofundar neles aqui.

R Sahu
fonte
Essa é uma boa resposta se for necessário suportar compiladores anteriores ao C ++ 11. Caso contrário, eu preferiria a resposta de Caleths. std :: função permite a captura, que é uma alternativa muito mais segura para o * vazio
Teimpz
1

Eu diria que você deve repensar a necessidade de ter o log associado à sua biblioteca; Especialmente para C ++, onde não há interface de logger padrão.

Aplicativos diferentes têm políticas diferentes em relação ao log. Uma biblioteca deve ser independente de política.

O objetivo de uma biblioteca é fornecer um serviço e, de preferência, indicar se uma solicitação para esse serviço foi bem-sucedida ou falhou; idealmente, com uma indicação de por que [falhou] por meio de erro, código de retorno, exceção ... Se o seu desejo de registrar for porque uma função fornecida pode falhar em vários locais, você pode estar tentando fazer muito em uma função. Talvez não , mas considere a possibilidade.

Daniel
fonte
@xaxxon Não vejo por que isso não é uma resposta. De acordo com Como escrevo uma boa resposta? an [...] answer can be “don’t do that" [...].
Doubleyou
1

É fácil escrever uma biblioteca que faça interface facilmente com o sistema de log do aplicativo host, desde que você saiba o que é esse sistema. Quando há várias possibilidades para isso, é mais difícil, e você fica constrangido pela tirania do menor denominador comum. Você poderia ter uma camada de compatibilidade que adapta sua biblioteca aos vários sistemas de log, mas agora não pode confiar em algum recurso exclusivo e útil que apenas um sistema possui. Se o usuário final tiver o outro sistema, esse recurso exclusivo útil não estará lá.

No mundo .Net, existem (pelo menos) dois sistemas de log comumente usados, log4net e NLog. Ambos são semelhantes, mas não idênticos. Eu já estava usando o log4net e comecei a usar o NHibernate (uma biblioteca ORM). Felizmente, ele usa o log4net internamente, por isso foi fácil adicioná-lo a um projeto. (E aqui eu discordo da resposta de @ Daniel: às vezes é tremendamente útil para o NHibernate registrar sua atividade detalhadamente no mesmo sistema que estou usando em outros lugares, sem trabalho extra.) Se eu já investi no NLog, isso significa que Eu mudo ou tenho um projeto que usa ambos.

Pelo menos com o log, geralmente há a opção de criar um aplicativo de mensagem personalizado que você pode configurar para encaminhar uma mensagem registrada em um sistema para outro sistema de log. Portanto, se você já estava usando o NLog e realmente queria continuar com ele, mas também o NHibernate, eu poderia cerrar os dentes e escrever um aplicativo de log4net que encaminha cada mensagem para o NLog. Seria fácil se as APIs fossem semelhantes.

As alternativas são realmente escolher o melhor sistema para suas necessidades disponível e usá-lo sem reservas, ou ter algum tipo de camada adaptadora, que está sujeita ao menor problema de denominador comum. O que não é uma resposta muito satisfatória.

Carl Raymond
fonte
Isso, mais o fato de que o C ++ dificulta as coisas fáceis e as coisas difíceis ainda mais difíceis.
Rwong 01/05/19