Custom Log Format no varnishncsa
by moutinho on Apr.26, 2012, under Linux, Varnish
Para quem precisa definir um padrão diferente do “apache commons log” para servidores Varnish… esta tarefa trivial esconde um problema chato de solucionar.
De acordo com a documentação (varnishncsa), o parametro -F serve exatamente para este proposito. Mas nem tudo são flores, pois ao tentar declarar este padrão direto ao DAEMON_OPTS do init script (/etc/init.d/varnishncsa – redhat/centos) o processo silenciosamente entra em modo “default” e segue sem erros…
O problema mora nas aspas que precisam ser “escaped” corretamente (+ info) e acabam por se misturar com as aspas do atributo.
A forma mais simples de contornar este problema é trabalhar com mais um atributo e adiciona-lo na execução, conforme o script abaixo:
#! /bin/sh
#
# varnishncsa Control the Varnish NCSA logging daemon
#
# chkconfig: - 90 10
# description: Varnish Cache logging daemon
# processname: varnishncsa
# config:
# pidfile: /var/run/varnishncsa.pid
### BEGIN INIT INFO
# Provides: varnishncsa
# Required-Start: $network $local_fs $remote_fs
# Required-Stop: $network $local_fs $remote_fs
# Default-Start:
# Default-Stop:
# Short-Description: start and stop varnishncsa
# Description: Varnish Cache NCSA logging daemon
### END INIT INFO
# Source function library.
. /etc/init.d/functions
retval=0
pidfile="/var/run/varnishncsa.pid"
lockfile="/var/lock/subsys/varnishncsa"
logfile="/var/log/varnish/varnishncsa.log"
exec="/usr/bin/varnishncsa"
prog="varnishncsa"
LOG_FORMAT="-F \"\"%{X-IP}i\" %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-agent}i\"\""
DAEMON_OPTS="-c -a -w $logfile -P $pidfile -D"
# Include varnishncsa defaults
[ -e /etc/sysconfig/varnishncsa ] && . /etc/sysconfig/varnishncsa
start() {
if [ ! -x $exec ]
then
echo $exec not found
exit 5
fi
echo -n "Starting varnish ncsa logging daemon: "
daemon --pidfile $pidfile $exec "$DAEMON_OPTS" "$LOG_FORMAT"
echo
return $retval
}
stop() {
echo -n "Stopping varnish ncsa logging daemon: "
killproc -p $pidfile $prog
retval=$?
echo
[ $retval -eq 0 ] && rm -f $lockfile
return $retval
}
restart() {
stop
start
}
reload() {
restart
}
force_reload() {
restart
}
rh_status() {
status -p $pidfile $prog
}
rh_status_q() {
rh_status >/dev/null 2>&1
}
# See how we were called.
case "$1" in
start)
rh_status_q && exit 0
$1
;;
stop)
rh_status_q || exit 0
$1
;;
restart)
$1
;;
reload)
rh_status_q || exit 7
$1
;;
force-reload)
force_reload
;;
status)
rh_status
;;
condrestart|try-restart)
rh_status_q || exit 0
restart
;;
*)
echo "Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
exit 2
esac
exit $?
Checar se processo está rodando – Shell script para processos 24/7
by moutinho on Apr.25, 2012, under Linux
Para processos criticos 24/7, que inexplicavelmente (ou explicavelmente) tem o habito de terminarem sua execução, segue um pequeno shell script para ajudá-los na tarefa de mantê-los no ar e evitar ter de trabalhar via VPN 3:00am.
#!/bin/bash
cd /directory/where/your/process/is
process='com.yourdomain.yourProcess'
if [ "$1" == 'stop' ]
then
ps -ef | grep -v grep | grep $process
if [ $? -eq 1 ]
then
echo "yourProcess [STOP] - No process found"
else
kill -9 `ps -ef | awk '/[c]om.yourdomain.yourProcess/{print $2}'`
echo "yourProcess [STOP] - Stopped"
fi
else
ps -ef | grep -v grep | grep $process
if [ $? -eq 1 ]
then
java -cp .:../lib/mysql-connector-java-5.1.16.jar com.yourdomain.yourProcess & > /dev/null
echo $$ > yourProcess.pid
echo "yourProcess [START] - Process started. Check logs for runtime problems"
else
echo "yourProcess [WARNING] - Process already running"
fi
fi
Para isso… basta colocar este processo no cron para verificar de 5 em 5 minutos se o processo ainda está sendo executado.
Modificando SQL com parametros de indexação do Solr (DIH)
by moutinho on Apr.24, 2012, under MySQL, Solr
Uma grande sacada na utilização do DataImportHandler (tirada do livro “Apache Solr 3 Enterprise Search Server“) é a possibilidade de passar parametros para o SQL que está contido no arquivo de configuração.
Um bom caso de uso é a possibilidade de informar via SQL se a extração de dados deve ser completa ou parcial (delta).
vamos ao exemplo:
<dataConfig>
<dataSource
type="JdbcDataSource"
name="ds"
driver="com.mysql.jdbc.Driver"
url="jdbc:mysql://127.0.0.1/my_db"
encoding="UTF-8"
user="usuario"
password="senha"
batchSize="-1" // usar somente com MySQL
/>
<document name="product">
<entity name="item"
dataSource="ds"
pk="key"
query="
select * from my_db.my_table
where ('${dataimporter.request.fullindex}' != 'false'
or
timestamp > date_sub(now(),interval 30 minute))
">
</entity>
</document>
</dataConfig>
Desta forma, o parametro “${dataimporter.request.fullindex}” será substituido pelo valor passado na requisição.
http://127.0.0.1/solr/myCore/dataimport?command=full-import&&fullindex=true
fará com que o SQL final seja interpretado como uma extração total (sem o parametro que define onde o timestamp deverá ser maior do que os ultimos 5 minutos)
http://127.0.0.1/solr/myCore/dataimport?command=full-import&&fullindex=false
fará com que o SQL final seja interpretado como uma extração parcial. (mesmo com o “command=full-import”)
Grails 2.0 com MongoDB – Connection refused
by moutinho on Apr.23, 2012, under Grails, MongoDB
Se você também está enfrentando problemas de recusa de conexão do MongoDB para sua app Grails (seja 2.0 ou não)…
Antes de começar a chutar seu desktop/notebook, verifique a porta de conexão configurada em seu DataSource.groovy:
Está escrito “port = 27107” certo ?
Pois bem… a documentação do plugin está errada (até a presente data).
A porta correta é 27017 e não a informada na documentação.
Este erro de documentação passa facilmente despercebido na hora de configurar e da uma certa dor de cabeça…
Escrevendo um transformador dih para Solr
by moutinho on Apr.20, 2012, under Solr
O primeiro passo de quem está desenvolvendo/configurando o Solr, geralmente é transformar um dado utilizando javascript declarado dentro do proprio arquivo de configuração… porém por experiência propria, a manutenção deste codigo se torna complexa (por falta de um bom debug) e é especialmente lento no processo de indexação.
Eis então que surge o transformador em formato de “lib” importada para o DIH (DIHCustomTransformer).
Se você já sabe que todos os seus documentos sofrerão algum tipo de transformação de dados, você já pode salvar algum tempo e declarar um transformador mais genérico (sem necessidade de declarar quais atributos sofrerão customização). Como por exemplo, um website aonde as url são construidas com algumas tecnicas de SEO combinadas e podem sofrer alteração durante sua edição (tornando uma persistência desta uri em banco uma opção ruim).
Chega de blábláblá…
Sua classe deverá implementar obrigatoriamente um metodo chamado “transformRow”. [ public Object transformRow(Map<String, Object> row) ]
private static final String IN_TAG = "rawTags";
private static final String TAG = "tags";
public Object transformRow(Map<String, Object> row) {
// Obter o valor da coluna retornada do DB.
String tag = (String) row.get(IN_TAG);
if (null != tag) {
String[] tagList = tag.split(",");
// adiciona como campo multi-valor as tags já "splitadas" por virgula.
row.put(TAG,tagList);
// remove o atributo nao tratado do documento a ser indexado. (basta nao declarar em seu schema, mas por boa pratica é interessante remover)
row.remove(IN_TAG);
}
}dificil não ? (LOL)
Apartir deste exemplo básico tudo se torna um jogo e quebra-cabeça.
public class customTransformer {
private static final String IN_TAG = "rawTags";
private static final String TAG = "tags";
private static final String URL = "url";
private static final String TITLE = "title";
private static final String DOMAIN = "domain";
private static final String FOLDER = "folder";
public Object transformRow(Map<String, Object> row) {
String title = (String) row.get(TITLE);
String domain = (String) row.get(DOMAIN);
String folder = (String) row.get(FOLDER);
StringBuffer url = new StringBuffer();
url.append("http://");
url.append(domain.toLowerCase());
url.append("/");
url.append(folder.toLowerCase());
url.append("/");
url.append(lowerCaseStripSpacesAndAccents(title));
url.append(".html");
String SeoUrl = url.toString();
row.put(URL, SeoUrl);
String tag = (String) row.get(IN_TAG);
if (null != tag) {
String[] tagList = tag.split(",");
row.put(TAG,tagList);
row.remove(IN_TAG);
}
}
obs: Antes de escrever um transformador customizado para o Solr é necessário ter bom conhecimento de como funciona o Data Import Handler.
Combinando Memcached, MySQL e codigo C para autenticar usuários de uma API RESTFull.
by moutinho on Sep.05, 2011, under Varnish
Depois de algum tempo pesquisando (e principalmente inspirado pelo post do Dr. Carter), resolvi implementar uma autenticação de clientes API (RESTfull) que precisava escalar.
O intuito deste post é compartilhar o pedaço de codigo (inline C) hoje responsável pela autenticação dos clientes e não como configurar o Varnish Cache para escalar websites.
O codigo consiste em pegar a chave de autenticação enviada através de um Header HTTP especifico e verificar sua autenticidade, combinando com a contagem de requests que o cliente possui permissão (tudo isso evitando ao maximo uma chamada ao banco de dados).
/* Import these modules in the begining of your VCL code */
C{
#include <string.h>
#include </usr/local/include/libmemcached/memcached.h>
#include <syslog.h>
#include </usr/include/mysql/mysql.h>
#include <stdio.h>
}C
/* Place the code inside your "vcl_recv" wherever you want (as longs as your requests passes thru the code) */
C{
memcached_server_st *servers = NULL;
memcached_st *memc;
memcached_return_t rc;
/*
I use a combination of "API Key + Client IP" to count the requests.
Adapt the key used to suit your needs.
*/
char key[50];
strcat(key, VRT_GetHdr(sp, HDR_REQ, "\012X-API-Key:"));
strcat(key, "_");
strcat(key, VRT_IP_string(sp, VRT_r_client_ip(sp)));
memc= memcached_create(NULL);
servers= memcached_server_list_append(servers, "localhost", 11211, &rc);
rc= memcached_server_push(memc, servers);
syslog(LOG_INFO, "Memcached connection = %s",memcached_strerror(memc, rc));
/* If the connection to memcached was succesfull we'll decrement the value of this key. */
if (rc == MEMCACHED_SUCCESS) {
rc = memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_CONNECT_TIMEOUT, 10);
uint64_t intval;
rc= memcached_decrement(memc, key, strlen(key), (uint64_t)1, &intval);
if (rc != MEMCACHED_SUCCESS) {
/*
If decrement has failed (probably the key is not in cache) we need to authenticate the key on database.
Remember to create a view (for safety reasons the mysql user is only privileged to use the created view) and to add an index on your table (for a fast read).
*/
syslog(LOG_INFO, "Key found in Memcached ? = %s",memcached_strerror(memc, rc));
MYSQL *conn;
MYSQL_RES *res;
MYSQL_ROW row;
char rows;
char query[200];
sprintf(query, "select request-limit from view where api_key = '%s'" ,VRT_GetHdr(sp, HDR_REQ, "\012X-API-Key:"));
conn = mysql_init(NULL);
if (mysql_real_connect (conn,"localhost","user","password","database_name",0,NULL,0) != NULL){
mysql_real_query(conn,query,strlen(query));
res = mysql_store_result(conn);
row = mysql_fetch_row(res);
if (row) {
syslog(LOG_INFO, "Result != NULL");
char *limit = row[0];
syslog(LOG_INFO,"Key found in MySQL with %s requests total.",limit);
rc= memcached_set(memc, key, strlen(key), limit, strlen(limit), (time_t)3600, (uint32_t)0);
syslog(LOG_INFO, "Key %s stored in Memcached ? = %s",key,memcached_strerror(memc, rc));
/*
If the API Key was found in database we collect the request limit value and store it.
I also set a 1 hour limit for this memcached object (3600) and log the response code from memcached.
*/
} else {
syslog(LOG_INFO, "Key: %s not found in MySQL.",VRT_GetHdr(sp, HDR_REQ, "\012X-API-Key:"));
VRT_error(sp, 401, "Unauthorized.");
VRT_done(sp, VCL_RET_ERROR);
/* The provided key was not found in database. Return a "401 Unauthorized" code. */
}
} else {
syslog(LOG_INFO,"MySQL: Error %u (%s)",mysql_errno (conn), mysql_error (conn));
VRT_error(sp, 500, "Internal server error.");
VRT_done(sp, VCL_RET_ERROR);
/* In case of a database connection problem we return a "500 Internal server error" code. */
}
mysql_free_result(res);
mysql_close(conn);
}
else {
syslog(LOG_INFO, "Key found in Memcached ? = %s",memcached_strerror(memc, rc));
syslog(LOG_INFO, "Decrement key %s in Memcached ? = %s",key,memcached_strerror(memc, rc));
if (intval < 1) {
syslog(LOG_INFO, "Key %s limit exceeded. Client Ip = %s",VRT_GetHdr(sp, HDR_REQ, "\012X-API-Key:"),VRT_IP_string(sp, VRT_r_client_ip(sp)));
VRT_error(sp, 403, "Forbidden");
VRT_done(sp, VCL_RET_ERROR);
/* If the decremented result from memcached is lower than "1" we return a "403 Forbidden" code. Client requests maxed-out. */
}
}
}
memcached_free(memc);
memcached_server_list_free(servers);
}C
para o carregamento das bibliotecas necessárias, você precisará adicionar ao script de inicialização do Varnish (/etc/sysconfig/varnish) os seguintes parametros:
-p ‘cc_command=exec cc -fpic -shared -Wl,-x -L/usr/local/include/libmemcached/memcached.h -L/usr/lib64/mysql -lmysqlclient -lmemcached -o %o %s’
obs1: você vai precisar ter instalado em seu servidor o mysql-client & mysql-devel package assim como a libmemcached.
A thank you note to the varnish mailing list which assisted me thru the entire process.