Archiv pro měsíc: Květen 2010

Automatické kopírování entitních objektů do DTO objektů

Před časem jsme vytvořili návrh aplikace, která mezi prezentační a aplikační vrstvou, namísto entitních objektů, přenáší tzv. DTO (Data Transfer Object) objekty. Základní výhodou tohoto přístupu předávání objektů je úplná nezávislost aplikační vrstvy na vrstvě prezentační. Při vývoji bylo nutné si co nejvíce usnadnit práci s kopírováním dat z entitního objektu do DTO objektu.

DTO objekt může nést atributy z jedné nebo i z několika entit. Cílem bylo vytvoření utilitky, která na vstup dostane entitní a DTO objekt a překopíruje data z entitního objektu do DTO objektu. Toto nám usnadní „ruční“ kopírování a neustálé psaní dto.setValue(entita.getValue). Alternativou k této funkčnosti je samozřejmě funkčnost opačná, tedy přenost dat z DTO objektu do entitního objektu.

Dalšími požadavky na funkčnost jsou:
1) možnost nastavit atribut DTO třídy tak, že se ho utilita nebude snažit nastavovat hodnotou z entitní třídy
2) možnost určit u atributu DTO třídy entitní třídu, ze které se bude nastavovat jeho hodnota

Právě podobně nastíněný problém je snadné vyřešit díky tzv. reflexi v kombinaci s vytvořením vlastních anotací.

Pro splnění požadavku (1) jsme si vytvořili následující anotaci:

package eu.bcv;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface BCVNonCopy {

}

Anotace @Retention nám říká, že naše anotace bude dostupná za běhu.

Pro splnění požadavku (2) jsme si vytvořili další anotaci:

package eu.bcv;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface BCVCopy {

	public Class entityClass() default Object.class;
	
}

Entitní objekt, který budeme v tomto vzorovém příkladu používat vypadá takto:

package eu.bcv.entity;

public class IdentityEntity {
	private String id;
	private Integer age;
	private boolean admin;
	private double data;
	private Boolean admin2;

	public IdentityEntity() {
		
	}

	// dale gettery a settery
}


DTO objekt, do kterého budeme kopírovat data vypadá takto:

package eu.bcv.dto;

import eu.bcv.BCVCopy;
import eu.bcv.BCVNonCopy;
import eu.bcv.entity.IdentityEntity;
import eu.bcv.entity.RoleEntity;

@BCVCopy(entityClass=IdentityEntity.class)
public class IdentityDTO {

	private String id;
	private Integer age;
	
	@BCVCopy(entityClass=RoleEntity.class)
	private String roleName;
	
	private boolean admin;
	
	private Boolean admin2;
	
	@BCVNonCopy
	private IdentityDTO parent = null;

	public IdentityDTO(String id, Integer age, String roleName, boolean admin,
			Boolean admin2, IdentityDTO parent) {
		super();
		this.id = id;
		this.age = age;
		this.roleName = roleName;
		this.admin = admin;
		this.admin2 = admin2;
		this.parent = parent;
	}

	public IdentityDTO(){
	}
	
	// dale gettery a settery
	
}

Další (a poslední) entitou, na které si budeme ukazovat řešení je třída RoleEntity:

package eu.bcv.entity;

public class RoleEntity {

	private String roleName;

	public RoleEntity(String roleName) {
		super();
		this.roleName = roleName;
	}
}

Nyní máme následující cíl: Z databáze si načteme entitní objekt IdentityEntity a RoleEntity. Chceme na prezentační vrstvu poslat DTO objekt, který bude obsahovat atributy z těchto dvou entit.

Podíváme-li se na třídu IdentityDTO tak zjistíme, že je anotovaná @BCVCopy(entityClass=IdentityEntity.class). To nám říká, že všechny atributy (není-li u jednotlivých atributů nastaveno jinak) budou nastavovány těmi atributy, které jsou ve třídě IdentityEntity a mají stejný název. Vyjímkou je atribut roleName, který bude nastavován hodnotou ze třídy RoleEntity. Atribut parent nebude automaticky nastavován vůbec.

Následující třída ReflectionUtils vyřeší zadaný úkol:

package eu.bcv;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class ReflectionUtils {
	
	/**
	 * Fill data from data transfer object into entity
	 * @param entity
	 * @param dto
	 * @return
	 * @throws Exception
	 */
	public static Object fillDTO(Object entity, Object dto) throws Exception {
		// Find class annotation
		BCVCopy bcvCopy = dto.getClass().getAnnotation(BCVCopy.class);
		
		for (Field field : dto.getClass().getDeclaredFields()) {
			if (field.getAnnotation(BCVNonCopy.class) != null) {
				//Field will not be copy
				continue;
			}

			BCVCopy attBcvCopy = field.getAnnotation(BCVCopy.class);
			if (field.getAnnotation(BCVCopy.class) == null) {
				if (entity.getClass() == bcvCopy.entityClass()) {
					//Default class is same as entity class
					dto = copyField(dto, entity, field);
				}
			} else {
				//Field has your own annotation
				if (entity.getClass() == attBcvCopy.entityClass()) {
					//Field´s annotation contain same class as entity class
					dto = copyField(dto, entity, field);
				}
			}
		}

		return dto;
	}

	public static Object fillEntity(Object entity, Object dto) throws Exception {
		// Find class annotation
		BCVCopy bcvCopy = dto.getClass().getAnnotation(BCVCopy.class);
		
		for (Field field : dto.getClass().getDeclaredFields()) {
			if (field.getAnnotation(BCVNonCopy.class) != null) {
				//Field will not be copy
				continue;
			}

			BCVCopy attBcvCopy = field.getAnnotation(BCVCopy.class);
			if (field.getAnnotation(BCVCopy.class) == null) {
				if (entity.getClass() == bcvCopy.entityClass()) {
					//Default class is same as entity class
					entity = copyField(entity, dto, field);
				}
			} else {
				//Field has your own annotation
				if (entity.getClass() == attBcvCopy.entityClass()) {
					//Field´s annotation contain same class as entity class
					entity = copyField(entity, dto, field);
				}
			}
		}
		return entity;
	}

	/**
	 * Copy field from source object into target object
	 * @param target
	 * @param source
	 * @param field
	 * @return
	 * @throws Exception
	 */
	private static Object copyField(Object target, Object source, Field field)
			throws Exception {
		String[] gettersParam = { "get", "is" };
		List sourceGetters = ReflectionUtils.findMethods(source
				.getClass().getMethods(), gettersParam);

		Method getter = null;
		Method setter = null;

		String getterName = constructGetterName(field);
		//Find getter in sourceGetters
		for (Method get : sourceGetters) {
			if (get.getName().equals(getterName)) {
				getter = get;
				break;
			}
		}

		String[] settersParam = { "set" };
		List targetSetters = ReflectionUtils.findMethods(target
				.getClass().getMethods(), settersParam);

		String setterName = constructSetterName(field);
		//Find setter in sourceSetters
		for (Method set : targetSetters) {
			if (set.getName().equals(setterName)) {
				// System.out.println("Nalezeno " + setterName);
				setter = set;
				break;
			}
		}

		if (getter == null || setter == null) {
			throw new Exception("No setter or getter found");
		} else {
			Object param = getter.invoke(source);
			setter.invoke(target, param);
		}

		return target;
	}

	/**
	 * Create getter name for input Field
	 * @param field
	 * @return
	 */
	private static String constructGetterName(Field field) {
		String name = field.getName();
		String prefix = "get";
		if (field.getGenericType().toString().equals("boolean")) {
			prefix = "is";
		}
		return prefix + name.substring(0, 1).toUpperCase()
				+ name.substring(1, name.length());
	}

	/**
	 * Create setter name for input Field
	 * @param field
	 * @return
	 */
	private static String constructSetterName(Field field) {
		String name = field.getName();
		return "set" + name.substring(0, 1).toUpperCase()
				+ name.substring(1, name.length());
	}
	
	/**
	 * Find methods in array which prefix equals input prefixs
	 * @param methods
	 * @param prefix
	 *            Example: "get" or "set"
	 * @return
	 */
	private static List findMethods(Method[] methods, String[] prefixs) {
		List result = new ArrayList();

		for (Method method : methods) {
			String name = method.getName();
			for (String prefix : prefixs) {
				if (name.startsWith(prefix)) {
					result.add(method);
					break;
				}
			}
		}

		return result;
	}

}

Nyní použijeme ReflectionUtils např. následujícím způsobem:

                IdentityEntity iEntita = new IdentityEntity();
		iEntita.setId("novaIdentita");
		iEntita.setAdmin(true);
		iEntita.setAdmin2(true);
		iEntita.setAge(23);
		iEntita.setData(3.3);
		
		RoleEntity rRole = new RoleEntity("BCV_blog");
		IdentityDTO iDTO = new IdentityDTO();
		
		iDTO = (IdentityDTO) ReflectionUtils.fillDTO(iEntita, iDTO);
		iDTO = (IdentityDTO) ReflectionUtils.fillDTO(rRole, iDTO);
		
		System.out.println("ID: " + iDTO.getId());
		System.out.println("AGE: " + iDTO.getAge());
		System.out.println("ADMIN: " + iDTO.isAdmin());
		System.out.println("ADMIN2: " + iDTO.getAdmin2());
		System.out.println("Role name: " + iDTO.getRoleName());

Tzv. vytvoříme entity IdentityEntity a RoleEntity (představme si, že jsme objekty získali z datové vrstvy). Vytvoříme prázdný DTO objekt. DTO předáme společně s entitními objekty třídě ReflectionUtils. Tato třída nám nasetuje do DTO objektu data. Pro kontrolu výsledku si necháme hodnoty DTO vypsat (v tomto případě do konzole).

A co se vlastně děje v ReflectionUtils? Z naší ukázky voláme metodu fillDTO. V té nejprve načteme anotaci BCVCopy, kterou je anotován DTO objekt. Poté procházíme všechny atributy DTO objektu a kopírujeme ty, pro které je splněna jedna z podmínek:

  • Atribut nemá anotaci BCVNonCopy a entitní třída na vstupu je shodná s hodnotou, kterou jsme získali z anotace BCVCopy DTO objektu.
  • Atribut nemá anotaci BCVNonCopy, atribut je anotován anotací BCVCopy, jejíž hodnota je shodná se zadanou entitní třídou.

Kopírování zajišťuje metoda copyField. Ta na vstupu dostane cílový a zdrojový objekt a dále atribut, který se má zkopírovat. Pomocí třídy findMethods nalezneme v entitní třídě gettery (procházíme všechny metody a vybíráme ty, které mají prefix „get“ nebo „is“). Poté z názvu atributu, který se bude kopírovat vytvoříme název getteru a vyhledáme getter v seznamu gettrů. Obdobně postupujeme pro nalezení setteru.

Pokud jsme nalezli ve zdrojovém objektu getter a v cílovém objektu setter, získáme hodnotu ze zdroje pomocí konstrukce getter.invoke(source). Nastavení provedeme pomocí konstrukce setter.invoke(target, param).

V závěru bych rád podotknul, že publikovaná verze není finální a kód by se dal napsat efektivněji. Berme tuto verzi spíše jako verzi výukovou.

Celá síle reflexe je zde tedy demostrována na jednom případu – zavolání metody. Tato zdánlivá banalita nám však vývoj velice usnadnila a zpříjemnila, neboť nebylo nutné všechny atributy kopírovat ručně.

Syslog-ng: logování nové generace

Syslog-ng je moderní (syslog-ng = syslog new generation – syslog nové generace) logovací nástroj pro sbíraní a zpracování logů. Samotná aplikace však logy neanalyzuje, jenom upravuje jejich výstupní podobu. Tento SW využíváme pro Webhosting Klenot.cz.

Syslog-ng disponuje širokou škálou možností konfigurace, je dobře škálovatelný, robustní a flexibilní. Syslog-ng představuje vhodné řešení pro nasazení na centrální logovací systém. Je dostupný pro mnohé operační sytémy (GNU/Linux, *BSD, Solarix, AIX, HP-UX, MS Windows, …) a různé platformy.

Samotná aplikace je dostupná ve třech variantách: Open Source Edition, Premium Edition a Store Box Edition. Komerční varianty (Premium a Store Box) přidávají oproti open source variantě několik vlastností, například podpora SSL/TLS, ukládaní logů do databází, atd. My budeme pracovat s open source variantou. Kompletní přehled vlastností a možností konfigurace můžeme najít v dokumentaci na stránkách projektu.

V případě logování na dekstopových stanicích nám často vyhovuje předvolené nastavení logování a výběr logovací aplikace a většinou do toho nepotřebujeme zasahovat. Na serverech je však situace jiná. Tady pracujeme s obrovským množstvím logů různých aplikací důležitých pro chod našeho serveru.

Na severu musíme zabezpečit vysokou dostupnost, spolehlivost a bezpečnost serverových aplikací. Proto v případě problémů musíme být schopni rychle dohledat příčinu a řešit případné potíže. Právě v takovýchto situacích bývájí nejčastějším zdrojem klasifikace problémů právě log soubory. Proto je důležité jakou aplikaci pro správu logů zvolíme a jak ji nakonfigurujeme. V případě, že používáme centrální logovací server to platí dvojnásob.

V dnešním zápisku si ukážeme instalaci a základní konfiguraci (vzhledem k možnostem aplikace) syslog-ng na dva servery, z nichž jeden bude představovat centrální logovací server (loghost) a druhý bude klientem, tj. logy bude jenom přeposílat na loghost. V obou případech se jedná o servery běžících na systému CentOS 5.

Instalace

Dřív než se pustíme do samotné instalace a konfigurace je vhodné ze systému odinstalovat aktuální syslog. V našem případě je to balíček sysklogd

[root@nps1:~]$ yum remove sysklogd

Balíček pro syslog-ng se nenachází v žádném ze standarních repozitářů pro CentOS 5. Proto si přidáme repozitář EPEL (Extra Packages for Enterprise Linux)

[root@nps1:~]$ rpm -Uhv http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-3.noarch.rpm

Teď už můžeme naistalovat syslog-ng (v našem případě verze 2.1.4)

[root@nps1:~]$ yum install syslog-ng

Samozřejme balíček syslog-ng naistalujeme uvedeným způsobem jak na logovací server (loghost) tak na klienta. V případě klienta můžeme použit i jinou varinatu syslog-u, která ale umí přeposílání logů přes síť.

Centrální logovací server

Princip práce syslog-ng je znázorněn na následujícim schematu

+-----------+
| source #1 |---+
+-----------+   |
	       ...	+-----------+	     +-----------+	+-------------+
		|------>| filter #1 |--...-->| filter #N |----->| destination |
	       ...	+-----------+	     +-----------+	+-------------+
+-----------+	|
| source #M |---+
+-----------+

Syslog sbírá zprávy ze zdrojů (source #1 .. source #M), které procházejí různymi filtry (filter #1 .. filter #N) a výsledek posílá do cílové destinace (destination). Jednotlivé prvky mechanizmu práce se definují v konfiguračním souboru /etc/syslog-ng/syslog-ng.conf. Konfigurace je rozdělena na pět základních direktiv (za popisem je uvedeno v tabulce několik voleb pro danou direktivu):

options – obsahuje globální nastavení pro syslog-ng

PARAMETR POPIS
sync (<pocet_radku>); počet řádek, které se uloží do bufferu než se zapíšou do souboru
time_reopen (<cas>); čas, za který znovu navázat spojení
time_reap (<cas>); čas, za který se má uzavřít cíl, když není používán
log_fifo_size (<pocet_radku>); počet řádků ve výstupní frontě
log_msg_size(<pocet_znaku<); maximmální délka zprávy (znaky)
keep_hostname (on/off); používat/nepoužívat hostname ze zprávy
use_dns (yes/no); používání DNS
use_fqdn (yes/no); používat plné doménové jméno
owner(<username>); předvolený vlastník log souborů
group(<username>); předvolená vlastnícká skupina log souborů
perm(<prava>); práva pro vytvořené log soubory
dir_owner(<username>); předvolený vlastník adresářů
dir_group(<username>); předvolená vlastnícká skupina adresářů
dir_perm(<prava>); práva pro vytvořené adresáře

source – definovaný zdroj zpráv, ze kterého bude syslog číst zpravý, jeden zdroj se můze může skládat z několika komponent/zdrojů

ZDROJ POPIS
file ("/cesta/k/souboru"); čtení zpráv ze zadaného souboru
unix-stream ("/cesta/k/socketu"); čtení zpráv ze zadaného socket-u
tcp (ip(<ip_adresa>) port(<port#>)); čtení zpráv ze síťového zdroje přes TCP protokol
udp (ip(<ip_adresa>) port(<port#>)); čtení zpráv ze síťového zdroje přes UDP protokol
internal (); interní zprávy syslogu

filter – definuje filter pro filtrování zpráv získaných ze zdrojů, filter se skládá z pravidel

PRAVIDLO POPIS
facility (<skupina_1>, ..., <skupina_N>); filtrování podle skupiny/skupin
match (<regex>); filtrování podle regulárního výrazu
level (<level_1>, ..., <level_N>); filtrování podle úrovně/úrovní zprávy/zpráv
filter (<filter>); filtrování podle jiného filtru
program (<program>); filtrování podle názvu programu

destination – cíl pro filtrovaná zprávy, cíle můžou být různých druhů

CÍL POPIS
file ("/cesta/k/souboru"); ukládá zprávy do souboru
unix-stream ("/cesta/k/socketu"); posílá zprávy na socket
tcp ("<ip_adresa>" port(<port#>)); posílá zprávy po síťi přes TCP protokol
udp ("<ip_adresa>" port(<port#>)); posílá zprávy po síťi přes UDP protokol
usertty (<username>); posílá zprávy nauživatelský terminál

U cest k souborům můžeme používat proměnné, které se dají použít předevšim pro srehlednění logů:

PROMĚNNÁ POPIS
$YEAR rok
$MONTH měsíc
$DAY den v měsíci
$HOUR hodina
$WEEK číslo týdne
$PROGRAM jméno programu
$HOST hostname
$SOURCEIP IP adresa

log – záznam, který definuje, které zdroje se mají zpracovat kterými filtry a do které destinace má být výsledek poslán (jeden záznam může obsahovat více zdojů a více filtrů, v takovém případě se budou filtry aplikavat v pořadí, v jakém jsou uvedeny)

LOG ZÁZNAM POPIS
log { source(<zdroj_1>); ...; source(<zdroj_M>); filter(<filter_1>); ...; filter(<filter_N>) destination(<destinace>); }; představuje jeden log

Pro kompletní výčet parametrů konfigurace viz manuálovou stránku syslog-ng.conf nebo dokumentaci. Teď si upravíme na našem loghostu konfiguraci pro syslog-ng

[root@nps1:~]$ vim /etc/syslog-ng/syslog-ng.conf
# globální nastavení
options {
        sync (0);
        time_reopen (10);
        log_fifo_size (1000);
        use_dns (no);
        use_fqdn (yes);
        create_dirs (yes);
        keep_hostname (no);
        owner(root);
        group(root);
        perm(0640);
        dir_owner(root);
        dir_group(root);
        dir_perm(0755);
};

# definice zdrojů
source s_sys {						#jméno zdroje - v našem případě dáme všechno do jednoho zdroje
        file ("/proc/kmsg" log_prefix("kernel: "));	#zdroj zpráv kernelu
        unix-stream ("/dev/log");			#standardní zdroj zpráv
        internal();					#zdroj interních zpráv syslogu
};

# definice destinací - použijeme proměnné pro spřehlednění
destination d_mesg { file("/var/log/syslog-ng/$HOST/$YEAR/$MONTH/$DAY/messages"); };
destination d_auth { file("/var/log/syslog-ng/$HOST/$YEAR/$MONTH/$DAY/secure"); };
destination d_mail { file("/var/log/syslog-ng/$HOST/$YEAR/$MONTH/$DAY/maillog"); };
destination d_spol { file("/var/log/syslog-ng/$HOST/$YEAR/$MONTH/$DAY/spooler"); };
destination d_boot { file("/var/log/syslog-ng/$HOST/$YEAR/$MONTH/$DAY/boot.log"); };
destination d_cron { file("/var/log/syslog-ng/$HOST/$YEAR/$MONTH/$DAY/cron"); };
destination d_kern { file("/var/log/syslog-ng/$HOST/$YEAR/$MONTH/$DAY/kern"); };
destination d_daem { file("/var/log/syslog-ng/$HOST/$YEAR/$MONTH/$DAY/daemon.log"); };
destination d_sysl { file("/var/log/syslog-ng/$HOST/$YEAR/$MONTH/$DAY/syslog"); };
destination d_mlal { usertty("*"); };

# definice filtrů - filtry můžeme kombinovat pomocí operátorů logických "and" a "or"
filter f_kernel     { facility(kern); };
filter f_daemon     { facility(daemon); };
filter f_syslog     { facility(syslog); };
filter f_default    { level(info..emerg) and		#projdou všechny zprávy levelu info..emerg kromě těch, které projdou jinými filtry
                        not filter(f_auth) and
                        not filter(f_mail) and
                        not filter(f_cron) and
                        not filter(f_daemon) and
                        not filter(f_syslog); };
filter f_auth       { facility(authpriv) or
                        facility(auth); };
filter f_mail       { facility(mail); };
filter f_emergency  { level(emerg); };
filter f_news       { facility(uucp) or
                        (facility(news)
                        and level(crit..emerg)); };
filter f_boot   { facility(local7); };
filter f_cron   { facility(cron); };

# definice logů - jeden zdroj proženem různymi filtry -> růzé cíle
log { source(s_sys); filter(f_kernel); destination(d_kern); };
log { source(s_sys); filter(f_default); destination(d_mesg); };
log { source(s_sys); filter(f_auth); destination(d_auth); };
log { source(s_sys); filter(f_mail); destination(d_mail); };
log { source(s_sys); filter(f_emergency); destination(d_mlal); };
log { source(s_sys); filter(f_news); destination(d_spol); };
log { source(s_sys); filter(f_boot); destination(d_boot); };
log { source(s_sys); filter(f_cron); destination(d_cron); };
log { source(s_sys); filter(f_daemon); destination(d_daem); };
log { source(s_sys); filter(f_syslog); destination(d_sysl); };

Nakonec ještě upravíme syslog-ng službu a spustíme

[root@nps1:~]$ chkconfig syslog-ng on
[root@nps1:~]$ service syslog-ng start {stop|restart}

Samotné log soubory nalezneme v adresáři, který jsme definovali v konfiguraci syslog-ng, tj. /var/log/syslog-ng, kde můžeme vidět adresáře, které si vytvořil syslog-ng podle proměnných. Zatím tam můžeme najít log soubory jenom pro loghost.

Konfigurace kliena

Těd když už máme funkční loghost, přidáme k jeho zdrojům také další server. Syslog na straně klienta již máme naistalovaného. Zůstává ještě upravit konfiguraci pro syslog-ng na straně klienta

[root@nps2:~]$ vim /etc/syslog-ng/syslog-ng.conf
# globální nastavení pro syslog-ng
options {
        log_fifo_size (8192);
        use_fqdn (yes);
};

# definice zdrojů - zdroje zpráv na straně klienta
source s_sys {
        file ("/proc/kmsg" log_prefix("kernel: "));
        unix-stream ("/dev/log");
        internal();
};

# definice cílů - všechno posíláme na loghosta přes UDP
destination d_loghost { udp("<ip_adresa_loghosta>" port(<port#>)); };

# defince filtrů - odfiltrujeme jenom zprávy levelu debug
filter f_defaut { level(info..emerg); };

# definice logů - nakonec to všechno pošleme na loghost
log { source(s_sys); filter(f_default); destination(d_loghost); };

Syslog jsme nakonfigurovali tak, že všchny zprávy posílá na loghost, kde se budou zpracovávat podle definovaných pravidel. Podobně jako na straně loghosta upravíme službu a spustíme

[root@nps2:~]$ chkconfig syslog-ng on
[root@nps2:~]$ service syslog-ng start {stop|restart}

Aby syslog na straně loghosta zbíral zprávy z dané adresy na daném portu, musíme ještě v konfiguraci na straně loghosta přidat síťovej zdroj

[root@nps1:~]$ vim /etc/syslog-ng/syslog-ng.conf
...
source s_sys {
        file ("/proc/kmsg" log_prefix("kernel: "));
        unix-stream ("/dev/log");
        internal();
        udp(ip(<ip_adresa_loghostu>) port(<port#>)); #síťovej zdroj, adresa našeho loghostu, na dané adrese/portu bude zbírat zprávy
};
...

Restartujeme syslog-ng na straně loghostu aby se nám načetla nová konfigurace se síťovým zdrojem zpráv.

[root@nps1:~]$ service syslog-ng restart

najdeme v adresáři /var/log/syslog-ng (v příslušných podadresářích) i logy pro našeho klienta případně další stanice organizované podle proměnných použitých v konfiguraci loghostu, tj. HOST, YEAR, MONTH a DAY

[root@nps1:~]$ tree -d /var/log/syslog-ng
.
|-- nps1.xxxxxx.xx
|   `-- 2010
|       `-- 05
|           |-- 05
|           |-- 06
|           |-- 07
|           |-- 08
|           `-- 09
`-- nps2.xxxxxx.xx
    `-- 2010
        `-- 05
            |-- 05
            |-- 06
            |-- 07
            |-- 08
            `-- 09

Syslog-ng je efektivní nástroj pro organizaci našich logů a vhodným řešením pro centrální logovací systém. Možnosti aplikace jsou výrazně širší než bylo obsaženo v dnešním zápisku.

Jak již bylo v úvodu zmíněno, syslog-ng neanalyzuje logy a tak je metoda prohlížení a nalýza zpráv na volbě správce systému. Tento fakt se dá eliminovat použitím některé aplikace (například logwatch) pro analýzu logů, která periodicky prohlíží logy podle konfigurace a reportuje, například zasíláním emailů, případné zjištěné problémy, které jsou obsažené ve zprávách. Tímto způsobem se tak dá alespoň částečně zautomatizovat proces zpracování a analýzy logů. Částečně proto, protože některé aplikace si spravují logy samy a zaspisují zprávy do různých souborů a v různých formátech. V takovém případě je potřeba např. přidat do syslog-u novej zdroj, který odpovídá logům pro danou aplikaci nebo na straně aplikace upravit posílaní zpráv do souboru/socketu, který je zdrojem zpráv syslogu.

Munin – monitoring serveru

Munin je jednou z nejznámějších monitorovacích alpikací, která se používá především na serverových stanicích. Je napsaný v jazyku Perl a je dostupný pro většinu platforem. Munin s hromažďuje různá systémová data jako například vyťížení procesoru, síťový trafik, obsazenost diskových oddílů, využití paměti, atd. a ty vynáší přehledně do grafů, které periodicky aktualizuje.

Zdrovojá data může Munin získávat z lokální stanice nebo také ze vzdálených serverů. Navíc existuje pro Munin obrovské množství pluginů pro monitorování množstí parametrů různých aplikací, například pluginy pro web server, ftp server, poštovní server, atd. Některé pluginy jsou však použitelné jenom na některých platformách (Linux, FreeBSD, NetBSD, Solaris, AIX, HP-UX, atd.).

V dnešním zápisku se zaměřím na instalaci a základnou konfiguraci aplikace Munin na systému CentOS 5.

Grafový server – Munin master

Aplikace Munin se skládá ze dvou částí, které také odpovídají balíčkům. První komponentou je aplikace munin master, která představuje serverovou část, tzv. „grafový server“. Ta se stará poskytování výstupních dat, tj. grafů, které se ukládají ve formě obrázků do zvoleného adresáře.

Druhou komponentou je aplikace munin node, která představuje uzel pro přístup grafového serveru. Jejím ůkolem je zpřístupnení stanice grafovému serveru.

V našem případě budeme monitovat dva servery. První (například alpha.domena.com) bude sloužit jako munin master (tam se budou ukládat výtupní data) a druhý (například beta.domena.com), ze kterého budeme data jen odebírat. Na alpha.domena.com musíme tedy naistalovat munin master (zdroj výstupních dat) a munin node (přístupový uzel pro gravový server) a na beta.domena.com nám stačí už jenom munin node (tam nebudeme výstupní data ukládat). Chceme-li ukládat výstupná data na jednom serveru, analogoicky to platí i pro další stanice.

Vzhledem k tomu, že se Munin nenachází ve standardních repozitářích distribude CentOS 5, přidáme si repozitář RPMforge, ze kterého Munin naistalujeme. Nejdřív si ze stránek RPMforge stáhneme balíček pro přidání repozitáře. Vybereme variantu pro RHEL5/CentOS5 a balíček naistalujeme (včetně importu klíčů)

[root@alpha:~]$ wget http://packages.sw.be/rpmforge-release/rpmforge-release-0.5.1-1.el5.rf.i386.rpm
[root@alpha:~]$ rpm -Uhv rpmforge-release-0.5.1-1.el5.rf.i386.rpm

Na server alpha.domena.com naistalujeme munin master a munin node

[root@alpha:~]$ yum install munin munin-node

Konfigurace jak pro munin master tak pro munin node se nachází v adresáři /etc/munin (soubor munin.conf pro munin master a munin-node.conf pro munin-node). Upravíme konfiguraci pro munin master

[root@alpha:~]$ vim /etc/munin/munin.conf
...
#zdroj dat (aplikace používá RRDTool) - představuje umístnění rrd databází
dbdir   /var/lib/munin

#umístnění grafů (obrázků), html a dalších dokumentů pro zobrazování monitoringu
htmldir /var/www/munin

#umístnění logů aplikace
logdir  /var/log/munin

#umístnění stavových souborů (pid)
rundir  /var/run/munin

#umístnění šablon pro zobrazování výstupu
tmpldir /etc/munin/templates

#strom hostů, tj. serverů, kterých monitor se má ukládat/zobrazovat na grafovém serveru
[alpha.domena.com]	#přidá hostname alpha.domena.com do skupiny domena.com
    address 127.0.0.1	#IP adresa/hostname domény alpha.domena.com
    use_node_name yes	#munin master se nachází na alpha.domena.com, proto adresa localhostu
...

a změníme vlastníka adresáře /var/www/munin na munin:munin

[root@alpha:~]$ chown -R munin:munin /var/www/munin

Pro prohlížení monitoringu serveru při uvedené konfiguraci musíme mít v systému naistalovaný web server (např. Apache HTTP server) s nastavením hodnoty direktivy DocumentRoot na /var/www (ve většině instalací Apache HTTP serveru je to předvolená hodnota, i proto jsme zvolili hodnotu htmldir jak je uvedeno výše). Následuje nastavení konfigurace lokálního uzlu na alpha.domena.com tak, aby mohl grafový server mohl na tento uzel přistupovat

[root@alpha:~]$ vim /etc/munin/munin-node.conf
...
#port, na kterém bude munin-node poslouchat
port 4949

#IP adresay, na kterých bude munin-node poslouchat
host *

#uživatel, pod kterým bude munin-node běžet
user munin

#skupina, pod kterou bude munin-node běžet
group munin

#IP adresy grafových serverů, tj. stanic, které můžou přistupovat na uzel
#adresy se musí zadávat ve tvaru regulárních výrazů
allow ^127\.0\.0\.1$	#jedná se o uzel, který se nachází na stejném serveru jako Munin master
...

Restartujeme web server

[root@alpha:~]$ service httpd restart

a můžeme prohlížet monitor našeho serveru na URL http://alpha.domena.com/munin/ (nezapomenout na lomítko na konci URL).
Vytížení CPU
Síťový traffic
Využití paměti
Konkrétní tvar URL adresy závisí od hodnoty htmldir a od nastavení web serveru. Zatím máme k dispozici monitoring jenom pro server alpha.domena.com.

Přidání uzlu

Dále je třeba přidat na grafový server monitor pro náš druhý server, tj. beta.domena.com. Na tento server naistalujeme munin node, tj. přístupový uzel pro grafový server alpha.domena.com

[root@beta:~]$ yum install munin-node

Aby mohl grafový server shromažďovat data ze serveru beta.domena.com, musíme nakonfigurovat uzel nakonfiurovat tak, aby měl grafový server povolen přístup na náš uzel. Upravíme tedy konfiguraci pro munin-node, která se nachází v souboru /etc/munin/munin-node.conf (samozřejmě upravujeme konfiguraci na serveru beta.domena.com). Upravíme podobně jako v případě serveru alpha.domena.com

[root@beta:~]$ vim /etc/munin/munin-node.conf
...
#IP adresy grafových serverů, tj. stanic, které můžou přistupovat na uzel
#adresy se musí zadávat ve tvaru regulárních výrazů
allow <adresa_grafového_serveru>	#v našem případě IP adresa alpha.domena.com
...

Nakonec ještě upravíme službu munin-node a zrestartujeme

[root@beta:~]$ chkconfig munin-node on
[root@beta:~]$ service munin-node restart

Také nezapomenout povolit port ve firewall-u, na kterém munin-node poslouchá.

Pro prohlížení na grafovém serveru i monitor pro náš druhý server, tj. beta.domena.com, musíme tuto stanici také přidat na straně grafového serveru. To uděláme přidáním položky do stromu (v našem případě do stejného jako v prvním případě vzhledem ke stejné doméně domena.com) v konfiguraci grafového serveru

[root@alpha:~]$ vim /etc/munin/munin.conf
...
[alpha.domena.com]
    address 127.0.0.1
    use_node_name yes

[beta.domena.com]		#přidá hostname beta.domena.com do skupiny domena.com
    address <IP/hostname>	#IP adresa/hostname pro beta.domena.com
    use_node_name yes	
...

Teď už můžeme na URL http://alpha.domena.com/munin/ prohlížet monitor pro obě naše stanice (musíme chvilku počkat až se načtou nová data z beta.doma.com, případně aktualizovat stránku).

Pluginy

Jak již bylo zmíněno, pro Munin existuje velké množství pluginů pro monitorování různých (především serverových) aplikací. Již při základní instalaci Muninu se do systému instaluje několik desítek pluginů. Tyto pluginy se nacházejí v adresáři /usr/share/munin/plugins. Aby jsme aktivovali některý plugin, stačí nám v nejjednoduším případě vytvořit symlink na požadovaný plugin v adresáři /etc/munin/plugins. Například pro monitorování MySQL serveru zadáme příkaz

[root@alpha:~]$ ln -s /usr/share/munin/plugins/mysql_* /etc/munin/plugins/

Konfigurace pro jednolivé pluginy se nachází v souborech v adresáři /etc/munin/pluginconf.d/. Konfigurace pro jednotlivé pluginy nebo skupiny pluginů se v konfiguračních souborech definují do hranaých závorek, pod kterými se nacházejí volitelné direktivy a jejich hodnoty, viz příklad

...
[<plugin_name>*]		#název pluginu nebo skupiny pluginů
user <username>			#uživatel, pod kterým bude plugin běžet
group <groupname>		#skupina, pod kterou bude plugin běžet
command <přikaz>		#příkaz, který se vykoná namísto pluginu
env.<proměnná> <hodnota>	#nastaví hodnotu proměnné v prostředí
				# daného pluginu na danou hodnotu
...

Pomocí pluginů si můžeme nastavit profil motitorování serveru na vybrané aplikace. V případě, že chcete monitorovat parametr, pro který nemáte v systému plugin, tak je možné si tento plugin napsat nebo skusit najít na MuninExchange, kde jsou volně dostupné desítky dalších pluginů pro různé aplikace a různé platformy a kam také můžete přispívat vlastními pluginy.

Munin se s oblibou používá na mnoha serverech a je často prvním zdrojem při zjišťování potíží, kterých důsledkem bývá nadměrné vytížení systémoých zdrojů nebo konkrétních aplikací. Dále nám se také může hodit při optimalizaci výkonu serveru.