Usando o LOG de uma forma útil

Olá, estou com uma dúvida gostaria de expor a todos.
Atualmente existem ferramentas muito boas de se fazer log-trace(log4j), mas gostaria de sabe se alguém já precisou utilizar log para “restaurar” e “registrar” as ações do usuário em uma aplicação.

Estou perguntando isso pois acredito ser uma maneira muito mais útil de se utilizar o log ao invés de se utilizar log com informações inúteis do tipo “iniciando login”,“login finalizado”, “falha de não sei o que”, em fim log-trace, não estou dizendo que isso é inútil mas é um tipo de log que não faz muito sentido em alguns casos.

Utilizando o log de operações do usuário seria possível criar macros de execução, testes, apoio ao usuário e um que eu acretido ser o maior ganho de todos, o fim dos Banco de Dados. Por quê? Com as informações de todas as operações do usuário registradas no log é possível restaurar a aplicação ao seu último estado, por tanto, eu tenho todas as informações necessárias para dar continuidade nos processos da aplicação.
Mas é claro esta teoria não está totalmente completa, tem muito mais coisas envolvidas neste processo, mas gostaria que vocês comentassem a respeito.

Eu não conheço nenhuma ferramenta de log que suporte a esta teoria, se alguém souber…

Vamos discutir… rsrsrs :smiley:

Quanto aos benefícios “log-trace” eu não tenho dúvidas conheço as utilidades fornecidas pelo log4j. Com um sistema de log de operações executadas pelos usuários da aplicação é possível se ter muito, mais muito mais utilidade de um log, acho que não fui claro quanto minha idéia.
Quando se tem um log registrando as operações do usuário aliado a uma boa modelagem OO, construindo façades de serviços e que estes façades são o único ponto de entrada e saída da aplicação, então desta forma podemos saber exatamente qual é o estado da aplicação.
A idéia de não se utlizar o banco de dados é um simples benefício que este tipo de log irá agregar a aplicação.
O banco de dados pode ser dispensável se estivermos pensando em uma arquitetura orientada a serviços. Não sei se estou sendo claro, ou compreendido.
Todas as informações necessárias estão nos objetos e os mesmo por sua vez, na memória, por tanto não precisso de perssistencia.

ptrecenti,

Se você for gravar todas as ações do usuário para que o sistema tenha uma espécie de “desfazer”, haja espaço em disco! Tem que ser alguma coisa muito, muito específica e mesmo assim não sei se valeria a pena, pois dependendo do que for ser desfeito, geraria um efeito cascata.

Agora quanto a substituir o banco de dados não acho uma boa idéia, pois a segurança que este oferece para guardar informações é extraordinária se compararmos a arquivos de log por exemplo.

ASOBrasil

Condorco a erradicação do banco de dados seria muito radical, e a respeito da segurança do arquivo de log é tópico que o pessoal de infra deveria discutir mas também nada impede de se colocar este log em banco de dados.
A respeito do tamanho deste arquivo, bom, são operações pontuais, e registra tudo o que ele faz em suas “interfaces” de comunicação com o sistema.
A idéia de “desfazer” é uma forma muito simples de ver a complenitude deste tipo de log, podemos ter sistemas de apoio ao usuário onde o atendente verifica o log específicamente daquele usuário e com o caso de uso em mãos sabe exatamente o passo que ele executou que gerou um fluxo alternativo, e o usuário tem a capacidade de voltar a passos anteriores e executar novamente ao fluxo básico.
E por aí vai…
To viajando muito…

ptrecenti,

Acho que você não está viajando muito não, você só está querendo que agilizar algum processo dependendo da ação que o usuário tomou. Vejo que é uma coisa legal mas que precisa ser bem pensada se realmente o ganho vai ser útil, pois o sistema vai ficar mais lento se ficar gravando tudo o que o usuário fizer/alterar em um processo. IMHO ainda construimos sistemas que não permitem uma pró-atividade grande hoje em dia, mas com o tempo acho que isso vai mudar bastante.

Não estudei a respeito e talvez eu esteja falando uma baita asneira, mas acho que você poderia aliar o poder do que você logou com o JBoos Rules!
Leia blog: http://edgarsilva.com.br/

ASOBrasil

A utilização deste log não é uma alternativa a banco de dados relacionais mas claro temos a opção também de um banco de dados oo como o http://www.db4o.com/ que tem uma boa proposta.
A questão do tempo de registro desta informação claro, é relevante mas eu não preciso e não quero esperar por este processo terminar para poder continuar com a execução dos processos de negócio.

[quote=ptrecenti]
Todas as informações necessárias estão nos objetos e os mesmo por sua vez, na memória, por tanto não precisso de perssistencia.[/quote]

Talvez eu não tenha compreendido, mas vc não está falando de CACHE?

Não isso não é caché, todos os objetos colaboram entre si, bom vou tentar elecuidar um pouco o exemplo colocando um sistema que estou recentemente estudando. É um sistema aparentemente simples de reservar sala. segue o código, por favor acredito que deve existir algumas falhas de OO mas foi a maneira mais coerente que consegui fazer, por tanto sugestões são bem vindas. Este é o código servidor que será acessado via Web service através de seus façades. Segue

Esta classes é responsável por controlar todas as salas e a superalocação de equipamentos.

public class ReservaSalas {

	// Atributos privados
	private Set<Sala> salas;

	// Construtor
	public ReservaSalas() {
		salas = RepositorioSalas.getInstancia().listarTodas();
	}

	// Métodos públicos
	
	public Reserva criarReserva(Usuario usuario, Sala sala, Date dataHora, String motivo, Set<Equipamento> equipamentos) throws ReservaException {
		Reserva reserva = null;
		if (validarEquipamentos(dataHora, equipamentos)) {
			reserva = new Reserva();
			reserva.criarReserva(usuario, sala, dataHora, motivo, equipamentos);
		}
		return reserva;
	}

	
	public Reserva alterarReserva(Usuario usuario, Sala sala, Date dataHora, String motivo, Set<Equipamento> equipamentos) {
		Reserva reserva = null;
		if (validarEquipamentos(dataHora, equipamentos)) {
			reserva = sala.consultaReservaPorDataHora(dataHora);
			reserva.alterarReserva(usuario, motivo, equipamentos);
		}
		return reserva;
	}

	
	public void liberarReserva(Sala sala, Date dataHora) {
		Reserva reserva = sala.consultaReservaPorDataHora(dataHora);
		reserva.liberarReserva();
	}

	
	public Set<Sala> getSalas() {
		Set<Sala> copia = new HashSet<Sala>();
		for (Sala s: salas){
			copia.add(s);
		}			
		return copia;
	}

	
	public int countSalas() {
		return salas.size();
	}

	
	public boolean validarEquipamentos(Date dataHora, Set<Equipamento> equipamentos) {
		for (Sala s : salas) {
			Reserva reserva = s.consultaReservaPorDataHora(dataHora);
			if (reserva.countEquipamentos() != 0) {
				for (Equipamento e : equipamentos) {
					if (reserva.contemEquipamento(e)) {
						return false;
					}
				}
			}
		}
		return true;
	}
	

}

A Sala é responsável por registrar uma reserva e efetuar consultas.

public class Sala {

	//Constantes privadas 
	private static final String nomeSalaIdNulo = "O nome ou o id não podem ser nulo.";
	
	//Atributos privados
	private String salaId;
	private String nome;
	private Set<Reserva> reservas;
	
	//Construtor
	public Sala(String salaId, String nome){
		if ( nome == null || salaId == null ) {
			throw new SalaRuntimeException(nomeSalaIdNulo);
		}
		this.nome = nome;
		this.salaId = salaId;
		reservas = new HashSet<Reserva>();
	}
	

	//Métodos públicos
	
	
	public boolean registrarReserva(Reserva reserva){
		return reservas.add(reserva);
	}
	
	
	public boolean removerReserva(Reserva reserva){
		return reservas.remove(reserva);
	}
	
	
	public String getNome(){
		return nome;
	}
	
	
	public String getSalaId(){
		return salaId;
	}
	
	
	public Reserva consultaReservaPorDataHora(Date dataHora){
		for (Reserva r: reservas){
			if (r.getDataHora().equals(dataHora) ){
				return r;
			}
		}
		return null;
	}
	
	
	public Set<Reserva> consultarReservaPorPeriodo(Date dataInicial, Date dataFinal){
		Set<Reserva> result = new HashSet<Reserva>();
		for (Reserva r: reservas){
			if (r.getDataHora().after(dataInicial) && r.getDataHora().before(dataFinal) ){
				result.add(r);
			}
		}
		return result;
	}
	
	
	public Set<Reserva> consultarReservaPorDataHoraEquipamento(Date dataHora, Equipamento equipamento){
		Set<Reserva> result = new HashSet<Reserva>();
		for (Reserva r: reservas){
			if (r.getDataHora().equals(dataHora) && r.contemEquipamento(equipamento) ){
				result.add(r);
			}
		}
		return result;
	}

	
	public int countReservas(){
		return reservas.size();
	}
}

Esta é a reserva uma reserva tem data hora, motivo da reserva, quem está reservando, a qual sala ela pertence, e quais equipamentos serão utilizados. Fora isso ela faz verificações se os equipamentos podem ser alocados dependendo do perfil do usuário.

public class Reserva {

	// Contantes privadas
	private static final String equipamentoNulo = "O equipamento não pode ser nulo.";
	private static final String dataHoraInvalida = "Data ou hora inválido(s).";
	private static final String usuarioNulo = "O Usuário não pode ser nulo.";
	private static final String usuarioInvalido = "O Usuário inválido.";

	// Atributos privados
	private Sala sala;
	private Usuario usuario;
	private Date dataHora;
	private String motivo;
	private Set<Equipamento> equipamentos;

	// Construtor
	public Reserva() {
		sala = null;
		usuario = null;
		dataHora = null;
		motivo = null;
		equipamentos = new HashSet<Equipamento>();
	}

	// Métodos Públicos
	
	public void criarReserva(Usuario usuario, Sala sala, Date dataHora, String motivo, Set<Equipamento> equipamentos) throws ReservaException {
		if (this.sala == null) {
			setUsuario(usuario);
			setSala(sala);
			setDataHora(dataHora);
			setMotivo(motivo);
			setEquipamentos(equipamentos);
			sala.registrarReserva(this);
		}
	}

	
	public void alterarReserva(Usuario usuario, String motivo, Set<Equipamento> equipamentos) {
		if (this.sala != null) {
			setUsuario(usuario);
			setMotivo(motivo);
			setEquipamentos(equipamentos);
		}
	}

	
	public void liberarReserva() {
		if (this.sala != null) {
			sala.removerReserva(this);
		}
	}

	
	public Date getDataHora() {
		return dataHora;
	}

	
	public String getMotivo() {
		return motivo;
	}

	
	public int countEquipamentos() {
		return equipamentos.size();
	}

	
	public Set<Equipamento> getEquipamentos() {
		HashSet<Equipamento> equipamentos = new HashSet<Equipamento>();
		for (Equipamento e : this.equipamentos)
			equipamentos.add(e);
		return equipamentos;
	}

	
	public boolean contemEquipamento(Equipamento equipamento) {

		if (equipamento == null) {
			throw new ReservaRuntimeException(equipamentoNulo);
		}

		if (countEquipamentos() == 0) {
			return false;
		}

		return equipamentos.contains(equipamento);
	}

	// Métodos privados
	
	private void setDataHora(Date dataHora) throws ReservaException {
		boolean horaValida = validarHora(dataHora);
		boolean dataValida = validarData(dataHora);
		if (horaValida == true && dataValida == true) {
			this.dataHora = dataHora;
			return;
		}
		throw new ReservaException(dataHoraInvalida);
	}

	
	private boolean validarHora(Date hora) {
		String sHora = Calendario.getCalendario().formatarHora(hora);
		for (String horarios : Calendario.HORARIOS) {
			if (horarios.equals(sHora)) {
				return true;
			}
		}
		return false;
	}

	
	private boolean validarData(Date data) {
		return Calendario.getCalendario().dataDentroSemana(data);
	}

	
	private void setSala(Sala sala) {
		this.sala = sala;
	}

	
	private void setUsuario(Usuario usuario) {

		if (usuario == null) {
			throw new ReservaRuntimeException(usuarioNulo);
		}

		if (!usuario.isValid()) {
			throw new ReservaRuntimeException(usuarioInvalido);
		}

		if (this.usuario.equals(usuario)) {
			return;
		}

		this.usuario = usuario;
	}

	
	private void setEquipamentos(Set<Equipamento> equipamentos) {

		if (equipamentos == null) {
			this.equipamentos.clear();
			return;
		}

		if (equipamentos.equals(this.equipamentos)) {
			return;
		}

		if (usuario.getPerfil().validarEquipamentos(equipamentos)) {
			this.equipamentos = equipamentos;
		}

	}

	
	private void setMotivo(String motivo) {
		this.motivo = motivo;
	}

}
public class Usuario {

	//Constantes privadas 
	private static final String nomeNulo = "O nome não pode ser nulo.";
	private static final String emailNulo = "O email não pode ser nulo.";
	
	//Atributos privados
	private String usuarioToken;
	
	private String nome;

	private String email;

	private String senha;

	private Perfil perfil;

	//Construtor
	public Usuario() {
		usuarioToken = null;
		nome = null;
		email = null;
		senha = null;
		perfil = null;
	}

	//Métodos públicos
	
	public boolean validar(String email, String senha) {
		
		return false;
	}

	private void setUsuarioToken(String token){
		this.usuarioToken = token;
	}
	
	public void setNome(String nome) {
		if (nome == null){
			throw new IllegalArgumentException(nomeNulo);
		}
		this.nome = nome;
	}

	
	public void setEmail(String email) {
		if (email == null){
			throw new IllegalArgumentException(emailNulo);
		}
		this.email = email;
	}

	
	public void setSenha(String senha) {
		this.senha = senha;
	}

	
	public void setPerfil(Perfil perfil) {
		this.perfil = perfil;
	}

	
	public String getEmail() {
		return email;
	}

	
	public String getNome() {
		return nome;
	}

	
	public Perfil getPerfil() {
		return perfil;
	}

	
	public String getSenha() {
		return senha;
	}

	
	public boolean isValid() {

		if (nome == null || email == null || senha == null || perfil == null){
			return false;
		}

		if (nome.length() == 0 || email.length() == 0 || senha.length() == 0 || perfil.countEquipamentos() == 0){
			return false;
		}

		return true;
	}

}
public class Perfil {

	// Constantes privadas
	private static final String equipamentoNulo = "O(s) equipamento(s) não pode(m) ser nulo(s).";

	// Atributos privados
	private String nome;

	private Set<Equipamento> equipamentos;

	// Construtores
	public Perfil(String nome, Equipamento equipamento) {
		if (equipamento == null) {
			throw new IllegalArgumentException(equipamentoNulo);
		}
		setNome(nome);
		this.equipamentos = new HashSet<Equipamento>();
		equipamentos.add(equipamento);
	}

	public Perfil(String nome, Set<Equipamento> equipamentos) {
		setNome(nome);
		this.equipamentos = new HashSet<Equipamento>();
		setEquipamentos(equipamentos);
	}

	// Métodos públicos
	
	public int countEquipamentos() {
		return equipamentos.size();
	}

	
	public Set<Equipamento> getEquipamentos() {
		return equipamentos;
	}

	
	public String getNome() {
		return nome;
	}

	
	public boolean validarEquipamentos(Set<Equipamento> equipamentos) {

		boolean equipamentoInvalido = false;
		if (nome.length() > 0 && equipamentos.size() > 0) {
			for (Equipamento e1 : equipamentos) {
				for (Equipamento e2 : this.equipamentos) {
					if (e1.equals(e2)) {
						equipamentoInvalido = false;
						break;
					}
					equipamentoInvalido = true;
				}
			}
		}
		return equipamentoInvalido;
	}

	// Métodos privados
	
	private void setEquipamentos(Set<Equipamento> equipamentos) {
		if (equipamentos == null) {
			throw new IllegalArgumentException(equipamentoNulo);
		}
		this.equipamentos = equipamentos;
	}

	
	private void setNome(String nome) {
		this.nome = nome;
	}

}

Bom chega.

Fora estes tenho os objetos GerenciadorUsuarios, GerenciadorPerfis, RepositorioEquipamentos, RepositorioSalas e Calendario.
Com este grupo de objetos fornecendo cada um seus serviços, preciso expor estes serviços para serem usados pelas “telas”, no meu caso Swing.
Eu vejo que tenho aplicações distintas executando serviços distintos, mas preciso todos para compor minha aplicação, por tanto, tenho que fazer um Façade para “agrupar” os serviços e assim afunilar minha entrada de dados.
IMHO os façades devem representar rigorasamente casos de uso, e os métodos dentro do façade os passos do caso de uso. Estou trabalhando nisso agora.

Com isso feito só preciso logar estas operações e seus parêmetros.

[quote=ptrecenti]
Com isso feito só preciso logar estas operações e seus parêmetros.[/quote]

Certo, mas para restaurar o estado vc precisa ler os arquivos de log, correto? Vc não acha o BD mais eficaz para isso?

Os façades tem essa responsabilidade de restaurar estas informações, claro que se estivermos pesando em um arquivo de 1 mês, é pouco mas seria um arquivo considerável, por tanto a idéia seria serializar estes objetos talves diariamente ou de hora em hora, para que o trabalho de leitura deste arquivo não seja demorado e o sistema pode ser restaurado a partir da última serialização, que foi devidamente registrada no log.

A minha dúvida nessa abordagem é como ligar com a evolução das classes. A medida que altero o código, especialmente quando altero a forma que uma classe trabalha, muitas vezes tenho que alterar a forma com que a classe é persistida.

Vamos supor que eu modifique a minha arquitetura, aplicando uma refatoração como substituir switch/case por padrão State/Strategy. Vai significar também que troquei um atributo da classe que era inteiro por uma classe, que não teria qualquer registro de uso nos logs. Como a arquitetura lidaria com esse tipo de situação?

Só uma coisa… o que você está falando, assim como o seufagner ressaltou, não é prevalência?

Me parece um conceito muito similar ao do Prevayler. Se não é similar, onde estariam as diferenças?

Sim gostaria de perdir desculpas ao seufagner pois subestimei a capacidade do prevayler simplesmente imaginei que ele fosse um reositório oo, mas pelo o que vi é algo que parece ser o que estou procurando.

Desculpe e obrigado seufagner…

Preciso estudar mais a respeito do prevayler.

Não entendi a relação do log com o banco de dados.

Mas enfim, com o log você tem um controle maior de informações sobre seu sistema e como ele está funcionando! Classificando com níveis, utilizando appenders específicos para cada pacote (ou nível hierárquico de pacotes) da sua aplicação durante as várias etapas no desenvolvimento. Não quer ver certas informações mais? Mude o nível de log, por exemplo, de DEBUG para INFO… (exemplos do Log4j, framework de longe mais utilizado para tal propósito). Você ainda pode gravar em um arquivo ou para níveis de ERROR ou FATAL enviar um email (pré-configurando o appender para tal tarefa).

É muita coisa! Procura dar uma lida mais proveitosa em algum material sobre log4j especificamente que você vai sentir os benefícios

[]s

Leia sobre o Prevayler:

http://sourceforge.net/projects/prevayler